Two-lane packing, and the shelves ship with the export

Eric's wall answered the orientation question with a screenshot full
of red: the capacity model assumed one flat stack per opening, so his
spine-out cubes read 200% full. His reviewer's prescription, built to
its acceptance cases: each opening packs a FLAT lane (thinnest axes
against interior height, claiming the widest flat box's width) and a
STANDING lane beside it (boxes on edge against the remaining width;
standing boxes must fit height and depth upright). The largest-
footprint class lies flat, smaller boxes stand, any box's lane is
flippable per-assignment (▬/▮ toggle in the opening view, ✱ marks an
override), and ⚠ now means NO packing fits — not merely "tall stack".
Unmeasured boxes take no lane but keep their honesty tag; dual fill
bars show each lane's budget; a broken opening is never offered by
the suggester. On the real wall: the double-wides and half the cubes
went green (7 games = 13.12" flat + 2.91" standing), and the
remaining warnings mark cubes that genuinely hold 12-16 boxes.
Both reviewer acceptance tests pass verbatim.

And the export gains the humanity Eric asked for: game pages say
where each box lives, containers list their contents, and a shelves
page draws the wall as it physically stands — proportional cells,
zones, descriptions, every opening linking its residents. Publishing
a shelf layout is a choice: --no-shelves keeps the layer out.
"shelves" joins "art" as a reserved slug. 387 tests.

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:53:42 -04:00
co-authored by Claude Fable 5
parent d6ff5e73e9
commit e90a53fb6c
20 changed files with 1930 additions and 59 deletions
+60 -2
View File
@@ -312,7 +312,7 @@ def test_cli_export_wiring(tmp_path, monkeypatch):
received = {}
def fake_run_export(cfg, out, *, title):
def fake_run_export(cfg, out, *, title, include_shelves=True):
received.update(out=out, title=title)
return {"games": 0, "downloaded": 0, "failures": 0, "removed": 0}
@@ -325,7 +325,7 @@ def test_cli_export_wiring(tmp_path, monkeypatch):
assert received["title"] == "T" and received["out"] == tmp_path / "s"
assert runner.invoke(app, ["export"]).exit_code != 0 # --out is required
def failing_run_export(cfg, out, *, title):
def failing_run_export(cfg, out, *, title, include_shelves=True):
return {"games": 1, "downloaded": 0, "failures": 2, "removed": 0}
monkeypatch.setattr("bggpipe.export.run_export", failing_run_export)
@@ -346,3 +346,61 @@ def test_colophon_card_closes_the_shelf(tmp_path):
assert (out / "art" / "bggpipe-piper.jpg").exists()
# the filter only ever hides <a class="game"> — the colophon is a div
assert '<div class="game colophon"' in index
def test_shelves_export_and_optout(tmp_path):
"""The wall ships with the site — locations on game pages, a shelves
page drawn as the physical arrangement — unless the owner opts out
(publishing a shelf layout is a choice)."""
from bggpipe.shelves import save_furniture, save_locations
cfg = _cfg(tmp_path)
save_furniture(
cfg,
[
{
"name": "Den Kallax",
"description": "the wall behind the couch",
"openings": [
{
"id": "den-a1",
"label": "A1",
"zone": "oversize",
"width_in": 26.5,
"height_in": 13.25,
"depth_in": 15.4,
},
{
"id": "den-b1",
"label": "B1",
"zone": "cubes",
"width_in": 13.25,
"height_in": 13.25,
"depth_in": 15.4,
},
],
}
],
)
save_locations(cfg, {"240:24621": {"opening_id": "den-b1", "note": ""}})
out = tmp_path / "site"
run_export(cfg, out, fetch=_cdn([]), sleep=lambda s: None)
shelves = (out / "shelves" / "index.html").read_text()
assert "Den Kallax" in shelves and "the wall behind the couch" in shelves
assert "Britannia" in shelves # the resident links from its opening
assert 'href="../britannia/"' in shelves
detail = (out / "britannia" / "index.html").read_text()
assert "where it lives" in detail and "Den Kallax · B1" in detail
# containment renders on both pages
local = (out / "britannia-2" / "index.html").read_text()
assert "in this box" not in local # the localgame contains nothing
index = (out / "index.html").read_text()
assert "see the shelves" in index
# opt-out: no shelves page, no location lines, no link
out2 = tmp_path / "site2"
run_export(cfg, out2, include_shelves=False, fetch=_cdn([]), sleep=lambda s: None)
assert not (out2 / "shelves").exists()
assert "where it lives" not in (out2 / "britannia" / "index.html").read_text()
assert "see the shelves" not in (out2 / "index.html").read_text()