Test and deploy / test-and-deploy (push) Successful in 53s
Academy (/academy, 'Learn' nav group): - 10 lessons / 45 slides across two tracks. Roasting: seed anatomy and composition, drying & the turning point, Maillard, first crack & development (DTR), curve/RoR reading (crash, flick, stall), defects → one-change discipline. Brewing: extraction physics (dissolving order, yield, grind, temp/time/ratio), then technique per brewer family — immersion (French press, AeroPress, Clever/Hario Switch, cold brew, cupping), percolation (bloom, V60, Chemex/Kalita, batch, percolator), espresso & pressure (puck prep, shot reading, dialing in, moka) - One cohesive scene system: 25 parameterized animated SVG scenes drawn from the app's palette (beans, curves, phase bars, brewers reusing the silhouette library), CSS keyframe animations, reduced-motion support - Narration: Azure TTS (en-US-Andrew HD, eastus) pre-generated to 45 committed MP3s by scripts/generate-academy-audio.mjs; slides speak phonetic respellings (my-YARD, KEM-ex, MOH-kah…) while captions show normal spelling; hands-free autoplay advances after each clip - Player: slide dots, keyboard arrows/space, per-slide captions Roast detail: 'Open cupping session' button next to cup notes (opens or creates the linked plan's session) — its score and flavors flow into the updated .alog download. Co-Authored-By: Claude Fable 5 <[email protected]>
116 lines
8.2 KiB
Markdown
116 lines
8.2 KiB
Markdown
# 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.
|
||
- **Finished-roast log with Pi agent review** — upload one or more finished Artisan `.alog`
|
||
files against a plan (or standalone from `/roasts`); each is stored verbatim (downloadable
|
||
as a backup), parsed, and deep-reviewed asynchronously by the same zero-tool Pi agent
|
||
pattern as prefill (grade, highlights, concerns, next-batch suggestions, plan-vs-actual).
|
||
The `/roasts` page lists every actual roast and renders its curve with the plan overlaid.
|
||
- **Brewing section** — the app is split into Roasting and Brewing workflows in the nav.
|
||
`/beans` manages roasted/purchased coffee (with LLM URL prefill from a roaster's product
|
||
page); `/brews` logs every cup against a bean with a silhouette brewer picker (immersion /
|
||
percolation / espresso & pressure — 13 methods), recipe fields (dose, water/yield, auto
|
||
ratio, grind, temp, times), a 0–10 rating, and tasting notes. Bean remaining weight is
|
||
derived from logged brew doses. Green inventory's lot form also prefills from a URL.
|
||
- **Plan chat + learned roaster behavior** — an "Ask the LLM" drawer on the planner chats
|
||
about the open plan, grounded in the computed ledger, the learned pace profile, and a
|
||
roaster-behavior profile (`/api/roaster-profile`) aggregated from every uploaded .alog
|
||
(turning-point lag, phase RoR, median milestone temps). The same profile feeds roast
|
||
reviews and supplies the planner curve's fallback milestone temps.
|
||
- **API + tokens + Swagger** — every capability (including admin) is a JSON endpoint,
|
||
documented by a hand-authored OpenAPI 3 spec at `/api/openapi.json` and a self-hosted
|
||
Swagger UI at `/api-docs`. Users generate bearer tokens (`rpt_…`) on the Account page;
|
||
token requests skip CSRF (header-borne credentials can't be forged cross-site).
|
||
- **Backup** — Admin → Backup exports the entire database as one JSON file and can import
|
||
it back transactionally (delete-and-restore, refuses backups with no active admin, keeps
|
||
the importing admin's session when possible). Users can download their own data from the
|
||
Account page.
|
||
- **Academy** — `/academy` teaches roasting and brewing as narrated, animated slide lessons:
|
||
green-bean physics through every roast stage (drying, Maillard, first crack, development,
|
||
curve reading, defects) and extraction plus technique for every brewer family. Narration is
|
||
Azure TTS (`en-US-Andrew` HD voice), pre-generated to committed MP3s by
|
||
`scripts/generate-academy-audio.mjs` (reads `AZURE_VOICE_KEY`/`AZURE_VOICE` from `.env`,
|
||
region eastus — run it locally after editing lesson scripts; production never needs the
|
||
key). Slides carry display transcripts as captions and phonetic respellings for the voice.
|
||
- **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, `[email protected]`, 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 `/run/pi-agent-config`. Its entrypoint copies that seed into the non-root Node user's writable runtime configuration directory before startup: Pi's credential storage needs to create a lock beside `auth.json`. This lets `/api/prefill` use the configured model without baking credentials into the image or mutating the host configuration. 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. Which configured
|
||
model runs prefill and roast reviews is chosen in the admin page's **LLM** section
|
||
(`app_settings.llm_model`, "Auto" = first available); the UI refers to the model backend
|
||
simply as "the LLM".
|
||
|
||
## 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.
|