Files
booth/ROADMAP.md
T
vh 3296a868fa chore(release): v0.6.0 — U6, benches
The sixth of seven v1 units. A bench is a running thing, registered: identity
is the normalized URL so re-posting updates the row instead of appending a
fifth, `booth link` refuses the one shape that now has a better home, and the
board marks the rows whose booths are gone without deleting a single one.

Minor rather than patch, approved by the operator. Two capabilities arrived and
one verb changed behaviour for seventeen agent handles, which is the
push-notification bar in the tier test: `booth bench` is new, the board gained
a dead marker, and `booth link` now refuses a booth URL and a credentialed one.

444 -> 607 tests across the unit and its three cold gates. All four review
gates closed: an in-session seam review (three real contract defects, including
one that would have 404'd the whole board page), an in-session adversarial pass
(four defects, one of them this repo's own FIFO lesson recurring in a new
file), and three cold cross-frontier panels -- contract paraphrase, code-vs-
contract, and a diff-scoped bug hunt -- folded in full with exactly one finding
declined and its reasoning recorded.

Measured before contracted, and the measurement changed the unit: the design
doc's headline 69% rot was two defects wearing one number, and U5 had already
closed the larger half. Identity is the FULL normalized URL rather than the
origin because origin identity merges eight distinct gitea repositories, three
unrelated model cards, and the two LRPG surfaces the design doc itself names as
an example of two real benches.
2026-09-22 14:21:23 -07:00

9.3 KiB

The Booth — roadmap

Design: docs/design/information-architecture.md. Current version: 0.6.0 (U1 through U6 landed; extracted from eshpfi 2026-09-21).

v1 target

What must be true to cut 1.0. Seven capabilities, each closing a measured defect — not a wish. The measurements are in the IA doc.

# capability closes unit
1 One item record — landed ce598b3 captions never reach the zoom view (never sent, not lost) U1
2 Marks — landed c7f9437, released v0.2.0 5 mechanisms for 1 job; operator→session loop runs through chat U2
3 Declared embed seam — landed 87e2c53, released v0.5.0 6 regexes injected into arbitrary author HTML, load-bearing for asks U3
4 Derived lifetime — landed c3a97c1, released v0.4.0 70% of booths on the .forever escape hatch (54% when first counted) U4
5 Self-announcing booths — landed c015a91, released v0.3.0 job 5 had no home, so it lived on the link board as 145 dead rows U5
6 Benches — landed 1c3ce5d, released v0.6.0 69% link-board rot (re-measured: 178 booth rows + 8 bench re-posts) U6
7 Navigation at 270 items — sections, rail, filters, grid keyboard one flat wall; subfolder structure discarded at render U7

Ordering is dependency-driven, not priority-driven: U1 → U2 → {U3, U4, U5} → U7, with U6 independent of all of them (different storage, different surface) and therefore the safest thing to land first or in parallel.

U1 through U6 are landed. U7 is the last unit before the 1.0 cut — its only dependency was {U3, U4, U5} and that closed with U3.

⚠ Before starting U7, read persistent-memory.d/2026-09-21-u7-section-premise-half-wrong.md, and re-count the booths first. Half its premise is already known to be wrong — every booth that actually needs navigation is FLAT — and the booth set churned again on 2026-09-22: the four large booths U7 was sized against (pancake-v3-full and pancake-v4-full at 270 items, sindra20-engines, sindra-finalists) have all been swept. The largest live booth is now miranda-is at 92 items, flat. Two of 23 booths have subfolders (pewpew-ui-brief, dfa-concepts) and both are reports — the job where grid navigation matters least. Sections, one of U7's four named components, buys close to nothing. The rail, the filters and the grid keyboard are the unit.

U5's adoption is a measured prediction, not a finished result, and it is TWO predictions rather than one. The operator declined a fleetwide announcement so that adoption could be told apart from design; within fifty minutes of the deploy a peer that had been told nothing (comfy-dev) created a booth and it announced itself with a handle and an empty why. That is the split:

  • The handle rides for free. It is written by booth new and booth add, so every existing caller starts announcing without learning anything.
  • The why has to be learned. It needs someone to know the flag exists.

Both get re-measured on 2026-09-29:

find ~/booth-data -maxdepth 2 -name .booth.json | wc -l            # free
grep -l '"why": "[^"]' ~/booth-data/*/.booth.json | wc -l          # learned

A high first count with a near-zero second is the predicted shape of "nobody was told" — an adoption failure fixed by announcing, which is a different thing from nobody wanting it. Same instrument as U4's .forever prediction below.

Cross-cutting invariant — deterministic order, everywhere

Every ordered collection the Booth renders must have a stated, deterministic order. Not "usually stable", not "whatever the filesystem yields" — a rule someone can name, that produces the same sequence on every render of the same state. The rule itself is free to be anything defensible: byte order over a path, creation time, an explicit number, even an arbitrary-but-recorded sequence. What is forbidden is no rule.

This matters more here than in most services because the Booth's whole job is comparison. The operator is judging pancake-v3-full against pancake-v4-full, tile 47 against tile 47. If the order shifts between two page loads — or differs between the gallery, the zoom ring, the zip manifest and the marks read — then every positional reference the operator makes ("the third one from the left", "the one after the banded one") is silently wrong, and a flag or a note lands on the wrong artifact. Non-determinism does not present as a bug report; it presents as the operator's judgment being quietly misfiled.

Where it already binds, and what the rule is in each case:

collection rule
items in a booth sorted(rel) — byte order over the booth-relative path (U1 INV-3)
the zoom prev/next ring the item order, filtered to images — same sequence, one source
caption sidecar resolution sorted scan, so two media files sharing a stem resolve the same way every time (a real non-determinism U1 removed)
marks in a booth (created, id) — time, with the id as tie-break so two marks written in the same second cannot swap
legacy ask import (mtime, name), which is the order list_asks gave them
link board rows pinned first, then newest-first
a booth's announcement not a collection — one flat record per booth, nothing to order (U5)
the bench registry (state rank, name casefolded, id) — live before promoted before retired, then alphabetical, with the id as a TOTAL tie-break so two benches sharing a name cannot swap (U6)
the link board's dead marker not an order — a per-row stamp read from the existing order_for_display sequence, so marking cannot move a row (U6)
embed anchors in a verbatim report document order — what querySelectorAll yields, so the author's markup decides (U3)
the embed tail (fragments the author did not place) payload order, which is the marks order (created, id) — one rule, whether a fragment lands at an anchor or at the end (U3)
questions within a pick declaration order, in the payload's questions LIST — carried by the format rather than by object-key insertion order (U3)

U3's three rows are the first case where the rule binds across a language boundary: the order is decided in Python and honoured in JavaScript, and a browser test asserts it rather than a string assertion that could not see it.

U4 added no ordered collection — a booth's lifetime is one state per booth, not a sequence — so the rule above did not need a new row. The three lifetime surfaces (index card, booth header, marks page) render through ONE macro precisely so they cannot disagree, which is the same property stated for ordering: one rule, one place, every surface reading it.

Where it is still to be decided, and must be before the unit ships: U7's section ordering and its compare pairing (sections need a stated order among themselves, not just within; pairing by filename needs a rule for what happens to an unpaired file). U6's bench listing is settled — the row above. Compare pairing is parked to v1.1 with compare mode itself, so U7 carries one undecided rule, not two.

The test for any new ordered surface: can you write the rule down in one line? If not, it does not have one yet.

Explicitly NOT in v1

  • Backward compatibility with the ask CLI verbs. Pre-1.0, and ask / asks / answer become thin aliases over marks rather than a second code path. The 17 consuming handles get one althing note naming the change — the one case where telling peers is real coordination and not a broadcast.
  • A migration that deletes anything. links.md is archived verbatim and committed before the registry is seeded from it.

Parking lot

Deferred with a home, per the anti-creep gate. Default is park; these were weighed against the v1 path and lost on purpose.

item why parked
Compare mode — pair-by-name A/B across subfolders The best idea in the set, and the only one that is a new capability rather than a fix for a measured defect. The four-booth pancake-v3/v4 dance still works. First thing in v1.1.
Virtualized / progressive grid loading Speculative. 270 <img loading="lazy"> may be fine. Measure the real booth before optimising it — if it renders inside a second, this is invented work.
Bench uptime history + graphs The v1 need is "is it dead", which one flag answers. A time series is a different product.
Cross-booth search No evidence of the need in the usage data.
Per-viewer state (who has seen what) The Booth has one viewer. Revisit if that stops being true.
Auth Standing non-goal. LAN/mesh-internal. Blur stays cosmetic and says so.

Post-v1, already committed

  • SVOS theme retrofit by design-dev. Runs as a parallel track, not a v1 gate: we own the information architecture (it is driven by the measurement above), design-dev owns the visual and interaction system. The handoff is a /vor-ui brief written against the landed v1 structure — the same shape hamr-dev and pewpew-dev used.

Gate

A proposed feature is on the v1 path or it is parked. Default: parked. When both are defensible, park it — same asymmetry as the patch-default in SemVer. Applies regardless of who proposed it.