The problem
It began as a touch-screen catalog for a trade-show booth. The same data then had to run a sales website and a USB edition that opens with no internet — and the staff had to edit all of it themselves.
Decisions
- Moving from static JSON to a database meant swapping one data-loader file. It rebuilds the old catalog.json shape and keeps four accessor signatures: page code untouched, a 21-product sample diffed to zero.
- Two builds, online and offline. The file:// limits — module CORS, blocked fetch, absolute paths — were solved with an IIFE build, inlined data, relative paths and self-hosted fonts.
- The kiosk’s existing idle timer now re-fetches the data, so edits appear with no new infrastructure.
What broke — and the fix
- On first real use, every save in the admin app failed. RLS was on for the admin table but it had no self-read policy, so “admins only” checks always saw nothing. One policy fixed it; a round-trip check now gates every release.
- The site was fixed but the USB still showed the old version: its shell was a prebuilt bundle committed to the repo. A check now fails when a prebuild is older than its source — proven by running it against the old shell.
- Card images were cropped, invisible in code. A headless measurement showed square 324 px images inside a 203 px 16:10 box. Reviews now measure rendered size, not code.
Results
- 329 products · 4 categories · 26 subcategories · 758 images · 364 specs
- 66 dimension tables read from 65 PDF pages (53 new)
- 124 slides of client markup: 232 fixes applied, 0 missed
- USB build about 43–45 MB: 860 images and 827 references, 0 missing
- 57 offline pages: 0 broken images, 0 external requests
- Permissions measured: anonymous read 200 / anonymous write 401