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:
co-authored by
Claude Fable 5
parent
7d7d3bb8da
commit
f0cf4ec271
@@ -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
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
+5
-2
@@ -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
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user