Test and deploy / test-and-deploy (push) Successful in 1m34s
New plan reset the form correctly but left the Plans drawer open over
it, so the reset was invisible until the user closed the drawer
themselves. Now closes it immediately, matching selectPlan().
Also adds a delete button per plan in the Plans drawer (server route
already existed) - deleting the currently open plan resets to a fresh
blank one, matching New plan.
Why-panel teaching callouts ("WHY CULTIVAR SETS THE CLOCK" etc.) now
default collapsed instead of wall-to-wall expanded on first load, with
an "Expand all" button in the header on both the planner and cupping
pages. Persistence flips from tracking collapsed-vs-default-open to
expanded-vs-default-closed to match.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
269 lines
11 KiB
HTML
269 lines
11 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||
<title>Cupping — Roast Planner</title>
|
||
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
|
||
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
|
||
<meta name="theme-color" content="#A8481A" />
|
||
</head>
|
||
<body>
|
||
<div class="app-shell">
|
||
<nav class="side-nav" id="side-nav" aria-label="Main navigation">
|
||
<div class="side-nav-brand">
|
||
<span class="brand-mark" aria-hidden="true">◐</span>
|
||
<span class="brand-name">Roast Planner</span>
|
||
</div>
|
||
<div class="nav-group">
|
||
<a class="nav-item" href="/app"
|
||
><span class="nav-icon" aria-hidden="true">◐</span
|
||
><span class="nav-label">Planner</span></a
|
||
>
|
||
<a class="nav-item" href="/account#your-plans"
|
||
><span class="nav-icon" aria-hidden="true">▤</span
|
||
><span class="nav-label">Plans</span></a
|
||
>
|
||
<a class="nav-item" href="/inventory"
|
||
><span class="nav-icon" aria-hidden="true">▥</span
|
||
><span class="nav-label">Inventory</span></a
|
||
>
|
||
<a class="nav-item" href="/cupping" aria-current="page"
|
||
><span class="nav-icon" aria-hidden="true">◒</span
|
||
><span class="nav-label">Cupping</span></a
|
||
>
|
||
<a class="nav-item" href="/account"
|
||
><span class="nav-icon" aria-hidden="true">◔</span
|
||
><span class="nav-label">Account</span></a
|
||
>
|
||
<a class="nav-item hidden" id="nav-admin" href="/admin"
|
||
><span class="nav-icon" aria-hidden="true">⚙</span
|
||
><span class="nav-label">Admin</span></a
|
||
>
|
||
</div>
|
||
<div class="nav-spacer"></div>
|
||
<button
|
||
class="icon-btn nav-collapse-toggle"
|
||
type="button"
|
||
id="nav-collapse"
|
||
aria-label="Collapse navigation"
|
||
title="Collapse navigation"
|
||
>
|
||
«
|
||
</button>
|
||
<div class="nav-user">
|
||
<div class="nav-user-avatar" id="nav-user-avatar" aria-hidden="true"></div>
|
||
<div class="nav-user-detail">
|
||
<span class="nav-user-email" id="account-email"></span>
|
||
<button class="nav-user-logout" type="button" id="btn-logout">
|
||
Log out
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</nav>
|
||
|
||
<div class="app-workspace" id="app-workspace">
|
||
<header class="app-header">
|
||
<div class="header-row-top">
|
||
<button
|
||
class="icon-btn nav-hamburger"
|
||
type="button"
|
||
id="nav-hamburger"
|
||
aria-label="Open navigation"
|
||
aria-expanded="false"
|
||
>
|
||
☰
|
||
</button>
|
||
<div class="brand">
|
||
<div class="brand-text">
|
||
<h1 id="cupping-title">Cupping</h1>
|
||
<p class="brand-sub" id="cupping-subtitle">
|
||
Tasting and scoring sessions
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="header-actions hidden" id="session-autosave-wrap">
|
||
<span class="autosave-chip" id="cupping-autosave-status"
|
||
><span class="dot"></span
|
||
><span class="autosave-text">Not saved yet</span></span
|
||
>
|
||
<button id="btn-expand-why" type="button" class="ghost-btn">
|
||
Expand all "why" notes
|
||
</button>
|
||
</div>
|
||
</header>
|
||
<div class="drawer-overlay hidden" id="drawer-overlay"></div>
|
||
|
||
<main class="page-content wide" id="cupping-list-view">
|
||
<section class="panel-card" id="cupping-sessions-list">
|
||
<div class="panel-head"><h2>Cupping sessions</h2></div>
|
||
<div class="panel-body">
|
||
<div class="table-wrap">
|
||
<table class="data-table" id="sessions-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Coffee</th>
|
||
<th class="num">Score</th>
|
||
<th class="num">Cups</th>
|
||
<th>Flavors</th>
|
||
<th>Updated</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="sessions-body"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel-card" id="new-session-card">
|
||
<div class="panel-head"><h2>New session</h2></div>
|
||
<div class="panel-body">
|
||
<form id="new-session-form" class="inline-form">
|
||
<label class="field"
|
||
><span class="field-label">Roast (optional)</span>
|
||
<select class="field-input" id="new-session-plan">
|
||
<option value="">— no linked roast —</option>
|
||
</select></label
|
||
>
|
||
<label class="field"
|
||
><span class="field-label">Cups</span
|
||
><input
|
||
class="field-input"
|
||
type="number"
|
||
id="new-session-cups"
|
||
min="1"
|
||
max="12"
|
||
value="5"
|
||
/></label>
|
||
<button class="primary-btn" type="submit">
|
||
Start cupping
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<main class="page-content wide hidden" id="cupping-session-view">
|
||
<div class="workspace-grid">
|
||
<div class="form-col">
|
||
<section class="panel-card" id="cup-scores">
|
||
<div class="panel-head">
|
||
<h2>Scores</h2>
|
||
<label class="field" style="margin:0">
|
||
<span class="field-label">Cups</span>
|
||
<input
|
||
class="field-input sm"
|
||
type="number"
|
||
id="cup-count"
|
||
min="1"
|
||
max="12"
|
||
/>
|
||
</label>
|
||
</div>
|
||
<div class="panel-body">
|
||
<details class="why-panel" data-why="cup-scores">
|
||
<summary>Why the scale starts at 6</summary>
|
||
<p>
|
||
The 6–10 range is a specialty-grading convention:
|
||
anything below specialty grade simply isn't scored, so
|
||
6 is the floor of the scale, not "zero". Score in
|
||
quarter-point steps and leave an attribute unscored
|
||
(—) rather than guessing — an unscored attribute
|
||
contributes nothing, which is honest; a guessed 7.5
|
||
pollutes every comparison you make later. The tick
|
||
rows count cups, not quality: five clean cups out of
|
||
five is full marks even if the coffee is merely good.
|
||
</p>
|
||
</details>
|
||
<div id="cup-score-rows"></div>
|
||
<div id="cup-tick-rows"></div>
|
||
<details class="cupping-defects">
|
||
<summary>Defects</summary>
|
||
<div id="cup-defect-rows"></div>
|
||
</details>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel-card" id="cup-flavors">
|
||
<div class="panel-head"><h2>Flavors</h2></div>
|
||
<div class="panel-body">
|
||
<details class="why-panel" data-why="cup-flavors">
|
||
<summary>Tag what you can point at</summary>
|
||
<p>
|
||
A tag is only useful if you could defend it to
|
||
another taster with the cup in front of you. Two or
|
||
three confident descriptors beat a dozen hopeful ones
|
||
— the list is capped at 32, but a session that needs
|
||
10 is already suspect.
|
||
</p>
|
||
</details>
|
||
<p class="field-note hidden" id="flavor-limit-note">
|
||
32-tag limit reached.
|
||
</p>
|
||
<div id="flavor-families"></div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel-card" id="cup-notes">
|
||
<div class="panel-head"><h2>Notes</h2></div>
|
||
<div class="panel-body">
|
||
<details class="why-panel" data-why="cup-notes">
|
||
<summary>Notes outlive scores</summary>
|
||
<p>
|
||
Numbers compare roasts; sentences explain them. Write
|
||
what you'd want to read before roasting this coffee
|
||
again — texture, where it fell apart as it cooled,
|
||
what you'd change.
|
||
</p>
|
||
</details>
|
||
<textarea
|
||
class="field-input"
|
||
id="cup-notes-text"
|
||
maxlength="4000"
|
||
rows="5"
|
||
aria-label="Cupping notes"
|
||
></textarea>
|
||
<p class="field-note"><span id="cup-notes-count">0</span>/4000</p>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<aside class="dock">
|
||
<div class="dock-card">
|
||
<div class="dock-card-head"><h2>Score</h2></div>
|
||
<div class="stat-tile" style="box-shadow:none;border:none;padding:0">
|
||
<div class="stat-tile-label">Total score</div>
|
||
<div class="stat-tile-value" id="cup-total">0.00</div>
|
||
</div>
|
||
<p class="dock-note">Recomputed by the server on every save.</p>
|
||
<div class="chip-group" id="flavor-chips"></div>
|
||
</div>
|
||
<div class="dock-card">
|
||
<div class="dock-card-head"><h2>Radar</h2></div>
|
||
<svg
|
||
id="cup-radar"
|
||
viewBox="0 0 240 240"
|
||
role="img"
|
||
aria-label="Cupping score radar chart"
|
||
></svg>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
</div>
|
||
<script>
|
||
// Runs synchronously at parse time (before the deferred module script below) so opening
|
||
// /cupping?session=… never flashes the empty sessions list first while cupping.js's own
|
||
// async init (which awaits the auth check) is still getting underway.
|
||
if (new URLSearchParams(location.search).get("session")) {
|
||
document.getElementById("cupping-list-view").classList.add("hidden");
|
||
document.getElementById("cupping-session-view").classList.remove("hidden");
|
||
}
|
||
</script>
|
||
<script type="module" src="/js/cupping.js?v=__ASSET_VERSION__"></script>
|
||
</body>
|
||
</html>
|