Openings get descriptions: zone is the keyword, this is the sentence

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
Eric Wagoner
2026-08-09 14:29:44 -04:00
co-authored by Claude Fable 5
parent a23b97d2f9
commit ce4b0e09d4
4 changed files with 33 additions and 1 deletions
+1 -1
View File
@@ -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
+9
View File
@@ -35,6 +35,7 @@
<b id="sheettitle"></b>
<button id="sheetclose" aria-label="close"></button>
</div>
<p id="sheetdesc" class="meta"></p>
<div id="sheetgames"></div>
<input type="search" id="sheetsearch" placeholder="add a game — search the library…">
<div id="sheetmatches"></div>
@@ -43,6 +44,9 @@
<form id="openingform" class="editform">
<label>Label <input name="label"></label>
<label>Zone <input name="zone"></label>
<label class="wide">Description
<textarea name="description" rows="2"
placeholder="tall bookcase by the window — kids reach the bottom rows"></textarea></label>
<label>W <input name="width_in" size="5"></label>
<label>H <input name="height_in" size="5"></label>
<label>D <input name="depth_in" size="5"></label>
@@ -93,6 +97,7 @@ function openingCell(o) {
? `${o.games}${o.unmeasured ? ` · ${o.unmeasured} unmeasured` : ""}`
: "—";
return `<button class="opening" data-id="${esc(o.id)}"
${o.description ? `title="${esc(o.description)}"` : ""}
style="flex-grow:${Math.round((o.width_in || 13.25) * 4)}">
<span class="olabel">${esc(o.label)}${warn}</span>
${o.zone ? `<span class="ozone">${esc(o.zone)}</span>` : ""}
@@ -205,9 +210,12 @@ function sheetRender() {
: `<button class="unassign" data-key="${esc(g.key)}">remove</button>`}
</div>`).join("")
: `<p class="meta">Nothing here yet.</p>`;
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,
+3
View File
@@ -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
+20
View File
@@ -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"