# menu-admin Uploads, orders and publishes the customer-facing menus of a restaurant Site as photographed Pages, replacing the `scp`-a-JPG workflow. One instance per Site; `docs/plan.md` is the agreed design and `CONTEXT.md` is the glossary. ## Status Built: Site config, the Menu Set store, the image pipeline, the customer-facing renderer (landing page, Section pages, archived sets, media route) with the approved design, the admin (sign in, create a set, upload and reorder Pages, preview, publish, restore, delete), the `verify`/`repair` CLI, the container and the Ansible playbook. Not yet done: the parallel-run deployment on `monoco`, which needs a human present. ## Deploying ```sh ./deploy.sh 2026-09-22 # tests, build, push to registry.tanshu.com ``` then the playbook in `ansible-playbooks/menu-admin/` — its readme covers the parallel run, the `site.env` step that gives the admin a password, and cutover. After deploying, check the running site from the outside (read-only, exits non-zero on failure, so it can gate a cutover): ```sh npm run check-site -- https://menu-new.hopsngrains.com ``` ## Layout ``` src/config.ts Site config: brand, theme, Sections, limits src/storage/manifest.ts Manifest type, validation, atomic JSON writes src/storage/sets.ts The Menu Set store: create, add Pages, publish, restore, delete src/storage/validate.ts Boot validation: fatal problems and warnings src/images/pipeline.ts sharp variants: Original, Viewing image, Thumbnail src/auth.ts scrypt password hash, session cookie, login throttle, CSRF src/render/ HTML builder, document shell, the customer pages, the admin src/routes/public.ts Landing, Section, archive and media routes src/routes/admin.ts Sign in, dashboard, editor, upload, publish, restore, delete src/server.ts buildApp (what tests drive) and main (what the container runs) public/ Hand-written CSS, ~2KB of progressive-enhancement JS, Archivo config/ Per-Site config, one file per Site test/ node:test suites docs/plan.md The agreed plan docs/adr/ Decisions worth remembering deploy.sh Tests, build, push ``` The Ansible playbook lives in `ansible-playbooks/menu-admin/`, beside the other playbooks, because that is where it is run from. ## Working on it Node 24 or newer runs the TypeScript sources directly. ```sh npm install npm test # node:test npm run typecheck # tsc --noEmit npm run seed # placeholder Pages in .dev/data, for design review npm run dev # http://localhost:3000 against .dev ``` To sign in locally, put a password hash in `.dev/site.env`: ```sh printf 'ADMIN_PASSWORD_HASH=%s\nSESSION_SECRET=%s\n' \ "$(node -e "import('./src/auth.ts').then(async m => process.stdout.write(await m.hashPassword('your password')))")" \ "$(node -e "process.stdout.write(require('node:crypto').randomBytes(32).toString('base64url'))")" \ > .dev/site.env ``` ## Who can do what `/` and `/
/` are public. `/admin` needs the password; a Draft's Pages are served only to a signed-in session, and Originals have no route at all. ## Storage model ``` data/ ├── originals/
//.. # kept, never served ├── drafts/
// # manifest.json + variants └── published/
// # manifest.json + variants ``` The directory decides a Menu Set's state; the manifest carries metadata only. `` is the valid-until date, else the publication date. The Current Menu Set for a Section is the published one with the newest `publishedAt`. Published sets are immutable: changing a menu means publishing a new set.