Minor rather than patch, and the tie-break rule says default to patch, so the reason is worth stating: a capability arrived AND one left. Report authors gain a declared public API -- one line, `<script src="/_booth/embed.js" defer>`, plus the `data-booth-mark` anchor syntax -- and the verbatim path loses no-JavaScript operation, which it had since it existed. That asymmetry is what makes it not a tie. Either half alone would have been defensible as a patch. Operator approved 2026-09-22.
8.2 KiB
The Booth — roadmap
Design: docs/design/information-architecture.md.
Current version: 0.5.0 (U1, U2, U3, U4 and U5 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 | ce598b3 |
captions never reach the zoom view (never sent, not lost) | U1 |
| 2 | c7f9437, released v0.2.0 |
5 mechanisms for 1 job; operator→session loop runs through chat | U2 |
| 3 | 87e2c53, released v0.5.0 |
6 regexes injected into arbitrary author HTML, load-bearing for asks | U3 |
| 4 | c3a97c1, released v0.4.0 |
70% of booths on the .forever escape hatch (54% when first counted) |
U4 |
| 5 | c015a91, released v0.3.0 |
job 5 had no home, so it lived on the link board as 145 dead rows | U5 |
| 6 | Benches — registry, identity, enforced rule, migration | 69% link-board rot; the same bench posted 5× | 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, U2, U3, U4 and U5 are landed — the whole middle tier is closed. U6
remains independent and unstarted; U7 is now unblocked, since its only
dependency was {U3, U4, U5}. Two units left to v1, and they do not depend on
each other, so either can go next.
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 newandbooth add, so every existing caller starts announcing without learning anything. - The
whyhas 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) |
| 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), and U6's bench listing.
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
askCLI verbs. Pre-1.0, andask/asks/answerbecome thin aliases overmarksrather 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.mdis 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-uibrief written against the landed v1 structure — the same shapehamr-devandpewpew-devused.
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.