<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Roastwarden - Releases</title>
    <subtitle>Self-hosted coffee roasting software, written in Rust: follow the roast live, compare it, repeat what worked, and keep green coffee and bag labels in one place.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://roastwarden.com/releases/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-11-03T00:00:00+00:00</updated>
    <id>https://roastwarden.com/releases/atom.xml</id>
    <entry xml:lang="en">
        <title>Roastwarden 0.25.0</title>
        <published>2026-11-03T00:00:00+00:00</published>
        <updated>2026-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-25-0/"/>
        <id>https://roastwarden.com/releases/0-25-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-25-0/">&lt;p&gt;rustRoast is now Roastwarden (roastwarden.com): coffee roaster control, written in Rust. The
rename goes all the way, from the dashboard and docs to the crates, the image, the variables
and the names a roaster sees, so the interface standard is public with its final names. An
install made as rustRoast upgrades in place: old variable names still work, the database and
backups are renamed at start, label designs and browsers carry over, and older roaster
firmware keeps its fail-safe for this release.&lt;/p&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;rustRoast is now Roastwarden&lt;/strong&gt; (Next 72, owner 2026-11-02; roastwarden.com). Every name
moves: the dashboard, the docs, the crates (&lt;code&gt;roastwarden-core&lt;/code&gt;, &lt;code&gt;-mqtt&lt;/code&gt;, &lt;code&gt;-autotune&lt;/code&gt;,
&lt;code&gt;-server&lt;/code&gt;), the server binary &lt;code&gt;roastwarden-server&lt;/code&gt;, the image
&lt;code&gt;roastwarden&lt;/code&gt;, the repository, the environment variables
(&lt;code&gt;ROASTWARDEN_*&lt;/code&gt;), the Agent Skill (&lt;code&gt;roastwarden&lt;/code&gt;), the database file &lt;code&gt;roastwarden.db&lt;/code&gt; and
its backups &lt;code&gt;roastwarden-&amp;lt;time&amp;gt;.db&lt;/code&gt;, the label helpers &lt;code&gt;roastwarden.typ&lt;/code&gt;, the report data
schemas &lt;code&gt;roastwarden.report.*&lt;/code&gt;, the metrics &lt;code&gt;roastwarden_*&lt;/code&gt;, the server&#39;s MQTT user, the
server&#39;s presence topic &lt;code&gt;roastwarden/server/status&lt;/code&gt;, the mDNS service &lt;code&gt;_roastwarden._tcp&lt;/code&gt;
and Home Assistant&#39;s ids and bridge topic. The &lt;code&gt;roast&lt;/code&gt; CLI and the roasters&#39; own topics
(&lt;code&gt;roaster/&amp;lt;id&amp;gt;/…&lt;/code&gt;) keep their names.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An install made as rustRoast upgrades in place&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/running-it/&quot;&gt;Running it&lt;/a&gt;, &quot;Upgrading from
rustRoast&quot;): &lt;code&gt;RUSTROAST_*&lt;/code&gt; variables are still read, with a warning naming them; the server
renames &lt;code&gt;rustroast.db&lt;/code&gt; (and its &lt;code&gt;-wal&lt;/code&gt;, &lt;code&gt;-shm&lt;/code&gt;) and every &lt;code&gt;rustroast-&amp;lt;time&amp;gt;.db&lt;/code&gt; backup at its
first start; migration 057 points saved label designs at &lt;code&gt;roastwarden.typ&lt;/code&gt;; the dashboard
moves its stored key and preferences to the new names, so nobody signs in again. For one
release the server also announces itself on &lt;code&gt;rustroast/server/status&lt;/code&gt; (with its own last
will) and browses &lt;code&gt;_rustroast._tcp&lt;/code&gt;, so firmware older than ESP32 3.7.0 keeps its fail-safe
until it is flashed; both go in the next release. Home Assistant, if it was on, finds the
roasters as new devices: delete the old ones.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.26.0</title>
        <published>2026-11-03T00:00:00+00:00</published>
        <updated>2026-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-26-0/"/>
        <id>https://roastwarden.com/releases/0-26-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-26-0/">&lt;p&gt;Profiles can be shared: a public link shows a profile&#39;s curve, targets and events and offers it
as a file, and another Roastwarden imports it from the link. The names from before the rename
are gone from the wire (firmware must watch &lt;code&gt;roastwarden/server/status&lt;/code&gt;, as the reference
ESP32 firmware does from 3.7.0), and the owner&#39;s production moved to
its new address.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shared profiles&lt;/strong&gt; (Next 72, &lt;a href=&quot;https://roastwarden.com/docs/profiles/&quot;&gt;Profiles&lt;/a&gt;, &quot;Sharing a profile&quot;): a public link for a
profile, &lt;code&gt;/P/&amp;lt;code&amp;gt;&lt;/code&gt;, whose page shows its curve, targets and events and offers it as
&lt;code&gt;profile.json&lt;/code&gt; (Roastwarden) and &lt;code&gt;profile.alog&lt;/code&gt; (Artisan); revocable, views and downloads
counted. &lt;strong&gt;Import from a link&lt;/strong&gt; reads another server&#39;s shared profile (public addresses
only, the address pinned, no redirects) and makes a profile that says where it came from.
Dashboard: a profile&#39;s page → Share, Profiles → Import from a link; agents: &lt;code&gt;roast profile share | shares | unshare | import-link&lt;/code&gt;; eval task 55. Migration 058.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Production moved to its new address&lt;/strong&gt; (the owner&#39;s instance; owner 2026-11-03:
&quot;fix things correctly and not by implementing a bunch of redirects&quot;): the old name answers
nothing. Browsers sign in and allow notifications again, the installed app is installed
again, and labels printed before with a QR code to the old name open nothing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;removed&quot;&gt;Removed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The names from before the rename, on the wire&lt;/strong&gt; (Next 72): the server no longer announces
itself on &lt;code&gt;rustroast/server/status&lt;/code&gt; (it clears that topic&#39;s retained message at start) or
browses &lt;code&gt;_rustroast._tcp&lt;/code&gt;, and both ACLs drop the old topic. Roaster firmware must watch
&lt;code&gt;roastwarden/server/status&lt;/code&gt; (the reference ESP32 firmware from 3.7.0). Old &lt;code&gt;RUSTROAST_*&lt;/code&gt;
variables and the database&#39;s old name are still read (&lt;a href=&quot;https://roastwarden.com/docs/running-it/&quot;&gt;Running it&lt;/a&gt;, &quot;Upgrading from
rustRoast&quot;).&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.27.0</title>
        <published>2026-11-03T00:00:00+00:00</published>
        <updated>2026-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-27-0/"/>
        <id>https://roastwarden.com/releases/0-27-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-27-0/">&lt;p&gt;The server&#39;s public address can be set in Settings → Roasting, so share pages, label QR codes
and approval links point where others can open them. After the rename, the last backup names
its file again, and a share link that is no longer active says so plainly. Checked live: a
server outside the LAN imported a shared profile from production over the internet.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Public address&lt;/strong&gt; in Settings → Roasting (&lt;code&gt;public_url&lt;/code&gt;): where share pages, label QR codes
and approval links point. It could only be set through the API before, and unset, a link
made by &lt;code&gt;roast&lt;/code&gt; on the server itself read &lt;code&gt;http://localhost:8080&lt;/code&gt;. The field fills in the
page&#39;s own address; &lt;code&gt;INSTALL.md&lt;/code&gt; says when to set it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The last backup names its file again&lt;/strong&gt;: after the rename (0.25.0) the backups were renamed
&lt;code&gt;roastwarden-&amp;lt;time&amp;gt;.db&lt;/code&gt; at start, but the record of the last run still named the old
&lt;code&gt;rustroast-&amp;lt;time&amp;gt;.db&lt;/code&gt;, so Settings → Backups and &lt;code&gt;roast status&lt;/code&gt; showed a copy that no longer
existed. Migration 059 renames the stored names the same way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A share link that is not active has its own error code&lt;/strong&gt;, &lt;code&gt;LINK_NOT_ACTIVE&lt;/code&gt; (404), so
&lt;code&gt;roast profile import-link&lt;/code&gt; says to ask for a new link instead of &quot;list what exists with
&lt;code&gt;roast roasts&lt;/code&gt;…&quot;.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.27.1</title>
        <published>2026-11-03T00:00:00+00:00</published>
        <updated>2026-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-27-1/"/>
        <id>https://roastwarden.com/releases/0-27-1/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-27-1/">&lt;p&gt;Two report pages get back the room they lost, and the README and user guide show the app:
Roasters, Label Studio, a share page and Reports, from a copy of the owner&#39;s own data.&lt;/p&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Traceability and roast-day plan pages breathe again&lt;/strong&gt;: on the trace report each roast&#39;s
small labels (Roasted, Roaster · profile, Green → roasted, Left) sat on the values under
them, and on the plan sheet a roast&#39;s title and its &quot;Last time&quot; note crowded the line above.
Both have room now.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;docs&quot;&gt;Docs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Screenshots in the README and the user guide&lt;/strong&gt;: Roasters, Add roaster, Label Studio (the
designs, a design read for sale, a Print sheet), a share page on a phone, the Reports page
and two roasts compared, all from a copy of production&#39;s data (&lt;code&gt;docs/images/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;scripts/dev/guide-shots.mjs&lt;/code&gt;&lt;/strong&gt; retakes them, and &lt;code&gt;docs/DEVELOPMENT.md&lt;/code&gt;, &quot;Guide
screenshots&quot;, says how to make the cleaned copy it runs on.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.27.2</title>
        <published>2026-11-03T00:00:00+00:00</published>
        <updated>2026-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-27-2/"/>
        <id>https://roastwarden.com/releases/0-27-2/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-27-2/">&lt;p&gt;On Beans, every button at the top now opens its form where you can see it.&lt;/p&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Beans&#39; Add bean, Record an order and Plan a roast open in view&lt;/strong&gt;: their forms opened
further down the page (above the stock table, inside Planned roasts), out of sight, so
the only sign a click worked was the scroll bar changing. All four forms, Add from a link
included, now open in one panel right under the buttons, titled with what it is, its button
marked as open and the first field focused; the same button or the × closes it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;docs&quot;&gt;Docs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The Printers screenshot is in the user guide too.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.28.0</title>
        <published>2026-11-03T00:00:00+00:00</published>
        <updated>2026-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-28-0/"/>
        <id>https://roastwarden.com/releases/0-28-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-28-0/">&lt;p&gt;Roastwarden has a mark of its own: Warden, a shield keeping the amber flame, on the top bar, the favicon, the installed app&#39;s icon, the reports and the share pages.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A mark of its own&lt;/strong&gt;: Roastwarden&#39;s mark is now &quot;Warden&quot;, a shield in
the text colour keeping the amber flame, drawn for the name and picked by the owner from
fourteen marks over four rounds shown on the real app. It replaces Lucide&#39;s stock flame in
the top bar, the favicon (the shield follows the browser&#39;s colour scheme), the installed
app&#39;s icons (192, 512, maskable, apple-touch), and in one colour it heads the reports&#39;
footer line and the share pages&#39; &quot;Made with Roastwarden&quot;. README opens with the wordmark.
The SVGs live in &lt;code&gt;crates/server/assets/brand/&lt;/code&gt;; &lt;code&gt;scripts/dev/brand-icons.mjs&lt;/code&gt; renders the
bitmaps. The look stays Chart paper.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;docs&quot;&gt;Docs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Every guide screenshot retaken with the mark (README, USER-GUIDE, the Label Studio and
Reports guides).&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.29.0</title>
        <published>2026-11-03T00:00:00+00:00</published>
        <updated>2026-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-29-0/"/>
        <id>https://roastwarden.com/releases/0-29-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-29-0/">&lt;p&gt;Roastwarden has a public site, roastwarden.com: a home page, the documentation with search, and every release with a feed, made from the repository&#39;s own docs at each release.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;roastwarden.com, the project&#39;s site&lt;/strong&gt;: a home page, the documentation
(41 pages in six groups, with search) and every release with a feed, in the dashboard&#39;s
Chart paper look, light and dark, on a laptop and a phone. It is made from the repository&#39;s
own docs and changelog at each release, so it cannot drift from them: &lt;code&gt;site/sync.py&lt;/code&gt; turns
each published doc into a page (its links to other docs work, a doc named by its file reads
as its title), and the build fails on a dead link or anchor, or on a page that names the
owner&#39;s own machines. Built with Zola and Pagefind, served by static-web-server (all single
Rust binaries, the newest releases), with its own security headers; &lt;code&gt;site.yml&lt;/code&gt; checks it on
every push and deploys it at each release tag (&lt;code&gt;site/README.md&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;docs&quot;&gt;Docs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The docs the site publishes speak of &quot;your server&quot; and &quot;the host&quot; where they named the
owner&#39;s machines (DEPLOY, INSTALL, the user guide, the CLI, labels, agents).&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.30.0</title>
        <published>2026-11-03T00:00:00+00:00</published>
        <updated>2026-11-03T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-30-0/"/>
        <id>https://roastwarden.com/releases/0-30-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-30-0/">&lt;p&gt;roastwarden.com now speaks to roasters: &quot;Understand every roast. Build on every batch.&quot;, docs that open with what a roaster needs (the design notes stay in the repository), and an AI agent set up in three commands, &lt;code&gt;roast login&lt;/code&gt;, &lt;code&gt;roast skill install&lt;/code&gt; and &lt;code&gt;roast status&lt;/code&gt;. On a phone the Roast page&#39;s chart is about three quarters wider.&lt;/p&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;roastwarden.com speaks to roasters&lt;/strong&gt;: the home page leads with what the
app does for a roaster, &quot;Understand every roast. Build on every batch.&quot;, under &quot;Self-hosted
coffee roasting software, written in Rust&quot;; four outcomes (see what&#39;s happening, learn what
changed, repeat what worked, keep the batch together) replace the architecture pillars, and
&quot;The roast runs on the server&quot; explains the browser is only a window onto the roast. &quot;Will it
work with my roaster?&quot; answers logging and control apart. &quot;Any roaster, safely&quot; became
&quot;Connect your roaster, choose the control&quot;, which says what it does.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The docs have a roaster&#39;s part and design notes&lt;/strong&gt;: each published doc opens with what a
roaster needs (what it is, how to use it, its settings, the &lt;code&gt;roast&lt;/code&gt; command), and the site
publishes only that part; the design notes stay below a &lt;code&gt;&amp;lt;!-- site: end --&amp;gt;&lt;/code&gt; line in the same
file, so the two cannot drift apart. The build fails if the published part names another
project, a roadmap item, a dated decision or a code path (&lt;code&gt;site/README.md&lt;/code&gt;, &quot;Each doc has two
readers&quot;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The Roast page&#39;s chart on a phone&lt;/strong&gt; gives the curves the room: below 560 px wide the
temperature and RoR scales are narrower and carry no unit labels, and the 0–100 % scale is
left out (heater and fan show in the readouts and on touch), so the plot is about three
quarters wider. Wider screens are unchanged.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The simulated roaster runs the fluid bed&#39;s tuned PI gains&lt;/strong&gt; (8.355 / 0.22269 / 0, from its
server autotune) instead of the gains from before its tuning, whose D term made the heater
jump with every noisy reading: the simulator&#39;s heater now looks like the real roaster&#39;s.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The site&#39;s and README&#39;s pictures: the Roast page (laptop and phone) retaken with a smooth
heater, Label Studio showing the built-in designs, and Reports in the home page&#39;s gallery.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;An agent set up in three commands&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/agents/&quot;&gt;Agents&lt;/a&gt;, &quot;Setting up an agent&quot;):
&lt;code&gt;roast login &amp;lt;server&amp;gt;&lt;/code&gt; checks an API token and keeps the server and token in
&lt;code&gt;~/.config/roastwarden/roast.json&lt;/code&gt; (mode 600; never the owner key), so &lt;code&gt;roast&lt;/code&gt; and an
agent&#39;s shell need no environment; &lt;code&gt;roast skill install claude-code | hermes | agents&lt;/code&gt;
(or &lt;code&gt;--dir&lt;/code&gt;) writes the Agent Skill, now built into &lt;code&gt;roast&lt;/code&gt;, where Claude Code, Hermes or
Codex and other Agent Skills readers find it, naming the server; &lt;code&gt;roast skill show&lt;/code&gt; prints
it for any other agent; &lt;code&gt;roast logout&lt;/code&gt; forgets the token. Settings → Tokens shows the three
lines with the server&#39;s address once a token is made. A flag or &lt;code&gt;ROASTWARDEN_URL&lt;/code&gt; /
&lt;code&gt;ROASTWARDEN_TOKEN&lt;/code&gt; still wins over what was saved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The home page shows what an agent does&lt;/strong&gt;: &quot;Ask your agent&quot;, with what you can say and how
its token, approvals and the audit log keep it in hand; Reports, typeset by the built-in
Typst engine, is a feature of its own.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search&lt;/strong&gt;: the home page&#39;s title and description name what it is (&quot;self-hosted coffee
roasting software&quot;, Artisan Roaster Scope in full), schema.org &lt;code&gt;SoftwareApplication&lt;/code&gt; data,
and each release tells IndexNow (Bing and others) about every page.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The Read-only token preset&#39;s description said it reads the audit log; only Admin tokens and
the owner key do. &lt;code&gt;roast audit --help&lt;/code&gt; now lists the &lt;code&gt;labels&lt;/code&gt; kind.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.20.0</title>
        <published>2026-11-02T00:00:00+00:00</published>
        <updated>2026-11-02T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-20-0/"/>
        <id>https://roastwarden.com/releases/0-20-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-20-0/">&lt;p&gt;Phidgets, the thermocouple boards most gas roasters log with in Artisan, now work in rustRoast:
through Phidgets&#39; own network server, with no Phidgets software in rustRoast itself. Artisan&#39;s 13
Phidgets machines (Diedrich, Mill City and others) set up the way Artisan has them, so 189 of
its 264 machines now come across.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phidgets temperature boards&lt;/strong&gt; (Next 67, &lt;a href=&quot;https://roastwarden.com/docs/phidgets/&quot;&gt;Phidgets boards&lt;/a&gt;): a 1048, a TMP1101 on a VINT
hub, a TMP1100, a 1051 or a TMP1200 RTD, read through Phidgets&#39; network server on the computer
the board is plugged into. rustRoast speaks the network protocol itself (no Phidgets library in
the image): it logs in with the server&#39;s password, picks the board (serial number, hub port),
opens the inputs the mapping names (&lt;code&gt;ch0&lt;/code&gt;–&lt;code&gt;ch7&lt;/code&gt;; BT ch0 and ET ch1 by default, as Artisan),
sets each input&#39;s data interval and thermocouple type (or RTD element and wiring), and makes
each sample the mean of the readings since the last. Roasters → Add roaster → &quot;Phidgets
temperature board&quot;, or &lt;code&gt;roast roaster add ID --transport phidget22 --host … [--password …]&lt;/code&gt;;
the connection test lists the server&#39;s boards and reads them; a network server on the LAN is
found by mDNS. A Phidget connection only reads.&lt;/li&gt;
&lt;li&gt;The 13 Phidgets machines of Artisan&#39;s catalogue (Diedrich&#39;s 4 and 6 sensor logging, Mill City,
VNT, Titanium, Tostabar, Hive&#39;s Data Dome, Phidget&#39;s own databridges) are set up the way Artisan
has them: 189 of its 264 machines now (&lt;code&gt;roast roaster add ID --artisan … --host …&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tools/mock-device/mock_phidget22.py&lt;/code&gt;, a Phidget22 network server for the local stack, and
&lt;code&gt;scripts/dev/phidget22-check.sh&lt;/code&gt;: Phidgets&#39; own client against the mock and the real network
server (Docker) against rustRoast&#39;s.&lt;/li&gt;
&lt;li&gt;Hermes eval task 46: a Diedrich&#39;s 1048 on a Raspberry Pi&#39;s network server, from Artisan&#39;s
catalogue.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A password in a connection&#39;s config (a Phidget network server&#39;s) is shown as &lt;code&gt;********&lt;/code&gt; in
every answer, to the dashboard and the CLI alike; sending the mask back keeps the saved one.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.20.1</title>
        <published>2026-11-02T00:00:00+00:00</published>
        <updated>2026-11-02T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-20-1/"/>
        <id>https://roastwarden.com/releases/0-20-1/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-20-1/">&lt;p&gt;Add from a link now says which bags a Shopify supplier has sold out (it never could: the page
it read had no stock), and keeps each bag&#39;s shop id for the cart links to come. With it, the
research on letting an agent shop for green coffee: cart links and orders from receipts are
worth building, an agent-run store cart is not yet.&lt;/p&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Add from a link shows which bags are sold out&lt;/strong&gt; at the Shopify suppliers. The import read
Shopify&#39;s &lt;code&gt;/products/&amp;lt;handle&amp;gt;.json&lt;/code&gt;, which has no availability, so every bag showed as
unknown; it now reads the theme&#39;s &lt;code&gt;/products/&amp;lt;handle&amp;gt;.js&lt;/code&gt; (one request, &lt;code&gt;.json&lt;/code&gt; as the
fallback). A sold-out Sweet Maria&#39;s coffee shows its one size as sold out.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Theta Ridge&#39;s store links&lt;/strong&gt; (&lt;code&gt;shop.thetaridgecoffee.com&lt;/code&gt;) are read, as a named alias; the
fetch rules are unchanged.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Each bag size in an import&#39;s draft keeps the shop&#39;s own id (&lt;code&gt;variant_id&lt;/code&gt;) and the draft the
&lt;code&gt;product_id&lt;/code&gt;, and Shopify&#39;s currency when it says: what a cart link will need
(&lt;code&gt;docs/research/supplier-automation-2026-11-02.md&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Research: shopping for green coffee by agent&lt;/strong&gt; (&lt;code&gt;docs/research/supplier-automation-2026-11-02.md&lt;/code&gt;):
the suppliers&#39; UCP profiles, a multi-coffee cart link proven at Sweet Maria&#39;s and Burman, why
the store cannot return a hand-checked-out order, and a receipt-driven order import with
ordered → shipped → received. Go for cart links and receipts (proposed Next 77, 78); the
agent-managed MCP cart not now.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.21.0</title>
        <published>2026-11-02T00:00:00+00:00</published>
        <updated>2026-11-02T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-21-0/"/>
        <id>https://roastwarden.com/releases/0-21-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-21-0/">&lt;p&gt;Orders now come into rustRoast from their receipts: an agent reads the supplier&#39;s
confirmation email, the server checks that it adds up to the cent, is not there already and
which of your beans each line is, and keeps the email with the order, privately. The coffee is
on order until the box comes, and only then stock, priced with its share of shipping and tax.
The shopping list turns into one cart link per supplier that opens its cart with your bags
already in it; you check out there.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Orders from receipts&lt;/strong&gt; (Next 77, &lt;a href=&quot;https://roastwarden.com/docs/green-coffee/&quot;&gt;Green coffee&lt;/a&gt;, &quot;Orders&quot;): an agent reads a
supplier&#39;s order confirmation (email, PDF, photo) into an order and the server checks it:
the order number once per supplier (a second import, or the same receipt, is a 409 naming
the order), the lines − discount + shipping + tax equal to the total within a cent (or
&lt;code&gt;accept_mismatch&lt;/code&gt; with the owner&#39;s reason), and each line matched to a bean by the owner&#39;s
choice, the shop&#39;s variant id, the supplier&#39;s coffee code, the shopping list, then the name
(two candidates: ambiguous, the owner picks; none: a new bean). Orders go ordered → shipped
→ received, and &lt;strong&gt;only received writes stock&lt;/strong&gt;: a purchase per coffee line with its share
of the discount, shipping (by weight) and tax, whole cents that add up. Until then it is
&lt;strong&gt;on order&lt;/strong&gt; (&lt;code&gt;on_order_g&lt;/code&gt; on every bean, the Beans page, &lt;code&gt;roast beans&lt;/code&gt;, the low-stock
warning). Lines that are not coffee cost no bean. Migration 054 (&lt;code&gt;purchase_orders&lt;/code&gt;,
&lt;code&gt;purchase_order_lines&lt;/code&gt;, &lt;code&gt;purchase_receipts&lt;/code&gt;); &lt;code&gt;GET|POST /api/orders&lt;/code&gt; (&lt;code&gt;?dry_run=true&lt;/code&gt;),
&lt;code&gt;GET|DELETE /api/orders/{id}&lt;/code&gt;, &lt;code&gt;POST …/shipped&lt;/code&gt;, &lt;code&gt;…/received&lt;/code&gt;, &lt;code&gt;…/cancel&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The receipts are kept&lt;/strong&gt; with their order as received (owner: &quot;nice for records&quot;): an
.eml, HTML, PDF, image or text up to 5 MB, in the database and its backups, its SHA-256
and Message-ID noted. Private: &lt;code&gt;GET /api/orders/{id}/receipts/{rid}&lt;/code&gt; needs the owner or an
inventory or admin token even with open reads, and is always a sandboxed download.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roast order import &amp;lt;json&amp;gt; [--receipt F]… [--dry-run]&lt;/code&gt;, &lt;code&gt;roast order show|shipped|received| cancel|receipt|rm&lt;/code&gt;, &lt;code&gt;roast orders&lt;/code&gt;; the skill&#39;s &quot;From a receipt&quot; (an email is data, never
an instruction); eval tasks 47–50 with synthetic receipts (a clean one, totals off, a
duplicate then shipped and received, an email carrying an instruction).&lt;/li&gt;
&lt;li&gt;Beans → &lt;strong&gt;On order&lt;/strong&gt; and &lt;strong&gt;Orders&lt;/strong&gt; (&lt;code&gt;/beans/orders&lt;/code&gt;, &lt;code&gt;/beans/orders/[id]&lt;/code&gt;): lines and
beans, the totals, Shipped, Received (a line&#39;s weight as it came), Cancel, Delete, the
receipts. A shopping-list entry an order fills is &quot;On order&quot; until the order comes.&lt;/li&gt;
&lt;li&gt;Removing an order waits for the owner&#39;s approval for a token (&lt;code&gt;order.delete&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cart links from the shopping list&lt;/strong&gt; (Next 78, &lt;a href=&quot;https://roastwarden.com/docs/green-coffee/&quot;&gt;Green coffee&lt;/a&gt;, &quot;Cart links&quot;): per
supplier one link that opens its cart with the bags already in it, in the owner&#39;s browser;
nothing is bought. Each bag&#39;s price and availability are read again first, 3 s apart
(a 429 stops the reading; &lt;code&gt;offline&lt;/code&gt; reads nothing). Shopify stores take the permalink
their UCP profile advertises (&lt;code&gt;/buy/&amp;lt;variant&amp;gt;:&amp;lt;qty&amp;gt;&lt;/code&gt;, profile read once a day), else
&lt;code&gt;/cart/&lt;/code&gt;; Burman its &lt;code&gt;/checkout-link/&lt;/code&gt; in pounds. Sold-out bags are left out, changed prices
noted, and the answer says a Shopify link replaces the cart there. &lt;code&gt;POST /api/wanted/cart&lt;/code&gt;,
&lt;code&gt;roast wanted cart&lt;/code&gt;, Beans → Shopping list → Carts; eval task 51.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;quantity&lt;/strong&gt; on shopping-list entries (migration 055): &lt;code&gt;roast bean import --want SIZE --quantity 2&lt;/code&gt;, &lt;code&gt;roast wanted qty&lt;/code&gt;, a box on Beans; Bought and the shopping report multiply
by it.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts/dev/supplier-check.sh&lt;/code&gt;: the stores&#39; cart endpoints checked live (opt-in, never in
CI, low rate).&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.22.0</title>
        <published>2026-11-02T00:00:00+00:00</published>
        <updated>2026-11-02T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-22-0/"/>
        <id>https://roastwarden.com/releases/0-22-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-22-0/">&lt;p&gt;rustRoast now installs as one container: the server can run its own MQTT broker, with the
same locked-down rules as the Mosquitto it shipped with, so a Raspberry Pi needs nothing but
Docker, and a new install guide walks a new owner through it. Tested on arm64. The install
test also found two problems now fixed: a data folder the server cannot write stops it with
the fix in the message instead of running on a database in memory, and the broker restarts
cleanly.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One container, with the server&#39;s own MQTT broker&lt;/strong&gt; (Next 68, &lt;a href=&quot;https://roastwarden.com/docs/install/&quot;&gt;Install&lt;/a&gt;,
&lt;a href=&quot;https://roastwarden.com/docs/running-it/&quot;&gt;Running it&lt;/a&gt;, &quot;One container&quot;): &lt;code&gt;RUSTROAST_BROKER_ADDR=0.0.0.0:1883&lt;/code&gt; makes the server
run its own broker (rmqtt as a library, &lt;code&gt;broker.rs&lt;/code&gt;) and sign in to it itself, so an install
on a Raspberry Pi is one container and no Mosquitto. The same rules as the shipped Mosquitto:
no anonymous clients, the roasters in &lt;code&gt;MQTT_DEVICES&lt;/code&gt; each kept to &lt;code&gt;roaster/&amp;lt;id&amp;gt;/#&lt;/code&gt; and
reading the server&#39;s presence, retained messages kept in memory; &lt;code&gt;MQTT_PASSWORD&lt;/code&gt; is
optional. &lt;code&gt;docker-compose.single.yml&lt;/code&gt; (host networking, so mDNS reaches the LAN; every
&lt;code&gt;.env&lt;/code&gt; variable passed through) and it goes in each release&#39;s compose bundle with
&lt;code&gt;INSTALL.md&lt;/code&gt;, a step-by-step guide for a new owner. &lt;code&gt;GET /api/about&lt;/code&gt; and &lt;code&gt;roast status&lt;/code&gt;
have &lt;code&gt;mqtt_broker&lt;/code&gt; (where it listens, which roasters may sign in); Settings → About says
it on the MQTT line. Its passwords never reach the log, whatever &lt;code&gt;RUST_LOG&lt;/code&gt; asks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;rustroast_mqtt_connected&lt;/code&gt; in &lt;code&gt;/metrics&lt;/code&gt; read 0 while connected&lt;/strong&gt;: the client connected
before the loop that sets it subscribed to its events; it now starts from the client&#39;s state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A database that cannot be opened stops the server&lt;/strong&gt; instead of running on one in memory:
on a fresh install whose &lt;code&gt;data/&lt;/code&gt; folder the server (uid 999) could not write, it started
normally with a single warning and would have lost every roast at the next restart (found
installing on arm64). It now exits with the path and the fix (&lt;code&gt;sudo chown 999:999 data&lt;/code&gt;);
the owner-key error says the same.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The built-in broker binds again at once after a restart&lt;/strong&gt; (&lt;code&gt;SO_REUSEADDR&lt;/code&gt;): without it
&lt;code&gt;docker compose restart&lt;/code&gt; stopped the server with &quot;address in use&quot; for a minute.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The live-prediction test&#39;s CI flake&lt;/strong&gt; (&lt;code&gt;a_running_roast_is_predicted_kept_and_scored&lt;/code&gt;):
the background refit could fit a half-inserted history roast, a model that never reached
first crack; the test now fits after its history is in, and says the last answer it saw
when it fails. Production could not hit it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The skill&#39;s receipt flow&lt;/strong&gt;: a clean check on an order the owner asked to record is
recorded at once (it stopped to ask before; eval tasks 47 and 50); eval task 46&#39;s setup
hooks were missing. Eval tasks 46–51 pass on hermes.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.23.0</title>
        <published>2026-11-02T00:00:00+00:00</published>
        <updated>2026-11-02T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-23-0/"/>
        <id>https://roastwarden.com/releases/0-23-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-23-0/">&lt;p&gt;The live prediction now warns before the RoR goes wrong late in a roast: a crash, a flick
after first crack, or BT stalling short of DROP, from what this roaster&#39;s own model expects
in the next two minutes and what the RoR shows now, each with its reason. Alarms can fire on
them, and each roast&#39;s score says whether the warnings were right. Replayed on the owner&#39;s
roasts it gave no false crash or stall, and foresaw the RoR&#39;s rise after first crack a minute
ahead.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RoR warnings, live&lt;/strong&gt; (Next 69, &lt;a href=&quot;https://roastwarden.com/docs/prediction/&quot;&gt;Prediction&lt;/a&gt; &quot;RoR warnings&quot;): from DRY end to DROP
the live prediction warns before the RoR crashes (8 °C/min and half of it within a minute),
flicks up after first crack (2 °C/min above its low for 20 s) or BT stalls short of DROP
(1 °C/min or less for 30 s), from what the roaster&#39;s own model expects in the next two
minutes and what the RoR shows now, each with its reason (the heater at 100 % and where BT
settles at this fan, first crack&#39;s BT, the profile&#39;s own RoR, the PID catching up). The
scope&#39;s readout shows them with the BT model on; the alarm conditions RoR crash / flick /
BT stall ahead (&lt;code&gt;crash_ahead&lt;/code&gt;, &lt;code&gt;flick_ahead&lt;/code&gt;, &lt;code&gt;stall_ahead&lt;/code&gt;: seconds ahead, 0–120) fire on
them, and &quot;Add suggested&quot; adds the three. Each roast keeps the warnings it got (migration
056) and its prediction score sets them against what its RoR did (&lt;code&gt;ror_warnings&lt;/code&gt;,
&lt;code&gt;ror_events&lt;/code&gt;), on Roasters → Model and in &lt;code&gt;roast review&lt;/code&gt;; &lt;code&gt;roast live&lt;/code&gt; prints the RoR now,
ahead and the warnings. Replayed on the owner&#39;s roasts: no false crash or stall, the rise
after first crack foreseen 65 s ahead on two of them. Eval task 52.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.24.0</title>
        <published>2026-11-02T00:00:00+00:00</published>
        <updated>2026-11-02T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-24-0/"/>
        <id>https://roastwarden.com/releases/0-24-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-24-0/">&lt;p&gt;Two things for people beyond the owner&#39;s own setup. Each roaster can now show up in Home
Assistant as a device, read only: temperatures, RoR, heater, fan, the roast&#39;s phase and time,
and the RoR warning, through the server&#39;s own broker or Home Assistant&#39;s, checked against
Home Assistant itself. And a support bundle makes one file for a bug report, with every
secret removed and no green coffee or receipts in it.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Home Assistant discovery&lt;/strong&gt; (Next 70, &lt;a href=&quot;https://roastwarden.com/docs/home-assistant/&quot;&gt;Home Assistant&lt;/a&gt;): each roaster shows up in
Home Assistant as a device over MQTT discovery: bean and environment temperature, rate of
rise, setpoint, heater, fan, the roast&#39;s phase, roast time, whether a roast is open, its
name and the RoR warning (with its reason). Off until switched on (Settings → Home
Assistant, &lt;code&gt;roast home-assistant --on&lt;/code&gt;), read only: no entity sends a command. It publishes
through the server&#39;s own broker or Home Assistant&#39;s (&lt;code&gt;mqtt://host:port&lt;/code&gt;, a user and a
password kept as a secret). &lt;code&gt;MQTT_HOME_ASSISTANT_PASSWORD&lt;/code&gt; gives the built-in broker and
the shipped Mosquitto a &lt;code&gt;homeassistant&lt;/code&gt; user that may only read the discovery, the state
and the server&#39;s presence. Entities go unavailable when the roaster stops reporting or the
server stops, and disappear when discovery is switched off. Checked against Home Assistant
2026.10.4 on both brokers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Support bundle&lt;/strong&gt; (Next 71, &lt;a href=&quot;https://roastwarden.com/docs/support/&quot;&gt;Support bundle&lt;/a&gt;): Settings → About → Download the support
bundle, or &lt;code&gt;roast support-bundle&lt;/code&gt;, makes one JSON file for a bug report: the versions, the
environment&#39;s variable names, the roasters&#39; setups, contracts and newest readings, the
newest roast with its telemetry, settings, problems and the server&#39;s last 2000 log lines.
Every password, key and token is removed (fields named like secrets masked, then every
secret the server knows cut out of the text, logs included); green coffee, orders, receipts,
tokens and the audit log are left out.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.15.0</title>
        <published>2026-11-01T00:00:00+00:00</published>
        <updated>2026-11-01T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-15-0/"/>
        <id>https://roastwarden.com/releases/0-15-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-15-0/">&lt;p&gt;A roaster Artisan knows is now set up the way Artisan has it. rustRoast carries Artisan&#39;s own
machine catalogue: name your machine (or give your &lt;code&gt;.aset&lt;/code&gt; file) and its Modbus connection,
BT and ET, burner, air and drum outputs and extra curves are filled in, starting at control
off. A Modbus roaster may have several controllers on one RS-485 bus.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A roaster from Artisan&#39;s machine catalogue&lt;/strong&gt; (Next 65, &lt;a href=&quot;https://roastwarden.com/docs/artisan-machines/&quot;&gt;Roasters from Artisan&#39;s catalogue&lt;/a&gt;).
Artisan&#39;s own machine setups (264 &lt;code&gt;.aset&lt;/code&gt; files, trimmed and embedded from a pinned Artisan
commit by &lt;code&gt;scripts/artisan-machines.py&lt;/code&gt;; AGPL like rustRoast, named in the notices) become
rustRoast roasters: the Modbus RTU or TCP connection, BT and ET, the extra curves as channels,
the burner, air, drum and damper sliders as outputs with Artisan&#39;s factor and offset, the PID&#39;s
setpoint, at control off, with notes on what was guessed or left out. 145 machines plan a
roaster; the rest (Siemens S7, Phidgets, …) say why not. &lt;code&gt;GET /api/artisan/machines&lt;/code&gt;,
&lt;code&gt;POST /api/artisan/machines/plan&lt;/code&gt; (nothing saved), &lt;code&gt;roast roaster machines&lt;/code&gt;, &lt;code&gt;roast roaster add ID --artisan BRAND/MODEL | --aset FILE [--dry-run]&lt;/code&gt;, and Add roaster&#39;s &quot;Start from an
Artisan machine&quot;. Hermes eval task 42.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A Modbus register on its own slave&lt;/strong&gt; (migration 053): a register map entry&#39;s &lt;code&gt;unit_id&lt;/code&gt;
puts it on another controller of the RS-485 bus than the connection&#39;s, read and written at
its own address; &lt;code&gt;roast roaster map --register KIND:ADDRESS@SLAVE:TYPE:ROLE&lt;/code&gt;, a Slave column
in the register map editor.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.15.1</title>
        <published>2026-11-01T00:00:00+00:00</published>
        <updated>2026-11-01T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-15-1/"/>
        <id>https://roastwarden.com/releases/0-15-1/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-15-1/">&lt;p&gt;A fix for the command that lists Artisan&#39;s machines.&lt;/p&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;roast roaster machines coffed sr5&lt;/code&gt; takes several words without quotes (it took one and
refused the rest).&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.16.0</title>
        <published>2026-11-01T00:00:00+00:00</published>
        <updated>2026-11-01T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-16-0/"/>
        <id>https://roastwarden.com/releases/0-16-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-16-0/">&lt;p&gt;More of Artisan&#39;s machines are set up from its catalogue: 176 of 264 now (145 before). Fuji PID
roasters, Arduino TC4 machines (Hottop, Kaleido), Probat and Prisma over Artisan&#39;s WebSocket,
ROEST over MQTT, the Loring Smart Roast (BCD values) and Delta DTA/DTK lines (Modbus ASCII)
come with their connection, mapping, commands, outputs and channels, starting at control off.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;More of Artisan&#39;s machines set up from its catalogue&lt;/strong&gt; (Next 73,
&lt;a href=&quot;https://roastwarden.com/docs/artisan-machines/&quot;&gt;Roasters from Artisan&#39;s catalogue&lt;/a&gt;): 176 of the 264 machines now plan a roaster (145 before).
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fuji PIDs&lt;/strong&gt; (Sedona Elite, Phoenix ORO, Santoker, Wintop, Bideli): Artisan&#39;s Fuji
protocol is Modbus RTU, so a register map from its PXG, PXR and PXF tables: each PV as BT or
ET, the ET controller&#39;s output as the heater&#39;s read-back, its SV as the setpoint.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Arduino TC4 machines&lt;/strong&gt; (Hottop TC4, Kaleido Legacy): the serial TC4 connection as stock
aArtisan answers, the sliders&#39; serial commands as templates, the fan&#39;s output.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artisan&#39;s WebSocket machines&lt;/strong&gt; (Probat P Series III, G/UG, Sample Roaster, Prisma PLC):
the URL, Artisan&#39;s request poll, an ingest mapping of every channel, the sliders&#39;
&lt;code&gt;send(…)&lt;/code&gt; frames as templates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artisan&#39;s MQTT machines&lt;/strong&gt; (ROEST 100, 200, P3000): an ingest mapping of the channels&#39;
paths; the topic is the owner&#39;s (ROEST&#39;s data bridged into the server&#39;s broker).&lt;/li&gt;
&lt;li&gt;A curve named RoR is a channel in °C/min; a formula that is a scale (&lt;code&gt;x*1000&lt;/code&gt;) is applied.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BCD register values&lt;/strong&gt;: data types &lt;code&gt;bcd16&lt;/code&gt; and &lt;code&gt;bcd32&lt;/code&gt; in a register map (the Loring Smart
Roast&#39;s BT, ET and burner), read and written by the Modbus poller.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modbus ASCII&lt;/strong&gt;: &lt;code&gt;framing: &quot;ascii&quot;&lt;/code&gt; on a Modbus RTU connection (7 or 8 data bits), a client
of rustRoast&#39;s own (&lt;code&gt;modbus_ascii.rs&lt;/code&gt;), for Delta DTA and DTK controllers and 9 more machines;
&lt;code&gt;roast roaster add --transport modbus_rtu --framing ascii&lt;/code&gt;, Framing in the dashboard&#39;s RTU
forms.&lt;/li&gt;
&lt;li&gt;A WebSocket command template may carry &lt;code&gt;{id}&lt;/code&gt;, a request number, as Artisan&#39;s protocol wants.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roast roaster add --artisan&lt;/code&gt; takes &lt;code&gt;--telemetry-topic&lt;/code&gt; (MQTT) and &lt;code&gt;--host&lt;/code&gt; or
&lt;code&gt;--device-url&lt;/code&gt; (a WebSocket) for what the file cannot know; &lt;code&gt;--dry-run&lt;/code&gt; shows the mapping,
poll and templates. The Add roaster wizard takes every kind of plan.&lt;/li&gt;
&lt;li&gt;Hermes eval task 43: a Probat set up from Artisan&#39;s catalogue.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.17.0</title>
        <published>2026-11-01T00:00:00+00:00</published>
        <updated>2026-11-01T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-17-0/"/>
        <id>https://roastwarden.com/releases/0-17-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-17-0/">&lt;p&gt;The Roasters section reworked for three users at once: a home roaster, a small shop and a
builder of custom roasters. Add roaster is four steps (the roaster and its kind, how it
connects, a live check, then control), one connection form serves the wizard and a roaster&#39;s
page, and the roaster cards say at a glance which roasters are reading and ready.&lt;/p&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Roasters section, one pass for three users&lt;/strong&gt; (Next 74, &lt;code&gt;docs/ui-structure.md&lt;/code&gt;, &quot;Roasters
and Add roaster&quot;): a home roaster, a small shop and a builder of custom roasters, with the
common path first and the rest folded where it is needed (no basic/advanced mode).
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Add roaster is four steps&lt;/strong&gt;: Roaster (name, the id from it, the kind; a known machine from
Artisan&#39;s catalogue in one line; description and location folded) → Connection (how it
connects in plain words, only the needed fields, an Advanced fold; a Modbus roaster&#39;s
registers in the same step; &lt;strong&gt;Save and check&lt;/strong&gt; saves it at control off) → Check (the live
check; Test the connection; &lt;strong&gt;Map its fields&lt;/strong&gt; right there) → Control (Off, Manual or Full).
The kind now comes first, and the check comes before control is switched on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One connection form&lt;/strong&gt; for the wizard and a roaster&#39;s Add Connection (the second copy had
fewer fields); it fits a phone (the five protocol tabs ran off the screen).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Roaster cards&lt;/strong&gt; show reading or silent, kind, connection, control level and the check&#39;s
verdict; one &lt;strong&gt;Found, not added yet&lt;/strong&gt; list replaces the two tinted boxes; connection
templates are a small link at the bottom.&lt;/li&gt;
&lt;li&gt;A roaster&#39;s header shows its kind and control level; &lt;strong&gt;Delete&lt;/strong&gt; moved into Edit.&lt;/li&gt;
&lt;li&gt;The browser checks walk the wizard&#39;s common path at laptop and phone sizes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Add roaster&#39;s MQTT topic prefix defaulted to a bare &lt;code&gt;roaster/&lt;/code&gt;, so a roaster added with the
defaults never heard its data; it is now &lt;code&gt;roaster/&lt;/code&gt; and the roaster&#39;s id, as the firmware sends.&lt;/li&gt;
&lt;li&gt;A roaster&#39;s connection row no longer runs off a phone screen; the device contract says &quot;not
heard since the server started&quot; without &quot;API error 404&quot;.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.18.0</title>
        <published>2026-11-01T00:00:00+00:00</published>
        <updated>2026-11-01T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-18-0/"/>
        <id>https://roastwarden.com/releases/0-18-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-18-0/">&lt;p&gt;For anyone who builds their own roaster: one guide to what rustRoast expects of a roaster and
how to pick a transport, with four small working example roasters (an ESP32 on MQTT, a Modbus
TCP register layout, the TC4 serial lines, a WebSocket server), each tested end to end. Adding
a roaster that was already streaming now works from the command line, and a register of your
own can have a scale.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Connect your roaster&lt;/strong&gt; (Next 75, &lt;a href=&quot;https://roastwarden.com/docs/connect-your-roaster/&quot;&gt;Connect your roaster&lt;/a&gt;): for anyone who programs
their own roaster, what rustRoast expects on one page (readings, rates and units, the commands
each control level sends, emergency stop, the roaster&#39;s own safety, what the check reads), how
to pick a transport, and four small working example roasters in &lt;code&gt;examples/roasters/&lt;/code&gt;: an
ESP32 on MQTT with rustRoast&#39;s own names (nothing to map), a Modbus TCP register layout (roles
and a scale; Artisan reads it too), the TC4 serial lines as stock aArtisan answers them, and a
WebSocket server with its own JSON (a mapping and command templates). Each sketch compiles for
an ESP32 (&lt;code&gt;scripts/dev/examples-build.sh&lt;/code&gt;), each &lt;code&gt;roaster.json&lt;/code&gt; is tested end to end (the check
ok at control off, then heater, fan and emergency stop in the device&#39;s own syntax), and
&lt;code&gt;tools/mock-device/example_roaster.py&lt;/code&gt; runs each without its hardware. Add roaster&#39;s
Connection step links the guide.&lt;/li&gt;
&lt;li&gt;The register map editor has a &lt;strong&gt;Scale&lt;/strong&gt; column (0.1 for a temperature in tenths), and
&lt;code&gt;roast roaster map --register&lt;/code&gt; takes one on the type (&lt;code&gt;holding:0:int16*0.1:bt&lt;/code&gt;): a builder&#39;s
own int16 × 10 registers could not be entered before.&lt;/li&gt;
&lt;li&gt;Hermes eval task 44: a home-built ESP32 already streaming on MQTT, set up as a logger and
checked.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;roast roaster add&lt;/code&gt; on a roaster the server had already found streaming (MQTT auto-discovery)
failed with a 500: it now sets that roaster up in place, changing the connection it was heard
on, and refuses one that was added already (&lt;code&gt;CONFLICT&lt;/code&gt;). &lt;code&gt;POST /api/devices&lt;/code&gt; with an id that
exists answers 409 saying so, instead of a 500.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hardware-integration-guide.md&lt;/code&gt;: the field reference no longer calls the setpoint, mode and
heater enable required, and its Modbus slave example read the request at the wrong offsets.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.19.0</title>
        <published>2026-11-01T00:00:00+00:00</published>
        <updated>2026-11-01T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-19-0/"/>
        <id>https://roastwarden.com/releases/0-19-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-19-0/">&lt;p&gt;For anyone coming from Artisan: keep Artisan as the screen while rustRoast runs the roaster.
The server&#39;s Modbus slave serves the roaster to Artisan, and the server writes the Artisan
setup file that reads it, checked against Artisan 4.2.0 itself. The slave is now read-only
unless writes are switched on, and its writes go through the roaster&#39;s control gate and the
audit log.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Artisan as a screen&lt;/strong&gt; (Next 66, &lt;a href=&quot;https://roastwarden.com/docs/artisan-screen/&quot;&gt;Artisan as a screen&lt;/a&gt;): an Artisan user keeps Artisan
while rustRoast runs the roaster. The server&#39;s Modbus slave (&lt;code&gt;RUSTROAST_MODBUS_ADDR&lt;/code&gt;) serves
the roaster&#39;s BT, ET, heater and fan, and writes the Artisan setup that reads them (Settings →
About → Artisan screen, &lt;code&gt;roast artisan-screen --aset FILE&lt;/code&gt;, or &lt;code&gt;artisan/rustRoast.aset&lt;/code&gt; in the
compose bundle): Help → Load Settings in Artisan, then ON. Checked against Artisan 4.2.0 itself,
headless (&lt;code&gt;scripts/dev/artisan-screen-check.sh&lt;/code&gt;), in °C and °F. Read-only unless
&lt;code&gt;RUSTROAST_MODBUS_WRITES=on&lt;/code&gt;; then Artisan&#39;s Air and Burner sliders go through the roaster&#39;s
control gate like any command. The compose file has the lines, commented out.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roast artisan-screen&lt;/code&gt; and &lt;code&gt;roast status&lt;/code&gt; say whether the slave is on, the roaster it serves
and when Artisan last read; &lt;code&gt;GET /api/about&lt;/code&gt; has it as &lt;code&gt;artisan_screen&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Hermes eval task 45: an Artisan user asks for the file to load, read-only.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The Modbus slave serves the one real roaster heard when &lt;code&gt;RUSTROAST_MODBUS_DEVICE_ID&lt;/code&gt; is
unset (it named &lt;code&gt;esp32-001&lt;/code&gt;, which no roaster is), never a virtual one; with none or several it
serves nothing and says why. A read while the roaster is silent is refused (exception 0x0B) so
Artisan draws a gap, not 0 °C; the holding registers read back the roaster&#39;s own values.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The Modbus slave took writes from anyone who reached its port and sent them to the roaster
with no record. Writes are now off unless switched on, go through the roaster&#39;s control gate
when on, and every one, refused or not, is in the audit log (actor &lt;code&gt;modbus&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.10.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-10-0/"/>
        <id>https://roastwarden.com/releases/0-10-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-10-0/">&lt;p&gt;Safe to share, first steps (phase 1). A fresh install no longer runs open: with no key set it
makes its own owner key at first start, keeps it beside the database and logs it once, and
wrong keys or tokens are rate limited. Writes that read first wait for the database instead of
failing under load, the supplier pages in the tests are synthetic, and the deploy guide now
says what secrets a backup holds. The owner&#39;s install, which sets its key, works as before.&lt;/p&gt;
&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Secure by default&lt;/strong&gt; (ROADMAP Next 58, &lt;a href=&quot;https://roastwarden.com/docs/security/&quot;&gt;Security&lt;/a&gt;): a server on a network address with no
&lt;code&gt;RUSTROAST_API_KEY&lt;/code&gt; no longer runs open. It makes a 48-digit owner key at its first start,
keeps it in &lt;code&gt;owner.key&lt;/code&gt; beside the database (mode 600) and logs it once; the dashboard&#39;s key
prompt says where to find it. &lt;code&gt;RUSTROAST_AUTH=off&lt;/code&gt; keeps it open on purpose; a loopback
address with no key stays open (the local dev stack). An owner key shorter than 16
characters stops the start. An install with a key set (production) is unchanged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrong credentials are rate limited&lt;/strong&gt;: ten refused keys or tokens from one client within a
minute get 429 &lt;code&gt;TOO_MANY_FAILURES&lt;/code&gt; (with &lt;code&gt;Retry-After&lt;/code&gt;) for any credential until the minute
ends; reads with no credential carry on. &lt;code&gt;roast&lt;/code&gt; exits 5 with a hint to wait.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Supplier test pages are synthetic&lt;/strong&gt; (Next 61): the saved supplier answers the import&#39;s tests
read keep each site&#39;s shape and the product facts, with invented text in place of the
suppliers&#39; own copy.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DEPLOY.md said backups hold no secret&lt;/strong&gt;: they hold API token hashes, the Web Push key and
the notification tokens; it says so now.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Writes that read first wait for the database instead of failing&lt;/strong&gt;: saving
a label design&#39;s new version, a print run&#39;s state or a printer took a read snapshot first and
could fail with &quot;database is locked&quot; when telemetry wrote in between; they now take the write
lock at the start (&lt;code&gt;db::begin_write&lt;/code&gt;), as a new roast does since 0.9.0. Foreign keys are set
on every database connection by the connect options (they already were, by sqlx&#39;s default).&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.11.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-11-0/"/>
        <id>https://roastwarden.com/releases/0-11-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-11-0/">&lt;p&gt;Safe to share, continued (phase 1). The dashboard signs in instead of keeping the owner key:
each browser trades the key (or a token) once for a sign-in cookie it cannot read, and the
owner sees and ends signed-in browsers. Reads need a sign-in unless an install opens them (the
owner&#39;s does, behind Pocket ID), and the audit log is the owner&#39;s and admin tokens&#39; only. The
shipped MQTT broker no longer takes anonymous clients: the server and each roaster sign in, and
a roaster may use only its own topics (the owner&#39;s ESP32 is on firmware 3.6.0, which does). The
owner&#39;s homelab files moved to &lt;code&gt;contrib/homelab/&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A real login and protected reads&lt;/strong&gt; (ROADMAP Next 59, &lt;a href=&quot;https://roastwarden.com/docs/security/&quot;&gt;Security&lt;/a&gt; &quot;Logins&quot;): the dashboard
no longer keeps the owner key in the browser. It signs the browser in once with the key or a
token and gets an HttpOnly, SameSite=Strict cookie for 30 days from its last use; a key an
older dashboard saved is traded for a sign-in at its next load and removed. A new owner key
signs every owner browser out, a revoked token its browsers; the owner sees the signed-in
browsers under Settings → This browser and signs one or all out. A write carried by the
cookie must come from the dashboard&#39;s own page (403 &lt;code&gt;CROSS_SITE&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reads need a credential&lt;/strong&gt; on a server with a key, unless &lt;code&gt;RUSTROAST_READS=open&lt;/code&gt; (a trusted
LAN, or behind a proxy that signs people in; production sets it, so phones and second screens
past Pocket ID read as before). Health, the app shell, the API&#39;s description and share pages
stay open.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The shipped Mosquitto is locked&lt;/strong&gt; (Next 60, &lt;a href=&quot;https://roastwarden.com/docs/security/&quot;&gt;Security&lt;/a&gt; &quot;MQTT&quot;): no anonymous clients,
one MQTT listener (the WebSockets one is gone). The server signs in as &lt;code&gt;rustroast&lt;/code&gt; with
&lt;code&gt;MQTT_PASSWORD&lt;/code&gt; (now required in &lt;code&gt;.env&lt;/code&gt;); each roaster signs in as its device id
(&lt;code&gt;MQTT_DEVICES=&amp;lt;id&amp;gt;:&amp;lt;password&amp;gt;&lt;/code&gt;), and the ACL keeps it to &lt;code&gt;roaster/&amp;lt;id&amp;gt;/#&lt;/code&gt; and reading the
server&#39;s presence. A one-shot &lt;code&gt;mqtt-users&lt;/code&gt; service writes the password file and ACL at every
start. The owner&#39;s ESP32 runs firmware 3.6.0, which signs in.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The audit log is the owner&#39;s and admin tokens&#39; only&lt;/strong&gt;, whatever the read setting: its
bodies hold every change made. &lt;code&gt;roast audit&lt;/code&gt; needs an admin token.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The owner&#39;s leftovers are out&lt;/strong&gt; (Next 62): About links the CHANGELOG of the repository the
image was built from (&lt;code&gt;RUSTROAST_REPO_URL&lt;/code&gt;, stamped by CI; no link without it) instead of the
owner&#39;s Forgejo; &lt;code&gt;.env.example&lt;/code&gt; no longer names the owner&#39;s broker; the old
&lt;code&gt;start-server.sh&lt;/code&gt;, &lt;code&gt;STARTUP_INSTRUCTIONS.md&lt;/code&gt; and &lt;code&gt;tasks/&lt;/code&gt; are gone. The homelab deployment
(compose file, &lt;code&gt;deploy.sh&lt;/code&gt;, the hermes sync and how CI deploys to the owner&#39;s VM) moved to
&lt;code&gt;contrib/homelab/&lt;/code&gt;; &lt;a href=&quot;https://roastwarden.com/docs/running-it/&quot;&gt;Running it&lt;/a&gt; keeps what any install needs.&lt;/li&gt;
&lt;li&gt;The Hermes eval&#39;s Kenya cupping paragraph is written for the eval, no supplier&#39;s text.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The API shapes test read the purchases report for &quot;this month&quot;, whose chart came and went
with the date (on the 1st it held only that day&#39;s purchases); it reads from October now.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.12.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-12-0/"/>
        <id>https://roastwarden.com/releases/0-12-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-12-0/">&lt;p&gt;A cleaner top bar: the red STOP button shows only while there is something to stop (a roast
open, the heater on, the fan or drum running, hot beans) and stays out of the way on a cold,
idle roaster.&lt;/p&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;STOP shows only when there is something to stop&lt;/strong&gt; (owner, 2026-10-31): the header&#39;s red
STOP is there while a roast is open or the roaster&#39;s heater is on, its fan or drum runs, or
the beans are at 50 °C or more, and hidden on a cold idle roaster (where it would only run
the fan at full), on a roaster gone quiet, and on one whose control is off. Its tooltip says
why it shows.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.13.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-13-0/"/>
        <id>https://roastwarden.com/releases/0-13-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-13-0/">&lt;p&gt;After an emergency stop, the top bar&#39;s STOP turns into a Fan off button once the roaster has
cooled, so quieting the fan is one tap instead of a trip to the Roast page&#39;s slider.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fan off after an emergency stop&lt;/strong&gt; (owner, 2026-10-31): STOP runs the fan at full; once the
heater is off and the roaster (BT and ET) has cooled below the roast sequence&#39;s cool-down
temperature, the header&#39;s STOP becomes &lt;strong&gt;Fan off&lt;/strong&gt;, one tap instead of the Roast page&#39;s
slider. While it is still hot the button stays STOP, as the fan is what cools it; while the
server cools after DROP it turns the fan off itself.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.14.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-14-0/"/>
        <id>https://roastwarden.com/releases/0-14-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-14-0/">&lt;p&gt;rustRoast now has a licence: the GNU AGPL, version 3 or later, the same as Artisan. Settings →
About links it, the source code of the running version and the notices of every library and
font inside. Release images also run on arm64 (a Raspberry Pi), and each release carries a
compose bundle to install it with.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Licence: AGPL-3.0-or-later&lt;/strong&gt; (owner, 2026-10-31). &lt;code&gt;LICENSE&lt;/code&gt;, the licence in every crate
and the dashboard&#39;s &lt;code&gt;package.json&lt;/code&gt;, a README section, and &lt;a href=&quot;https://roastwarden.com/docs/contributing/&quot;&gt;Contributing&lt;/a&gt;: contributions
are AGPL with a sign-off and a grant that lets the maintainer also license them commercially.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Third-party notices in the image&lt;/strong&gt;: &lt;code&gt;/licenses/server.txt&lt;/code&gt; (every Rust crate with its
licence, the crates&#39; NOTICE files, the label fonts; cargo-about, &lt;code&gt;scripts/notices.sh&lt;/code&gt;) and
&lt;code&gt;/licenses/dashboard.txt&lt;/code&gt; (every npm package in the bundle; &lt;code&gt;apps/dashboard/notices.js&lt;/code&gt;),
beside &lt;code&gt;/licenses/LICENSE.txt&lt;/code&gt;. Settings → About links them and the source of the running
revision (&lt;code&gt;source_url&lt;/code&gt; in &lt;code&gt;GET /api/about&lt;/code&gt; and &lt;code&gt;roast status&lt;/code&gt;, with &lt;code&gt;licence&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Licence checks&lt;/strong&gt;: &lt;code&gt;cargo deny check licenses&lt;/code&gt; in CI&#39;s Audit job (&lt;code&gt;deny.toml&lt;/code&gt;), and the
dashboard&#39;s build fails on a bundled package under a licence not allowed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;arm64 images&lt;/strong&gt; (Next 64): a release tag publishes the image for linux/amd64 and
linux/arm64 (a Raspberry Pi 4 or 5), built on the amd64 runner with no emulation (Rust
cross-compiles; the runtime stage only copies). Pushes to &lt;code&gt;main&lt;/code&gt; stay amd64, so deploys do
not wait for it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A compose bundle with each release&lt;/strong&gt;: &lt;code&gt;rustroast-compose-vX.Y.Z.tar.gz&lt;/code&gt; (the compose file
pinned to the release, the Mosquitto config, &lt;code&gt;.env.example&lt;/code&gt;, the licence) beside the &lt;code&gt;roast&lt;/code&gt;
binaries; the release notes name the image. &lt;code&gt;docker-compose.yml&lt;/code&gt; takes &lt;code&gt;RUSTROAST_IMAGE&lt;/code&gt; and
&lt;code&gt;RUSTROAST_VERSION&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The image&#39;s runtime is assembled without running commands on the target: the &lt;code&gt;rustroast&lt;/code&gt;
user (same uid, 999) and the CA certificates come from a native stage; &lt;code&gt;curl&lt;/code&gt; stays in the
amd64 image only (the owner&#39;s deploy script uses it).&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.5.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-5-0/"/>
        <id>https://roastwarden.com/releases/0-5-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-5-0/">&lt;p&gt;Everything since v0.4.0 (2026-10-26): roasting itself, then the paperwork again. Autotune 2
(the server&#39;s relay test and PI designs) and the device contract with the gains held by the
server; deploys that wait for the roaster and a drive that resumes after a restart; roast
analysis in the server (&lt;code&gt;roast review&lt;/code&gt;, &lt;code&gt;roast compare&lt;/code&gt;); auto DROP by time, BT or
development; the roaster&#39;s own thermal model and its live prediction; roasted coffee with
its rest window; a new bean&#39;s first roast; the hardening of the release review. Then labels
and reports polished from a review: label notes, quicker labels after a roast, a report
comparing two roasts, smarter plan and profile reports, a layout check on every report.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Compare two roasts on paper&lt;/strong&gt; (ROADMAP Next 38; &lt;a href=&quot;https://roastwarden.com/docs/reports/&quot;&gt;PDF reports&lt;/a&gt;): a one-page report for
an A/B experiment: both roasts&#39; BT and RoR on one chart with their first crack and drop,
heater and fan strips, the phases on one time scale, first crack, drop, development and loss
as &quot;A → B&quot;, every &lt;code&gt;roast compare&lt;/code&gt; row with B − A, and each roast&#39;s notes.
&lt;code&gt;GET /api/reports/compare?roasts=A,B&lt;/code&gt; (one roast: it and the roaster&#39;s finished roast
before; none: the newest two); &lt;code&gt;roast report compare [A] [B]&lt;/code&gt;; Reports → &quot;Compare two
roasts&quot;, History → Compare → &quot;Compare report&quot;; eval task 37.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Label notes and a label name&lt;/strong&gt; (ROADMAP Next 37; &lt;a href=&quot;https://roastwarden.com/docs/labels/&quot;&gt;Bag labels&lt;/a&gt;, &lt;a href=&quot;https://roastwarden.com/docs/green-coffee/&quot;&gt;Green coffee&lt;/a&gt;):
a bean&#39;s own few tasting words (&quot;chocolate · coconut · apple&quot;) and a shorter name for its
labels, migration 044; with none, the bean suggests notes from the supplier&#39;s (&lt;code&gt;Use …&lt;/code&gt; on the
bean form). Labels print, first found: the notes asked for, the bean&#39;s label notes, the
roast&#39;s cupping notes, the supplier&#39;s cut at a sentence. &lt;code&gt;roast bean add|edit --label-notes --short-name&lt;/code&gt;; the skill says how to write them; eval task 36.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gift labels link to the roast&#39;s share page&lt;/strong&gt;: a design names its own QR default
(&lt;code&gt;qr: &quot;share&quot;&lt;/code&gt;; gift does) with a caption, sized by the code&#39;s modules. A gift PDF or print
makes the roast&#39;s share link if it has none; previews still do not.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Printed it, in view&lt;/strong&gt;: Download, Print and Printed it stay on screen on
a roast&#39;s Labels page and Label Studio → Print; Printed it moves the sheet&#39;s next free label
on there. One item editor on both pages. Labels and Report links on the after-roast summary
(once weighed) and on each roasted lot on Beans.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The report check&lt;/strong&gt;: every report kind&#39;s tests lay it out and fail on a
last page with under 20 mm of content, colliding text or text past the paper.&lt;/li&gt;
&lt;li&gt;The roast-day plan shows each planned coffee&#39;s last roast on its profile (first crack, drop,
loss, the notes&#39; result) and units in its write-in boxes; a profile with no roasts prints its
target curve and what to expect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;roast profile edit --expect KEY=LOW-HIGH&lt;/code&gt;&lt;/strong&gt;: a profile&#39;s own expected range (development,
loss, DTR, colour) over its level&#39;s default, as the profile editor sets it; &lt;code&gt;profile show&lt;/code&gt;
lists them. Light espresso on production: development 1:45–2:45 (owner 2026-10-30), so its
2:15 is no longer flagged against the light level&#39;s 1:00–2:00.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A new bean&#39;s first roast&lt;/strong&gt; (ROADMAP Next 33; &lt;a href=&quot;https://roastwarden.com/docs/new-bean/&quot;&gt;A new bean&#39;s first roast&lt;/a&gt;): before the first roast
of a bean on a roaster, what to expect: the profile to start from, first crack&#39;s BT and time
on the profile from this roaster&#39;s own roasts (± 3 °C, ± 4 °C for naturals, honeys,
wet-hulled and unknown processes), a natural&#39;s quiet first crack, a &quot;drop by development&quot;
offer when the profile&#39;s auto DROP is by time, a wet-hulled, low-density or wet bean&#39;s slower
drying, and the supplier&#39;s level and espresso advice quoted. After a roast, while the bean
has fewer than three on its profile: where first crack came, how far to move the drop to keep
the profile&#39;s development, development or loss outside the level&#39;s range in 15 s steps, a
late first crack, and &quot;if it tastes…&quot;. The bean page (&quot;First roast&quot; / &quot;Next roast of this
bean&quot;), the Roast page&#39;s step card (one line, the rest behind its ?), History → a roast.
&lt;code&gt;GET /api/beans/{id}/guide&lt;/code&gt;; &lt;code&gt;roast bean guide&lt;/code&gt;, &lt;code&gt;roast review&lt;/code&gt; prints the next-roast lines;
the skill&#39;s &quot;A new bean&quot;, eval task 35. Nothing is stored and nothing changes a profile by
itself.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Roasted coffee: resting and ready&lt;/strong&gt; (ROADMAP Next 31; &lt;a href=&quot;https://roastwarden.com/docs/roasted/&quot;&gt;Roasted coffee&lt;/a&gt;): every finished,
weighed roast is roasted coffee with its rest window: resting until the bag label&#39;s &quot;best
from&quot; date (by purpose and level), ready until 35 days (light) or 28 (darker), then past its
best. The Beans page opens with it: per espresso and filter what is ready, what is resting
and when the next is ready, how fast it goes (from coffee recorded as used) and the day to
roast again so a new roast has rested in time, running low below 200 g; each roast&#39;s coffee
left with Used…, Gave… and Finished; History → a roast shows what is left of it. Settings →
Roasting has the best-until days and the low mark. &lt;code&gt;GET /api/roasted&lt;/code&gt;, &lt;code&gt;GET /api/roasted/{id}&lt;/code&gt;, &lt;code&gt;POST /api/roasted/{id}/moves&lt;/code&gt; (used, given, discarded in grams, doses,
bags or all; or a count of what is left), &lt;code&gt;DELETE …/moves/{move}&lt;/code&gt;, migration 043. &lt;code&gt;roast roasted [--for espresso|filter] [--ready|--resting]&lt;/code&gt;, &lt;code&gt;roasted show|use|give|toss|count|undo&lt;/code&gt;
(a roast by id, &lt;code&gt;#N&lt;/code&gt;, &lt;code&gt;latest&lt;/code&gt; or name), the skill&#39;s &quot;Roasted coffee&quot;, eval task 34.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prediction from the roaster&#39;s own model&lt;/strong&gt; (ROADMAP Next 30; &lt;a href=&quot;https://roastwarden.com/docs/prediction/&quot;&gt;Prediction&lt;/a&gt;): each
roaster gets a thermal model fitted from its own roasts (&lt;code&gt;dBT/dt = gain · lag(heater/fan) + g(BT)&lt;/code&gt;, starting from the virtual roaster&#39;s physics), refitted after each roast and scored
with every roast left out in turn: on esp32_roaster_01&#39;s 12 roasts BT two minutes ahead is
within 1.6 °C RMS with the heater as it was, and with the roaster&#39;s PID on the plan (scored
where the roaster stayed in auto), against the scope projection&#39;s 34.5 °C. While a
roast runs the server predicts BT for the next two minutes under the plan (the drive&#39;s
setpoints with the roaster&#39;s PID, a held setpoint, or held heater and fan) with a 90 % band
and its reason, first crack from the BT it came at on this bean and profile, DROP from the
profile, and when the heater would run out; every 15 s it keeps the prediction with the roast
and scores it against the old projection. The scope&#39;s &lt;strong&gt;BT model&lt;/strong&gt; series (off by default)
beside &lt;code&gt;BT →&lt;/code&gt;; Roasters → Overview → &lt;strong&gt;Model&lt;/strong&gt; (scores, where BT settles, Refit). &lt;code&gt;GET /api/roaster/{id}/model&lt;/code&gt;, &lt;code&gt;POST …/model/fit&lt;/code&gt;, &lt;code&gt;GET /api/roaster/{id}/prediction&lt;/code&gt;, &lt;code&gt;GET /api/sessions/{id}/prediction&lt;/code&gt;, migration 042. &lt;code&gt;roast roaster model [--fit]&lt;/code&gt;, &lt;code&gt;roast live&lt;/code&gt;
and &lt;code&gt;roast review&lt;/code&gt; show it; the skill, eval task 33.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto DROP&lt;/strong&gt; (ROADMAP Next 29; &lt;a href=&quot;https://roastwarden.com/docs/profile-drive/&quot;&gt;Profile drive&lt;/a&gt;, &quot;Auto DROP&quot;): a profile can let Profile
drive mark DROP by itself, off by default: at its drop time, at its drop temperature (from
first crack on) or at its development time after first crack is marked. A countdown
(Settings → Roasting, 10 s by default) ends on the drop itself: the Roast page shows it with
Cancel (for that roast only; a resumed drive keeps it cancelled) and phones get a notice;
then DROP&#39;s step runs as for any DROP (heater off, fan to cool). It stays off when the
roaster&#39;s DROP step is off. The profile editor and page, &lt;code&gt;PATCH /replay&lt;/code&gt; &lt;code&gt;{&quot;auto_drop&quot;: false}&lt;/code&gt;, migration 041. &lt;code&gt;roast profile edit --auto-drop time|bt|development|off&lt;/code&gt;, &lt;code&gt;profile show&lt;/code&gt; and &lt;code&gt;roast live&lt;/code&gt; (the countdown), the skill, eval task 32; drive-check drops its roast
this way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Roast analysis&lt;/strong&gt; (ROADMAP Next 28; &lt;a href=&quot;https://roastwarden.com/docs/analysis/&quot;&gt;Roast review and compare&lt;/a&gt;): one reading of a roast in the
server: marks and phases, RoR (the early peak, each minute, the late low, the FC → drop
slope), BT against its profile (at 0:30–2:00, the overshoot after CHARGE, RMS from TP), how
the heater and fan were used (first 30 s, time at 90 % or more, the last minute), its numbers
judged against what was expected, and plain notes. &lt;code&gt;GET /api/sessions/{id}/analysis&lt;/code&gt;; &lt;code&gt;GET /api/analysis/compare&lt;/code&gt; lines 2–8 roasts up against one (every number with its difference,
the curves every 30 s, from when BT stays within 0.5 °C). &lt;code&gt;roast review &amp;lt;roast&amp;gt; [--vs &amp;lt;roast&amp;gt;]&lt;/code&gt; and &lt;code&gt;roast compare&lt;/code&gt;, which take &lt;code&gt;#12&lt;/code&gt; (a batch number) as well as an id or
&lt;code&gt;latest&lt;/code&gt;; the skill&#39;s &quot;Reading a roast&quot;; eval task 31.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resume a drive cut by a restart&lt;/strong&gt; (ROADMAP Next 26; &lt;a href=&quot;https://roastwarden.com/docs/profile-drive/&quot;&gt;Profile drive&lt;/a&gt;, &quot;Resume after a
restart&quot;): on boot a Profile drive a crash, a power cut or a restart by hand cut mid-roast
is kept as a stopped run where it stopped; the Roast page offers &lt;strong&gt;Heater on and resume
drive&lt;/strong&gt; with the old settings, and the profile picks up at the time it had reached, the time
lost added to its offset (the Drive tab shows &quot;profile at 4:52 (+0:40)&quot;). &lt;code&gt;roast live&lt;/code&gt; shows it with the resume
call; &lt;code&gt;POST /replay&lt;/code&gt; takes &lt;code&gt;resume_at_profile_seconds&lt;/code&gt;. Eval task 30.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploys wait for the roaster&lt;/strong&gt; (ROADMAP Next 25; &lt;a href=&quot;https://roastwarden.com/docs/running-it/&quot;&gt;Running it&lt;/a&gt;): &lt;code&gt;GET /api/busy&lt;/code&gt; says
whether a roaster is in the middle of something a restart would cut (a profile drive, the
server&#39;s autotune, cooling after DROP, its heater on, an open roast with beans at 50 °C or
more; the virtual roaster never counts). The deploy waits while a real roaster is busy (up to
2 h). &lt;code&gt;roast busy [--wait MINUTES]&lt;/code&gt; (exit 1 while busy), &lt;code&gt;roast status&lt;/code&gt;, Settings → About →
Roasters, the skill, eval task 29.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The workflow on the CI runners&lt;/strong&gt; (&lt;code&gt;docs/DEVELOPMENT.md&lt;/code&gt;, &quot;The loop&quot;): the dashboard&#39;s browser
checks (a driven roast and every page) run in CI against the image just built, and the deploy
waits for them; after the deploy CI installs the skill and &lt;code&gt;roast&lt;/code&gt; on hermes
(&lt;a href=&quot;https://roastwarden.com/docs/agents/&quot;&gt;Agents&lt;/a&gt;, &quot;Kept current&quot;); Lint checks that a feature commit ships its agent path
and docs (&lt;code&gt;scripts/ci/agent-path.sh&lt;/code&gt;). &lt;code&gt;scripts/dev/prepush.sh&lt;/code&gt; and &lt;code&gt;scripts/dev/ci-watch.sh&lt;/code&gt;
for the laptop. &lt;code&gt;RUSTROAST_MQTT=loopback&lt;/code&gt; runs the server with no broker (the virtual roaster
only); the local stack uses it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The device contract&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/control/&quot;&gt;Control&lt;/a&gt;, &quot;Device contract&quot;; ROADMAP Next 24): what a
roaster must do for rustRoast (BT/ET out, heater and fan % in, over-temperature cutoffs,
sensor checks, the fan interlock, emergency stop, a comms watchdog, a retained presence) and
what it may offer as caps (onboard PID, feedforward, lease, time-proportioned heater,
oversampling, RoR limiter). The server runs the roast; the device keeps it safe.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gains after a boot&lt;/strong&gt;: the server holds each roaster&#39;s PID gains (the newest stored change,
or the gain schedule&#39;s for the fan) and sends them on the first sample after the server
starts and when the roaster&#39;s uptime goes back (it rebooted), if the roaster reports others.
Any transport.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A roaster against the contract&lt;/strong&gt;: &lt;code&gt;GET /api/roaster/{id}/contract&lt;/code&gt;, &lt;code&gt;roast roaster contract&lt;/code&gt;, and Roasters → Overview → Device contract: its firmware, the readings it sends,
its caps, and whether its PID gains are the ones the server holds, with the last check after
a boot (what it came up with, what the server sent). Eval task 28. First used on the real
roaster&#39;s 3.5.0 flash (2026-10-27): it came up on 5.5 / 0.085 / 0, the server sent back
5.561 / 0.08471 / 0.&lt;/li&gt;
&lt;li&gt;The roaster page&#39;s header stacks on a phone (the name, &quot;Selected roaster&quot; and the status
badge overlapped).&lt;/li&gt;
&lt;li&gt;Firmware 3.5.0 (Esp32_FluidBedCoffeeRoaster), per the contract: the onboard relay autotune
(with its overshoot guard and Ku/Tu rules) and the NVS-stored gains are gone. Gains live in
RAM and boot on the build&#39;s defaults (the Autotune 2 PI at 72 % fan) until the server sends
them; &lt;code&gt;control/pid&lt;/code&gt; and serial &lt;code&gt;PID;T&lt;/code&gt; no longer write flash.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autotune 2&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/autotune/&quot;&gt;Autotune&lt;/a&gt;; ROADMAP Next 23): the server runs the autotune on any
roaster that takes a heater % and reports BT, the virtual roaster too (manual heater, a
control lease): a heat-up that hands over early by the expected coast, the sensor&#39;s noise
for the switching band, a symmetric relay that finds the holding power, an asymmetric one
(the larger swing downward, where a heat-limited roaster has room). Every sample is kept;
the analysis fits integrator, first- and second-order models with dead time, checks them
against the oscillation&#39;s own harmonics and Berner&#39;s half-period ratio, and designs the PI
by SIMC (one knob, tight to smooth) with AMIGO and the old Ku/Tu rules beside it, each with
Ms, margins, what happens if the roaster changes, the heater jitter and the ramp lag. The
dashboard&#39;s Autotune tab shows the record against the model and the knob; the gain schedule
keeps each row&#39;s model and designs the gains between rows. API &lt;code&gt;…/tune/start|stop&lt;/code&gt;,
&lt;code&gt;GET …/tune&lt;/code&gt;, &lt;code&gt;…/autotune/runs/{id}/samples|design|analyse&lt;/code&gt;; &lt;code&gt;roast roaster tuning --run ID [--tau-c N]&lt;/code&gt;; eval task 27. The method is its own crate (&lt;code&gt;crates/autotune&lt;/code&gt;), with no roaster
knowledge, for reuse. Research: &lt;code&gt;docs/research/autotune-2026-10-26/&lt;/code&gt;. Migration 038.&lt;/li&gt;
&lt;li&gt;Firmware 3.4.0 (Esp32_FluidBedCoffeeRoaster): the thermocouples are read four times a second
and averaged (about half the noise, finer than one 0.25 °C step); temperatures go out with
two decimals. The virtual roaster reads the same way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The lookahead adapts&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/profile-drive/&quot;&gt;Profile drive&lt;/a&gt;, &quot;Lookahead&quot;; ROADMAP Next 22): Profile drive&#39;s
setpoint lead starts at the lookahead and, from a minute after CHARGE, follows how far BT
runs ahead of or behind the profile, between 0 and the lookahead. With the fixed 20 s lead
BT ran 10 s, then 20 s, ahead of the profile on the real roasts: first crack at 7:29
against the profile&#39;s 7:50. Settings → Roasting → &quot;Adapt to the roaster&quot; (on); the drive
tab shows the lead in use (&lt;code&gt;lookahead_now&lt;/code&gt;; &lt;code&gt;adapt_lookahead&lt;/code&gt; when starting a drive).&lt;/li&gt;
&lt;li&gt;The gain schedule&#39;s &lt;strong&gt;advice&lt;/strong&gt; (&lt;code&gt;docs/autotune.md&lt;/code&gt;, &quot;Checking a schedule&quot;): rows with a D
term (on this sensor it turns each 0.25 °C step into a heater jump) and neighbouring rows
that disagree, under the schedule editor and in &lt;code&gt;roast roaster tuning&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Each telemetry sample of a roast keeps the control fields the roaster sent (applied
heater, RoR limiter, PID gains, mode, heater power, stop code, feedforward) as &lt;code&gt;extras&lt;/code&gt;
(migration 037), and its time to the millisecond.&lt;/li&gt;
&lt;li&gt;The Roast page keeps the screen on while a roast runs; the Dev readout says when BT
reaches the profile&#39;s drop temperature at the RoR now.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Settings → About and &lt;code&gt;roast status&lt;/code&gt; say how far past its release a build is (&quot;0.4.0 + 66
commits since its release&quot;, with the commit&#39;s date: &lt;code&gt;build&lt;/code&gt; in &lt;code&gt;GET /api/about&lt;/code&gt;, from &lt;code&gt;git describe&lt;/code&gt; stamped by CI), and the dashboard says when an open tab is older than the server
(by revision, not only by version, which moves only at a release).&lt;/li&gt;
&lt;li&gt;A repeat Download of the same labels returns the run not yet printed (made in the last 24 h,
not sent or printed; &lt;code&gt;reused: true&lt;/code&gt;) instead of a new one; &lt;code&gt;new: true&lt;/code&gt; / &lt;code&gt;roast labels run --new&lt;/code&gt; always makes one.&lt;/li&gt;
&lt;li&gt;The message field follows the design: &quot;To&quot; with just the name for a design that prints its
own To, hidden for a design with a text &lt;code&gt;to&lt;/code&gt; setting.&lt;/li&gt;
&lt;li&gt;One brew recipe: &lt;code&gt;l.brew&lt;/code&gt; on labels is the share page&#39;s, by level (light espresso 1:2.5 at
94 °C, 18 g → 45 g; the share page said 1:2 for every espresso).&lt;/li&gt;
&lt;li&gt;Label text: &quot;Light · Espresso&quot;, weights as &quot;250 g · 8.8 oz&quot; rounded down like the net weight,
plain grows its type and centres on roomy labels (6874, 5163); the check&#39;s &lt;code&gt;cut&lt;/code&gt; says how
much of the label was free (&lt;code&gt;report.free&lt;/code&gt;) and two soft issues leave a label ok: &lt;code&gt;dropped&lt;/code&gt;
(name, roast date or best-from not printed) and &lt;code&gt;hint&lt;/code&gt; (shop with no brand).&lt;/li&gt;
&lt;li&gt;The coffee report&#39;s small multiples are one overlay of every roast&#39;s BT and RoR, the newest
in colour (schema &lt;code&gt;coffee/2&lt;/code&gt;; fixes its A4 break); the coffee, profile and period tables
name each roast.&lt;/li&gt;
&lt;li&gt;The first-crack prompt goes by BT: within 2 °C of the profile&#39;s first-crack BT (else
196 °C), after dry end. The RoR-dip rule it replaces came 45 s to 2½ minutes late on
every real roast (the PID hides the dip).&lt;/li&gt;
&lt;li&gt;Auto DRY end comes at the roast profile&#39;s own dry-end BT when it has one, else
&lt;code&gt;auto_dry_temp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The setpoint goes to the roaster in 0.2 °C steps (&lt;code&gt;follow_hysteresis_c&lt;/code&gt;; migration 035
moves a stored 1.0): 1 °C steps kicked the device PID every 7–12 s late in a roast.&lt;/li&gt;
&lt;li&gt;An autotune does not start while a roast is on the roaster (409) or below 45 % fan;
&lt;code&gt;mode: step_response&lt;/code&gt; and &lt;code&gt;aggressiveness&lt;/code&gt; are gone (the firmware has neither).&lt;/li&gt;
&lt;li&gt;Fan assist judges the heater the roaster applied (&lt;code&gt;heaterApplied&lt;/code&gt;), not the commanded one.&lt;/li&gt;
&lt;li&gt;The virtual roaster&#39;s PID works from the noisy 0.25 °C reading, as the firmware&#39;s does.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;removed&quot;&gt;Removed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The firmware&#39;s own autotune on the server side (firmware 3.5.0 has none; Autotune 2 replaces
it): &lt;code&gt;POST …/autotune/start|stop|apply&lt;/code&gt;, &lt;code&gt;GET …/autotune/status[/latest]&lt;/code&gt; and
&lt;code&gt;…/autotune/results[/latest]&lt;/code&gt;, the &lt;code&gt;autotune&lt;/code&gt; events on both dashboard WebSockets, the
dashboard&#39;s &quot;Onboard (firmware)&quot; mode and its scope overlay and banner, &lt;code&gt;roast roaster tuning --messages&lt;/code&gt;, and the &lt;code&gt;autotune_status&lt;/code&gt; / &lt;code&gt;autotune_results&lt;/code&gt; tables (migration 040). Stored
firmware runs stay in the run history, their rules still apply.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST …/control/ror_limit&lt;/code&gt; answers 409 for a roaster that lists its caps without &lt;code&gt;ror&lt;/code&gt;
(firmware 3.4.1 dropped its RoR limiter and ignored the command).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&quot;To for Sam&quot; on gift labels&lt;/strong&gt; (Next 35, L9): the owner&#39;s saved stamp (v2) and noel-tag (v3)
designs drop a message&#39;s own &quot;for&quot; or &quot;to&quot; before their &quot;To&quot; and wrap a long message to two
lines; the label check counts &quot;To Sam&quot; as the message &quot;for Sam&quot;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dates by the owner&#39;s time zone&lt;/strong&gt; (Next 35, L8): labels, share pages, print runs, roasted
coffee and reports made by the CLI or an agent were dated in UTC (a late-evening roast printed
as the next day). A &lt;code&gt;time_zone&lt;/code&gt; setting (Settings → Roasting, e.g. &lt;code&gt;America/Chicago&lt;/code&gt;) dates any
request that sends no offset of its own; the CLI sends its offset only when it is not UTC.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Labels and reports polish&lt;/strong&gt;.
Labels: long names and gift messages shrink, then wrap to two balanced lines (round labels
too; &lt;code&gt;wrap&lt;/code&gt; in &lt;code&gt;rustroast.typ&lt;/code&gt;); notes fill the room left (&lt;code&gt;clamp&lt;/code&gt; with a height); previews
and checks no longer create share links (a placeholder code; only a PDF, print or run makes
one); the check no longer reports printed fields as missing (labels get &lt;code&gt;roasted_iso&lt;/code&gt;,
&lt;code&gt;best_from_iso&lt;/code&gt;, &lt;code&gt;rest_days&lt;/code&gt;); the calibration page reads offsets from mm scales 3 mm inside
each label, clear of the printer&#39;s margin. Reports: rings on the spread strips no longer
collide, no page holds only a closing note, the roast report&#39;s RoR stays on its chart, table
numbers line up (grams below 1 kg), a year or month in progress says &quot;so far&quot; and folds
empty months, &quot;usual&quot; ranges never go below zero (the dashboard too), overlay and
heater/fan labels pushed apart, clearer wording (&quot;Cost per kg roasted&quot;, &quot;above light
1:00–2:00&quot;). Dashboard: Enter in the Typst editor makes a new line instead of taking a
completion; the cupping Scores, coffee and profile reports open on something real; a report
preview clears on an error or a new kind and Download waits for one; Print finds roasts by
coffee; a roast&#39;s label preview never shows a stale answer; Printers asks each printer again
and shows the answer&#39;s age; Save version is disabled with nothing changed; Label History
reads on a phone; &lt;code&gt;GET /api/sessions/{id}/cost&lt;/code&gt; answers &lt;code&gt;null&lt;/code&gt; instead of 404 when the cost
is unknown.&lt;/li&gt;
&lt;li&gt;The Modbus master reads each block of its register map from the block&#39;s first register
(it read from address 0, so a map above register ~125 failed every poll) and never writes a
register marked not writable.&lt;/li&gt;
&lt;li&gt;The &quot;Profile drive stopped&quot; banner made the Roast page&#39;s right column scroll at 1280×720
and 1366×768 (also the heater-cut one): below 820 px tall its explanation is a tooltip.&lt;/li&gt;
&lt;li&gt;Settings → About on a phone: the schema name of migration 040 was wider than the screen and
scrolled the page sideways; it wraps now.&lt;/li&gt;
&lt;li&gt;Starting the server autotune right after a DROP: the roast&#39;s cooling ended inside the test
and switched the fan off, so the fan interlock held the heater at 0 and the test never
heated. Starting a test now ends the cooling first, as a new CHARGE does.&lt;/li&gt;
&lt;li&gt;&quot;Behind profile&quot; (and any &lt;code&gt;delta_*&lt;/code&gt; alarm) no longer fires after DROP: the profile holds its
last BT past its end, so the cooling beans read 9 °C behind 7 s after DROP (batches #6, #8).&lt;/li&gt;
&lt;li&gt;A drive on the real roaster stopped 4:08 into batch #6: firmware 3.4.0 read a lease renewal
that landed mid-check as 49 days old and cut the heater (stop code 7) 0.5 s after the server
renewed it. Firmware 3.4.1 signs the lease&#39;s age and reads it before the clock; the server&#39;s
stop reason now says when the roaster missed a renewal instead of &quot;the server went quiet&quot;.&lt;/li&gt;
&lt;li&gt;Firmware 3.4.1 drops the RoR limiter: on the firmware&#39;s 9 s RoR it cut the heater 19 times
while the drive caught up after that stop (30 °C/min above 150 °C), fighting Profile drive.
The over-temperature cutoffs, sensor checks, fan interlock and lease stay.&lt;/li&gt;
&lt;li&gt;A server restart after an Autotune 2 run stored the firmware&#39;s last autotune again as a new
run (its retained results message was compared with the newest run of any kind, now with the
newest firmware run). Migration 039 removes the copies.&lt;/li&gt;
&lt;li&gt;Autotune 2&#39;s hold-out check warned falsely on a drifting lag-dominant run (the real roaster
from cold, 2026-10-27): fitted again without the last cycle, the main lag slid to 6000 s
against the drift term. The hold-out now keeps the full fit&#39;s main lag.&lt;/li&gt;
&lt;li&gt;The gain schedule editor: emptying a gain box sent &lt;code&gt;null&lt;/code&gt; and the save failed with a server
error. An empty Kd now saves as 0; an empty Kp or Ki says so and stops the save. A row
typed over by hand no longer shows the autotune rule it came from (&quot;edited&quot;).&lt;/li&gt;
&lt;li&gt;First crack and dry end were counted from the session start where the roast time counts
from CHARGE (History&#39;s FC, &lt;code&gt;roast roasts&lt;/code&gt;, the Phases tab&#39;s shares and DTR); migration 036
corrects the stored times. The drying RoR starts at CHARGE.&lt;/li&gt;
&lt;li&gt;Resuming a drive&#39;s setpoint or heater left the roaster in the mode pressed by hand, so BT
stopped following while the drive showed live; it now restores the drive&#39;s mode.&lt;/li&gt;
&lt;li&gt;Pausing a roast stopped its drive&#39;s lease renewal and the roaster cut its heater 10 s
later; Pause stops the recording only.&lt;/li&gt;
&lt;li&gt;A drive kept renewing its lease with no telemetry; it now lets the lease lapse after 8 s
and stops at 20 s.&lt;/li&gt;
&lt;li&gt;A stop could let a tick&#39;s command land after DROP&#39;s heater off and cooling fan; engaging
a ghost on the server loop sent the integral it built while watching (up to 100 %); a gain
change or a stale reading stepped the server PID&#39;s output.&lt;/li&gt;
&lt;li&gt;A profile fan below 45 % reached the roaster, whose interlock then gated the heater.&lt;/li&gt;
&lt;li&gt;A new CHARGE while the last roast still cooled could get that roast&#39;s fan-off.&lt;/li&gt;
&lt;li&gt;Scheduled gains were not resent after the roaster rebooted; undo could store the
schedule&#39;s RAM gains instead of the set stored before.&lt;/li&gt;
&lt;li&gt;&quot;Behind profile&quot; (and any rule armed after the turning point) never armed on a cold
charge, which has no turning point.&lt;/li&gt;
&lt;li&gt;The MQTT consumer spun on a closed channel.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Hardening with no visible change: the audit
log masks secret settings (the webhook URL and notification tokens, one at a time or in a
batch) and reads at most 1 MB of a body before auth; a notification test never sends a
stored token to a server the test changed (type the token again to test another server);
label assets are served sandboxed, so an uploaded SVG runs no script; an &lt;code&gt;.alog&lt;/code&gt; nested more
than 64 deep is refused instead of overflowing the stack.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.5.1</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-5-1/"/>
        <id>https://roastwarden.com/releases/0-5-1/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-5-1/">&lt;p&gt;One fix: Settings → About names the release on a release&#39;s own build.&lt;/p&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Settings → About read &quot;0.5.0 + 68 commits since its release&quot; on the 0.5.0 release itself: a
release commit deploys before its tag exists, so &lt;code&gt;git describe&lt;/code&gt; still counted from v0.4.0.
A version newer than the newest tag is now that release, with no count.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.5.2</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-5-2/"/>
        <id>https://roastwarden.com/releases/0-5-2/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-5-2/">&lt;p&gt;The overview docs catch up and stay caught up: README, CLAUDE.md, the dashboard&#39;s structure doc
and the user guide describe rustRoast as it is now, and a check in CI fails when code gains a
module, page, crate, setting or doc they do not mention.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scripts/ci/overview-docs.sh&lt;/code&gt;, in CI&#39;s Lint, &lt;code&gt;prepush.sh&lt;/code&gt; and &lt;code&gt;gates.sh&lt;/code&gt;: fails when a crate,
a server module, a dashboard section or page, or a &lt;code&gt;RUSTROAST_*&lt;/code&gt; / &lt;code&gt;MQTT_*&lt;/code&gt; variable the code
reads is missing from CLAUDE.md&#39;s Layout, README.md or &lt;code&gt;docs/ui-structure.md&lt;/code&gt;, or a doc is
not named in CLAUDE.md&#39;s &quot;Read first&quot;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;README rewritten for what rustRoast is now: the &lt;code&gt;roast&lt;/code&gt; CLI and skill, labels and reports,
green and roasted coffee, prediction, the virtual roaster, Autotune 2 and the
&lt;code&gt;rustroast-autotune&lt;/code&gt; crate, &lt;code&gt;/api/about&lt;/code&gt; and &lt;code&gt;/api/busy&lt;/code&gt;, and every environment variable
(the virtual roaster, the label sandbox, the build stamps, the CLI&#39;s URL and token).&lt;/li&gt;
&lt;li&gt;CLAUDE.md&#39;s Layout lists every server module (eleven were missing, and labels&#39; share and
retail, every reports file); &lt;code&gt;ui-structure.md&lt;/code&gt; every page; the user guide&#39;s top bar names
Labels and Reports; &quot;Read first&quot; names &lt;code&gt;new-bean.md&lt;/code&gt;, &lt;code&gt;green-coffee-sources.md&lt;/code&gt; and
&lt;code&gt;svrctlrs-parity.md&lt;/code&gt;. Every session now refreshes the overview docs before its release
(CLAUDE.md, &quot;Sessions&quot;).&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.6.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-6-0/"/>
        <id>https://roastwarden.com/releases/0-6-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-6-0/">&lt;p&gt;The first step toward other roasters: each roaster now says what rustRoast may send it. A
roaster added from now on only records until the owner switches its control on; the ESP32 and
the virtual roaster keep full control and work as before.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Roaster setup and the control gate&lt;/strong&gt; (ROADMAP Next 41, &lt;a href=&quot;https://roastwarden.com/docs/roaster-setup/&quot;&gt;Roaster kind and control level&lt;/a&gt;): each
roaster has a kind and a control level, &lt;code&gt;off&lt;/code&gt; (logs only: nothing is sent, not even STOP),
&lt;code&gt;manual&lt;/code&gt; (heater, fan, heater on/off, STOP) or &lt;code&gt;full&lt;/code&gt;. One check in the transport registry
refuses what the level does not allow, for every feature (control, the roast sequence,
alarm actions, Profile drive, fan assist, the gain schedule, the autotune, the Modbus
bridge): 409 &lt;code&gt;CONTROL_OFF&lt;/code&gt;. A roaster added or discovered from now on starts &lt;code&gt;off&lt;/code&gt;; the
ones that existed (the ESP32, the virtual roaster) are &lt;code&gt;full&lt;/code&gt; and work as before.
Roasters → a roaster → Control opens with Setup (owner only); the Roast page shows what a
roaster below full cannot do; Roasters → Add ends with the setup. &lt;code&gt;GET|PUT /api/roaster/{id}/setup&lt;/code&gt;; &lt;code&gt;roast roasters&lt;/code&gt; and &lt;code&gt;roast roaster show&lt;/code&gt; print the level.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.7.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-7-0/"/>
        <id>https://roastwarden.com/releases/0-7-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-7-0/">&lt;p&gt;Other roasters, step two: a roaster now declares its outputs (heater or burner, fan or air)
with ranges and a fan floor, and its kind brings sensible defaults, so a drum no longer gets
the fluid bed&#39;s CHARGE sequence or 45 % fan floor. Modbus RTU reaches PID controllers and
commercial drums over RS-485, the TC4 reads stock aArtisan boards, and connections apply at
once. The ESP32 works as before.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Outputs and kinds&#39; defaults&lt;/strong&gt; (ROADMAP Next 42, &lt;a href=&quot;https://roastwarden.com/docs/roaster-setup/&quot;&gt;Roaster kind and control level&lt;/a&gt;): a roaster&#39;s
setup declares its outputs (heater or burner, fan or air; each with a range and a step) and
a fan floor while heating. The control gate refuses a command for an output the roaster does
not have (409 &lt;code&gt;OUTPUT_NOT_DECLARED&lt;/code&gt;), clamps heater and fan to their ranges, and raises a fan
below the floor while the heater is on. The four hard-coded 45 % floors (roast sequence, fan
assist, Profile drive, autotune) are now the roaster&#39;s. Choosing a kind writes its defaults:
a drum&#39;s or a logger&#39;s roast sequence only records, and only the fluid bed has the 45 %
floor. Setup → Outputs edits them; the Roast page shows only declared outputs, with their
names and ranges; &lt;code&gt;roast roaster show&lt;/code&gt; prints them. Migration 046; the ESP32 keeps heater and
fan 0–100 % and its 45 % floor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modbus RTU&lt;/strong&gt; (ROADMAP Next 43, &lt;a href=&quot;https://roastwarden.com/docs/wire-formats/&quot;&gt;Wire formats&lt;/a&gt;): a serial RS-485
connection for PID controllers and commercial drums, with the same register map and commands
as Modbus TCP, in the Add roaster wizard and a roaster&#39;s connections.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The TC4 parser follows &lt;code&gt;CHAN&lt;/code&gt;&lt;/strong&gt;: a serial TC4 connection reads stock
aArtisan replies (only the active channels, then heater, fan and SV) with &lt;code&gt;reply_format: active&lt;/code&gt;, so heater and fan land in the right fields and SV is no longer read as the heater %;
old connections keep the fixed four-channel layout. Channels 3 and 4 come through as the live
fields &lt;code&gt;t3&lt;/code&gt; and &lt;code&gt;t4&lt;/code&gt;; the fan&#39;s output is a setting (IO3, OT2, DCFAN or none), and emergency
stop sends the fan to full on that output.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Pollers start, restart and stop as soon as a connection, register map or device status
changes, with no server restart. Register maps and connection settings are checked when
saved; a bad one gets a 400 saying what is wrong.&lt;/li&gt;
&lt;li&gt;Modbus reads cover coils and discrete inputs, each request has a timeout, and a read the
device refuses drops only its registers. A command to a register marked read-only fails and
names it.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.8.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-8-0/"/>
        <id>https://roastwarden.com/releases/0-8-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-8-0/">&lt;p&gt;Other roasters, step three, which finishes &quot;Any roaster&quot; phase 2. A device can now keep its own
field names, units (°F), MQTT topics and command syntax: the connection maps them, so nothing
needs reflashing. A drum roaster&#39;s drum speed and damper are outputs like the heater and fan.
Extra probes the owner declares (an exhaust, a third thermocouple, gas pressure) are kept with
each roast, drawn, usable in alarms and exported to Artisan. An agent can add, map and check a
new roaster (&lt;code&gt;roast roaster add | map | check&lt;/code&gt;) without ever switching its control on. A
simulated gas drum shows all of it working. The ESP32 works as before.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Adding a roaster: &lt;code&gt;roast roaster add | map | check&lt;/code&gt;&lt;/strong&gt; (ROADMAP Next 47,
&lt;a href=&quot;https://roastwarden.com/docs/add-roaster/&quot;&gt;Add a roaster&lt;/a&gt;): an agent (admin token) adds a roaster, its connection (presets: stock
aArtisan, Tasmota, ESPHome, Artisan WebSocket) and its setup at control off; maps its fields
(&lt;code&gt;--field&lt;/code&gt;, &lt;code&gt;--units&lt;/code&gt;, &lt;code&gt;--topic&lt;/code&gt;, &lt;code&gt;--column&lt;/code&gt;, &lt;code&gt;--register&lt;/code&gt;, &lt;code&gt;--command&lt;/code&gt;), trying the mapping
on the newest message first (&lt;code&gt;--try&lt;/code&gt;); and checks it. The server keeps each connection&#39;s
&lt;strong&gt;newest raw message&lt;/strong&gt; (&lt;code&gt;GET /api/roaster/{id}/raw&lt;/code&gt;; MQTT, WebSocket, TC4 lines, Modbus
registers) and &lt;strong&gt;previews&lt;/strong&gt; a mapping on it (&lt;code&gt;POST …/ingest/preview&lt;/code&gt;). The &lt;strong&gt;check&lt;/strong&gt;
(&lt;code&gt;GET …/check&lt;/code&gt;; dashboard: the roaster&#39;s Check tab) judges the rate, BT and ET, units (°F
at idle), declared outputs and channels, caps and control; the owner&#39;s &lt;strong&gt;write check&lt;/strong&gt;
(&lt;code&gt;POST …/check/write&lt;/code&gt;) sends heater 10 % to a cold roaster and always ends with emergency
stop. Roasters → a connection → &lt;strong&gt;Mapping&lt;/strong&gt; edits the mapping with the live preview.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An admin token may set up a roaster while its control is off and stays off&lt;/strong&gt; (session 23
decision, owner to confirm; &lt;a href=&quot;https://roastwarden.com/docs/roaster-setup/&quot;&gt;Roaster kind and control level&lt;/a&gt;, &quot;Tokens and the setup&quot;): raising control
stays the owner key&#39;s.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The simulated drum &lt;code&gt;virtual_drum&lt;/code&gt;&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/virtual-roaster/&quot;&gt;Simulated roasters&lt;/a&gt;): a gas drum beside the
fluid bed simulator that speaks its own MQTT in °F, set up through the normal connection,
mapping and setup on first switch-on (Roasters → Simulator, &lt;code&gt;PUT /api/virtual-roaster {&quot;drum&quot;: true}&lt;/code&gt;, &lt;code&gt;RUSTROAST_VIRTUAL_DRUM&lt;/code&gt;); turning point near 1:30 and first crack near
9 minutes at 60 % burner.&lt;/li&gt;
&lt;li&gt;Hermes tasks 38 (set up a TC4 logger, control off) and 39 (check the simulated drum).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ingest mapping and command templates&lt;/strong&gt; (ROADMAP Next 45, &lt;a href=&quot;https://roastwarden.com/docs/ingest-mapping/&quot;&gt;Mapping a device&#39;s own names&lt;/a&gt;): a
connection may carry its device&#39;s own field names, units, topics and command syntax, so a
device works without reflashing. MQTT: a topic prefix and telemetry topic of its own, JSON
paths per role (&lt;code&gt;bt ← MAX31855.Temperature&lt;/code&gt;), °F, scale and offset, extra channels, per-field
topics (ESPHome style: a sample on BT&#39;s message, fields older than 10 s left out), and
command templates (&lt;code&gt;heater → cmnd/roaster/Dimmer {value}&lt;/code&gt;); the server subscribes to the
topics as connections change, and a topic belongs to one roaster (400 naming the other). The
WebSocket client: the same, plus a &lt;code&gt;poll&lt;/code&gt; message with &lt;code&gt;{id}&lt;/code&gt; for devices that answer
requests (Artisan&#39;s WebSocket protocol). TC4: &lt;code&gt;units: F&lt;/code&gt; (&lt;code&gt;UNITS;F&lt;/code&gt;) and &lt;code&gt;columns&lt;/code&gt; for a
device&#39;s own CSV; templates for its lines. Modbus: a &lt;strong&gt;role&lt;/strong&gt; per register (migration 047;
the register map editor&#39;s Role column), read as the role&#39;s field and written by its
command, °F registers converted. A connection without these keys works as before; the
ESP32&#39;s is unchanged. &lt;code&gt;roast roaster show&lt;/code&gt; prints a mapping line per connection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drum and damper&lt;/strong&gt;: two more outputs a roaster&#39;s setup may declare (no
kind declares them), &lt;code&gt;POST /api/roaster/{id}/control/drum|damper&lt;/code&gt;, gated like the fan
(manual and full; undeclared 409 &lt;code&gt;OUTPUT_NOT_DECLARED&lt;/code&gt;; shaped to the range), carried by
every transport (a TC4 needs a template), kept with each sample, and a Drum and a Damper
slider on the Roast page&#39;s Control tab only when declared. Not yet in Profile drive, alarm
actions or the roast sequence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Declared channels&lt;/strong&gt; (ROADMAP Next 46, &lt;a href=&quot;https://roastwarden.com/docs/channels/&quot;&gt;Extra channels&lt;/a&gt;): a roaster&#39;s setup declares the
extra values it sends that the owner wants kept (an exhaust probe, a TC4&#39;s &lt;code&gt;t3&lt;/code&gt;, burner
pressure): key, label, unit (&lt;code&gt;C&lt;/code&gt;, &lt;code&gt;%&lt;/code&gt; or another, with an optional top), at most 8; migration
048. Each roast keeps a declared channel with every sample (&lt;code&gt;session_telemetry.extras&lt;/code&gt;, from
the in-memory setups); the scope draws it (°C on the temperature axis in the owner&#39;s unit, %
on the output axis, another unit on its own right-hand axis) with a legend toggle, and so do
the history chart and the PDF roast report. Alarms gain &lt;code&gt;channel_above&lt;/code&gt; / &lt;code&gt;channel_below&lt;/code&gt;
with a &lt;code&gt;channel&lt;/code&gt; key; the &lt;code&gt;.alog&lt;/code&gt; export writes the channels as Artisan &lt;code&gt;+Virtual&lt;/code&gt; extra
devices (two curves each) and still writes no &lt;code&gt;extra*&lt;/code&gt; key when none is declared. &lt;code&gt;roast roaster show&lt;/code&gt; lists the channels and &lt;code&gt;roast review&lt;/code&gt; prints each at CHARGE, first crack and
DROP (&lt;code&gt;channels&lt;/code&gt;). The Setup panel&#39;s Channels block declares them (keys offered from what the
roaster sends now). The ESP32 and the virtual roaster declare none and are unchanged.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.9.0</title>
        <published>2026-10-31T00:00:00+00:00</published>
        <updated>2026-10-31T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-9-0/"/>
        <id>https://roastwarden.com/releases/0-9-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-9-0/">&lt;p&gt;Lean home, deep roastery (phase 3). A home install can hide what it doesn&#39;t use: six optional
modules (reports, cupping, tuning, agents, costs, labels) behind three presets, and a new
install asks once what kind of roasting it is for. Weights show in grams or ounces per
browser. A roastery can record packed bags, whole bean or ground, straight from a label print,
and trace any bag back to its green lot. Drum roasters get two seed profiles, an agent may ask
to switch a roaster&#39;s control on (the owner approves), each roast keeps its own channels, and
Matrix rooms of the newest room version are accepted. The owner&#39;s install looks as before:
every module stays on.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Modules and a first-run page&lt;/strong&gt; (ROADMAP Next 49, &lt;a href=&quot;https://roastwarden.com/docs/modules/&quot;&gt;Modules and first run&lt;/a&gt;): six optional parts
(reports, cupping, tuning, agents, costs, labels) switch on and off in Settings → Modules,
with presets Home, Enthusiast and Roastery; an off module leaves the nav, menus and buttons
(its API stays). An install from before keeps everything on. A new, empty install opens
&lt;code&gt;/welcome&lt;/code&gt; once: the kind of roasting, temperature and weight units, time zone. &lt;code&gt;GET /api/about&lt;/code&gt; and &lt;code&gt;roast status&lt;/code&gt; report the modules; &lt;code&gt;roast modules&lt;/code&gt; changes them. Hermes task 40.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weight units&lt;/strong&gt; (Next 50): metric (g, kg) or US (oz, lb) per browser, Settings → This
browser; every weight field takes &quot;8 oz&quot;, &quot;2 lb&quot; or &quot;250 g&quot;. Labels still print both.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packed bags, ground coffee and traceability&lt;/strong&gt; (Next 51, &lt;a href=&quot;https://roastwarden.com/docs/roasted/&quot;&gt;Roasted coffee&lt;/a&gt;,
&lt;a href=&quot;https://roastwarden.com/docs/reports/&quot;&gt;PDF reports&lt;/a&gt;): a &lt;em&gt;packed&lt;/em&gt; move on a roasted lot (bags of a size, whole bean or ground
with a grind), from Beans → Packed…, &lt;code&gt;roast roasted pack &amp;lt;roast&amp;gt; 3x250g --ground espresso&lt;/code&gt;, or
a label print run (&quot;Take 3 × 250 g off this roast&quot;). Ground bags&#39; labels say so and are best
within 14 days of packing (&lt;code&gt;best_until_days_ground&lt;/code&gt;). &lt;code&gt;roast trace&lt;/code&gt; and the Traceability report
follow a roast back to its green lot, or a coffee to every bag. Migration 050. Hermes task 41.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Drum seed profiles and profiles by roaster kind&lt;/strong&gt; (Next 52, &lt;a href=&quot;https://roastwarden.com/docs/profiles/&quot;&gt;Profiles&lt;/a&gt;): a profile
can name the kind of roaster it is for (migration 051); two drum seeds (light filter, medium
omni); the library lists the selected roaster&#39;s kind first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An admin token may ask to switch a roaster&#39;s control on&lt;/strong&gt; (Next 48, owner 2026-10-31):
&lt;code&gt;roast roaster control &amp;lt;id&amp;gt; manual&lt;/code&gt;, held for the owner&#39;s approval; turning it off is at once.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Each roast keeps its channel list&lt;/strong&gt; (Next 48, migration 049), so history, reports and
Artisan export stay right after a roaster&#39;s probes change.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Modbus register map is editable on the roaster&#39;s page&lt;/strong&gt; (Next 48).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The interface standard&#39;s three tiers&lt;/strong&gt; (Logger, Manual, Full) are in
&lt;a href=&quot;https://roastwarden.com/docs/wire-formats/&quot;&gt;Wire formats&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;TC4 extra columns that are temperatures follow the device&#39;s unit (°F converted); a column
may override it (&lt;code&gt;exhaust:F&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;The skill&#39;s examples say &lt;code&gt;&amp;lt;roaster&amp;gt;&lt;/code&gt;, not the owner&#39;s ESP32 (Next 53).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Matrix rooms without a server part&lt;/strong&gt; (Next 54): since Matrix spec v1.16 a room version 12
id is &lt;code&gt;!&lt;/code&gt; and a hash, with no &lt;code&gt;:server&lt;/code&gt;; Settings → Notifications refused it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A roast that would not start under load&lt;/strong&gt;: creating a roast could fail with &quot;database is
locked&quot; when telemetry was written at the same moment; it now waits its turn.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.4.0</title>
        <published>2026-10-26T00:00:00+00:00</published>
        <updated>2026-10-26T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-4-0/"/>
        <id>https://roastwarden.com/releases/0-4-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-4-0/">&lt;p&gt;Everything since v0.3.0 (2026-10-25): the paperwork around a roast. An agent can do
everything the app does (&lt;code&gt;roast&lt;/code&gt; 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).&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reports&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/reports/&quot;&gt;PDF reports&lt;/a&gt;, 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, &lt;code&gt;format=json&lt;/code&gt;), with charts drawn natively in Typst. First, the &lt;strong&gt;roast report&lt;/strong&gt;:
the coffee; eight figures judged against the profile&#39;s range, its level&#39;s or this
roaster&#39;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. &lt;strong&gt;Reports&lt;/strong&gt; in the top bar with the kinds, paper, units and the server&#39;s own
pages as the preview; History → a roast → Report opens it there (the browser-printed
page is gone, its address redirects). &lt;code&gt;GET /api/reports&lt;/code&gt;, &lt;code&gt;GET /api/reports/roast/{id}&lt;/code&gt;
(&lt;code&gt;format=pdf|svg|png|json&lt;/code&gt;, &lt;code&gt;paper&lt;/code&gt;, &lt;code&gt;units&lt;/code&gt;, &lt;code&gt;tz&lt;/code&gt;); settings &lt;code&gt;report_paper&lt;/code&gt; (letter,
a4) and &lt;code&gt;report_units&lt;/code&gt; (C, F) in Settings → Roasting. &lt;code&gt;roast report roast &amp;lt;roast&amp;gt;&lt;/code&gt;,
&lt;code&gt;roast report list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cupping sheet&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/reports/&quot;&gt;PDF reports&lt;/a&gt;): 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&#39;s scores as a table with a dot strip per attribute and
the best named. &lt;code&gt;GET /api/reports/cupping&lt;/code&gt;, &lt;code&gt;roast report cupping [ROAST…] --blank&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Green inventory&lt;/strong&gt; and &lt;strong&gt;shopping list&lt;/strong&gt; reports (&lt;a href=&quot;https://roastwarden.com/docs/reports/&quot;&gt;PDF reports&lt;/a&gt;): 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 (&lt;code&gt;roast report inventory [--count]&lt;/code&gt;);
the shopping list as one order per supplier with the shipping its earlier orders carried,
then what is running low (&lt;code&gt;roast report shopping&lt;/code&gt;). Links from Beans.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coffee&lt;/strong&gt; and &lt;strong&gt;profile&lt;/strong&gt; reports (&lt;a href=&quot;https://roastwarden.com/docs/reports/&quot;&gt;PDF reports&lt;/a&gt;): a coffee&#39;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 (&lt;code&gt;roast report coffee&lt;/code&gt;); a profile&#39;s roasts ranked with an average row,
overlaid on the profile, and a dot strip each for first crack, drop, DTR and loss
(&lt;code&gt;roast report profile [--roaster]&lt;/code&gt;). Report on a bean&#39;s and a profile&#39;s page. The cupping
form lists its samples in the codes&#39; order, so the order the roasts were picked in tells
nothing, and the key gives each roast&#39;s date.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Period&lt;/strong&gt;, &lt;strong&gt;purchases&lt;/strong&gt; and &lt;strong&gt;roast-day plan&lt;/strong&gt; reports (&lt;a href=&quot;https://roastwarden.com/docs/reports/&quot;&gt;PDF reports&lt;/a&gt;): 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.
&lt;code&gt;roast report period|purchases|plan&lt;/code&gt;. Inventory dates older than ten months carry their
year, and a coffee&#39;s age is in months up to two years.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent parity check&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/agents/&quot;&gt;Agents&lt;/a&gt;): every API operation has an entry in
&lt;code&gt;bin/roast/surface.rs&lt;/code&gt;, the &lt;code&gt;roast&lt;/code&gt; 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
&lt;a href=&quot;https://roastwarden.com/docs/cli/&quot;&gt;The roast CLI&lt;/a&gt; or the skill, or a preset that cannot reach its call. &lt;code&gt;roast openapi --find&lt;/code&gt;
shows the command per operation.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roast&lt;/code&gt; commands for what the table showed missing: &lt;code&gt;edit&lt;/code&gt; (a roast&#39;s notes, rating,
colour, level, weights), &lt;code&gt;cup&lt;/code&gt; (cupping scores), &lt;code&gt;export&lt;/code&gt; / &lt;code&gt;import&lt;/code&gt; (&lt;code&gt;.alog&lt;/code&gt;, CSV), &lt;code&gt;rm&lt;/code&gt;;
&lt;code&gt;bean edit&lt;/code&gt;, &lt;code&gt;bean stock edit&lt;/code&gt; / &lt;code&gt;rm&lt;/code&gt; (fix a purchase), &lt;code&gt;order&lt;/code&gt; (several coffees, one
shipping charge); &lt;code&gt;plan edit&lt;/code&gt; / &lt;code&gt;move&lt;/code&gt; / &lt;code&gt;rm&lt;/code&gt;; &lt;code&gt;profile history&lt;/code&gt; / &lt;code&gt;edit&lt;/code&gt; / &lt;code&gt;save&lt;/code&gt; /
&lt;code&gt;import&lt;/code&gt; / &lt;code&gt;export&lt;/code&gt; / &lt;code&gt;rm&lt;/code&gt;; &lt;code&gt;roasters&lt;/code&gt;, &lt;code&gt;roaster show&lt;/code&gt;, &lt;code&gt;roaster tuning&lt;/code&gt;, &lt;code&gt;live&lt;/code&gt; (only
reads); &lt;code&gt;notifications [--test]&lt;/code&gt;, &lt;code&gt;settings [set]&lt;/code&gt;; &lt;code&gt;backups --check&lt;/code&gt;; &lt;code&gt;landmark --rm&lt;/code&gt;;
&lt;code&gt;show latest&lt;/code&gt;. &lt;code&gt;status&lt;/code&gt; names the token and its preset.&lt;/li&gt;
&lt;li&gt;Hermes eval tasks 8 (an order of two coffees) and 9 (how a profile&#39;s roasts went).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bag labels&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/labels/&quot;&gt;Bag labels&lt;/a&gt;): History → a roast → Labels… makes a PDF of labels on
Avery 5163, 6874 or 5294 sheets (or the owner&#39;s, &lt;code&gt;label_sheets&lt;/code&gt;), in a plain or a gift
design (or the owner&#39;s Typst files and fonts in &lt;code&gt;data/labels/&lt;/code&gt;), from any label of a
part-used sheet, with the server&#39;s preview; a calibration page and per-sheet offsets;
&quot;best from&quot; from sourced rest windows (Settings → Roasting). Laid out by Typst 0.15.
&lt;code&gt;roast labels&lt;/code&gt; and eval task 10.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Label studio, part 1&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/labels/&quot;&gt;Bag labels&lt;/a&gt;, &quot;Designing a label&quot;; ROADMAP Next 19):
&lt;code&gt;roast labels design check draft.typ&lt;/code&gt; (&lt;code&gt;POST /api/labels/check&lt;/code&gt;) lays one label out and
answers Typst&#39;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&#39;s source (&lt;code&gt;roast labels &amp;lt;roast&amp;gt; --source draft.typ&lt;/code&gt;, &lt;code&gt;POST /api/labels/render&lt;/code&gt;).
Designs declare parameters with defaults (&lt;code&gt;#let design = (params: …)&lt;/code&gt;, &lt;code&gt;--set art=cup&lt;/code&gt;).
New built-in designs: &lt;strong&gt;script&lt;/strong&gt; (calligraphy and a drawn flourish), &lt;strong&gt;ascii&lt;/strong&gt; (a
typewriter ticket with a drawing in characters), &lt;strong&gt;cherry&lt;/strong&gt; (a drawn coffee branch); the
JetBrains Mono NL font. An agents&#39; design guide (&lt;code&gt;skills/rustroast/label-design.md&lt;/code&gt;, its
examples compiled by a test) and Hermes eval task 11.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Label studio, part 2&lt;/strong&gt;: saved label designs with every version (&lt;code&gt;sam-gift@2&lt;/code&gt;; saved only
when they compile; rollback), assets (images and fonts, &lt;code&gt;image(&quot;logo.svg&quot;)&lt;/code&gt;), brand kits
(&lt;code&gt;l.brand&lt;/code&gt;: name, tagline, place, logo, colours, fonts) and a coffee&#39;s own label (a bean&#39;s
design, parameters and brand, used for its roasts). In the database (migration 030), so in
the backup. &lt;code&gt;roast labels design list|show|add|edit|rollback|use|rm&lt;/code&gt;,
&lt;code&gt;roast labels asset …&lt;/code&gt;, &lt;code&gt;roast labels brand …&lt;/code&gt;; deletes held for approval; eval task 12.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Label studio, part 3&lt;/strong&gt;: Settings → Labels, a gallery of every design as the server
renders it on 4 × 2, 3¾ × 3 and round labels, each saved design&#39;s versions and source with
&quot;Back to this&quot;, the assets and brands. The Labels page shows a design&#39;s parameters as a
form (text, ASCII art, colours, choices), picks a brand, starts with the coffee&#39;s own label,
and keeps a choice as the coffee&#39;s label (&quot;Use for …&quot;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Label studio, part 4: printing&lt;/strong&gt; over IPP to the label printer (&lt;code&gt;label_printer_url&lt;/code&gt;, the
owner&#39;s HP Color LaserJet Pro M252dw): the PDF at actual size (&lt;code&gt;print-scaling&lt;/code&gt; &lt;code&gt;none&lt;/code&gt;) on
the &quot;labels&quot; paper type, after asking the printer&#39;s state (a door open answers why,
nothing is sent). The Labels page&#39;s &lt;strong&gt;Print&lt;/strong&gt;; &lt;code&gt;roast labels &amp;lt;roast&amp;gt; --print&lt;/code&gt;,
&lt;code&gt;roast labels printer&lt;/code&gt;. A token&#39;s print is always held for the owner&#39;s approval, with the
sheet to load and the first label; eval task 13.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Label Studio, its own section&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/labels/&quot;&gt;Bag labels&lt;/a&gt;, &quot;Label Studio&quot;; ROADMAP Next 20,
part 1): &lt;strong&gt;Labels&lt;/strong&gt; 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 &lt;code&gt;codemirror-lang-typst&lt;/code&gt;: Typst 0.15&#39;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&#39;s errors are underlined at their span (&lt;code&gt;from&lt;/code&gt;/&lt;code&gt;to&lt;/code&gt; 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&#39;s own label (Coffees). A new built-in design,
&lt;strong&gt;shop&lt;/strong&gt;: the brand on top and, in the bottom 30 %, &quot;Whole bean coffee&quot;, the net weight and
who packed it, where. New label fields &lt;code&gt;net&lt;/code&gt; (&quot;Net wt 8.8 oz (250 g)&quot;, never rounded up)
and &lt;code&gt;weight_g&lt;/code&gt;. A check can lay a design out on a coffee (&lt;code&gt;bean&lt;/code&gt;, &lt;code&gt;roast labels design check --bean&lt;/code&gt;) or with a brand kit that is not saved (&lt;code&gt;brand_kit&lt;/code&gt;). Research:
&lt;code&gt;docs/research/studio-2026-10-25/&lt;/code&gt;, &lt;code&gt;docs/research/reports-2026-10-25/&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Print runs&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/labels/&quot;&gt;Bag labels&lt;/a&gt;, &quot;Print runs&quot;; 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&#39;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&#39;s own Download and Print are runs too.
&lt;code&gt;roast labels run|runs|reprint|sheets&lt;/code&gt;; &lt;code&gt;POST /api/label-runs&lt;/code&gt; and friends; a token&#39;s
print of a run is held for the owner with what to load.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Printers&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/labels/&quot;&gt;Bag labels&lt;/a&gt;, &quot;Printers&quot;; 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 &lt;strong&gt;calibration per sheet&lt;/strong&gt; (from the calibration
page&#39;s first and last labels; corners more than 0.5 mm apart are refused as scaling).
Printers &lt;strong&gt;without PDF&lt;/strong&gt; 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).
&lt;strong&gt;&lt;code&gt;ipps&lt;/code&gt;&lt;/strong&gt; with the printer&#39;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&#39;s Labels page.
&lt;code&gt;roast labels printer list|add|edit|rm|default|find|certificate|calibrate&lt;/code&gt;, &lt;code&gt;--printer&lt;/code&gt;,
&lt;code&gt;--tray&lt;/code&gt;, &lt;code&gt;--color&lt;/code&gt; on prints. The &lt;code&gt;label_printer_url&lt;/code&gt; setting became the default printer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The retail check&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/labels/&quot;&gt;Bag labels&lt;/a&gt;, &quot;The retail check&quot;; 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&#39;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&#39;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&#39;s (&lt;code&gt;label_bags&lt;/code&gt;, &lt;code&gt;label_bag&lt;/code&gt;; Brands tab). The report&#39;s lines carry &lt;code&gt;bold&lt;/code&gt;,
&lt;code&gt;x_mm&lt;/code&gt; and &lt;code&gt;cap_mm&lt;/code&gt; from the font&#39;s metrics. Designs get &lt;code&gt;l.retail&lt;/code&gt; and a &lt;code&gt;tall&lt;/code&gt; helper;
the shop design passes for a 12 oz pouch on 5163 and 6874. The design page&#39;s &quot;For sale on&quot;;
&lt;code&gt;roast labels design check --retail [BAG]&lt;/code&gt; and &lt;code&gt;--diff &amp;lt;design@N&amp;gt;&lt;/code&gt;; eval task 16.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The share page&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/labels/&quot;&gt;Bag labels&lt;/a&gt;, &quot;The share page&quot;; Next 20, part 5; migration 033):
what a bag&#39;s QR code opens for the person holding it, with no login. A public page per
roast at &lt;code&gt;/R/&amp;lt;code&amp;gt;&lt;/code&gt; (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&#39;s doses; never the roast&#39;s id, cost or stock. No script, nothing
loaded from elsewhere, its own CSP, &lt;code&gt;noindex&lt;/code&gt;, &lt;code&gt;no-referrer&lt;/code&gt;, &lt;code&gt;no-store&lt;/code&gt;. Scans counted
without addresses; a revoked link is the same 404 as an unknown one. Labels choose what the
QR code opens (&lt;code&gt;qr&lt;/code&gt;: &lt;code&gt;app&lt;/code&gt;, &lt;code&gt;share&lt;/code&gt;, &lt;code&gt;none&lt;/code&gt; or an address; &lt;code&gt;label_qr&lt;/code&gt; the default; the
Labels and Print pages&#39; &lt;strong&gt;QR code opens&lt;/strong&gt;), the share address in capitals so the QR code
is smaller. History → a roast → &lt;strong&gt;Share page&lt;/strong&gt;; &lt;code&gt;roast share [list|revoke]&lt;/code&gt;, &lt;code&gt;--qr&lt;/code&gt; on
label commands; a print&#39;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.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roast weigh &amp;lt;roast&amp;gt; &amp;lt;grams&amp;gt;&lt;/code&gt; and &lt;code&gt;roast landmark &amp;lt;roast&amp;gt; fc --at 7:30&lt;/code&gt;: an agent records
the roasted weight and moves a landmark after a roast (&lt;code&gt;latest&lt;/code&gt; for the newest roast).
Grams typed are kept as typed (85.75, not 85.8).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A smoothed heater line&lt;/strong&gt; (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 (&lt;code&gt;lib/scope/smooth.ts&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pages keep their place&lt;/strong&gt; (&lt;code&gt;docs/ui-structure.md&lt;/code&gt;, &quot;What a page keeps&quot;): 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&#39;s view
(Roast, 10 min, 30 min, All) and History&#39;s search, filters and sort survive a trip to
another page. For the browser tab.&lt;/li&gt;
&lt;li&gt;Settings → Roasting explains the RoR smoothing window and each algorithm (moving average,
weighted, Savitzky-Golay), and says the server uses them at once.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;shop&lt;/strong&gt; 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.&lt;/li&gt;
&lt;li&gt;Every label render runs in a &lt;strong&gt;sandboxed worker process&lt;/strong&gt; (the server binary as
&lt;code&gt;rustroast-server label-render&lt;/code&gt;): 10 s, 1 GB of address space and 15 s of CPU, then it is
stopped and the design&#39;s error says why; the server is unaffected. Typst does not stop a
long &lt;code&gt;for&lt;/code&gt; loop or a huge array by itself.&lt;/li&gt;
&lt;li&gt;The built-in plain and gift designs on round labels: the gift&#39;s top line and dates no
longer run past the circle, the plain&#39;s QR code is larger with a clear margin (both
found by the new check). &lt;code&gt;fit&lt;/code&gt; no longer goes below its &lt;code&gt;min&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A token may edit a &lt;strong&gt;finished&lt;/strong&gt; roast&#39;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 (&lt;a href=&quot;https://roastwarden.com/docs/control/&quot;&gt;Control&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;History says &quot;roasting&quot; for the roast in progress, beside its name, which no longer hides
it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cupping scores&lt;/strong&gt; follow the classic SCA form (&lt;a href=&quot;https://roastwarden.com/docs/cupping/&quot;&gt;Cupping&lt;/a&gt;): 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 (&lt;code&gt;taint_cups&lt;/code&gt;,
&lt;code&gt;fault_cups&lt;/code&gt;, &lt;code&gt;roast cup --taint/--fault&lt;/code&gt;) and come off the total; the score is the final
score (&lt;code&gt;final_score&lt;/code&gt;: the ten&#39;s total less defects, with &lt;code&gt;total_score&lt;/code&gt;, &lt;code&gt;defects&lt;/code&gt; and its
&lt;code&gt;band&lt;/code&gt;) where it was the mean of the attributes (&lt;code&gt;overall_score&lt;/code&gt;, 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).&lt;/li&gt;
&lt;li&gt;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&#39;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&#39;s task 17).&lt;/li&gt;
&lt;li&gt;A token could not print a roast&#39;s labels (&lt;code&gt;POST /api/sessions/{id}/labels/print&lt;/code&gt;) 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&#39;s
preset still decides.&lt;/li&gt;
&lt;li&gt;Label prints that the printer took and then aborted (0 pages, nothing printed, &quot;sent&quot;
shown): the job now names the paper&#39;s size and tray as well as its type, rustRoast writes
the whole request before reading the printer&#39;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: &quot;feed the sheet into tray-1 and press OK&quot;) or was
aborted.&lt;/li&gt;
&lt;li&gt;Release binaries are stripped, and &lt;code&gt;roast&lt;/code&gt; embeds the checked-in OpenAPI document instead of
linking the server&#39;s router: after bag labels (Typst) the server was 85 MB and &lt;code&gt;roast&lt;/code&gt;
81 MB; now 64 MB and 11 MB.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--roaster virtual_roaster&lt;/code&gt; (and any roaster heard but not set up in Roasters) was &quot;not
found&quot; for &lt;code&gt;plan add&lt;/code&gt;: the CLI now knows the roasters heard on MQTT and the simulator.&lt;/li&gt;
&lt;li&gt;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).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roast profiles&lt;/code&gt;, &lt;code&gt;roast profile show&lt;/code&gt; and &lt;code&gt;roast plan add --profile&lt;/code&gt; saw only the seed
(&quot;public&quot;) 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.&lt;/li&gt;
&lt;li&gt;The Roast page&#39;s Control tab scrolled at 1280×720 while Profile drive ran with fan assist
on; &lt;code&gt;drive-check.mjs&lt;/code&gt; now checks both tabs at 1280×720 with fan assist on.&lt;/li&gt;
&lt;li&gt;After DROP the scope still projected BT and RoR (&quot;−236 in 2:00&quot;).&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;A guidance band showed beside N/A on a roast&#39;s FC and drop temperatures; an empty
rating showed an amber dash in light.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.3.0</title>
        <published>2026-10-25T00:00:00+00:00</published>
        <updated>2026-10-25T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-3-0/"/>
        <id>https://roastwarden.com/releases/0-3-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-3-0/">&lt;p&gt;Everything since v0.2.0 (2026-10-23): rustRoast for daily use. Roast guidance reads each
roast against its profile and references; green coffee with purchases, costs, supplier
links and a shopping list; the roast sequence; nightly backups with a checked restore;
tokens, approvals and an audit log; notifications to a phone; the &lt;code&gt;roast&lt;/code&gt; command line and
an agent skill; an OpenAPI document from the code; a user guide. Releases now carry &lt;code&gt;roast&lt;/code&gt;
for Linux x86_64 and aarch64.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User guide&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/user-guide/&quot;&gt;User guide&lt;/a&gt;): setting up, roasting, History, profiles, beans and
costs, agents and tokens, the audit log, backups and restore, with screenshots.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Releases with the &lt;code&gt;roast&lt;/code&gt; binaries&lt;/strong&gt; (&lt;code&gt;.forgejo/workflows/release.yml&lt;/code&gt;, after svrctlrs):
a &lt;code&gt;v*&lt;/code&gt; tag that equals every crate&#39;s and the dashboard&#39;s version publishes a Forgejo
release with its CHANGELOG section and &lt;code&gt;roast&lt;/code&gt; for Linux x86_64 and aarch64 with SHA-256
sums (&lt;code&gt;docs/DEVELOPMENT.md&lt;/code&gt;, &quot;Releasing&quot;). &lt;code&gt;scripts/ci/check-version.sh&lt;/code&gt; checks a tag
before it is pushed; a push to &lt;code&gt;ci/release&lt;/code&gt; is a dry run.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit log filters and search&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/control/&quot;&gt;Control&lt;/a&gt;, &quot;Audit log&quot;): every entry has a kind
(control, roast, profile, beans, roaster, settings, access, backup, other); Settings →
Audit filters by kind, roaster, token and &quot;refused or failed&quot;, and searches the path,
body, token, user, address and client, on the server, so &quot;Older&quot; pages through the
matches. &lt;code&gt;GET /api/audit?kind=&amp;amp;q=&amp;amp;token=&amp;amp;failed=&lt;/code&gt;, &lt;code&gt;roast audit --kind --find --by --failed&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Purchases and what the coffee cost&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/green-coffee/&quot;&gt;Green coffee&lt;/a&gt;, migration 027): a purchase
records price, shipping, fees, currency, supplier, crop year and arrival; an order of
several beans splits its shipping by weight (Beans → Record an order, &lt;code&gt;POST /api/purchases&lt;/code&gt;); ledger rows can be edited or removed. A bean&#39;s cost per kg is the
weighted average of what is left. Each roast shows its coffee cost, per kg roasted and per
espresso and filter dose; Beans → Spending sums each month. Settings → Roasting sets the
currency, doses and bag size. &lt;code&gt;roast bean stock add … --price --shipping --currency&lt;/code&gt;,
&lt;code&gt;roast costs&lt;/code&gt;, and &lt;code&gt;roast show&lt;/code&gt; with the roast&#39;s cost.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add from a supplier&#39;s link and a shopping list&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/green-coffee/&quot;&gt;Green coffee&lt;/a&gt;, migrations
028–029): Beans → Add from a link previews a coffee from Sweet Maria&#39;s, Bodhi Leaf, Theta
Ridge, Coffee Bean Corral, Showroom, Happy Mug or Burman (specs, score, every bag size&#39;s
price; only those https hosts are fetched, adapters tested on saved pages) and saves it as
a bean, a shopping-list entry or a purchase. &quot;Bought&quot; on the shopping list adds the bean
and its priced purchase. Beans keep the supplier&#39;s details (region, producer, altitude,
grade, screen, certifications, score, link, the rest as labels), and the bean form
suggests earlier values plus seeded countries, processes and varieties. &lt;code&gt;roast bean import &amp;lt;link&amp;gt; [--want|--bought SIZE]&lt;/code&gt;, &lt;code&gt;roast wanted&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;roast&lt;/code&gt; command line&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/cli/&quot;&gt;The roast CLI&lt;/a&gt;, after svrctlrs&#39;s &lt;code&gt;svrctl&lt;/code&gt;): status, roasts and
one roast with its events, beans, stock purchases and corrections (&lt;code&gt;5kg&lt;/code&gt;, &lt;code&gt;2lb&lt;/code&gt; read as
grams), the roast plan, profiles, the audit log, backups, the OpenAPI document and any API
call, with an API token. Compact JSON without nulls on a pipe, text on a terminal, errors
with a code and a hint, and fixed exit codes (0 done … 3 a human must act … 7
unreachable). &lt;code&gt;backup&lt;/code&gt; and &lt;code&gt;restore&lt;/code&gt; run next to the database without the server
(&lt;code&gt;docker exec rustroast roast backup&lt;/code&gt;). The image ships it and uses &lt;code&gt;roast health&lt;/code&gt; as its
healthcheck. The server crate is now a library with two binaries.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;inventory&lt;/code&gt; token preset&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/control/&quot;&gt;Control&lt;/a&gt;, &quot;Tokens&quot;): reads, beans, stock and the roast
plan, nothing on a roaster or a roast, so an agent that records a purchase needs no admin
token. A test checks that every route a preset allows is in the OpenAPI document.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Approvals&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/control/&quot;&gt;Control&lt;/a&gt;, &quot;Approvals&quot;, after svrctlrs): a token that asks first (the
default for new tokens) cannot start a roast on a physical roaster or delete a roast, bean,
profile or roaster until the owner approves it in Settings → Approvals. The request goes out
through every notification channel with a link; the call waits an hour, and on approval runs
as the token through every check again. &lt;code&gt;roast&lt;/code&gt; exits 3 for a held call and waits with
&lt;code&gt;--wait&lt;/code&gt;; &lt;code&gt;roast bean rm&lt;/code&gt; and &lt;code&gt;roast approval&lt;/code&gt;. Settings → Tokens sets &quot;ask first&quot; per token.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agent skill&lt;/strong&gt; &lt;code&gt;skills/rustroast/SKILL.md&lt;/code&gt; (Hermes, Claude Code) and a Hermes eval
(&lt;code&gt;scripts/eval/hermes/&lt;/code&gt;, five tasks from &quot;I bought 5 kg of Ethiopia Guji&quot; to a roast start
on the real roaster): 6/6 with a cloud model and with local Qwen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;roast&lt;/code&gt; lists are for finding, as svrctl&#39;s: no timestamps, descriptions or user agents
(&lt;code&gt;profile show&lt;/code&gt; has a profile&#39;s); &lt;code&gt;beans --low&lt;/code&gt;; &lt;code&gt;openapi --find &amp;lt;word&amp;gt;&lt;/code&gt; lists only the
matching calls instead of the 320 KB document. The skill is linked for Claude Code in
&lt;code&gt;.claude/skills/&lt;/code&gt; as svrctlrs&#39;s is.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;OpenAPI document from the code&lt;/strong&gt; (&lt;code&gt;GET /api-docs/openapi.json&lt;/code&gt;, Swagger UI at &lt;code&gt;/docs&lt;/code&gt;,
committed as &lt;code&gt;docs/openapi.json&lt;/code&gt;): every &lt;code&gt;/api/&lt;/code&gt; operation with its parameters, request
and response types and errors, in place of the hand-written document that covered 28
routes. A route is served and documented by the same line, and tests fail when the
document is stale or an &lt;code&gt;/api/&lt;/code&gt; route is left out (&lt;code&gt;docs/DEVELOPMENT.md&lt;/code&gt;, &quot;OpenAPI&quot;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Roast sequence&lt;/strong&gt; per roaster (&lt;a href=&quot;https://roastwarden.com/docs/roast-sequence/&quot;&gt;Roast sequence&lt;/a&gt;): Charge turns heater power on,
sets the fan and starts Profile drive; Drop stops the drive, cuts heater power and cools
until BT and ET are below 45 °C, then the fan stops and Cool end is marked. Each step can be
switched off under Roasters → the roaster → Control; the step card says what Charge and
Drop will do.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cancel&lt;/strong&gt; a roast from the Roast page: the drive stops, heater power goes off, a hot
chamber cools, and nothing is saved.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resume after a heater cut&lt;/strong&gt;: when the roaster cuts its heater mid-drive, the Roast page
says so (with the firmware&#39;s reason from 3.3.2 on) and offers one tap to switch heater
power on and resume Profile drive; the roaster is left in Manual at 0 % meanwhile.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Undo&lt;/strong&gt; in the toast after marking a landmark (not for Charge and Drop, which act on the
roaster).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Beans and planned roasts&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/batches/&quot;&gt;Planned roasts and batches&lt;/a&gt;): a Beans section with green stock (a
ledger of purchases, corrections and roasts, with a low-stock note) and a queue of planned
roasts (bean, green weight, profile, roaster). The Roast page offers the next planned roast
(&quot;Start planned roast&quot;, Skip, Unplanned) and a bean picker that says what the roast leaves.
A roast from a bean takes its green weight off the stock; Cancel gives it back.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Batch numbers&lt;/strong&gt; per roaster, from 1 (existing roasts numbered by date), shown in History
(sortable &lt;code&gt;#&lt;/code&gt; column, search &lt;code&gt;#12&lt;/code&gt;), on the roast, the report and the Roast page; editable;
exported to Artisan as &lt;code&gt;roastbatchnr&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Profile guidance&lt;/strong&gt;: the profile page shows the level among the colour tiles, the results
it should give and how the profile&#39;s roasts ran on the selected roaster, with what to edit
(first crack against the profile&#39;s mark, development, loss and colour); the profile editor
judges the curve&#39;s development against the level as you edit and shows the phase split.
RoR on the History roast chart. Four more seed profiles: Extra-light filter (Nordic),
Cupping (SCA sample roast), Light espresso with a short development, Medium-light omni
(migration 021).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backups and restore&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/running-it/&quot;&gt;Running it&lt;/a&gt;, &quot;Backups&quot;): a nightly &lt;code&gt;VACUUM INTO&lt;/code&gt; copy of the
database with retention (14 by default, at 03:00 UTC, delayed while a roast runs), and
Settings → Backups with the last copy, the next run, &lt;strong&gt;Back up now&lt;/strong&gt;, a &lt;strong&gt;Check&lt;/strong&gt; of any
copy and a &lt;strong&gt;Restore&lt;/strong&gt; that checks it and puts it in place at the next start (the old
database is moved aside). A restore drill is in &lt;code&gt;DEPLOY.md&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Roast guidance&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/roast-guidance/&quot;&gt;Roast guidance&lt;/a&gt;): the app reads a roast&#39;s numbers against
cited, dated references and this roaster&#39;s own history. Profiles carry a roast level (the
SCA colour tiles on ground Agtron), a purpose and optional expected ranges; the History
roast page shows up to three lines naming only what is off and a thin range band beside
weight loss, colour, development, total time and FC / drop temperature, each with its
basis and sources; the report prints them, the History table marks values outside with a
dot, the Roast page shows the expected development window and, after Complete, takes the
weights and shows the summary. Temperatures and times are judged only against the
profile&#39;s own roasts (from three). &lt;code&gt;GET /api/profiles/{id}/history&lt;/code&gt;; migration 019.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Statistics&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/statistics/&quot;&gt;Statistics&lt;/a&gt;): AUC per phase and the RoR slope from first crack to
drop on a roast&#39;s page, its report and Compare; Compare&#39;s consistency band (mean ± σ of BT
and RoR), BT spread and a mean ± σ row; live AUC against the profile&#39;s in the Roast page&#39;s
Phases tab; the AUC start and base in Settings → Roasting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Audit log&lt;/strong&gt; (Settings → Audit, &lt;a href=&quot;https://roastwarden.com/docs/control/&quot;&gt;Control&lt;/a&gt;): every request that changed something,
refused ones included, with the credential, the proxy&#39;s signed-in user, the browser and
the address, the roaster and the body (secrets masked). Kept a year. Migration 022.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security headers&lt;/strong&gt; on every response (Content-Security-Policy, &lt;code&gt;X-Frame-Options&lt;/code&gt;,
&lt;code&gt;nosniff&lt;/code&gt;, &lt;code&gt;Referrer-Policy&lt;/code&gt;, &lt;code&gt;Permissions-Policy&lt;/code&gt;), &lt;a href=&quot;https://roastwarden.com/docs/security/&quot;&gt;Security&lt;/a&gt;, and CI gates for
&lt;code&gt;cargo audit&lt;/code&gt; (ignores explained in &lt;code&gt;.cargo/audit.toml&lt;/code&gt;) and &lt;code&gt;npm audit&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;API tokens&lt;/strong&gt; (Settings → Tokens, &lt;a href=&quot;https://roastwarden.com/docs/control/&quot;&gt;Control&lt;/a&gt; &quot;Tokens&quot;, &lt;a href=&quot;https://roastwarden.com/docs/security/&quot;&gt;Security&lt;/a&gt;): &lt;code&gt;rr_…&lt;/code&gt;
tokens for scripts and agents, stored hashed and shown once, with an expiry and three
presets (read-only, roast, admin) checked by method and path, deny by default. Only the
owner key manages them. A token changes nothing for a roaster other than &lt;code&gt;virtual_roaster&lt;/code&gt;
unless the owner switches on &quot;Tokens may control this roaster&quot; (Roasters → the roaster →
Control). The audit log names the token. Migration 023.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Alarm notifications and an installable app&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/push/&quot;&gt;Push and the installed app&lt;/a&gt;): the dashboard installs
as an app (manifest, icons, a service worker that caches only build assets), and every
fired alarm is pushed to the devices that turned notifications on (Settings → This
browser), with a test button and a device list. Web Push is encrypted and signed in the
server (RFC 8291 / 8292, no OpenSSL). Migration 024.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Notification channels&lt;/strong&gt; (&lt;a href=&quot;https://roastwarden.com/docs/notifications/&quot;&gt;Notifications&lt;/a&gt;): alarms also go to Gotify (or rstify),
ntfy, a Matrix room and the webhook, each switched on, saved and tested on its own card
under Settings → Notifications (the test uses the values as typed). Copied from svrctlrs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;System events&lt;/strong&gt; through the same channels, once when a problem starts and once when it
clears: the nightly backup failed, a roaster sent nothing outside a roast, a bean fell
below its low-stock mark. Open problems survive a restart. Migration 025.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Settings → About&lt;/strong&gt;: dashboard and server versions, revision, schema, uptime, MQTT and
database health, last backup, open problems and a link to this changelog at the running
revision. Every response carries an &lt;code&gt;x-request-id&lt;/code&gt;. The installed app has Roast, Beans and
History shortcuts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;security&quot;&gt;Security&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Secret settings (notification tokens, the webhook URL) are never read back through the
API, only whether they are set; a form resending them empty keeps them. Setting values
are checked on the way in (&lt;code&gt;PUT /api/settings&lt;/code&gt; writes several, all or none).&lt;/li&gt;
&lt;li&gt;The owner key is no longer accepted in a query string, except on the device WebSocket
(&lt;code&gt;/ws/device/…&lt;/code&gt;), where a roaster may not be able to set a header.&lt;/li&gt;
&lt;li&gt;Restoring a backup, managing tokens and the per-roaster token switch need the owner key.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One JSON error body&lt;/strong&gt; for every API error (&lt;a href=&quot;https://roastwarden.com/docs/control/&quot;&gt;Control&lt;/a&gt;, &quot;Errors&quot;):
&lt;code&gt;{&quot;success&quot;: false, &quot;error&quot;: {&quot;code&quot;, &quot;message&quot;}}&lt;/code&gt;, with codes such as &lt;code&gt;TOKEN_EXPIRED&lt;/code&gt; or
&lt;code&gt;REAL_HARDWARE&lt;/code&gt; for refused credentials; an unknown &lt;code&gt;/api/&lt;/code&gt; path is a JSON 404 instead of
the dashboard&#39;s page.&lt;/li&gt;
&lt;li&gt;The alarm webhook moved from Settings → Alarms to Settings → Notifications
(&lt;code&gt;alarm_webhook_url&lt;/code&gt; becomes &lt;code&gt;notify_webhook_url&lt;/code&gt;); its payload is unchanged.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stack upgrade&lt;/strong&gt; (&lt;code&gt;docs/stack-upgrade.md&lt;/code&gt;), step 1: dependencies updated within their
lines (tokio 1.54, rustls 0.23.46, anyhow, bytes; svelte 5.57, Kit 2.70, echarts 6.1,
Tailwind 4.3, Vite 7.3.7), prometheus 0.14 without protobuf, thiserror 2, hostname 0.4.
The unused utoipa and utoipa-swagger-ui crates and the adapter-auto package are gone; the
image builds on a pinned &lt;code&gt;rust:1.99-bookworm&lt;/code&gt; without OpenSSL; the dev shell is on
svrctlrs&#39;s nixpkgs (Rust 1.99, &lt;code&gt;cargo audit&lt;/code&gt;); CI clippy checks every target and dead code.&lt;/li&gt;
&lt;li&gt;Step 2: axum 0.8.9 with one tokio-tungstenite (0.29, axum&#39;s), tower-http 0.6.11. A POST
whose JSON body is optional still accepts an empty body; a malformed one is now a 400
instead of being ignored.&lt;/li&gt;
&lt;li&gt;Step 3: sqlx 0.8.6 without a TLS feature (clears RUSTSEC-2024-0363 and sqlx&#39;s rustls 0.21
chain). Its &lt;code&gt;RETURNING&lt;/code&gt; rows commit at once now.&lt;/li&gt;
&lt;li&gt;Step 4: rumqttc 0.25.1 on rustls 0.23 with ring as the one crypto provider (no aws-lc),
tokio-modbus 0.17, mdns-sd 0.21. One rustls in the build.&lt;/li&gt;
&lt;li&gt;Step 5: the dashboard on SvelteKit 3.1, Vite 8, Vitest 5, TypeScript 6, adapter-static 4,
jsdom 30 and &lt;code&gt;@lucide/svelte&lt;/code&gt; (&lt;code&gt;lucide-svelte&lt;/code&gt; is deprecated); &lt;code&gt;npm audit&lt;/code&gt; reports nothing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;removed&quot;&gt;Removed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The raw telemetry log (&lt;code&gt;telemetry&lt;/code&gt; table, every payload kept 7 days) and
&lt;code&gt;GET /api/roaster/{device_id}/telemetry&lt;/code&gt;, which read it: nothing else did, and it was most
of the database (379 MB → 8 MB on production). Live views use the 2 h ring; roasts keep
their own telemetry. &lt;code&gt;RUSTROAST_DB_RETENTION_SECS&lt;/code&gt; is gone. Migration 020.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;roast bean add --url&lt;/code&gt; (the offer&#39;s link) collided with the global &lt;code&gt;--url&lt;/code&gt; (the server):
it is &lt;code&gt;--link&lt;/code&gt; now, and a test fails when a subcommand reuses a global flag&#39;s name.&lt;/li&gt;
&lt;li&gt;Turning notifications on in the installed app failed with &quot;the app&#39;s service worker did
not start&quot;: the edge login redirected the browser&#39;s fetch of &lt;code&gt;/service-worker.js&lt;/code&gt;. The
static app shell now skips the login (&lt;a href=&quot;https://roastwarden.com/docs/running-it/&quot;&gt;Running it&lt;/a&gt;, &quot;Access&quot;).&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roast&lt;/code&gt; inside the container used the owner key from &lt;code&gt;RUSTROAST_API_KEY&lt;/code&gt; when no token was
given, so anything that could &lt;code&gt;docker exec&lt;/code&gt; acted as the owner (an agent in the eval did);
it uses only &lt;code&gt;--token&lt;/code&gt; / &lt;code&gt;RUSTROAST_TOKEN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Settings → Notifications was wider than a phone when a channel&#39;s last error held a long
URL (a Matrix send URL); the error now wraps.&lt;/li&gt;
&lt;li&gt;A bean&#39;s stock could read 3199.9998 g after a correction (moves summed as 32-bit
floats); stock is whole tenths of a gram.&lt;/li&gt;
&lt;li&gt;Applying a staged restore could fail with &quot;could not move rustroast.db-wal aside&quot; when
SQLite removed that file at the same moment.&lt;/li&gt;
&lt;li&gt;Errors from the device configuration API (&lt;code&gt;/api/devices&lt;/code&gt;, &lt;code&gt;/api/device-profiles&lt;/code&gt;) came as
&lt;code&gt;{&quot;error&quot;, &quot;status&quot;}&lt;/code&gt; instead of the one error body every other route uses.&lt;/li&gt;
&lt;li&gt;Editing a roast event (&lt;code&gt;PUT /api/sessions/{id}/events/{event_id}&lt;/code&gt;) always failed with a 500:
it set an &lt;code&gt;updated_at&lt;/code&gt; column the table never had. An unknown event is now a 404 and an
empty edit a 400.&lt;/li&gt;
&lt;li&gt;The History roast page and the profile page scrolled sideways on a phone.&lt;/li&gt;
&lt;li&gt;The stored AUC ran from the start of recording and through the cooling; it now runs from
CHARGE (or the AUC start) to DROP.&lt;/li&gt;
&lt;li&gt;Saving a roast&#39;s details after typing a weight failed with &quot;c.trim is not a function&quot;
(number inputs hand back numbers), and the first roasted weight entered left weight loss
empty until the next save.&lt;/li&gt;
&lt;li&gt;The Roast page&#39;s &quot;Autotune finished&quot; banner no longer applies gains itself: it saved the
firmware&#39;s own ZN-classic gains over a converted gain-schedule row. It links to the
Autotune tab, where the rule is chosen.&lt;/li&gt;
&lt;li&gt;The tab icon is the rustRoast flame (it was an amber circle that read as a warning).&lt;/li&gt;
&lt;li&gt;Profile drive no longer lags the profile for the first two minutes after Charge (on the
real roaster 7–11 °C behind, heater 2–40 %): the firmware started its PID from 0 % output,
which loaded an integral of about −96 %. The sequence now starts the drive from a heater
output (&lt;code&gt;charge_heater_pct&lt;/code&gt;, 60 %). Drop and Cancel leave the roaster in Manual at 0 %.
The virtual roaster copies the firmware&#39;s PID start, so it shows the same lag without it.&lt;/li&gt;
&lt;li&gt;Roast page, Control tab: the fan slider and setpoint box follow the roaster when Profile
drive, the roast sequence or an alarm changes them (they were read once, on load).&lt;/li&gt;
&lt;li&gt;Roast page, Control tab: the profile-drive note is one line, so the tab fits mid-drive at
1280×720 too; the setpoint box is wide enough for one decimal in the new face.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.2.0</title>
        <published>2026-10-23T00:00:00+00:00</published>
        <updated>2026-10-23T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-2-0/"/>
        <id>https://roastwarden.com/releases/0-2-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-2-0/">&lt;p&gt;Everything since v0.1.0 (2026-03-02). rustRoast became a server-centric Artisan
replacement: the server runs the roast (replay, alarms, fan assist, auto events) and any
browser is a view.&lt;/p&gt;
&lt;h2 id=&quot;added&quot;&gt;Added&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Telemetry v2&lt;/strong&gt;: typed samples, a 2 h in-memory ring, CBOR WebSocket with sequence numbers
and gap-free catch-up after a reload or reconnect; one server-side RoR for every transport.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live scope&lt;/strong&gt; on uPlot: phases, landmarks, projections, the profile with its RoR and Δ band,
development guidance after first crack, time-axis views, tracking RMS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profiles v2&lt;/strong&gt;: heater feedforward points, events by time or BT, recorded background
curves, save a roast as a profile, fluid-bed seed profiles (light filter, medium filter,
light espresso). Profile pages read like an Artisan background; profiles keep beans, batch
size and roaster.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artisan interop&lt;/strong&gt;: import roasts and profiles from &lt;code&gt;.alog&lt;/code&gt;, export roasts and profiles to
&lt;code&gt;.alog&lt;/code&gt; that Artisan opens as a roast or a background (&lt;a href=&quot;https://roastwarden.com/docs/profiles/&quot;&gt;Profiles&lt;/a&gt;); import and
export alarm files (&lt;code&gt;.alrm&lt;/code&gt;), with a row-by-row list of what does not map
(&lt;a href=&quot;https://roastwarden.com/docs/alarms/&quot;&gt;Alarms and auto events&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profile drive (replay engine)&lt;/strong&gt;: setpoint, feedforward or feedforward + PID; events by
time or BT; align on CHARGE/TP/DRY/FC; ghost dry run; per-channel pause; a control lease that
sends the roaster to safe if the server stops.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Alarms&lt;/strong&gt; in the server with actions on the roaster, arming, repeats, BT-vs-profile and
lost-telemetry conditions, auto DRY and DROP, webhook and sound.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Control&lt;/strong&gt;: relay autotune with quality checks and history, PID gain schedule by fan,
fan assist for a heat-limited roaster, heater and fan in percent end to end.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Virtual roaster&lt;/strong&gt;: a closed-loop simulator in the server, fitted to the real roaster,
speaking the firmware&#39;s MQTT.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Roast records&lt;/strong&gt;: roast properties (purpose, colour, moisture, density, flags, rating),
printable report, ranking table with CSV, comparator with a reference roast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transports and setup&lt;/strong&gt;: transport registry (MQTT, WebSocket, Modbus TCP, serial TC4),
mDNS discovery, API-key auth, tracked migrations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dashboard structure&lt;/strong&gt; (&lt;code&gt;docs/ui-structure.md&lt;/code&gt;): Roast cockpit that fits the window with
one next step per stage, History, Profiles, Roasters (tuning per roaster), Settings;
global roaster picker and STOP; °C/°F; light and dark.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;changed&quot;&gt;Changed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A new look, &quot;Chart paper&quot;&lt;/strong&gt;: a chart recorder&#39;s paper and ink (carbon blue in dark,
recorder-green paper in light), Atkinson Hyperlegible Next, ink-blue actions. Buttons,
focus, go, selection and &quot;where you are&quot; have their own colour tokens.&lt;/li&gt;
&lt;li&gt;Pages moved to &lt;code&gt;/history&lt;/code&gt;, &lt;code&gt;/roasters&lt;/code&gt; (one URL per roaster tab) and &lt;code&gt;/settings/...&lt;/code&gt;; the
old &lt;code&gt;/sessions/...&lt;/code&gt; and &lt;code&gt;/devices/...&lt;/code&gt; URLs redirect.&lt;/li&gt;
&lt;li&gt;Heater and fan are percentages on the wire and in the API (firmware 3.3.0+).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;fixed&quot;&gt;Fixed&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The profile overlay no longer disappears during a virtual roast.&lt;/li&gt;
&lt;li&gt;Landmarks marked on one screen reach the others at once (the row was committed after the
broadcast). The same late commit is gone from every other &lt;code&gt;RETURNING&lt;/code&gt; write: session
create, start, pause, resume and complete, devices, device profiles, connections, cupping.&lt;/li&gt;
&lt;li&gt;The auto turning point ignores samples before a late CHARGE, and a cold charge (BT never
dips) gets no turning point instead of one at 0:00; the Roast page says why.&lt;/li&gt;
&lt;li&gt;Scope: landmark labels close together (Charge, Dry end on a phone) stack instead of
overlapping, and event values along the bottom no longer pile up; projections wait 30 s
after CHARGE and never fit across it, so they no longer swing right after charging.&lt;/li&gt;
&lt;li&gt;The Profile drive tab shows the drive&#39;s state and profile time (Live, Ghost, Paused).&lt;/li&gt;
&lt;li&gt;Saving a profile in the designer no longer clears its first-crack target.&lt;/li&gt;
&lt;li&gt;Artisan: 0 % burner and air events survive import; exported roasts no longer replace the
extra-device setup of the Artisan that opens them.&lt;/li&gt;
&lt;/ul&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Roastwarden 0.1.0</title>
        <published>2026-03-02T00:00:00+00:00</published>
        <updated>2026-03-02T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://roastwarden.com/releases/0-1-0/"/>
        <id>https://roastwarden.com/releases/0-1-0/</id>
        
        <content type="html" xml:base="https://roastwarden.com/releases/0-1-0/">&lt;p&gt;First tagged version: server, MQTT, dashboard with sessions, profiles, designer, transposer,
cupping, comparison and exports.&lt;/p&gt;
</content>
        
    </entry>
</feed>
