Add brewing section, plan chat, roaster learning, API tokens, Swagger docs, and full backup
Test and deploy / test-and-deploy (push) Successful in 1m6s
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:
co-authored by
Claude Fable 5
parent
5efaeb63c9
commit
38c7d01e03
+17
-2
@@ -20,6 +20,7 @@ import { api, protectedFetch, csrfToken } from "./api.js?v=__ASSET_VERSION__";
|
||||
import { initSideNav } from "./nav.js?v=__ASSET_VERSION__";
|
||||
import { wireWhyPanels } from "./why-panels.js?v=__ASSET_VERSION__";
|
||||
import { initFieldHelp } from "./field-help.js?v=__ASSET_VERSION__";
|
||||
import { initPlanChat } from "./plan-chat-ui.js?v=__ASSET_VERSION__";
|
||||
import { showToast } from "./toast.js?v=__ASSET_VERSION__";
|
||||
|
||||
const FIELD_ID_SET = new Set(FIELD_IDS);
|
||||
@@ -38,6 +39,18 @@ const BAND_DOMAIN = [60, 220]; // shared °C domain for every band-track in the
|
||||
// the plan draft cache below.
|
||||
const MACHINE_PROFILE_KEY_PREFIX = "roastPlannerMachineProfile.v1";
|
||||
let machineProfile = null;
|
||||
// Learned roaster behavior from uploaded .alogs (server-aggregated) — used as the plan
|
||||
// curve's fallback milestone temps so the suggested curve matches this user's machine.
|
||||
let roasterProfile = null;
|
||||
|
||||
async function loadRoasterProfile() {
|
||||
try {
|
||||
const response = await fetch("/api/roaster-profile");
|
||||
if (response.ok) roasterProfile = (await response.json()).profile;
|
||||
} catch {
|
||||
/* offline — curve falls back to reference machine temps */
|
||||
}
|
||||
}
|
||||
|
||||
async function loadMachineProfile(userId) {
|
||||
const cacheKey = `${MACHINE_PROFILE_KEY_PREFIX}:${userId}`;
|
||||
@@ -464,7 +477,7 @@ function paintCurveInto(planGroup, refGroup, planPoints, ref) {
|
||||
}
|
||||
|
||||
function renderCurve(ledger) {
|
||||
const planPoints = buildPlanCurve(state.plan, ledger);
|
||||
const planPoints = buildPlanCurve(state.plan, ledger, roasterProfile);
|
||||
const ref = state.plan.reference;
|
||||
|
||||
paintCurveInto(
|
||||
@@ -775,6 +788,7 @@ function wireDrawers() {
|
||||
["nav-prefill", "panel-prefill"],
|
||||
["nav-alog", "panel-alog"],
|
||||
["nav-plans", "panel-plans"],
|
||||
["nav-chat", "panel-chat"],
|
||||
["nav-settings", "panel-settings"],
|
||||
["nav-import-export", "panel-import-export"],
|
||||
])
|
||||
@@ -1089,7 +1103,7 @@ async function init() {
|
||||
state.plan = localDraft ?? blankPlan();
|
||||
const draftRemoteId = remotePlanId;
|
||||
const draftSyncedAtAtLoad = draftSyncedAt;
|
||||
await Promise.all([loadPlans(), loadMachineProfile(user.id)]);
|
||||
await Promise.all([loadPlans(), loadMachineProfile(user.id), loadRoasterProfile()]);
|
||||
const requestedId = new URLSearchParams(location.search).get("plan");
|
||||
const selected = plans.find((plan) => plan.id === requestedId);
|
||||
// Prefer the local draft only when it targets this exact plan AND was last confirmed
|
||||
@@ -1165,6 +1179,7 @@ async function init() {
|
||||
renderFormFromPlan,
|
||||
});
|
||||
wireCuppingLink();
|
||||
initPlanChat({ getPlan: () => state.plan });
|
||||
renderMachineProfileNote();
|
||||
recompute();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user