Units get descriptions too — both levels of the furniture speak
Eric's correction: the opening-level description shipped, but the unit deserved one as well (my own example sentence was unit-scale prose). Units take a description at creation and edit it in place — a linkish add/edit control under the unit's name with an inline input — shown as quiet meta text on the card and stored in furniture.json. Label addresses, zone matches, descriptions explain; now at both scales. 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
ce4b0e09d4
commit
e58cc789f3
@@ -556,6 +556,12 @@ select {
|
||||
margin-bottom: .8rem; }
|
||||
.orow { display: flex; gap: .5rem; }
|
||||
.formlabel { margin: 1.2rem 0 .35rem; }
|
||||
.unitdesc { margin: -.3rem 0 .7rem; display: flex; gap: .6rem;
|
||||
align-items: center; flex-wrap: wrap; }
|
||||
.unitdesc .descform:not([hidden]) { display: flex; gap: .4rem; flex: 1 1 16rem; }
|
||||
.unitdesc .descform input { flex: 1 1 auto; font: inherit; font-size: .85rem;
|
||||
border: 2px solid var(--board-edge); border-radius: var(--radius);
|
||||
padding: .25rem .45rem; }
|
||||
.formlabel.small { font-weight: 700; font-size: .85rem;
|
||||
color: var(--ink-soft); margin: .8rem 0 .3rem; }
|
||||
button.opening {
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
<label>D <input name="depth_in" size="5"></label>
|
||||
</span>
|
||||
<label>Zone <input name="zone" placeholder="party games"></label>
|
||||
<label class="wide">Description
|
||||
<textarea name="description" rows="2"
|
||||
placeholder="the wall behind the couch — main game storage"></textarea></label>
|
||||
<span class="editactions">
|
||||
<button type="submit" class="primary">create</button>
|
||||
</span>
|
||||
@@ -113,6 +116,16 @@ function unitBlock(u) {
|
||||
<span class="editactions">
|
||||
<button class="delunit danger" data-unit="${esc(u.name)}">remove unit</button>
|
||||
</span></div>
|
||||
<p class="unitdesc">
|
||||
<span class="meta">${esc(u.description || "")}</span>
|
||||
<button class="linkish editunitdesc" data-unit="${esc(u.name)}">
|
||||
${u.description ? "edit description" : "add a description…"}</button>
|
||||
<span class="descform" hidden>
|
||||
<input class="unitdescinput" value="${esc(u.description || "")}"
|
||||
placeholder="the wall behind the couch">
|
||||
<button class="primary saveunitdesc" data-unit="${esc(u.name)}">save</button>
|
||||
</span>
|
||||
</p>
|
||||
${unitRows(u.openings)}
|
||||
<p class="formlabel small">Add an opening</p>
|
||||
<form class="editform addopeningform" data-unit="${esc(u.name)}">
|
||||
@@ -275,6 +288,17 @@ function wire() {
|
||||
if (confirm(`Remove "${b.dataset.unit}" and its openings? Its games become unshelved.`))
|
||||
post("/api/furniture/delete-unit", {name: b.dataset.unit});
|
||||
});
|
||||
document.querySelectorAll(".editunitdesc").forEach(b =>
|
||||
b.onclick = () => {
|
||||
const form = b.closest(".unitdesc").querySelector(".descform");
|
||||
form.hidden = !form.hidden;
|
||||
if (!form.hidden) form.querySelector("input").focus();
|
||||
});
|
||||
document.querySelectorAll(".saveunitdesc").forEach(b =>
|
||||
b.onclick = () => post("/api/furniture/edit-unit", {
|
||||
name: b.dataset.unit,
|
||||
description: b.closest(".descform").querySelector("input").value,
|
||||
}));
|
||||
document.querySelectorAll(".addopeningform").forEach(form =>
|
||||
form.onsubmit = async e => {
|
||||
e.preventDefault();
|
||||
@@ -410,7 +434,9 @@ unitForm.addEventListener("submit", async e => {
|
||||
// half-failure) is fine to fill, so a 409 on step one does not stop us
|
||||
const existing = SHELVES.units.find(u => u.name === f.name.value.trim());
|
||||
if (!existing || existing.openings.length) {
|
||||
const made = await post("/api/furniture/add-unit", {name: f.name.value});
|
||||
const made = await post("/api/furniture/add-unit", {
|
||||
name: f.name.value, description: f.description.value,
|
||||
});
|
||||
if (!made && !existing) return;
|
||||
}
|
||||
const filled = await post("/api/furniture/add-openings", {
|
||||
|
||||
@@ -228,6 +228,7 @@ class ResearchBody(BaseModel):
|
||||
|
||||
class UnitBody(BaseModel):
|
||||
name: str
|
||||
description: str = "" # free text: "the wall behind the couch"
|
||||
|
||||
|
||||
class OpeningsBody(BaseModel):
|
||||
@@ -1098,6 +1099,7 @@ def create_app(
|
||||
located_keys.add(key) # rides with its container
|
||||
out_units = []
|
||||
for unit in units:
|
||||
unit = {**unit, "description": unit.get("description", "")}
|
||||
out_openings = []
|
||||
for opening in unit.get("openings", []):
|
||||
here = residents.get(opening["id"], [])
|
||||
@@ -1121,7 +1123,13 @@ def create_app(
|
||||
],
|
||||
}
|
||||
)
|
||||
out_units.append({"name": unit["name"], "openings": out_openings})
|
||||
out_units.append(
|
||||
{
|
||||
"name": unit["name"],
|
||||
"description": unit.get("description", ""),
|
||||
"openings": out_openings,
|
||||
}
|
||||
)
|
||||
unshelved = []
|
||||
for key, entry in sorted(
|
||||
games.items(), key=lambda p: (p[1].get("name") or "").casefold()
|
||||
@@ -1198,10 +1206,26 @@ def create_app(
|
||||
def add(units):
|
||||
if any(u["name"] == name for u in units):
|
||||
raise HTTPException(409, f"a unit named {name!r} already exists")
|
||||
units.append({"name": name, "openings": []})
|
||||
units.append(
|
||||
{
|
||||
"name": name,
|
||||
"description": body.description.strip(),
|
||||
"openings": [],
|
||||
}
|
||||
)
|
||||
|
||||
return _mutate_furniture(add)
|
||||
|
||||
@app.post("/api/furniture/edit-unit")
|
||||
def api_edit_unit(body: UnitBody) -> dict:
|
||||
def apply(units):
|
||||
unit = next((u for u in units if u["name"] == body.name), None)
|
||||
if unit is None:
|
||||
raise HTTPException(404, "no such unit")
|
||||
unit["description"] = body.description.strip()
|
||||
|
||||
return _mutate_furniture(apply)
|
||||
|
||||
@app.post("/api/furniture/delete-unit")
|
||||
def api_delete_unit(body: UnitBody) -> dict:
|
||||
def drop(units):
|
||||
|
||||
@@ -820,3 +820,23 @@ def test_opening_descriptions_round_trip(tmp_path):
|
||||
|
||||
saved = _json.loads(cfg.furniture_path.read_text())["units"][0]["openings"][0]
|
||||
assert saved["description"] == "tall bookcase by the window"
|
||||
|
||||
|
||||
def test_unit_descriptions_round_trip(tmp_path):
|
||||
web, cfg = _web(tmp_path, {})
|
||||
web.post(
|
||||
"/api/furniture/add-unit",
|
||||
json={"name": "Den", "description": "the wall behind the couch"},
|
||||
)
|
||||
assert (
|
||||
web.get("/api/shelves").json()["units"][0]["description"]
|
||||
== "the wall behind the couch"
|
||||
)
|
||||
web.post(
|
||||
"/api/furniture/edit-unit",
|
||||
json={"name": "Den", "description": "now in the study"},
|
||||
)
|
||||
import json as _json
|
||||
|
||||
saved = _json.loads(cfg.furniture_path.read_text())["units"][0]
|
||||
assert saved["description"] == "now in the study"
|
||||
|
||||
Reference in New Issue
Block a user