# Roast Planner Webapp A fillable, live-computing web version of the manual coffee roast plan worksheet (`hope_roaster/roast-planner/manual-roast-planner.html`), plus: - **Prefill from a bean URL** — paste a roaster's product page URL; the server fetches it and uses the Pi Coding Agent SDK (zero-tool, one-turn extraction) to pull out whatever it can find (cultivar, origin, processing, roast level), then deterministically maps that to worksheet field IDs using the same reference tables as the paper worksheet. It never invents a first-crack anchor or a modifier — those only ever come from the reference tables, keyed off what the page actually states. - **Reference curve from an Artisan `.alog`** — upload a real roast log (or browse a local library directory) to overlay its actual milestones/curve on the same SVG grid as your plan, dashed and in a different color. - **Live ledger** — the worksheet's time-ledger math (first crack, yellow, Maillard, development, drop, and the four sanity-check ratios) recomputes as you type, using the exact same arithmetic as the paper worksheet (verified against both its worked examples). - **Print** — bakes typed values into the print layout and reuses the paper worksheet's print CSS, so `File > Print` produces the same 2-page front/back sheet. ## Run it ```bash cp .env.example .env # set strong secrets npm install npm test # local PostgreSQL stack (database is not published to the host) docker compose --env-file .env up --build ``` The public landing page is at `/`; plans require an account at `/app`. Production is configured for `https://roast.srmr.xyz`: retain `APP_ORIGIN=https://roast.srmr.xyz` and `COOKIE_SECURE=true` behind its HTTPS proxy. Database migrations in `db/migrations/` run at application startup exactly once. ### First administrator Generate `BOOTSTRAP_SETUP_TOKEN` with `openssl rand -base64 48`, keep it only in the deployment environment, then call `POST /api/auth/bootstrap` with that token, `snowspeeder@gmail.com`, and a 12+ character password. The endpoint can create that account only once. Remove the setup token after success; it is optional thereafter and no administrator password is stored in source control. ### Pi agent configuration in Docker The `app` service mounts `PI_AGENT_CONFIG_DIR` (default `./appdata/pi-agent`) read-only at `/home/node/.pi/agent`, the non-root Node user's Pi configuration directory. This lets `/api/prefill` use the same configured model at runtime without baking credentials into the image. The directory is ignored by Git and Docker build context; do not commit its contents. Before bringing up the stack, sync only the local Pi agent configuration you intend to make available to the container: ```bash mkdir -p appdata/pi-agent rsync -a --delete ~/.pi/agent/ appdata/pi-agent/ docker compose --env-file .env up --build ``` Set `PI_AGENT_CONFIG_DIR` to another protected host directory instead if preferred. Restrict access to that directory because it can contain provider credentials. The mount is read-only, so Pi cannot alter the host configuration. If deployed behind a reverse proxy, set `TRUST_PROXY` only to that proxy's specific IP/CIDR (or keep it blank when the app is directly exposed). ## Mobile and PWA use The planner is responsive and caches its app shell for offline use after the first visit. Browser installation and service-worker caching require HTTPS in production (localhost is exempt). Put the Docker container behind an HTTPS reverse proxy before using it as an installable PWA on a phone. Prefill requires a model configured via the `pi` CLI (`~/.pi/agent/{models,auth}.json`) — without one, `/api/prefill` returns `503 no_model` rather than crashing. Everything else (the form, ledger, curve, `.alog` upload) works with no model configured. ## Layout - `shared/` — browser-safe ESM modules imported by *both* the Node server and the browser (served statically at `/shared/`). This is the single source of truth for the ledger math, time parsing, and reference data tables — never duplicate this logic in `server/` or `public/js/`. - `server/` — Express routes: `/api/prefill`, `/api/alog`, `/api/alog/library[/:filename]`. - `public/` — the static frontend: `index.html` (the worksheet as a form), `worksheet.css` (a verbatim copy of the paper worksheet's print CSS — port changes from there, don't hand-edit divergently), `app.css` (screen-only chrome + form-control styling), `js/`. ## Where the reference numbers come from `shared/reference-data.js` is ported from `/Users/shane/dev/hope_roaster/roast-planner/manual-roast-planner.html`'s Step 1 (cultivars), Step 3 (processing), Step 4 (roast levels), Step 8 (machine bands), and Step 7 (sanity bands). If those tables change in the paper worksheet, port the change here too. ## Known gaps (v1) - Offline drafts are intentionally scoped to the authenticated browser account and are cleared on logout; account-backed plans remain the authoritative copy. - Roastetta (roastetta.com) integration is intentionally out of scope — it needs a headed, Cloudflare-clearing browser and the operator's own credentials. Use the `.alog` file picker, or point `ALOG_DIR` at wherever the `roastetta` skill already downloaded files.