From ce4b0e09d476ca2040bb819fcc688ddfa5f47997 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sun, 9 Aug 2026 14:29:44 -0400 Subject: [PATCH] Openings get descriptions: zone is the keyword, this is the sentence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eric's ask. Free text on any opening ("tall bookcase by the window — kids reach the bottom rows"), edited in the opening settings sheet, shown under the sheet's title, and surfaced as the cell's hover title on the wall diagram. Stored in furniture.json like everything else. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g --- data/furniture.json | 2 +- src/bggpipe/templates/pages/shelves.html | 9 +++++++++ src/bggpipe/webreview.py | 3 +++ tests/test_shelves.py | 20 ++++++++++++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/data/furniture.json b/data/furniture.json index b509381..70289cc 100644 --- a/data/furniture.json +++ b/data/furniture.json @@ -110,7 +110,7 @@ { "id": "library-kallax-d4", "label": "D4", - "zone": "cubes", + "zone": "campaign", "width_in": 13.25, "height_in": 13.25, "depth_in": 15.4 diff --git a/src/bggpipe/templates/pages/shelves.html b/src/bggpipe/templates/pages/shelves.html index 869ecd6..4bccac5 100644 --- a/src/bggpipe/templates/pages/shelves.html +++ b/src/bggpipe/templates/pages/shelves.html @@ -35,6 +35,7 @@ +

@@ -43,6 +44,9 @@
+ @@ -93,6 +97,7 @@ function openingCell(o) { ? `${o.games}${o.unmeasured ? ` · ${o.unmeasured} unmeasured` : ""}` : "—"; return ``} `).join("") : `

Nothing here yet.

`; + document.getElementById("sheetdesc").textContent = o.description || ""; + document.getElementById("sheetdesc").hidden = !o.description; const f = document.getElementById("openingform"); f.elements.label.value = o.label; f.elements.zone.value = o.zone || ""; + f.elements.description.value = o.description || ""; f.elements.width_in.value = o.width_in ?? ""; f.elements.height_in.value = o.height_in ?? ""; f.elements.depth_in.value = o.depth_in ?? ""; @@ -347,6 +355,7 @@ document.getElementById("openingform").addEventListener("submit", e => { } post("/api/furniture/edit-opening", { id: OPEN, label: f.label.value, zone: f.zone.value, + description: f.description.value, width_in: parseFloat(f.width_in.value) || null, height_in: parseFloat(f.height_in.value) || null, depth_in: parseFloat(f.depth_in.value) || null, diff --git a/src/bggpipe/webreview.py b/src/bggpipe/webreview.py index 694dc46..d1b588d 100644 --- a/src/bggpipe/webreview.py +++ b/src/bggpipe/webreview.py @@ -247,6 +247,7 @@ class OpeningEditBody(BaseModel): id: str label: str | None = None zone: str | None = None + description: str | None = None # free text for humans; zone is the keyword # all three or none: a partial size cannot exist (see the endpoint) width_in: float | None = None height_in: float | None = None @@ -1290,6 +1291,8 @@ def create_app( real["label"] = body.label.strip() or real["label"] if body.zone is not None: real["zone"] = body.zone.strip() + if body.description is not None: + real["description"] = body.description.strip() # dims are all-or-none: three values sets, three blanks makes # it virtual, anything else is a 400 — a partial edit must # never silently strip an opening of its size limits diff --git a/tests/test_shelves.py b/tests/test_shelves.py index 44038d7..604b112 100644 --- a/tests/test_shelves.py +++ b/tests/test_shelves.py @@ -800,3 +800,23 @@ def test_reunification_outranks_tightest_fit_and_full_shelves_stop_lying(tmp_pat exp = next(g for g in state["unshelved"] if g["name"].endswith("Tower")) first = exp["suggestions"][0] assert first["id"] == ids["A1"] and first["reunites"] is True + + +def test_opening_descriptions_round_trip(tmp_path): + web, cfg = _web(tmp_path, {}) + web.post("/api/furniture/add-unit", json={"name": "Den"}) + web.post("/api/furniture/add-openings", json={"unit": "Den", "label": "top"}) + oid = web.get("/api/shelves").json()["units"][0]["openings"][0]["id"] + web.post( + "/api/furniture/edit-opening", + json={ + "id": oid, + "description": "tall bookcase by the window", + }, + ) + opening = web.get("/api/shelves").json()["units"][0]["openings"][0] + assert opening["description"] == "tall bookcase by the window" + import json as _json + + saved = _json.loads(cfg.furniture_path.read_text())["units"][0]["openings"][0] + assert saved["description"] == "tall bookcase by the window"