Add brewing section, plan chat, roaster learning, API tokens, Swagger docs, and full backup
Test and deploy / test-and-deploy (push) Successful in 1m6s

Brewing:
- roasted_beans + brews tables; /beans (bean management with LLM URL
  prefill) and /brews (silhouette brewer picker across immersion/
  percolation/espresso, recipe fields, auto ratio, 0-10 rating, tasting
  notes); bean remaining weight derived from logged brew doses
- Green inventory lot form also prefills from a product URL

Navigation/UX:
- Side nav is now generated from one definition in nav.js, grouped
  Roasting / Brewing / account, consistent on every page

LLM:
- 'Ask the LLM' chat drawer on the planner (stateless /api/plan-chat)
  grounded in the plan, computed ledger, learned pace, and a new
  roaster-behavior profile aggregated from uploaded .alogs
  (/api/roaster-profile: TP lag, phase RoR, median milestone temps)
- The profile also feeds roast reviews and the planner curve's fallback
  milestone temps

API platform:
- User-generated bearer tokens (rpt_…) with account-page management;
  token requests skip CSRF; hand-authored OpenAPI 3 spec at
  /api/openapi.json rendered by self-hosted Swagger UI at /api-docs
- Full-database backup export/import (admin) + per-user data export

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Shane Maynard
2026-08-08 22:37:42 -04:00
co-authored by Claude Fable 5
parent 5efaeb63c9
commit 38c7d01e03
33 changed files with 3498 additions and 205 deletions
+134
View File
@@ -2866,3 +2866,137 @@ select.f {
padding: 0 0.5mm;
color: #1a1512;
}
/* ── Brewer picker + brew log (brews/beans pages) ─────────────────────── */
.brewer-cat-title {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ink-3);
margin: 14px 0 6px;
}
.brewer-cat-title:first-child {
margin-top: 0;
}
.brewer-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(94px, 1fr));
gap: 8px;
}
.brewer-tile {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 10px 6px 8px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: var(--surface-raised);
cursor: pointer;
color: var(--ink-2);
font: inherit;
transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.brewer-tile:hover {
border-color: var(--line-strong);
color: var(--ink);
}
.brewer-tile.selected {
border-color: var(--ember);
color: var(--ember-strong);
background: var(--ember-soft);
box-shadow: inset 0 0 0 1px var(--ember);
}
.brewer-tile svg {
width: 42px;
height: 42px;
display: block;
}
.brewer-tile .brewer-name {
font-size: 11.5px;
line-height: 1.2;
text-align: center;
}
.method-chip {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.method-chip svg {
width: 20px;
height: 20px;
flex: none;
color: var(--ink-2);
}
.rating-pill {
display: inline-block;
min-width: 34px;
text-align: center;
padding: 2px 8px;
border-radius: 999px;
background: var(--surface-sunken);
font-variant-numeric: tabular-nums;
font-weight: 600;
}
.rating-pill.good {
background: var(--pass-bg);
color: var(--pass);
}
.rating-pill.poor {
background: var(--fail-bg);
color: var(--fail);
}
.token-reveal {
background: var(--surface-sunken);
border: 1px dashed var(--line-strong);
border-radius: var(--radius-sm);
padding: 10px 12px;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 12.5px;
word-break: break-all;
user-select: all;
}
/* ── Plan chat drawer ─────────────────────────────────────────────────── */
.chat-thread {
display: flex;
flex-direction: column;
gap: 8px;
max-height: 50vh;
overflow-y: auto;
margin-bottom: 10px;
}
.chat-msg {
padding: 8px 11px;
border-radius: var(--radius-md);
font-size: 13px;
line-height: 1.45;
white-space: pre-wrap;
max-width: 92%;
}
.chat-msg.user {
align-self: flex-end;
background: var(--ember-soft);
color: var(--ember-strong);
}
.chat-msg.assistant {
align-self: flex-start;
background: var(--surface-sunken);
color: var(--ink);
}
.chat-msg.pending {
color: var(--ink-3);
font-style: italic;
}
.chat-input-row {
display: flex;
gap: 8px;
align-items: flex-end;
}
.chat-input-row textarea {
flex: 1;
resize: vertical;
min-height: 40px;
}