Add Equipment page: owned brewers and grinders filter the Brews UI
Test and deploy / test-and-deploy (push) Successful in 59s

- user_gear table (migration 008) + GET/PUT /api/gear (brewer keys
  validated against the taxonomy, grinders deduped/trimmed)
- /gear page: silhouette multi-select for owned brewers (autosaves) and
  a grinder list; nav gains Brewing → Equipment
- Brews page: with gear configured, the picker shows only owned brewers
  (plus the edited brew's method) with a show-all toggle; grinder field
  suggests your grinders; empty-gear state links to /gear
- Included in full backups, per-user export, and the OpenAPI spec

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Shane Maynard
2026-08-09 08:08:32 -04:00
co-authored by Claude Fable 5
parent 3bceb44ae3
commit e62b9601a2
10 changed files with 433 additions and 4 deletions
+46
View File
@@ -377,6 +377,52 @@ test("roaster profile aggregates uploaded roasts; plan chat is grounded in it",
assert.equal(chatCalls.length, 1);
});
test("gear: owned brewers/grinders round-trip, validate, and stay private", async () => {
const { app, agent } = await setup();
const { csrf } = await signup(agent, "[email protected]");
// Defaults to empty
const empty = await agent.get("/api/gear");
assert.equal(empty.status, 200);
assert.deepEqual(empty.body.gear, { brewers: [], grinders: [] });
// Save + read back (upsert twice to cover the ON CONFLICT path)
const first = await agent
.put("/api/gear")
.set("x-csrf-token", csrf)
.send({ brewers: ["v60", "aeropress"], grinders: ["Comandante C40"] });
assert.equal(first.status, 200);
const second = await agent
.put("/api/gear")
.set("x-csrf-token", csrf)
.send({ brewers: ["v60", "hario-switch", "v60"], grinders: ["Comandante C40", "DF64", " "] });
assert.equal(second.status, 200);
assert.deepEqual(second.body.gear.brewers, ["v60", "hario-switch"]); // deduped
assert.deepEqual(second.body.gear.grinders, ["Comandante C40", "DF64"]); // blank dropped
assert.deepEqual((await agent.get("/api/gear")).body.gear.brewers, ["v60", "hario-switch"]);
// Unknown brewer keys and non-array bodies are rejected
assert.equal(
(
await agent
.put("/api/gear")
.set("x-csrf-token", csrf)
.send({ brewers: ["teapot"], grinders: [] })
).status,
400,
);
assert.equal(
(await agent.put("/api/gear").set("x-csrf-token", csrf).send({ brewers: "v60", grinders: [] })).status,
400,
);
// Private per user
const request = (await import("supertest")).default;
const stranger = request.agent(app);
await signup(stranger, "[email protected]");
assert.deepEqual((await stranger.get("/api/gear")).body.gear.brewers, []);
});
test("per-user export and openapi spec", async () => {
const { agent } = await setup();
const { csrf } = await signup(agent, "[email protected]");