Releases ·

Roastwarden 0.4.0

Everything since v0.3.0 (2026-10-25): the paperwork around a roast. An agent can do everything the app does (roast covers every API operation, checked by a test); bag labels with a label studio (saved Typst designs with versions, assets, brands, a layout check, printing over IPP and share links); cupping after the classic SCA form; nine PDF reports made with Typst (roast, cupping sheet, green inventory, shopping list, coffee, profile, period, purchases, roast-day plan).

Added

  • Reports (PDF reports, ROADMAP Next 21): PDF documents the server makes with Typst in the label sandbox, from data prepared and formatted in Rust (one versioned JSON per kind, format=json), with charts drawn natively in Typst. First, the roast report: the coffee; eight figures judged against the profile's range, its level's or this roaster's usual (as roast guidance does); the curve with the profile, RoR, phases and events, heater and fan; phases and events tables; notes and cupping; one page unless it has more. Reports in the top bar with the kinds, paper, units and the server's own pages as the preview; History → a roast → Report opens it there (the browser-printed page is gone, its address redirects). GET /api/reports, GET /api/reports/roast/{id} (format=pdf|svg|png|json, paper, units, tz); settings report_paper (letter, a4) and report_units (C, F) in Settings → Roasting. roast report roast <roast>, roast report list.
  • Cupping sheet (PDF reports): a blank form to score on after the classic SCA protocol, for chosen roasts and samples of your own, each with a three-digit blind code (stable for the day) and the key on a page of its own; two samples a page with scales to mark, or six with boxes. Or a day's scores as a table with a dot strip per attribute and the best named. GET /api/reports/cupping, roast report cupping [ROAST…] --blank.
  • Green inventory and shopping list reports (PDF reports): stock with cost, value, age (6 and 12 month marks), planned green, the recent pace and when each coffee runs out, or a stock-take sheet to weigh against (roast report inventory [--count]); the shopping list as one order per supplier with the shipping its earlier orders carried, then what is running low (roast report shopping). Links from Beans.
  • Coffee and profile reports (PDF reports): a coffee's facts, stock and cost, its roasts as a table and small multiples on the same axes, its cuppings and ledger with a running balance (roast report coffee); a profile's roasts ranked with an average row, overlaid on the profile, and a dot strip each for first crack, drop, DTR and loss (roast report profile [--roaster]). Report on a bean's and a profile's page. The cupping form lists its samples in the codes' order, so the order the roasts were picked in tells nothing, and the key gives each roast's date.
  • Period, purchases and roast-day plan reports (PDF reports): a month, a range or the year in review (roasts with a sum row, green roasted and bought, landed cost, stock and worth at the end, month by month, each coffee); purchases order by order with shipping, fees and landed cost per kg over time; the planned roasts as a sheet for the roaster with the green to weigh (and what is short), targets and boxes to write in. roast report period|purchases|plan. Inventory dates older than ten months carry their year, and a coffee's age is in months up to two years.
  • Agent parity check (Agents): every API operation has an entry in bin/roast/surface.rs, the roast command that does it (with the least preset it needs) or why there is none; tests fail on a missing or stale entry, a command missing from The roast CLI or the skill, or a preset that cannot reach its call. roast openapi --find shows the command per operation.
  • roast commands for what the table showed missing: edit (a roast's notes, rating, colour, level, weights), cup (cupping scores), export / import (.alog, CSV), rm; bean edit, bean stock edit / rm (fix a purchase), order (several coffees, one shipping charge); plan edit / move / rm; profile history / edit / save / import / export / rm; roasters, roaster show, roaster tuning, live (only reads); notifications [--test], settings [set]; backups --check; landmark --rm; show latest. status names the token and its preset.
  • Hermes eval tasks 8 (an order of two coffees) and 9 (how a profile's roasts went).
  • Bag labels (Bag labels): History → a roast → Labels… makes a PDF of labels on Avery 5163, 6874 or 5294 sheets (or the owner's, label_sheets), in a plain or a gift design (or the owner's Typst files and fonts in data/labels/), from any label of a part-used sheet, with the server's preview; a calibration page and per-sheet offsets; "best from" from sourced rest windows (Settings → Roasting). Laid out by Typst 0.15. roast labels and eval task 10.
  • Label studio, part 1 (Bag labels, "Designing a label"; ROADMAP Next 19): roast labels design check draft.typ (POST /api/labels/check) lays one label out and answers Typst's errors with file and line, or a layout report: each line of text with its font, size and box in mm, and what to fix (past the edge, near it, overlapping, too long, cut, too small, a QR code too small or crowded, an unknown font, an undeclared parameter), the fields that printed, and a small PNG on request. One-off labels from a design's source (roast labels <roast> --source draft.typ, POST /api/labels/render). Designs declare parameters with defaults (#let design = (params: …), --set art=cup). New built-in designs: script (calligraphy and a drawn flourish), ascii (a typewriter ticket with a drawing in characters), cherry (a drawn coffee branch); the JetBrains Mono NL font. An agents' design guide (skills/rustroast/label-design.md, its examples compiled by a test) and Hermes eval task 11.
  • Label studio, part 2: saved label designs with every version (sam-gift@2; saved only when they compile; rollback), assets (images and fonts, image("logo.svg")), brand kits (l.brand: name, tagline, place, logo, colours, fonts) and a coffee's own label (a bean's design, parameters and brand, used for its roasts). In the database (migration 030), so in the backup. roast labels design list|show|add|edit|rollback|use|rm, roast labels asset …, roast labels brand …; deletes held for approval; eval task 12.
  • Label studio, part 3: Settings → Labels, a gallery of every design as the server renders it on 4 × 2, 3¾ × 3 and round labels, each saved design's versions and source with "Back to this", the assets and brands. The Labels page shows a design's parameters as a form (text, ASCII art, colours, choices), picks a brand, starts with the coffee's own label, and keeps a choice as the coffee's label ("Use for …").
  • Label studio, part 4: printing over IPP to the label printer (label_printer_url, the owner's HP Color LaserJet Pro M252dw): the PDF at actual size (print-scaling none) on the "labels" paper type, after asking the printer's state (a door open answers why, nothing is sent). The Labels page's Print; roast labels <roast> --print, roast labels printer. A token's print is always held for the owner's approval, with the sheet to load and the first label; eval task 13.
  • Label Studio, its own section (Bag labels, "Label Studio"; ROADMAP Next 20, part 1): Labels in the top bar (Settings → Labels moved there). The gallery shows every design on sample data at its true shape, with its settings and the coffees that use it. A design page edits the Typst source (CodeMirror 6 with codemirror-lang-typst: Typst 0.15's syntax, completion, folding, syntax errors as you type) beside the live check the server runs a moment after typing stops: the label, what to fix, the whole sheet, and the report as an agent reads it; Typst's errors are underlined at their span (from/to in each error); the last good picture stays, dimmed, while the source does not compile. Saving makes a version; versions compare side by side and roll back; a built-in design is saved under a new id. Asset upload (drop or pick; font samples; the Typst line to copy), brand kit forms with a live preview, and each coffee's own label (Coffees). A new built-in design, shop: the brand on top and, in the bottom 30 %, "Whole bean coffee", the net weight and who packed it, where. New label fields net ("Net wt 8.8 oz (250 g)", never rounded up) and weight_g. A check can lay a design out on a coffee (bean, roast labels design check --bean) or with a brand kit that is not saved (brand_kit). Research: docs/research/studio-2026-10-25/, docs/research/reports-2026-10-25/.
  • Print runs (Bag labels, "Print runs"; Next 20, part 2; migration 031): one sheet of labels for one roast or several (a gift run), each with its own design, settings, brand, copies, message and QR code. Label Studio → Print builds one beside the whole sheet; History keeps every run with what each label carried, its PDF and the printer's job states with times (followed until the printer finishes), and reprints all or chosen labels exactly as they were. Each sheet remembers its next free label, moved on when a job completes or a PDF is marked printed. A roast's own Download and Print are runs too. roast labels run|runs|reprint|sheets; POST /api/label-runs and friends; a token's print of a run is held for the owner with what to load.
  • Printers (Bag labels, "Printers"; Next 20, part 3; migration 032): more than one label printer, each asked what it can do and kept with its answer, with its tray, colour and how labels go to it, and its own calibration per sheet (from the calibration page's first and last labels; corners more than 0.5 mm apart are refused as scaling). Printers without PDF get PWG Raster or Apple URF made from the same layout (our own encoder, 300 dpi doubled for a 600 dpi printer, grey unless a page has colour). ipps with the printer's self-signed certificate trusted by its SHA-256 (a custom rustls verifier). Finding printers by mDNS where the server shares their network. Label Studio → Printers; a printer, tray and colour choice on Print and a roast's Labels page. roast labels printer list|add|edit|rm|default|find|certificate|calibrate, --printer, --tray, --color on prints. The label_printer_url setting became the default printer.
  • The retail check (Bag labels, "The retail check"; Next 20, part 4): a label for coffee that is sold, read against 21 CFR 101.3, 101.5 and 101.7 for the bag it goes on (the type height follows the bag's front: 3/16 in for a 12 oz pouch): the statement of identity (bold, half the largest print), the net weight (ounces and grams, never rounded up, bottom 30 %, its letters tall enough, space above it), the packer's name, city, state and ZIP, and nutrition words; with the size to use, and what no check can know. Bag types built in and the owner's (label_bags, label_bag; Brands tab). The report's lines carry bold, x_mm and cap_mm from the font's metrics. Designs get l.retail and a tall helper; the shop design passes for a 12 oz pouch on 5163 and 6874. The design page's "For sale on"; roast labels design check --retail [BAG] and --diff <design@N>; eval task 16.
  • The share page (Bag labels, "The share page"; Next 20, part 5; migration 033): what a bag's QR code opens for the person holding it, with no login. A public page per roast at /R/<code> (25 random base32 characters): the brand, a gift line, the coffee and origin, roast date, best from and how far into its rest it is, tasting notes, the curve on chart paper (°C and °F, development shaded, landmarks), roast and development time, a brew suggestion from the owner's doses; never the roast's id, cost or stock. No script, nothing loaded from elsewhere, its own CSP, noindex, no-referrer, no-store. Scans counted without addresses; a revoked link is the same 404 as an unknown one. Labels choose what the QR code opens (qr: app, share, none or an address; label_qr the default; the Labels and Print pages' QR code opens), the share address in capitals so the QR code is smaller. History → a roast → Share page; roast share [list|revoke], --qr on label commands; a print's approval says when its QR code opens a public page; eval task 17. Read-only tokens cannot make or revoke links, nor make one through a label.
  • roast weigh <roast> <grams> and roast landmark <roast> fc --at 7:30: an agent records the roasted weight and moves a landmark after a roast (latest for the newest roast). Grams typed are kept as typed (85.75, not 85.8).
  • A smoothed heater line (Settings → This browser → Heater on charts): the PID moves the heater every second, so its line jumps 10–20 points and crowds the scope. Smoothed draws a 15 s average: trailing on the live scope, centered on a finished roast in History. Display only (lib/scope/smooth.ts).
  • Pages keep their place (docs/ui-structure.md, "What a page keeps"): a top-bar link goes back to the page last open in that section (the bean you had open, Settings → Roasting); the link of the section you are in goes to its main page. The scope's view (Roast, 10 min, 30 min, All) and History's search, filters and sort survive a trip to another page. For the browser tab.
  • Settings → Roasting explains the RoR smoothing window and each algorithm (moving average, weighted, Savitzky-Golay), and says the server uses them at once.

Changed

  • The shop design makes room: when its lines do not all fit between the brand and the bottom zone, the least needed give way (the coffee line first, then the roast level), so nothing runs into the net weight and a gift message is kept.
  • Every label render runs in a sandboxed worker process (the server binary as rustroast-server label-render): 10 s, 1 GB of address space and 15 s of CPU, then it is stopped and the design's error says why; the server is unaffected. Typst does not stop a long for loop or a huge array by itself.
  • The built-in plain and gift designs on round labels: the gift's top line and dates no longer run past the circle, the plain's QR code is larger with a clear margin (both found by the new check). fit no longer goes below its min.
  • A token may edit a finished roast's record (details and weights, landmarks, cupping, Save as Profile) on any roaster: none of it reaches the roaster. Anything on a roast still running stays behind the per-roaster switch (Control).
  • The line under the top bar is the plain divider: the ink rule in the text colour read as a glitch in dark. Back links are grey instead of amber (amber is for BT and warnings). Settings → Alarms is flat like the other Settings pages.
  • History says "roasting" for the roast in progress, beside its name, which no longer hides it.

Fixed

  • Cupping scores follow the classic SCA form (Cupping): uniformity, clean cup and sweetness start at 10 (2 a cup over five cups; the editor shows the five cups) instead of 6, which cost a fresh form 12 points; taints and faults are recorded (taint_cups, fault_cups, roast cup --taint/--fault) and come off the total; the score is the final score (final_score: the ten's total less defects, with total_score, defects and its band) where it was the mean of the attributes (overall_score, gone). The server refuses scores the form does not allow (sweetness 7, acidity 7.3) with the reason, and an unknown roast with 404 (migration 034).
  • A call held for approval took over any request the same token had waiting for the same address, whatever its body: a second print of a roast got the first print's request back (2 labels in one design where 1 in another was asked), so approving it would have printed the wrong labels. Only the same body is the same request now; another is a request of its own (found by the Hermes eval's task 17).
  • A token could not print a roast's labels (POST /api/sessions/{id}/labels/print) when the roast was on a real roaster: the real-hardware rule held it back though a print never reaches the roaster. Printing and sharing a roast are now outside that rule; the token's preset still decides.
  • Label prints that the printer took and then aborted (0 pages, nothing printed, "sent" shown): the job now names the paper's size and tray as well as its type, rustRoast writes the whole request before reading the printer's answer (the HP answers early and the HTTP client stopped sending the PDF), and it follows the job to say whether it printed, waits for paper (sent with tray 1 empty: "feed the sheet into tray-1 and press OK") or was aborted.
  • Release binaries are stripped, and roast embeds the checked-in OpenAPI document instead of linking the server's router: after bag labels (Typst) the server was 85 MB and roast 81 MB; now 64 MB and 11 MB.
  • --roaster virtual_roaster (and any roaster heard but not set up in Roasters) was "not found" for plan add: the CLI now knows the roasters heard on MQTT and the simulator.
  • Moving, adding or removing a landmark on a finished roast left its numbers as they were at Complete: first crack moved off a lone early crack still showed the old first crack time, DTR, development RoR and AUC. They are recomputed now (status and end time stay).
  • roast profiles, roast profile show and roast plan add --profile saw only the seed ("public") profiles: one made in the app, imported, or saved from a roast is private by default, so an agent could not find or plan with it. The CLI now lists every profile.
  • The Roast page's Control tab scrolled at 1280×720 while Profile drive ran with fan assist on; drive-check.mjs now checks both tabs at 1280×720 with fan assist on.
  • After DROP the scope still projected BT and RoR ("−236 in 2:00").
  • The heater slider kept its last value after Drop or Cancel set the roaster to Manual at 0 %; it follows the roaster in Manual too, except while you drag it.
  • A guidance band showed beside N/A on a roast's FC and drop temperatures; an empty rating showed an amber dash in light.