Categories were baked into config/<site>.site.json, so adding one meant a rebuild and a redeploy. They are now data: data/sections.json, seeded from the config the first time, and managed in the admin (add, rename, reorder, remove). A category's web address is fixed when it is created because that is what a QR code points at, and a category cannot be removed while it still holds menus. The landing and the top bar were the ugliest parts. The landing was a column of hairlines with tiny meta — a broadsheet, and the paper that makes this app worth looking at was nowhere on it. It is now a shelf: each category is a row whose right edge is a sliver of that menu's first page, bleeding off the screen, with the shelf edge in brass and the name in heavy tight Archivo. The bar was a row of teal pills that would not survive more categories: it now names the menu you are reading and opens a sheet listing the rest, each with its own paper sliver, and the current one is the only teal thing on screen. The sheet is a link to the landing page, so navigating still works without the script. Also fixes the category manager's own layout, which I shipped unstyled, and the markup contract the route tests described.
4.3 KiB
Operating the menus
For whoever is on the other end of "the menu is wrong". The design is in
plan.md, the vocabulary in CONTEXT.md, and the deployment in
ansible-playbooks/menu-admin/readme.md.
Where things are
| Thing | Where |
|---|---|
| The app | container menu-app.<site> on monoco, image registry.tanshu.com/menu-admin:<tag> |
| Menu content | /var/lib/menu-app.<site>/data — originals/, drafts/, published/ |
| Admin | https://<vhost>/admin |
| The password | /var/lib/menu-app.<site>/site.env (mode 600, never in git) |
| Categories | data/sections.json, edited in the admin |
| Brand and theme | config/<site>.site.json in this repo, baked into the image |
Everyday tasks
Publish a new menu. Sign in at /admin, find the Section, fill in "New Menu
Set" (label, optional valid-until date), Create Draft, then drop the photos in.
Drag to reorder — the order in the editor is the order customers see. Press
Preview to check it, then Publish. Publishing archives whatever was current.
Put an older menu back. Open the Section's "older menus" fold in the admin, open the set you want and press "Make current". Its URL does not change.
Change the admin password. On the server:
bash /tmp/provision-site-env.sh /var/lib/menu-app.hopsngrains.com --force
docker restart menu-app.hopsngrains.com
Add, rename, reorder or remove a category. In the admin, open the Categories fold at the bottom of the dashboard. A new category starts empty, so customers do not see it until you publish a menu into it. Reordering there is the order customers see. Two rules: a category's web address is fixed when it is created (rename the name as often as you like — a printed QR code keeps working), and a category can only be removed once it holds no menus.
Change brand or theme. Edit config/<site>.site.json, rebuild and push
(./deploy.sh <tag>), then run the playbook with the new tag.
Fix a missing image. If a Page's thumbnail or viewing image is lost while its Original survives, the app can rebuild them:
docker run --rm --user 1000:1000 -e MENU_ROOT=/data \
-e MENU_SITE_CONFIG=/app/config/hopsngrains.site.json \
-v /var/lib/menu-app.hopsngrains.com:/data \
--entrypoint node registry.tanshu.com/menu-admin:<tag> src/cli/repair.ts
src/cli/verify.ts does the same inventory without changing anything, and is
what the server refuses to boot on.
When something looks wrong
| Symptom | Look here |
|---|---|
| Landing page says the menus are being updated | no Section has a Current Menu Set — publish one in the admin |
| A Section 404s | that Section has no Current set, or the URL is misspelled |
| Sign-in rejects the right password | docker logs menu-app.<site>, then check site.env exists and is owned by the app user |
| Admin is missing entirely | site.env was unreadable at boot, so the admin is disabled: run the provision script and docker restart |
| A photo uploads but looks wrong | the browser shrinks to 3000px before upload; that is expected, and the Original is kept |
| The container will not start | docker logs — a fatal integrity problem makes it refuse to serve a data directory it cannot trust |
| Caddy reload failed during a deploy | nothing is down: Caddy validates before applying. Read the error in the playbook output |
| Everything is slow | check the tag is current; a Viewing image is ~1600px, so a Section page should not be megabytes |
Logs. docker logs -f menu-app.<site> — JSON lines, one per event, including
every upload, publish, restore and failed sign-in.
Checking from outside. No sign-in, no writes, exits non-zero on failure, so it can gate a cutover:
npm run check-site -- https://menu.hopsngrains.com
Backups
The app is the only writer. The irreplaceable part is
/var/lib/menu-app.<site>/data/originals — the photos exactly as uploaded, from
which everything else can be regenerated with repair. Back that directory up;
site.env is worth keeping too (otherwise you re-provision the password).
Rolling back
The old nginx Site is kept, not deleted. To go back:
docker start nginx-menu.hopsngrains.com
then point Caddy back at it — the cutover section of the playbook readme has the exact invocation, including the flag that stops the app's snippet from being re-added.