- Lossless edits: the .TMS file must round-trip byte-for-byte when no changes are made.
- Safe edits: always create backups and show a change preview.
- Fast workflow: 10x4 keyboard UI first-class; 2-3 clicks to change a key.
- Clean UI: minimal chrome, bold typography, strong hierarchy.
- Offline-first: local-only, no external uploads.
- Import a .TMS file and see PLUs immediately.
- Assign any PLU to any key in a 10x4 layout, across 3 layers per key.
- Edit PLU name, price, unit type (kg vs pcs), and class/department.
- Bulk update prices, rename, and duplicate items.
- Export updated .TMS with backups and a diff preview.
- Frontend-only app using the File System Access API.
- Pros: no server, easiest install.
- Cons: limited to Chromium-based browsers.
- Local backend (Node + Express) + frontend (React + Vite).
- Pros: full file access, stable, cross-browser.
- Cons: requires running a local server.
- Desktop wrapper (Tauri or Electron) around Option A/B.
- Pros: native file access, easy distribution.
- Cons: packaging overhead.
Recommended: Option B or C for reliable file read/write.
- Tab-delimited lines.
- Sections with start markers and END markers.
- Decimal comma (e.g., 600,0).
- Mixed language text.
- Preserve line endings (CRLF vs LF).
- Preserve tabs and trailing empty fields.
- Preserve section ordering.
- Preserve unknown sections and fields.
- Never reformat numbers unless the user edits them.
- FileDoc
- rawHeaderLine
- lineEnding ("\n" or "\r\n")
- sections[] (original order)
- Section
- name
- startLineRaw
- rows[]
- endLineRaw
- Row
- rawLine
- fields[] (split by tab, preserve empty)
- kind (fields[0])
- parsed (optional typed view)
Use a config map to define indexes for each section:
- PLU: id, type, price, name, dept/class, etc.
- DPT, CLS, BAR, LAB, etc.
This allows you to adjust indexes without rewriting code.
File-level
- Line endings: CRLF (
\r\n). - Header:
ECS\tVER\tV3.15C5\tUTF-8. - Device strings (from TMT/INF):
Budry MFD-51 TM-xA V3.20EandTM-xA UC123 V3.20E. - Mixed-language text; some names render as garbled characters when decoded as UTF-8, so preserve raw bytes and allow encoding selection.
Sections (in order)
- DPT
- CLS
- PLU
- UNT (empty)
- BAR
- LAB
- SAL (empty)
- REP
- SCP
- TMS
- TMT
- INF
- TIM
Row counts
- DPT: 9
- CLS: 10
- PLU: 143
- UNT: 0
- BAR: 9
- LAB: 1266
- SAL: 0
- REP: 11330
- SCP: 128
- TMS: 500
- TMT: 28
- INF: 3
- TIM: 1
Record types + field counts
- DPT: 4 fields per line (e.g.,
DPT\t1\tT-Weight\t). - CLS: 11 fields per line (e.g.,
CLS\t1\tT-Weight\t1\t0\t0\t0\t0\t0\t0\t). - PLU: two observed formats:
- 69 fields (139 lines) – main format.
- 16 fields (2 lines) – short format (e.g., PLU IDs 8009, 8010 in this file).
- BAR: 9 fields per line (e.g.,
BAR\t1\tBarcode-1\t0\t0\t0\t0\tF600J500A000A000A000A000\t). - LAB section contains multiple record types:
LAB(40 fields)LAS(14 fields) – layout rowsLAE(2 fields) – end marker
- REP section contains multiple record types:
REP(11 fields)RES(19 fields)REE(2 fields)
- SCP: 5 fields per line (e.g.,
SCP\t0\t1\t9001\t). - TMS: 4 fields per line (e.g.,
TMS\t0\t12\t). - TMT: 4 fields per line (e.g.,
TMT\t0\t<text>\t). - INF section contains:
INA(10 fields)INM(5 fields)INF(6 fields) – contains store/device metadata
- TIM: 8 fields (single line) – appears to be date/time values.
PLU field observations (69-field format)
- [0]
PLU - [1] PLU ID (numeric)
- [2] observed constant
0 - [3] empty (tab)
- [4] observed
1or2(likely unit type: kg vs pcs) – treat as unit field. - [5] primary price (decimal comma, e.g.,
600,0) - [6], [7] additional price fields (often
0,0) - [14] observed
9(likely dept/class; confirm) - [15] item name (Sinhala/English/mixed)
- [64] short code/alias in some lines (e.g.,
test,whiterice-test) - Trailing fields include repeated blocks with values like
0,0,0, and127; treat as opaque until confirmed.
Encoding note
- File declares UTF-8, but some item names appear garbled when decoded; treat encoding as configurable and preserve raw bytes to avoid data loss.
- Read file as binary.
- Detect line ending.
- Try UTF-8; allow manual encoding if needed.
- Split into lines; keep raw lines.
- Identify sections by "start" and "END" markers.
- Store rows with raw fields.
- Rebuild each line from fields or rawLine if unchanged.
- Use original line ending.
- Write all lines in original order.
- Show side-by-side textual diff before export.
- Highlight only changed lines.
- PLU ID: required, numeric, unique.
- Price: valid decimal comma format.
- Name length: warn beyond limit (set default 30, configurable).
- Empty names allowed but flagged.
- Unit type required (kg or pcs); warn if unknown.
- Prevent duplicate key assignments unless user confirms.
- Typography: choose a bold, distinctive font (e.g., "Space Grotesk" or "Sora").
- Color: warm neutral base with a single bold accent (no purple).
- Surfaces: subtle gradients and soft borders.
- Buttons: pill or rounded-rect, strong contrast.
- Spacing: generous padding, 8pt grid.
- File status, last export, total PLUs, warnings.
- Quick actions: Import, Export, Keyboard, PLUs.
- Search + filters (weight/count, price range, favorites).
- Table view with inline edit.
- Right-side drawer for detailed edit.
- Bulk actions: price %, set type, copy, delete.
- 40-key grid with big targets and a Layer switch (Layer 1/2/3).
- Each key shows: Name (2 lines), Price, Unit (kg/pcs), PLU ID for the active layer.
- Color-coded by type.
- Drag PLU onto key (drops into active layer).
- Click key to edit assignment for active layer.
- Long-press to clear or duplicate within the layer.
- Layer tools: copy layer -> layer, swap layers, clear layer.
Use this as the canonical layout reference when building the 10x4 keyboard grid and 3-layer labels.
| Key | Row | Col | L1 (Main) | L2 (Red) | L3 (Blue) |
|---|---|---|---|---|---|
| 1 | 1 | 1 | කැරට් | බීට් | නෝකෝල් |
| 2 | 1 | 2 | බොංචි | මෑ කරල් | දඹල |
| 3 | 1 | 3 | ලීක්ස් | ගෝවා | ගෝවා මල් |
| 4 | 1 | 4 | අල | බතල | මඤ්ඤොක්කා |
| 5 | 1 | 5 | බී ළූණු | රතු ළූණු | ළූණු කොළ |
| 6 | 1 | 6 | තක්කාලි | අමු මිරිස් | මාළු මිරිස් |
| 7 | 1 | 7 | දෙහි | ඉඟුරු | සුදු ළූණු |
| 8 | 1 | 8 | වට්ටක්කා | පතෝල | බටානා |
| 9 | 1 | 9 | වම්බටු | බණ්ඩක්කා | කරවිල |
| 10 | 1 | 10 | පොල් | කරපිංචා | සේර |
| 11 | 2 | 1 | මුකුණුවැන්න | කංකුන් | ගොටුකොළ |
| 12 | 2 | 2 | නිවිති | සාරණ | තම්පලා |
| 13 | 2 | 3 | කොහිල | නෙළුම් අල | ඉං අල |
| 14 | 2 | 4 | රාබු | රතු රාබු | සුදු රාබු |
| 15 | 2 | 5 | පිපිඤ්ඤා | කැකිරි | පුහුල් |
| 16 | 2 | 6 | මී කිරි | කිතුල් පැණි | බිම්මල් |
| 17 | 2 | 7 | පොලොස් | දෙල් | කොස් |
| 18 | 2 | 8 | අළු කෙසෙල් | කෙසෙල් මුව | තිබ්බටු |
| 19 | 2 | 9 | මුරුංගා | තුඹ කරවිල | වැටකොළු |
| 20 | 2 | 10 | ලංකා අල | රට අල | කිරි අල |
| 21 | 3 | 1 | සලාද කොළ | අයිස්බර්ග් | රොකට් |
| 22 | 3 | 2 | බෙල් පෙපර් රතු | බෙල් පෙපර් කහ | බෙල් පෙපර් කොළ |
| 23 | 3 | 3 | බ්රොකලි | රතු ගෝවා | අතුගෝවා |
| 24 | 3 | 4 | සුකිනි | සැළඩ පිපිඤ්ඤා | චෙරි තක්කාලි |
| 25 | 3 | 5 | සැල්දරි | පාස්ලි | ලීක්ස් (සුප්) |
| 26 | 3 | 6 | බටර් බොංචි | කැබිලි | ෆ්රෙන්ච් බීන්ස් |
| 27 | 3 | 7 | මින්චි | කොත්තමල්ලි | බැසිල් |
| 28 | 3 | 8 | සීනි බතල | මස්කෙඩක්කා | විලාඩ් |
| 29 | 3 | 9 | GH තක්කාලි | රෝමන් | ප්රිසි |
| 30 | 3 | 10 | වෙනත් (Kg) | වෙනත් (Pcs) | බෑග් |
| 31 | 4 | 1 | ඇම්බුන් | සීනි | පුවාළු |
| 32 | 4 | 2 | ඇඹුල් | රතඹලා | නේන්ද්රා |
| 33 | 4 | 3 | ගස් ලබු | කොමඩු | අන්නාසි |
| 34 | 4 | 4 | ඇපල් රතු | ඇපල් කොළ | පෙයාර්ස් |
| 35 | 4 | 5 | දොඩම් | පිටරට දොඩම් | නාරං |
| 36 | 4 | 6 | මිදි කළු | මිදි කොළ | ස්ට්රෝබෙරි |
| 37 | 4 | 7 | කර්ත-කොලොම්බන් | උයන අඹ | අල්පොංසො |
| 38 | 4 | 8 | අලිගැටපේර | පේර | කාමරංගා |
| 39 | 4 | 9 | පැෂන් | නෙල්ලි | අඹරැල්ලා |
| 40 | 4 | 10 | දිවුල් | ලබු (පලතුරු) | ඩ්රැගන් ෆෘට් |
- Import: file picker, encoding selector, preview.
- Export: choose output name, create backup, diff preview.
- Encoding defaults.
- Field index config.
- Label length limits.
- Backup folder path.
- Parse mapping section into a KeyMap model with 3 layers.
- Update mapping section on edits (only the active layer slot).
- Create sidecar JSON mapping (same filename + .keys.json) with 3 layers.
- UI uses sidecar for layout.
- Offer export once mapping is confirmed.
- AppShell
- SidebarNav
- TopBar
- DashboardPanel
- PluTable
- PluEditDrawer
- KeyboardGrid
- KeyTile
- LayerSwitcher
- ImportModal
- ExportModal
- DiffViewer
- GET /file/open
- POST /file/save
- POST /file/backup
- POST /file/validate
- GET /file/diff
- /frontend
- /components
- /screens
- /styles
- /utils
- /backend
- /parser
- /writer
- /api
- /tests
- roundtrip.test.ts
- plu_edit.test.ts
- Round-trip: read -> write -> binary compare.
- PLU edit: only one line changes.
- Bulk update: correct price math with comma decimals.
- Non-ASCII: preserved after export.
- Key assignment: updates mapping only for the correct layer.
- Layer copy/swap: no cross-layer corruption.
- Parser + writer + tests.
- List + search + edit + bulk.
- 10x4 grid + drag drop + key edit.
- Encoding + diff + backups.
- Keyboard UX refinements + performance.
- Zero data loss in unchanged export.
- Key assignment is 2 clicks or less on the active layer.
- PLU edit reflected in output file.
- UI works on desktop + tablet.
- Exact keyboard mapping section name and field layout.
- Max label length for key display.
- Allowed PLU ID ranges and reserved IDs.
- Whether multiple price tiers exist.
- Confirm mapping section and PLU field indexes.
- Implement parser and round-trip test.
- Build PLU list + edit drawer.
- Build keyboard grid and assignment logic.