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:
co-authored by
Claude Fable 5
parent
a23b97d2f9
commit
ce4b0e09d4
+1
-1
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user