TODO-011: split the one-ticket page out of todo into its own view ticket

Now
DONE
Sev
MED

RULING (gritzko, 2026-08-04): todo is the TABULAR side only — the open-ticket board, a topic's list, the meta-pair filters. The one-ticket page becomes its OWN view named ticket. Today one view multiplexes both by arg shape (views/todo/todo.js:1-6: bare → board, ABC → topic list, ABC-123 → ticket page) and has grown to 1601 lines; the page arm and the table arm share an arg grammar they don't actually share (filters mean nothing on a page, a page key means nothing to a table).

Input

Context

Goals

ticket ABC-123 renders the one-ticket page exactly as todo ABC-123 does today (byte-parity is the RED/GREEN baseline); todo keeps board + topic list + filters only.

Constraints

WIP

Design decisions

TODOs

Blockers and bummers

Outcome

DONE — views/ticket/ticket.js is the one-ticket page view; todo is the board, the topic lists and the meta-pair filters, and nothing else.

Baseline parity (the RED/GREEN evidence)

The pre-split tree (be @ 94714be8, untouched copy) and the post-split tree were run over the SAME fixture — a thin todo/PAG/PAG-001.mkd and a fat todo/PAG/PAG-002/README.mkd, each carrying meta pairs (plain, ticket-valued, an UNREGISTERED Zzz:, a colon-carrying Rev:), a bare key mention, a [KEY] reflink, a named refdef reflink and a /wiki/Sample shortcut:

- SPELL " PAG-002" -> "todo PAG-002"        (See: value)     + "ticket PAG-002"
- SPELL " PAG-003" -> "todo PAG-003"        (Zzz: value)     + "ticket PAG-003"
- SPELL "PAG-002"  -> "todo PAG-002"        (bare body key)  + "ticket PAG-002"
- SPELL "[/PAG-003/]" -> "todo PAG-003"     (reflink run)    + "ticket PAG-003"
SPELL "Now:"     -> "todo PAG Now:*"      UNCHANGED (filter → the board)
SPELL " OPEN"    -> "todo PAG Now:OPEN"   UNCHANGED
SPELL "Sev:"/" MED"/"See:"/"Zzz:"/"Rev:"  UNCHANGED
SPELL "W"/"Sample" -> "cat wiki/Sample.mkd"  UNCHANGED

The board and topic listings show the mirror image: every KEY row went todo <KEY> → ticket <KEY>, while the topic header (GET → todo GET) and the [ ✔]/[ ✗] panel spells (done KEY / dont KEY) are untouched, and the visible tokens are byte-identical (41 board / 37 topic).

Refusal texts (all plain words, no bare code)

$ todo PAG-001
todo: 'PAG-001' names one ticket page, and this is the board — write 'ticket PAG-001'

for the page, or 'todo PAG' for the topic's list

$ todo PAG-001 Now:OPEN todo: 'PAG-001' names one ticket page, and this is the board — write 'ticket PAG-001'

for the page, or 'todo PAG Now:OPEN' for the topic's list

$ ticket ticket: which ticket? — write 'ticket ABC-123' for one ticket's page, or 'todo' for the open board $ ticket PAG ticket: 'PAG' names a topic, and a topic is a LIST — write 'todo PAG' for its open tickets,

or 'ticket PAG-123' for one ticket's page

$ ticket Now:OPEN ticket: 'Now:OPEN' is a filter, and a filter narrows a LISTING, never a page — write

'todo Now:OPEN' for that list; 'ticket' takes one ticket code and nothing else

$ ticket PAG-001 Now:OPEN ticket: 'Now:OPEN' is a filter, … — write 'todo PAG Now:OPEN' for that list; … $ ticket PAG-999 ticket: there is no ticket PAG-999 under todo/PAG/ — 'todo PAG' lists that topic's open tickets


`todo`'s own topic miss keeps its golden-pinned `todo: NOPE: TODONONE` line; every exit is non-zero.

Spell sweep, through the REAL pager (pty clicks, be-js-todo-nav)

ok  page-painted                        ok  board-painted
ok  see-value-navigates                 ok  board-inline-value-navigates
ok  see-value-is-not-a-filter           ok  board-inline-value-is-not-a-filter
ok  unknown-key-value-navigates         ok  list-painted
ok  key-half-still-filters              ok  list-key-row-opens-the-ticket-view
ok  key-half-answers

Each landing is asserted on BOTH the BRO-024 address bar (/: ticket NAV-002) and the painted body, so a todo answer that merely mentions the code cannot pass. New leg list-key-row-opens-the-ticket-view: a pty click on a topic-list KEY row lands on that ticket's page in the ticket view. be-js-todo-click pins the other direction: an in-page meta pair click still opens todo CLK Now:* / todo CLK Now:OPEN. be-js-work-view pins the work board's [?] → //: ticket PIN-1 (its pty leg fires the button and lands on the page).

Suite

Files

NEW  views/ticket/ticket.js          260   the page view (135 lines cut verbatim out of todo.js)
NEW  test/ticket/page/run.sh         ~150  parity vs the pre-split goldens + both refusal families
NEW  test/ticket/page/check.js       ~115  the page spell sweep
NEW  test/ticket/page/golden/PAG-001.plain, PAG-002.plain

views/todo/todo.js -246/+… page arm out, key spells → ticket, the refusal,

parseArgs/unscheme parameterised, shared exports

views/work/work.js 16 the [?] key branch mints ticket <KEY> core/loop.js 5 wrap.ticket = false (preserve the page's no-wrap) test/todo/view/{run.sh,check.js} page legs → ticket, board spells → ticket <KEY>,

the new todo <KEY> refusal leg

test/todo/meta/{run.sh,check.js} page legs → ticket, refusal + miss legs test/todo/nav/{run.sh,nav.py} landings → ticket, + the list-key-row leg test/todo/click/click.py the page leg → ticket CLK-001 test/todo/rows/check.js the row's nav spell test/work/view/{run.sh,check.js,clickpty.py} [?] → //: ticket PIN-1 test/done/buttons/check.js the row's nav spell


Open questions for gritzko

  1. *Refusal vs silent re-route* — took the ticket's stated DEFAULT (refuse). Say the word and todo ABC-123 can forward to ticket ABC-123 instead; the refusal is one bad() call in todo().
  2. *ticket's wrap default* — set to false (no-wrap) purely to preserve what the page had while todo served it. A ticket page is PROSE, so true (soft-wrap, the cat/diff/status default) may be what you actually want now that it has its own type.
  3. *The ticket miss line is plain words*, not the board's legacy todo: KEY: TODONONE. The errors-in-plain-words ruling says so; the BE-038 golden keeps todo's own line as it was. Say if you want the new view to carry the old shape for symmetry.
  4. *views/work/work.js was touched* (one literal) though the ticket scoped the change to views/ticket/ + views/todo/ — its [?] invite on a ticket-named worktree would otherwise have become a dead button.

Suggested commit message

TODO-011: split the one-ticket page out of `todo` into the view `ticket`