No placebo buttons: accept-overhang only offered when it would help

Eric clicked accept-overhang on two boxes in a Kallax cube and got an
"overhang ✓" pill with no lane behind it. The cube's flat pile claims
11.7 of 13.25 inches of width, leaving 1.55 inches of standing lane —
acknowledging waives depth and height, never width, so nothing changed.
That opening is overfull, not overhung, and the button was a placebo.

The sheet now probes before offering: a laneless resident gets the
accept button only when acknowledging would actually land it a lane;
otherwise it's marked "no room" with the honest tooltip. Acknowledged
boxes that DID land show "▮ standing, proud" (their lane isn't
flippable — proud of the shelf is the whole point), plus the
retractable "overhang ✓". Verified against the live wall: the three
Etherfields monoliths on the tower's big row read standing-proud, the
squeezed cube residents read no-room, zero page errors.

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 15:53:02 -04:00
co-authored by Claude Fable 5
parent 7d7d3bb8da
commit f0cf4ec271
9 changed files with 330 additions and 10 deletions
@@ -0,0 +1,65 @@
- generic [active] [ref=e1]:
- link "Skip to content" [ref=e2] [cursor=pointer]:
- /url: "#main"
- generic [ref=e3]:
- link "bggpipe shelf → BGG pipeline" [ref=e4] [cursor=pointer]:
- /url: /
- generic [ref=e5]:
- text: bggpipe
- generic [ref=e6]: shelf → BGG pipeline
- navigation "Primary" [ref=e7]:
- link "Pipeline" [ref=e8] [cursor=pointer]:
- /url: /
- link "Photos 4" [ref=e9] [cursor=pointer]:
- /url: /photos
- text: Photos
- generic [ref=e10]: "4"
- link "Titles" [ref=e11] [cursor=pointer]:
- /url: /titles
- link "Review" [ref=e12] [cursor=pointer]:
- /url: /review
- link "Queue" [ref=e13] [cursor=pointer]:
- /url: /queue
- link "Library" [ref=e14] [cursor=pointer]:
- /url: /library
- link "Shelves" [ref=e15] [cursor=pointer]:
- /url: /shelves
- link "Help" [ref=e16] [cursor=pointer]:
- /url: /help
- figure "art by Juniper" [ref=e17]:
- img "the bggpipe piper — a bagpiper whose bag is a board game box" [ref=e18]
- generic [ref=e20]: BoardGameGeek and BGG are trademarks of BoardGameGeek, LLC. bggpipe is an independent project, not affiliated with or endorsed by BoardGameGeek.
- generic [ref=e21]:
- status
- main [ref=e22]:
- heading "Shelves" [level=1] [ref=e23]
- heading "Add a unit" [level=2] [ref=e24]
- generic [ref=e25]:
- generic [ref=e26]:
- text: Unit name
- textbox "Unit name" [ref=e27]:
- /placeholder: Living room Kallax
- generic [ref=e28]:
- text: Preset
- combobox "Preset" [ref=e29]:
- option "IKEA Kallax cube (13.25 × 13.25 × 15.4)" [selected]
- option "IKEA Billy shelf (30.75 × 13 × 11)"
- option "custom size"
- option "no size — travel case, lent out…"
- generic [ref=e30]:
- text: Rows
- textbox "Rows" [ref=e31]: "1"
- generic [ref=e32]:
- text: Columns
- textbox "Columns" [ref=e33]: "1"
- generic [ref=e34]:
- text: Zone
- textbox "Zone" [ref=e35]:
- /placeholder: party games
- generic [ref=e36]:
- text: Description
- textbox "Description" [ref=e37]:
- /placeholder: the wall behind the couch — main game storage
- button "create" [ref=e39] [cursor=pointer]
- generic [ref=e40]: grid mode labels openings A1, A2… by row; you can edit, add, or remove openings afterward — repeat with another grid to stack sections (double-wides above cubes, say) in the same unit
- paragraph [ref=e42]: Loading…
+39 -2
View File
@@ -84,6 +84,7 @@
"opening_id": "library-kallax-c4"
},
"157969:237864": {
"acknowledged": true,
"note": "",
"opening_id": "library-kallax-b1"
},
@@ -108,6 +109,7 @@
"opening_id": "library-kallax-c4"
},
"166109:253058": {
"acknowledged": true,
"note": "",
"opening_id": "library-kallax-b1"
},
@@ -151,6 +153,10 @@
"note": "",
"opening_id": "library-kallax-d2"
},
"1784:110926": {
"note": "",
"opening_id": "library-right-shelving-2-d1"
},
"181304:536289": {
"note": "",
"opening_id": "library-kallax-b1"
@@ -263,6 +269,10 @@
"note": "",
"opening_id": "library-kallax-b3"
},
"258849:449806": {
"note": "",
"opening_id": "library-right-shelving-2-d1"
},
"258:20840": {
"note": "",
"opening_id": "library-kallax-b3"
@@ -284,8 +294,9 @@
"opening_id": "library-kallax-c2"
},
"280794:458300": {
"acknowledged": true,
"note": "",
"opening_id": "library-kallax-d3"
"opening_id": "library-right-shelving-e1"
},
"283155:462049": {
"note": "",
@@ -295,6 +306,19 @@
"note": "",
"opening_id": "library-kallax-d4"
},
"290840:474978": {
"note": "",
"opening_id": "library-right-shelving-e1"
},
"291130:475387": {
"acknowledged": true,
"note": "",
"opening_id": "library-right-shelving-e1"
},
"291131:475388": {
"note": "",
"opening_id": "library-right-shelving-e1"
},
"291457:476060": {
"note": "",
"opening_id": "library-kallax-d3"
@@ -311,6 +335,10 @@
"note": "",
"opening_id": "library-kallax-c2"
},
"311654": {
"note": "",
"opening_id": "library-right-shelving-2-d1"
},
"31260:297589": {
"note": "",
"opening_id": "library-kallax-d3"
@@ -351,6 +379,11 @@
"note": "",
"opening_id": "library-kallax-b2"
},
"351544:588185": {
"acknowledged": true,
"note": "",
"opening_id": "library-right-shelving-e1"
},
"352819:591147": {
"note": "",
"opening_id": "library-kallax-d1"
@@ -369,12 +402,16 @@
},
"358880:603694": {
"note": "",
"opening_id": "library-kallax-d3"
"opening_id": "library-right-shelving-e1"
},
"362205:612448": {
"note": "",
"opening_id": "library-kallax-d4"
},
"380459:743266": {
"note": "",
"opening_id": "library-right-shelving-2-d1"
},
"380837:650545": {
"note": "",
"opening_id": "library-kallax-c1"
+136
View File
@@ -0,0 +1,136 @@
name,opening,acknowledge
Gloomhaven,library-kallax-a1,
History of the World,library-kallax-a1,
Age of Renaissance,library-kallax-a1,
Simon's Cat Card Game,library-kallax-a1,
Skip-Bo,library-kallax-a1,
Snappy Dressers,library-kallax-a1,
Tem-Purr-A,library-kallax-a1,
Everdell: The Complete Collection,library-kallax-a2,
Bugs in the Kitchen,library-kallax-a2,
Risk: The Lord of the Rings,library-kallax-a2,
The Cat Game,library-kallax-a2,
The Rocky Horror Trivia Game,library-kallax-a2,
Utter Nonsense,library-kallax-a2,
Dixit,library-kallax-b1,
Flick 'em Up!,library-kallax-b1,
Googly Eyes,library-kallax-b1,
Meteor,library-kallax-b1,
Mysterium,library-kallax-b1,
Sheriff of Nottingham,library-kallax-b1,
Sheriff of Nottingham: Merry Men,library-kallax-b1,
Speechless,library-kallax-b2,
Superfight,library-kallax-b2,
The Red Dragon Inn 5: The Character Trove,library-kallax-b2,
The Red Dragon Inn Smorgasbox,library-kallax-b2,
Throw Throw Avocado,library-kallax-b2,
"Before I Kill You, Mister Spy...",library-kallax-b3,
Bluffaneer,library-kallax-b3,
Captain Marvel: Secret Skrulls,library-kallax-b3,
Cards Against Humanity,library-kallax-b3,
Code Master,library-kallax-b3,
Farkle,library-kallax-b3,
Fluxx,library-kallax-b3,
Give Me the Brain!,library-kallax-b3,
Go Fish Yourself,library-kallax-b3,
Greed Quest,library-kallax-b3,
Jenga,library-kallax-b3,
Joking Hazard,library-kallax-b3,
Kill Doctor Lucky,library-kallax-b3,
Poetry for Neanderthals,library-kallax-b3,
Rook,library-kallax-b3,
Scrawl,library-kallax-b3,
"...and then, we held hands.",library-kallax-b4,
Consentacle,library-kallax-b4,
Kensington,library-kallax-b4,
Mage Wars Academy,library-kallax-b4,
Onitama,library-kallax-b4,
Patchwork,library-kallax-b4,
5-Minute Dungeon,library-kallax-c1,
A Gentle Rain,library-kallax-c1,
Artistry,library-kallax-c1,
Artistry: Delightful Doorways Mini Expansion,library-kallax-c1,
Blink,library-kallax-c1,
Botany,library-kallax-c1,
Calico,library-kallax-c1,
Carcassonne,library-kallax-c1,
Castle Panic,library-kallax-c1,
Castle Panic: The Dark Titan,library-kallax-c1,
Castle Panic: The Wizard's Tower,library-kallax-c1,
CAT CRIMES,library-kallax-c1,
CAT STAX,library-kallax-c2,
Catan,library-kallax-c2,
Catan Dice Game,library-kallax-c2,
Catan: Junior,library-kallax-c2,
Cosmic Encounter,library-kallax-c2,
Dastardly Dirigibles,library-kallax-c2,
Deadly Doodles,library-kallax-c2,
Doctor Who: The Card Game,library-kallax-c2,
Doctor Who: The Game of Time & Space,library-kallax-c2,
Dragon Land,library-kallax-c2,
Dungeon!,library-kallax-c2,
Elite Sportz Equipment Table Top Curling,library-kallax-c2,
Evolution: The Beginning,library-kallax-c2,
Exploding Kittens,library-kallax-c2,
Forbidden Island,library-kallax-c3,
GRAVITY MAZE,library-kallax-c3,
Guillotine,library-kallax-c3,
Herbaceous,library-kallax-c3,
Hold Your Breath!,library-kallax-c3,
Huggermugger,library-kallax-c3,
Labyrinth,library-kallax-c3,
LASER MAZE BEAM-BENDING LOGIC GAME,library-kallax-c3,
Machi Koro,library-kallax-c3,
Mantis Falls,library-kallax-c3,
Munchkin,library-kallax-c3,
Munchkin Big Box,library-kallax-c3,
Murder at the Manor,library-kallax-c3,
"No Thank You, Evil!",library-kallax-c3,
Ouija,library-kallax-c4,
Pie Face!,library-kallax-c4,
Rivals,library-kallax-c4,
Robot Turtles,library-kallax-c4,
Royals,library-kallax-c4,
Santorini,library-kallax-c4,
Scrabble Slam!,library-kallax-c4,
Skylanders Portal Master,library-kallax-c4,
Small World,library-kallax-c4,
Sorcerer: The Game of Magical Conflict,library-kallax-c4,
SPANC: Space Pirate Amazon Ninja Catgirls,library-kallax-c4,
Super Mario Checkers Collector's Edition,library-kallax-c4,
Tang Garden,library-kallax-c4,
Terror in Meeple City,library-kallax-d1,
The Doom That Came to Atlantic City,library-kallax-d1,
The Office Trivia Game,library-kallax-d1,
The Simpsons Trivia Game,library-kallax-d1,
Ticket to Ride,library-kallax-d1,
Verdant,library-kallax-d1,
Völuspá,library-kallax-d1,
Walk the Plank!,library-kallax-d1,
Wiz-War,library-kallax-d1,
Wiz-War (Eighth Edition),library-kallax-d1,
Wiz-War (9th Edition),library-kallax-d1,
Advanced Civilization,library-kallax-d2,
Britannia,library-kallax-d2,
Civilization,library-kallax-d2,
Civilization: West Extension Map,library-kallax-d2,
Diplomacy,library-kallax-d2,
Flat Top,library-kallax-d2,
StarForce 'Alpha Centauri': Interstellar Conflict in the 25th Century,library-kallax-d2,
Agricola,library-kallax-d3,
Gloomhaven: Buttons & Bugs,library-kallax-d3,
Gloomhaven: Jaws of the Lion,library-kallax-d3,
Sleeping Gods,library-kallax-d4,
Sleeping Gods: Distant Skies,library-kallax-d4,
Sleeping Gods: Primeval Peril,library-kallax-d4,
Sleeping Gods: Tides of Ruin,library-kallax-d4,
Etherfields,library-right-shelving-e1,yes
Etherfields: Kittenburg Expansion,library-right-shelving-e1,yes
Etherfields: 5th Player Expansion,library-right-shelving-e1,yes
Etherfields: Funeral Witch Campaign,library-right-shelving-e1,yes
Etherfields: Sphinx Campaign,library-right-shelving-e1,yes
Etherfields: Stretch Goals Harpy & She-Wolf Campaigns,library-right-shelving-e1,yes
Alice is Missing,library-right-shelving-2-d1,
Alice is Missing: Silent Falls,library-right-shelving-2-d1,
Parsely,library-right-shelving-2-d1,
Dark Cults,library-right-shelving-2-d1,
1 name opening acknowledge
2 Gloomhaven library-kallax-a1
3 History of the World library-kallax-a1
4 Age of Renaissance library-kallax-a1
5 Simon's Cat Card Game library-kallax-a1
6 Skip-Bo library-kallax-a1
7 Snappy Dressers library-kallax-a1
8 Tem-Purr-A library-kallax-a1
9 Everdell: The Complete Collection library-kallax-a2
10 Bugs in the Kitchen library-kallax-a2
11 Risk: The Lord of the Rings library-kallax-a2
12 The Cat Game library-kallax-a2
13 The Rocky Horror Trivia Game library-kallax-a2
14 Utter Nonsense library-kallax-a2
15 Dixit library-kallax-b1
16 Flick 'em Up! library-kallax-b1
17 Googly Eyes library-kallax-b1
18 Meteor library-kallax-b1
19 Mysterium library-kallax-b1
20 Sheriff of Nottingham library-kallax-b1
21 Sheriff of Nottingham: Merry Men library-kallax-b1
22 Speechless library-kallax-b2
23 Superfight library-kallax-b2
24 The Red Dragon Inn 5: The Character Trove library-kallax-b2
25 The Red Dragon Inn Smorgasbox library-kallax-b2
26 Throw Throw Avocado library-kallax-b2
27 Before I Kill You, Mister Spy... library-kallax-b3
28 Bluffaneer library-kallax-b3
29 Captain Marvel: Secret Skrulls library-kallax-b3
30 Cards Against Humanity library-kallax-b3
31 Code Master library-kallax-b3
32 Farkle library-kallax-b3
33 Fluxx library-kallax-b3
34 Give Me the Brain! library-kallax-b3
35 Go Fish Yourself library-kallax-b3
36 Greed Quest library-kallax-b3
37 Jenga library-kallax-b3
38 Joking Hazard library-kallax-b3
39 Kill Doctor Lucky library-kallax-b3
40 Poetry for Neanderthals library-kallax-b3
41 Rook library-kallax-b3
42 Scrawl library-kallax-b3
43 ...and then, we held hands. library-kallax-b4
44 Consentacle library-kallax-b4
45 Kensington library-kallax-b4
46 Mage Wars Academy library-kallax-b4
47 Onitama library-kallax-b4
48 Patchwork library-kallax-b4
49 5-Minute Dungeon library-kallax-c1
50 A Gentle Rain library-kallax-c1
51 Artistry library-kallax-c1
52 Artistry: Delightful Doorways Mini Expansion library-kallax-c1
53 Blink library-kallax-c1
54 Botany library-kallax-c1
55 Calico library-kallax-c1
56 Carcassonne library-kallax-c1
57 Castle Panic library-kallax-c1
58 Castle Panic: The Dark Titan library-kallax-c1
59 Castle Panic: The Wizard's Tower library-kallax-c1
60 CAT CRIMES library-kallax-c1
61 CAT STAX library-kallax-c2
62 Catan library-kallax-c2
63 Catan Dice Game library-kallax-c2
64 Catan: Junior library-kallax-c2
65 Cosmic Encounter library-kallax-c2
66 Dastardly Dirigibles library-kallax-c2
67 Deadly Doodles library-kallax-c2
68 Doctor Who: The Card Game library-kallax-c2
69 Doctor Who: The Game of Time & Space library-kallax-c2
70 Dragon Land library-kallax-c2
71 Dungeon! library-kallax-c2
72 Elite Sportz Equipment Table Top Curling library-kallax-c2
73 Evolution: The Beginning library-kallax-c2
74 Exploding Kittens library-kallax-c2
75 Forbidden Island library-kallax-c3
76 GRAVITY MAZE library-kallax-c3
77 Guillotine library-kallax-c3
78 Herbaceous library-kallax-c3
79 Hold Your Breath! library-kallax-c3
80 Huggermugger library-kallax-c3
81 Labyrinth library-kallax-c3
82 LASER MAZE BEAM-BENDING LOGIC GAME library-kallax-c3
83 Machi Koro library-kallax-c3
84 Mantis Falls library-kallax-c3
85 Munchkin library-kallax-c3
86 Munchkin Big Box library-kallax-c3
87 Murder at the Manor library-kallax-c3
88 No Thank You, Evil! library-kallax-c3
89 Ouija library-kallax-c4
90 Pie Face! library-kallax-c4
91 Rivals library-kallax-c4
92 Robot Turtles library-kallax-c4
93 Royals library-kallax-c4
94 Santorini library-kallax-c4
95 Scrabble Slam! library-kallax-c4
96 Skylanders Portal Master library-kallax-c4
97 Small World library-kallax-c4
98 Sorcerer: The Game of Magical Conflict library-kallax-c4
99 SPANC: Space Pirate Amazon Ninja Catgirls library-kallax-c4
100 Super Mario Checkers Collector's Edition library-kallax-c4
101 Tang Garden library-kallax-c4
102 Terror in Meeple City library-kallax-d1
103 The Doom That Came to Atlantic City library-kallax-d1
104 The Office Trivia Game library-kallax-d1
105 The Simpsons Trivia Game library-kallax-d1
106 Ticket to Ride library-kallax-d1
107 Verdant library-kallax-d1
108 Völuspá library-kallax-d1
109 Walk the Plank! library-kallax-d1
110 Wiz-War library-kallax-d1
111 Wiz-War (Eighth Edition) library-kallax-d1
112 Wiz-War (9th Edition) library-kallax-d1
113 Advanced Civilization library-kallax-d2
114 Britannia library-kallax-d2
115 Civilization library-kallax-d2
116 Civilization: West Extension Map library-kallax-d2
117 Diplomacy library-kallax-d2
118 Flat Top library-kallax-d2
119 StarForce 'Alpha Centauri': Interstellar Conflict in the 25th Century library-kallax-d2
120 Agricola library-kallax-d3
121 Gloomhaven: Buttons & Bugs library-kallax-d3
122 Gloomhaven: Jaws of the Lion library-kallax-d3
123 Sleeping Gods library-kallax-d4
124 Sleeping Gods: Distant Skies library-kallax-d4
125 Sleeping Gods: Primeval Peril library-kallax-d4
126 Sleeping Gods: Tides of Ruin library-kallax-d4
127 Etherfields library-right-shelving-e1 yes
128 Etherfields: Kittenburg Expansion library-right-shelving-e1 yes
129 Etherfields: 5th Player Expansion library-right-shelving-e1 yes
130 Etherfields: Funeral Witch Campaign library-right-shelving-e1 yes
131 Etherfields: Sphinx Campaign library-right-shelving-e1 yes
132 Etherfields: Stretch Goals – Harpy & She-Wolf Campaigns library-right-shelving-e1 yes
133 Alice is Missing library-right-shelving-2-d1
134 Alice is Missing: Silent Falls library-right-shelving-2-d1
135 Parsely library-right-shelving-2-d1
136 Dark Cults library-right-shelving-2-d1
+5 -2
View File
@@ -165,8 +165,11 @@ whenever the opening is unchanged.
Some placements are deliberate misfits — square boxes spine-out on a
shallow shelf, standing proud of the edge. The checker will honestly
refuse those forever, so accept the overhang on purpose: the **accept
overhang** button in the opening's view (it appears on any resident the
lanes can't take), or an `acknowledge` column (`yes`) in the import CSV.
overhang** button in the opening's view, or an `acknowledge` column
(`yes`) in the import CSV. The button appears only when accepting would
actually give the box a lane; a resident squeezed out of a width-starved
opening is marked "no room" instead — that's overfull, not overhung, and
acknowledging wouldn't change the math.
An acknowledged box takes the standing lane without the depth check —
the width budget stays honest — and stops counting as a warning. Once
acknowledged, later imports needn't re-state it. `bggpipe dims` reports per-opening capacity, overfull warnings, and
+1 -3
View File
@@ -142,9 +142,7 @@ def run_dims_report(cfg: Config) -> dict:
typer.echo(f" ⚠ doesn't fit this opening at all: {name}")
for k, e in by_opening.get(oid, []):
if k in acked:
typer.echo(
f" overhangs, acknowledged: {e.get('name') or '?'}"
)
typer.echo(f" overhangs, acknowledged: {e.get('name') or '?'}")
homeless = [
entry.get("name") or "?"
for key, entry in games.items()
+1 -1
View File
@@ -30,7 +30,7 @@
<p><b><a href="/titles">Titles</a></b> — every read off your shelves, alphabetized, with its status and photos. This is the proofread checkpoint: <a href="#curation">edit, split, remove</a>. Its badge counts <span class="chip shaky">shaky read</span> lines — the model wasn't sure and nothing has verified them; filter to them, then press <b>✓ looks right</b> or edit each one.</p>
<p><b><a href="/review">Review</a></b> — the decisions only you can make: which game a title is, which edition a copy is, whether two same-game reads are really one box (merges show a veto), and whether an unmatched title is a real game BGG simply doesn't have (<b>keep locally</b>: it joins the Library, never uploads). Keyboard-first; see <a href="#keys">shortcuts</a>.</p>
<p><b><a href="/queue">Queue</a></b> — exactly what upload will do (new entries and version upgrades) and the log of everything it has done. Nothing reaches BGG that isn't visible here first. A job that fails is skipped by later runs (so one broken game can't loop forever); when any exist, the Pipeline's upload card offers a <b>retry N failed</b> checkbox. Each queued row shows what upload did with it — <span class="chip open">pending</span>, <span class="chip ok">done</span>, <span class="chip no">failed</span>, or <span class="chip no">retired</span> (a review decision since the last diff withdrew it). Finished rows stay listed until the next <b>diff</b> rebuilds the queue; the log below them is the permanent record.</p>
<p><b><a href="/shelves">Shelves</a></b> — where everything physically lives, drawn as it really stands: openings group into rows by label letter with widths proportional to their interiors, so the page is a diagram of your wall. Describe furniture once (presets for Kallax cubes and Billy shelves, grids of rows × columns — a later grid continues the row letters — custom sizes, and no-size spots like a travel case); units and openings both take a free-text description beside the zone keyword. Suggestions rank an opening holding a <b>series-mate or base game first</b> (marked ♥ — every placement you confirm teaches the suggester your organization), always against the <i>remaining</i> room, then tightest verified fit; a measured game with nowhere suitable says "no matching openings yet", and unmeasured games get no suggestions because can't-verify ≠ fits. Assign by tapping suggestions, searching inside an opening, or from any game's detail page. Capacity is a two-lane model — a flat stack against interior height plus a standing lane against the remaining width; the largest boxes lie flat, smaller ones stand, and each box's lane is flippable in the opening's view. ⚠ means <i>no packing fits</i>, not merely "tall stack" — and when the misfit is deliberate (boxes spine-out, proud of a shallow shelf) the <b>accept overhang</b> button in the opening's view accepts it: the box takes the standing lane, the width budget stays honest, and the warning goes quiet. Games stored inside another box ride along with their container, through whole chains. Your layout is local data only — never sent anywhere. Bulk-load a plan with <code>bggpipe shelve --import plan.csv</code> (name,opening, plus an optional acknowledge column for deliberate overhangs) — re-imports are upserts that keep notes, lanes, and acknowledgments — and check capacity anytime with <code>bggpipe dims</code>.</p>
<p><b><a href="/shelves">Shelves</a></b> — where everything physically lives, drawn as it really stands: openings group into rows by label letter with widths proportional to their interiors, so the page is a diagram of your wall. Describe furniture once (presets for Kallax cubes and Billy shelves, grids of rows × columns — a later grid continues the row letters — custom sizes, and no-size spots like a travel case); units and openings both take a free-text description beside the zone keyword. Suggestions rank an opening holding a <b>series-mate or base game first</b> (marked ♥ — every placement you confirm teaches the suggester your organization), always against the <i>remaining</i> room, then tightest verified fit; a measured game with nowhere suitable says "no matching openings yet", and unmeasured games get no suggestions because can't-verify ≠ fits. Assign by tapping suggestions, searching inside an opening, or from any game's detail page. Capacity is a two-lane model — a flat stack against interior height plus a standing lane against the remaining width; the largest boxes lie flat, smaller ones stand, and each box's lane is flippable in the opening's view. ⚠ means <i>no packing fits</i>, not merely "tall stack" — and when the misfit is deliberate (boxes spine-out, proud of a shallow shelf) the <b>accept overhang</b> button in the opening's view accepts it: the box takes the standing lane, the width budget stays honest, and the warning goes quiet. The button only appears when accepting would actually give the box a lane — an opening that's simply past its width budget says "no room" instead, because that's overfull, not overhung. Games stored inside another box ride along with their container, through whole chains. Your layout is local data only — never sent anywhere. Bulk-load a plan with <code>bggpipe shelve --import plan.csv</code> (name,opening, plus an optional acknowledge column for deliberate overhangs) — re-imports are upserts that keep notes, lanes, and acknowledgments — and check capacity anytime with <code>bggpipe dims</code>.</p>
<p><b><a href="/library">Library</a></b> — your enriched collection. Search titles, designers, mechanics and categories at once; filter by kind (board games, RPGs, off-BGG) or by how many people are playing tonight; sort by name, year, BGG rank, weight, or playing time. Click any game for its full detail: art, the usual stats, designers and mechanics, <b>your</b> edition, the shelf photos it was read from, and a link to its BGG page. RPG and off-BGG games live here too — identified and enriched, never uploaded. RPGs pull their designers, publishers and genres from RPGGeek; an off-BGG game's detail page lets you write its facts yourself and add a cover photo, since nothing else will ever have them (both are saved under <code>data/</code> and folded in by the next <b>enrich</b>).</p>
</div>
+8 -2
View File
@@ -234,13 +234,19 @@ function sheetRender() {
<a href="/library/game/${encodeURIComponent(g.key)}">${esc(g.name)}</a>
${g.measured ? "" : `<span class="chip open"
title="no trusted size — not counted in the fill bar">unmeasured</span>`}
${g.lane && !g.inherited
${g.acknowledged && g.lane
? `<span class="chip open" title="accepted overhang — sits standing,
proud of the shelf edge">▮ standing, proud</span>` : ""}
${g.laneless && !g.acknowledged && !g.ackable
? `<span class="chip open" title="no lane can take this box here —
the opening is past its packing budget; acknowledging wouldn't help">no room</span>` : ""}
${g.lane && !g.inherited && !g.acknowledged
? `<button class="lanebtn" data-key="${esc(g.key)}"
data-next="${g.lane === "flat" ? "standing" : "flat"}"
title="how this box sits — tap to flip${g.lane_override ? " (your override)" : ""}">
${g.lane === "flat" ? "▬ flat" : "▮ standing"}${g.lane_override ? " ✱" : ""}</button>`
: ""}
${(g.misfit || g.laneless || g.acknowledged) && !g.inherited
${!g.inherited && (g.acknowledged || g.ackable)
? (g.acknowledged
? `<button class="ackbtn on" data-key="${esc(g.key)}" data-ack="false"
title="you accepted this overhang — tap to retract">overhang ✓</button>`
+14
View File
@@ -1116,6 +1116,15 @@ def create_app(
for opening in unit.get("openings", []):
here = residents.get(opening["id"], [])
report = opening_report(opening, here, lane_overrides, acked)
def ackable(k: str, name: str, opening=opening, here=here) -> bool:
# offer "accept overhang" only when accepting would
# actually give the box a lane — a width-starved
# opening is overfull, not overhung, and the button
# would be a placebo there
probe = opening_report(opening, here, lane_overrides, acked | {k})
return name not in probe["laneless"]
out_openings.append(
{
**opening,
@@ -1133,6 +1142,11 @@ def create_app(
"laneless": (e.get("name") or "?")
in report["laneless"],
"acknowledged": k in acked,
"ackable": (
(e.get("name") or "?") in report["laneless"]
and k not in acked
and ackable(k, e.get("name") or "?")
),
}
for k, e in sorted(
here,
+61
View File
@@ -1095,3 +1095,64 @@ def test_import_acknowledge_column(tmp_path):
"lane": "standing",
"acknowledged": True,
}
def test_ackable_only_when_it_helps(tmp_path):
"""The accept-overhang button is offered only when acknowledging
would actually give the box a lane. A depth-blocked box on a shallow
shelf is ackable; a box squeezed out of a width-starved opening is
overfull, not overhung — no placebo button."""
web, cfg = _web(
tmp_path,
{
"1": _entry("Wide Flat", 13.0, 13.0, 12.0),
"2": _entry("Squeezed Out", 10.0, 10.0, 3.0),
"3": _entry("Etherfields", 11.8, 11.8, 2.9),
},
)
save_furniture(
cfg,
[
{
"name": "Mix",
"openings": [
{
"id": "cube",
"label": "A1",
"zone": "",
"width_in": 13.25,
"height_in": 13.25,
"depth_in": 15.4,
},
{
"id": "tower",
"label": "B1",
"zone": "",
"width_in": 23.25,
"height_in": 19.0,
"depth_in": 9.5,
},
],
}
],
)
save_locations(
cfg,
{
"1": {"opening_id": "cube", "note": ""},
"2": {"opening_id": "cube", "note": ""},
"3": {"opening_id": "tower", "note": ""},
},
)
units = web.get("/api/shelves").json()["units"]
by_label = {o["label"]: o for o in units[0]["openings"]}
cube = {g["name"]: g for g in by_label["A1"]["resident_games"]}
# the wide flat box claims 13.0" of a 13.25" opening: 0.25" of
# standing lane left — acknowledging Squeezed Out changes nothing
assert cube["Squeezed Out"]["laneless"] is True
assert cube["Squeezed Out"]["ackable"] is False
tower = {g["name"]: g for g in by_label["B1"]["resident_games"]}
# the classic overhang: no lane takes 11.8" of depth on a 9.5"
# shelf, but standing proud works — offer the button
assert tower["Etherfields"]["laneless"] is True
assert tower["Etherfields"]["ackable"] is True