todo into its own view ticketRULING (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).
uc ucnum* "-" dgt+ key rule, todo.js:4-5) and stays lexical — no path resolution in arg classing.todo/GET/GET-001.mkd or a fat todo/GET/GET-001/README.mkd off be.todoRoot() (URI-016 climb, never an env var).O spells, the pager stays arg-blind):
1. board/list ticket-key rows carry todo <KEY> spells today — a KEY row must now spell ticket <KEY>.
2. TODO-008 in-page ticket-valued meta links (See: BE-050) navigate to the ticket page — now ticket BE-050.
3. filter/topic spells (todo ABC Now:OPEN, argLineWith replacement, BRO-025) are table business and STAY todo spells, untouched.Now:/Sev: reading with the legacy header-mark fallback) is shared ground: the page shows a ticket's own pairs, the table filters on them. Share via requires, never duplicate.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.
todo ABC-123 no longer serves the page: it refuses in plain words pointing at ticket ABC-123 (errors-in-plain-words rule; if a silent re-route is preferred that is gritzko's call — record the question in the Outcome, default to the refusal).ticket; every filter/topic click stays in todo.ticket rejects non-key args in plain words (ticket ABC names the topic list it is not: point at todo ABC; filters likewise point at todo).O spells (BE-054); arg classes stay LEXICAL.todo/done/ never lists — unchanged; the ticket view CAN render a closed ticket by key (a page is not a listing; same fail-open spirit as todo.js:45-48).views/ticket/ + edits in views/todo/; registry entry for the new view; no other verbs.ticket stays byte-identical to today's page under todo (modulo the in-page spell verb name).core/registry.js build() resolves a bareword by FILE (views/<verb>/<verb>.js, then verbs/<verb>/) — dropping views/ticket/ticket.js in registers the view. Same for the test suite: test/CMakeLists.txt globs <verb>/<case>/run.sh, so test/ticket/page/ registers itself as be-js-ticket-page.views/todo/todo.js.* The page arm MOVED to ticket.js (refdefs, targetSpell, emitPage, pageLinks, the two tok helpers — 135 lines, cut verbatim); everything both views need stays in todo.js and is required from there: the key/topic LEXER (shape, ticketKey, keyTopic), the board root (boardDir), the page-file ladder (pageFile), the byte read (readBytes), EXTS, the arg-line grammar (parseArgs, argLineWith, spellWith) and the two click classifiers (navSpell, filterVal). Nothing is implemented twice; the edge is one-way (ticket.js → todo.js), so it needs no lazy require (unlike work.js ⇄ todo.js).parseArgs stays purely GRAMMATICAL and is shared.* It classes a token lexically (uc ucnum* "-" dgt+ unchanged) and never decides which view serves what. Its old "a ticket id takes no filters" check MOVED out to the two verbs, so each refusal can point at the other view and neither message is duplicated. unscheme grew a scheme parameter so the ONE DIS-060 parse serves todo:GET-1 and ticket:GET-1 alike.todo ABC-123 REFUSES (the ticket's stated default), it does not re-route.* The arg line IS the address bar (BRO-024/BRO-025), so a todo line answering with a page would lie about where the reader is. One refusal covers the filtered form too.ticket spell; a spell that lands on a LISTING is a todo spell. That is the whole rule, and navSpell() is its single point of truth for ticket-valued links. The in-page meta KEY/VALUE filter halves therefore stay todo spells minted through spellWith — a cross-view landing BY DESIGN, since a page carries no filters (a ticket-id arg line resolves to its TOPIC via argLineWith).views/work/work.js had to move too* (one literal): the [?] invite on a ticket-named worktree minted //: todo <KEY>, which the refusal would have turned into a dead button. It is //: ticket <KEY> now; the TOPIC-named branch is a listing and stays //: todo <TOPIC>.core/loop.js gains wrap.ticket = false.* An UNLISTED view type defaults to SOFT-wrap (pager.js wrapFor), so without the seed the split would have silently re-wrapped every ticket page. false preserves exactly what the page had under todo.ticket miss is PLAIN WORDS, not the board's legacy TODONONE.* todo's own miss line is golden-pinned (BE-038) and is untouched; the new view starts clean under the errors-in-plain-words ruling.todo ABC-123 page bytes for a thin and a fat ticket → the ticket parity fixture.views/ticket/ticket.js extracted; registry entry (none needed — resolution is by file); todo page arm removed with the plain-words pointer.ticket <KEY>; filter/topic spells untouched; pager click round-trip verified.todo ABC-123 refusal, ticket non-key refusals, existing todo suite green.be-js-todo-rowcache was ALREADY red on the base (TODO-010) and is not touched here.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.
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:
--plain: todo KEY vs ticket KEY differ in ONE byte run, the banner (todo PAG-001 → ticket PAG-001). Both goldens are committed under test/ticket/page/golden/.--tlv: the VISIBLE token stream (every non-O token, tag + bytes) is byte-identical, 111 tokens on each page.--tlv spells: the ONLY delta is the verb name on the spells that land on a PAGE.- 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).
$ 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.
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).
94714be8, before: *352/353*, one red — be-js-todo-rowcache (pre-existing, TODO-010: the and-the-row-grew-the-staged-tick pty leg).be-js-ticket-page.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
todo ABC-123 can forward to ticket ABC-123 instead; the refusal is one bad() call in todo().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.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.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.TODO-011: split the one-ticket page out of `todo` into the view `ticket`