Fix report pagination and rebuild saved plan navigation
Test and deploy / test-and-deploy (push) Successful in 1m43s

This commit is contained in:
snowspeeder committed 2026-09-05 15:47:44 -04:00
1 parent 824a964f9d
commit 5f3a015ea2
20 files changed
+1480 -809

No files matched your search

+5 -362
View File
@@ -64,8 +64,7 @@
<nav class="section-nav" id="section-nav" aria-label="Plan sections">
<a href="#sec-coffee">Plan</a>
<a href="#sec-bean">Bean</a>
<a href="#sec-machine">Machine</a>
<a href="#sec-roastlog">Log</a>
<a href="/roasts">Post-roast records</a>
</nav>
<div class="header-actions header-tools">
@@ -83,9 +82,7 @@
⚙ Tools ▾
</button>
<div class="header-tools-group" id="header-tools-group">
<button id="nav-plans" type="button" class="ghost-btn small">
▤ Plans
</button>
<a id="nav-plans" href="/plans" class="ghost-btn small">▤ Your plans</a>
<button id="nav-chat" type="button" class="ghost-btn small">
✳ Ask the LLM
</button>
@@ -259,37 +256,7 @@
</div>
</aside>
<aside
class="drawer hidden"
id="panel-plans"
aria-hidden="true"
role="dialog"
aria-modal="true"
aria-labelledby="plans-title"
tabindex="-1"
>
<div class="drawer-head">
<h3 id="plans-title">Your plans</h3>
<button
class="icon-btn"
type="button"
data-close-drawer
aria-label="Close"
>
✕
</button>
</div>
<div class="drawer-body">
<button id="btn-new-plan" type="button" class="primary-btn">
New plan
</button>
<p class="drawer-result">
Choose a plan to continue. Every edit is stored on this device first
and then synced when online.
</p>
<ul class="plan-list" id="plan-list" aria-live="polite"></ul>
</div>
</aside>
<aside
class="drawer hidden"
@@ -591,7 +558,7 @@
first-crack anchors is another valid time on the same
clock. An average of two temperatures means nothing: first
crack is a property of the chemistry, not a dial you set.
</p>
</p><div class="inventory-consume hidden" id="inventory-consume"><span id="inventory-consume-label"></span><button type="button" class="ghost-btn small" id="btn-consume">Draw from lot</button></div>
<p>
If one component is 60% or more of the batch, roast the
whole batch as that coffee and let the rest ride along. If
@@ -710,6 +677,7 @@
>
</div>
</div>
<label class="field">Development target (m:ss, optional override)<input class="field-input" name="4.4" placeholder="e.g. 1:15" /></label>
<div class="field-grid">
<label class="field" data-fid="3.2"
><span class="field-label">Dev modifier</span
@@ -862,332 +830,7 @@
</div>
</section>
<section class="panel-card" id="sec-machine">
<div class="panel-head"><h2>Machine Plan</h2></div>
<div class="panel-body">
<details class="why-panel" data-why="sec-machine">
<summary>
Why the rate of rise must fall, and never touch zero
</summary>
<p>
A bean that is still gaining heat, but gaining it more
slowly, is moving through its reactions in order. Two
shapes break that. The crash: RoR falls off a cliff just
after first crack, when the exotherm ends and an oversized
heat cut lands at the same moment. The bake: RoR flattens
toward zero, the bean sits at temperature without
progressing, and the cup goes flat, papery and
sweetness-free — nothing after the drop rescues a bake.
</p>
<p>
So the rule is: decline steadily from the
post-turning-point peak, through small frequent heat cuts
rather than a few big ones, and stay ahead of the
machine’s response delay — changes take time to affect the bean, so cut <em>before</em> the
exotherm, gently. If a phase would need a rate outside the
illustrative ranges to hit its milestone, the plan is not
reachable — fix the ledger, not the roaster.
</p>
</details>
<div class="subhead">Temperatures &amp; rate-of-rise</div>
<p class="field-note">Blank fields use an illustrative curve: charge 392°F, yellow 320°F, first crack 392°F. These are not preheat settings. First crack must be heard; enter your own probe readings to replace the illustration. Drop is derived from development time and a declining planned rise.</p>
<div class="field-grid">
<label class="field">Preheat setpoint<input class="field-input" name="preheat.tempC" placeholder="°F" /></label>
<label class="field">Preheat time (min)<input class="field-input" name="preheat.minutes" /></label>
<label class="field">Preheat fan (%)<input class="field-input" name="preheat.fanPct" /></label>
<label class="field">Drum (%)<input class="field-input" name="preheat.drumPct" /></label>
</div>
<label class="field-label">Development target (m:ss, optional override)<input class="field-input" name="4.4" placeholder="e.g. 1:15" /></label>
<div class="milestone-list">
<div
class="milestone-row band-row"
data-band-lo="133"
data-band-hi="195"
>
<div class="milestone-label">Charge</div>
<output class="milestone-plan" data-out="t-charge"
>0:00</output
>
<div class="band-track">
<div class="band-range"></div>
<div class="band-marker"></div>
</div>
<div class="unit-input sm">
<input
class="field-input sm"
name="temps.charge.tempC"
placeholder="°C"
aria-label="Charge planned temperature"
/><span class="unit">°C</span>
</div>
</div>
<div
class="milestone-row band-row"
data-band-lo="77"
data-band-hi="100"
>
<div class="milestone-label">Turning point</div>
<input
class="field-input sm milestone-time"
name="temps.tp.time"
placeholder="m:ss"
aria-label="Turning point planned time"
/>
<div class="band-track">
<div class="band-range"></div>
<div class="band-marker"></div>
</div>
<div class="unit-input sm">
<input
class="field-input sm"
name="temps.tp.tempC"
placeholder="°C"
aria-label="Turning point planned temperature"
/><span class="unit">°C</span>
</div>
</div>
<div
class="milestone-row band-row"
data-band-lo="148"
data-band-hi="166"
>
<div class="milestone-label">Yellow</div>
<output class="milestone-plan" data-out="t-yellow">—</output>
<div class="band-track">
<div class="band-range"></div>
<div class="band-marker"></div>
</div>
<div class="unit-input sm">
<input
class="field-input sm"
name="temps.yellow.tempC"
placeholder="°C"
aria-label="Yellow planned temperature"
/><span class="unit">°C</span>
</div>
</div>
<div
class="milestone-row band-row"
data-band-lo="176"
data-band-hi="187"
>
<div class="milestone-label">First crack</div>
<output class="milestone-plan" data-out="t-fc">—</output>
<div class="band-track">
<div class="band-range"></div>
<div class="band-marker"></div>
</div>
<div class="unit-input sm">
<input
class="field-input sm"
name="temps.fc.tempC"
placeholder="°C"
aria-label="First crack planned temperature"
/><span class="unit">°C</span>
</div>
</div>
<div
class="milestone-row band-row"
data-band-lo="183"
data-band-hi="197"
>
<div class="milestone-label">Drop</div>
<output class="milestone-plan" data-out="t-drop">—</output>
<div class="band-track">
<div class="band-range"></div>
<div class="band-marker"></div>
</div>
<div class="unit-input sm">
<input
class="field-input sm"
name="temps.drop.tempC"
placeholder="°C"
aria-label="Drop planned temperature"
/><span class="unit">°C</span>
</div>
</div>
</div>
<div class="subhead">Actuator schedule (planned)</div>
<p class="field-note">
The timing of your first heat cut is the main first-crack
lever — later cut, earlier crack. Say why you make each
change; that's what makes this log readable next week.
</p>
<div class="actuator-timeline" id="actuator-timeline"></div>
<button
type="button"
class="ghost-btn small"
id="btn-add-actuator"
>
+ Add step
</button>
</div>
</section>
<div class="phase-divider" role="separator" aria-label="Roast day begins">
<span class="phase-divider-label">Roast day</span>
<span class="phase-divider-text"
>Everything above is decided before you charge. Fill in from
here down at the machine, then after cupping. Print takes the
plan with you.</span
>
</div>
<section class="panel-card phase-later" id="sec-roastlog">
<div class="panel-head">
<h2>Roast Log — Plan vs. Actual</h2>
<span class="phase-chip">during the roast</span>
</div>
<div class="panel-body">
<details class="why-panel" data-why="sec-roastlog">
<summary>Record what you observed, never what you planned</summary>
<p>
A planned milestone written into the log as though it
happened destroys the only evidence you have. If you never
actually heard first crack, write that down — "not heard,
called at 8:10 by smell" is a useful record. "8:00"
because that was the plan is worse than nothing, because
next batch you will trust it. The plan column stays fixed
beside your entries precisely so the gap is visible. The
gap is the data.
</p>
</details>
<div class="milestone-list">
<div class="milestone-row log-row">
<div class="milestone-label">Charge</div>
<output class="milestone-plan" data-out="pa-charge"
>0:00</output
>
<input
class="field-input sm"
name="planActual.charge.actualTime"
placeholder="0:00"
aria-label="Charge actual time"
/>
<input
class="field-input sm"
name="planActual.charge.actualBt"
placeholder="°C"
aria-label="Charge actual temperature"
/>
<input
class="field-input note"
name="planActual.charge.note"
placeholder="Note"
aria-label="Charge note"
/>
</div>
<div class="milestone-row log-row">
<div class="milestone-label">Turning point</div>
<output class="milestone-plan" data-out="pa-tp">—</output>
<input
class="field-input sm"
name="planActual.tp.actualTime"
placeholder="0:00"
aria-label="Turning point actual time"
/>
<input
class="field-input sm"
name="planActual.tp.actualBt"
placeholder="°C"
aria-label="Turning point actual temperature"
/>
<input
class="field-input note"
name="planActual.tp.note"
placeholder="Note"
aria-label="Turning point note"
/>
</div>
<div class="milestone-row log-row">
<div class="milestone-label">Yellow</div>
<output class="milestone-plan" data-out="pa-yellow">—</output>
<input
class="field-input sm"
name="planActual.yellow.actualTime"
placeholder="0:00"
aria-label="Yellow actual time"
/>
<input
class="field-input sm"
name="planActual.yellow.actualBt"
placeholder="°C"
aria-label="Yellow actual temperature"
/>
<input
class="field-input note"
name="planActual.yellow.note"
placeholder="Note"
aria-label="Yellow note"
/>
</div>
<div class="milestone-row log-row">
<div class="milestone-label">First crack</div>
<output class="milestone-plan" data-out="pa-fc">—</output>
<input
class="field-input sm"
name="planActual.fc.actualTime"
placeholder="0:00"
aria-label="First crack actual time"
/>
<input
class="field-input sm"
name="planActual.fc.actualBt"
placeholder="°C"
aria-label="First crack actual temperature"
/>
<input
class="field-input note"
name="planActual.fc.note"
placeholder="Note"
aria-label="First crack note"
/>
</div>
<div class="milestone-row log-row">
<div class="milestone-label">Drop</div>
<output class="milestone-plan" data-out="pa-drop">—</output>
<input
class="field-input sm"
name="planActual.drop.actualTime"
placeholder="0:00"
aria-label="Drop actual time"
/>
<input
class="field-input sm"
name="planActual.drop.actualBt"
placeholder="°C"
aria-label="Drop actual temperature"
/>
<input
class="field-input note"
name="planActual.drop.note"
placeholder="Note"
aria-label="Drop note"
/>
</div>
</div>
<div class="inventory-consume hidden" id="inventory-consume">
<span class="inventory-consume-label" id="inventory-consume-label"></span>
<button type="button" class="ghost-btn small" id="btn-consume">
Draw from lot
</button>
</div>
<div
style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:12px"
>
<button type="button" class="ghost-btn" id="btn-open-cupping">
Open cupping session
</button>
<span class="field-note" id="cupping-link-note" style="margin:0"></span>
</div>
<p class="field-note">
After the roast, upload the .alog on the
<a href="/roasts">Roasts page</a> — weights, colour, cup notes,
and "one change next batch" now live there with the LLM review.
</p>
</div>
</section>
</div>