You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Status entry and pipeline band showing the work state #203
Show the work state on screen: a status entry ($.ui.status: map, ticket number and title, step) and a band above the prompt drawing the pipeline discuss -> plan -> execute -> verify with the current step highlighted (and, during an autonomous run, a tickets-done counter). Both read the work state as decided, redraw when it changes, stay quiet when no ticket is in play, and render on the terminal and desktop surfaces (tests loop over both).
Closed at the brief: the autonomous-run counter is added to the work state helper in this ticket (new field autoDone), not deferred to the auto loop ticket.
Steps
Helper: autoDone field (plugins/trailhead/templates/trailhead-work-state.js). New flag --auto-done <n> (non-negative integer, strict parse, usage error otherwise). Shape becomes { map, ticket, title, step, auto, autoDone, session, updatedAt }; autoDone defaults to 0, is kept by a merge like the other fields, is dropped by a ticket change unless passed again, and is reset to 0 whenever the merged auto is false. sanitizeState accepts a non-negative integer only. TDD: extend hooks/lib/work-state.test.js first (parse, merge, reset on auto false, invalid value rejected, old file without the field).
Skill prose: _shared/teamwork.md Work state paragraph and trailhead-work/references/auto.md say that during an autonomous run every set adds --auto true --auto-done <n>, n the tickets resolved so far in this run. Codex projection test pins the new form (bin/lib/codex-projection.test.js).
Pure view logic (plugins/trailhead/hooks/mod/work-state-view.ts, new): parseWorkState(text) validates the file the same way the helper does (bad JSON or no valid ticket/step -> null, missing autoDone -> 0); statusLine(state, isOrphaned) -> trailhead map #198 · #203 Title · plan (whiteboard in place of map #<n> when map is null; · auto 3 done appended during a run; the same line with · orphaned, resume? appended when orphaned (map, ticket, title and step kept); undefined when no state); pipeline(step) -> the four stages with done | current | todo (resolve = all done, paused = the stage is unknown, shown as a paused tag).
Mod wiring (plugins/trailhead/hooks/mod/register.ts): refresh($) reads .trailhead/work-state.json via $.fs.read (relative to the session cwd) and compares session with $.session.id() to set isOrphaned (a null session in the file is not orphaned). Outcomes: a missing file (rejection carrying ENOENT) or an unparseable one -> both atoms reset to null/false, $.ui.status(undefined), band gone; any other read error (transient) -> keep the last state untouched. Atoms are written only when the value changed, compared structurally on the normalised state (key-ordered JSON), never by reference (no redundant redraw), and the status is set only when its text changed. On session.end with reason: 'clear' (a /clear changes the session id and raises no session.start) a state still on screen is marked orphaned at once, since the session that wrote it is gone. Refresh is called on session.start, turn.start (re-checks against the new session id after a /clear), turn.complete, and on a Bash tool.call whose command names trailhead-work-state.js, after await next(e) so the helper has already written (the helper is the only writer, so this catches every step change mid-turn). Every hook passes through next(e); a refresh failure never blocks (caught, logged to debug).
Band (ui.render on AbovePrompt): reads both atoms reactively with read($, atom) (so a write redraws the band, no manual invalidate); next(e) when there is no state or a survey holds the band (e.props.hasSurvey); otherwise one row #203 Title (truncated to bodyColumns) and one row discuss › plan › execute › verify, the current stage bold in the accent colour, done stages plain, pending ones dim, plus auto · 3 done during a run; the whole band dim with an orphaned, resume? tag when orphaned. Only elements both terminal and desktop tables carry (Box, Text).
Tests (plugins/trailhead/tests/work-state-ui.test.ts, new), each looped over ['terminal', 'desktop']: the test's on answers fs.read and session.id and records ui.status; cases: no file (band draws nothing, status cleared); a valid file then the file removed (band and status cleared); a non-ENOENT read error keeps the last state; status text with a map and with map: null; each step highlights the right stage, resolve and paused; auto counter shown; orphaned dims and changes the status text; hasSurvey yields; a Bash tool.call naming the helper re-reads only after its next resolved (the test's tool.call answer writes the new file content) and redraws; an unchanged file produces no extra ui.status call and no redraw (render count unchanged); session.end with reason: 'clear' marks a shown state orphaned without a new turn. Existing mod.test.ts kept.
Reviewer: codex (2 rounds, read-only). All blocking concerns folded into the PLAN above.
Round 1
BLOCKING: status line omitted the map. Fixed: map #<n> (or whiteboard) in the line, tested with and without a map.
BLOCKING: a missing file vs a failed read was ambiguous. Fixed: ENOENT or an unparseable file clears both atoms, the status and the band; any other read error keeps the last state.
BLOCKING: redraw not guaranteed. Fixed: the band reads the atoms reactively with read($, atom).
BLOCKING: the tool.call refresh must follow the helper's run. Fixed: refresh after await next(e), tested.
NON-BLOCKING: /clear emits no session.start. Fixed: refresh on turn.start too.
NON-BLOCKING: missing tests (map format, file removed, hasSurvey, no redundant writes). Added to step 7.
Round 2
BLOCKING: /clear still not handled at once. Fixed: session.end with reason: 'clear' marks a shown state orphaned immediately, tested.
BLOCKING: the orphaned status dropped map, title and step. Fixed: the full line is kept with · orphaned, resume? appended (mockup updated to match).
NON-BLOCKING: "write only when changed" needs a structural comparator. Fixed: key-ordered JSON comparison, pinned by a no-redraw assertion.
The rounds are spent; the round 2 fixes are targeted and were not re-reviewed.
States: ticket in play, autonomous run with counter, paused, orphaned (after /clear), resolve, no ticket in play. Band: row 1 #<n> (bold accent) + title cut to the band width, [-] belongs to the engine; row 2 the four stages, done ✓ green, current ▸ bold accent, pending dim, ‖ paused or orphaned, resume? tag in amber, auto · <n> done right-aligned in amber. Status entry: trailhead map #<m> · #<n> <title> · <step>[ · auto <k> done][ · orphaned, resume?].
Mockup fidelity (approved artboard "Work state band and status"), each test run on terminal and desktop:
Mockup element / state
Reproduced
Proven by
1 Ticket in play: ✓ done green, ▸ current bold accent, pending dim, dim ›
yes
work-state-ui.test.ts "each step highlights its stage"
2 Auto run: auto · <n> done amber, right-aligned on row 2
yes (space-between row at bodyColumns; props checked, not paint)
auto counter test
3 Paused: names dim, ‖ paused amber
yes
step test (paused)
4 Orphaned after /clear: band dim, no bold, orphaned, resume? amber
yes
orphaned test + "/clear marks a shown state orphaned"
5 Resolve: all four ✓ green
yes
step test (resolve)
6 No ticket in play: nothing drawn, no status
yes
"no state file", file-removed tests
Row 1: #<n> bold #7aa2f7 + title cut with … to bodyColumns; engine's [-] not drawn
yes
truncation test at width 40
Status entry trailhead map #<m> · #<n> <title> · <step>[ · auto <k> done][ · orphaned, resume?], whiteboard variant
yes
status line tests
Deviations decided in Execute (none visible in the mockup): when a state is both paused and orphaned, only the orphaned tag shows; when orphaned, the auto · <n> done text is hidden from the band (still in the status line).
I1 (info): the band title cut counted UTF-16 units and could split an emoji's surrogate pair: fixed (fc13de6, code-point cut + test).
Goal-backward verification: status: met (14 claims met, mockup fidelity included). Its one note, /clear marking a work state with no session as orphaned, contradicting the "no session is never orphaned" rule: fixed (7192c93, + test).
Acceptance testing: guided live session (claude --plugin-dir plugins/trailhead), driven by the maintainer, in progress.
VERIFY (acceptance): guided live check, driven by the maintainer in claude --plugin-dir plugins/trailhead (Claude Code 2.1.296, terminal). Pass.
A work state written by another session: band dim with orphaned, resume? in amber, status entry with the orphaned suffix. Pass.
set --step execute from the test session: redraw right after the Bash call, no longer orphaned, ▸ execute in accent, verify dim. Pass.
--auto true --auto-done 3: auto · 3 done right-aligned in amber on row 2, auto 3 done in the status entry. Pass.
clear: band and status entry gone. Pass.
Found during the check: the engine prefixes a plugin's status with ⚠ trailhead: in its own warning colour, so the entry read trailhead: trailhead map .... With the maintainer's agreement the entry's own prefix was dropped (ce8ba9f, tests updated) and the mockup updated to match (version 1791709261-979c). The glyph and colour are the engine's and cannot be changed by the mod.
Resolved. The mod now shows the work state on screen.
Status entry ($.ui.status): map #198 · #203 <title> · <step>, with whiteboard for a map-less ticket, · auto <n> done during an autonomous run, · orphaned, resume? when the file belongs to another session; the engine prefixes it with ⚠ trailhead: (7b17720, ce8ba9f).
Band (AbovePrompt): #<n> + title cut to the band width, then discuss › plan › execute › verify with done ✓, current ▸ in accent, pending dim; ‖ paused, orphaned, resume? and the right-aligned auto · <n> done in amber; dims when orphaned, yields to a survey, draws nothing with no ticket in play (7b17720, fc13de6, 7192c93).
Refresh: reads .trailhead/work-state.json on session.start, turn.start, turn.complete and after a Bash call to the helper; a missing or unparseable file clears both, a transient read error keeps the last state; writes only on a real change; /clear marks a state with a session orphaned at once (7b17720, 7192c93).
Helper: new autoDone counter (--auto-done <n>, reset when auto is false), passed by the autonomous run; Codex projection pinned (0ca9566, 96b8257).
Pure view logic in hooks/mod/work-state-view.ts, contract autoDone + isOrphaned, module renamed register.tsx (85bf404, 7b17720); mod README updated (8b5c2b4).
Verification: VERIFY (review ledger clean, goal met) and acceptance (live check passed). Mockup: canvas page "Status entry and pipeline band (#203)".
Activity
ToRvaLDz commentedon Oct 11, 2026
PLAN
Closed at the brief: the autonomous-run counter is added to the work state helper in this ticket (new field
autoDone), not deferred to theautoloop ticket.Steps
autoDonefield (plugins/trailhead/templates/trailhead-work-state.js). New flag--auto-done <n>(non-negative integer, strict parse, usage error otherwise). Shape becomes{ map, ticket, title, step, auto, autoDone, session, updatedAt };autoDonedefaults to0, is kept by a merge like the other fields, is dropped by a ticket change unless passed again, and is reset to0whenever the mergedautoisfalse.sanitizeStateaccepts a non-negative integer only. TDD: extendhooks/lib/work-state.test.jsfirst (parse, merge, reset onauto false, invalid value rejected, old file without the field)._shared/teamwork.mdWork state paragraph andtrailhead-work/references/auto.mdsay that during an autonomous run everysetadds--auto true --auto-done <n>,nthe tickets resolved so far in this run. Codex projection test pins the new form (bin/lib/codex-projection.test.js).plugins/trailhead/types/index.d.ts):WorkStategainsautoDone: number;PluginState.trailheadgainsisOrphaned: boolean.plugins/trailhead/hooks/mod/work-state-view.ts, new):parseWorkState(text)validates the file the same way the helper does (bad JSON or no valid ticket/step ->null, missingautoDone->0);statusLine(state, isOrphaned)->trailhead map #198 · #203 Title · plan(whiteboardin place ofmap #<n>whenmapisnull;· auto 3 doneappended during a run; the same line with· orphaned, resume?appended when orphaned (map, ticket, title and step kept);undefinedwhen no state);pipeline(step)-> the four stages withdone | current | todo(resolve= all done,paused= the stage is unknown, shown as apausedtag).plugins/trailhead/hooks/mod/register.ts):refresh($)reads.trailhead/work-state.jsonvia$.fs.read(relative to the session cwd) and comparessessionwith$.session.id()to setisOrphaned(anullsession in the file is not orphaned). Outcomes: a missing file (rejection carryingENOENT) or an unparseable one -> both atoms reset tonull/false,$.ui.status(undefined), band gone; any other read error (transient) -> keep the last state untouched. Atoms are written only when the value changed, compared structurally on the normalised state (key-ordered JSON), never by reference (no redundant redraw), and the status is set only when its text changed. Onsession.endwithreason: 'clear'(a/clearchanges the session id and raises nosession.start) a state still on screen is marked orphaned at once, since the session that wrote it is gone. Refresh is called onsession.start,turn.start(re-checks against the new session id after a/clear),turn.complete, and on a Bashtool.callwhose command namestrailhead-work-state.js, afterawait next(e)so the helper has already written (the helper is the only writer, so this catches every step change mid-turn). Every hook passes throughnext(e); a refresh failure never blocks (caught, logged to debug).ui.renderonAbovePrompt): reads both atoms reactively withread($, atom)(so a write redraws the band, no manual invalidate);next(e)when there is no state or a survey holds the band (e.props.hasSurvey); otherwise one row#203 Title(truncated tobodyColumns) and one rowdiscuss › plan › execute › verify, the current stage bold in the accent colour, done stages plain, pending ones dim, plusauto · 3 doneduring a run; the whole band dim with anorphaned, resume?tag when orphaned. Only elements bothterminalanddesktoptables carry (Box,Text).plugins/trailhead/tests/work-state-ui.test.ts, new), each looped over['terminal', 'desktop']: the test'sonanswersfs.readandsession.idand recordsui.status; cases: no file (band draws nothing, status cleared); a valid file then the file removed (band and status cleared); a non-ENOENTread error keeps the last state; status text with a map and withmap: null; each step highlights the right stage,resolveandpaused; auto counter shown; orphaned dims and changes the status text;hasSurveyyields; a Bashtool.callnaming the helper re-reads only after itsnextresolved (the test'stool.callanswer writes the new file content) and redraws; an unchanged file produces no extraui.statuscall and no redraw (render count unchanged);session.endwithreason: 'clear'marks a shown state orphaned without a new turn. Existingmod.test.tskept.hooks/mod/README.mddrops "nothing visible yet" and describes the status and band. READMEs and/docsare left to the map's release ticket (fog 13), as with Scaffold the mod in the plugin with its dev and test loop #202.Seams (TDD per
tdd: seams)mergeState/parseArgs(node unit tests, red first);parseWorkState/statusLine/pipelineand theAbovePromptrender (mod tests, red first).Files touched
plugins/trailhead/templates/trailhead-work-state.js,plugins/trailhead/hooks/lib/work-state.test.js,plugins/trailhead/skills/_shared/teamwork.md,plugins/trailhead/skills/trailhead-work/references/auto.md,bin/lib/codex-projection.test.js,plugins/trailhead/types/index.d.ts,plugins/trailhead/hooks/mod/register.ts,plugins/trailhead/hooks/mod/work-state-view.ts(new),plugins/trailhead/tests/work-state-ui.test.ts(new),plugins/trailhead/hooks/mod/README.md.Verification criteria
npm testgreen (all node suites,claude plugin validate,claude plugin test).claude --plugin-dir plugins/trailheadsession in this repo shows the status and band for the ticket in play and redraws on a step change.ToRvaLDz commentedon Oct 11, 2026
PLAN-REVIEW
Reviewer: codex (2 rounds, read-only). All blocking concerns folded into the PLAN above.
Round 1
map #<n>(orwhiteboard) in the line, tested with and without a map.ENOENTor an unparseable file clears both atoms, the status and the band; any other read error keeps the last state.read($, atom).tool.callrefresh must follow the helper's run. Fixed: refresh afterawait next(e), tested./clearemits nosession.start. Fixed: refresh onturn.starttoo.hasSurvey, no redundant writes). Added to step 7.Round 2
/clearstill not handled at once. Fixed:session.endwithreason: 'clear'marks a shown state orphaned immediately, tested.· orphaned, resume?appended (mockup updated to match).The rounds are spent; the round 2 fixes are targeted and were not re-reviewed.
ToRvaLDz commentedon Oct 11, 2026
Mockup approved (explicit approval by the maintainer).
Canvas: https://claude.ai/artifact/JJYUnUqxymHmPMpbXx1n2e, page "Status entry and pipeline band (#203)", artboard "Work state band and status" (version 1791708479-d928). Re-fetched after approval, unchanged.
States: ticket in play, autonomous run with counter, paused, orphaned (after
/clear), resolve, no ticket in play. Band: row 1#<n>(bold accent) + title cut to the band width,[-]belongs to the engine; row 2 the four stages, done✓green, current▸bold accent, pending dim,‖ pausedororphaned, resume?tag in amber,auto · <n> doneright-aligned in amber. Status entry:trailhead map #<m> · #<n> <title> · <step>[ · auto <k> done][ · orphaned, resume?].ToRvaLDz commentedon Oct 11, 2026
Mockup fidelity (approved artboard "Work state band and status"), each test run on
terminalanddesktop:✓done green,▸current bold accent, pending dim, dim›work-state-ui.test.ts"each step highlights its stage"auto · <n> doneamber, right-aligned on row 2bodyColumns; props checked, not paint)‖ pausedamberpaused)/clear: band dim, no bold,orphaned, resume?amber✓greenresolve)#<n>bold#7aa2f7+ title cut with…tobodyColumns; engine's[-]not drawntrailhead map #<m> · #<n> <title> · <step>[ · auto <k> done][ · orphaned, resume?],whiteboardvariantDeviations decided in Execute (none visible in the mockup): when a state is both paused and orphaned, only the orphaned tag shows; when orphaned, the
auto · <n> donetext is hidden from the band (still in the status line).ToRvaLDz commentedon Oct 11, 2026
VERIFY
Tests:
node scripts/test.js23 ok/skipped, 0 failed (node suites,claude plugin validate,claude plugin test: 32 mod tests onterminalanddesktop).Code review (2 rounds, ticket diff = the
Refs: #203commits 0ca9566, 96b8257, 85bf404, 7b17720, 8b5c2b4, fc13de6, 7192c93):status: clean.critical: 0 · warning: 0 · info: 1
Findings ledger
Goal-backward verification:
status: met(14 claims met, mockup fidelity included). Its one note,/clearmarking a work state with no session as orphaned, contradicting the "no session is never orphaned" rule: fixed (7192c93, + test).Acceptance testing: guided live session (
claude --plugin-dir plugins/trailhead), driven by the maintainer, in progress.ToRvaLDz commentedon Oct 11, 2026
VERIFY (acceptance): guided live check, driven by the maintainer in
claude --plugin-dir plugins/trailhead(Claude Code 2.1.296, terminal). Pass.orphaned, resume?in amber, status entry with the orphaned suffix. Pass.set --step executefrom the test session: redraw right after the Bash call, no longer orphaned,▸ executein accent,verifydim. Pass.--auto true --auto-done 3:auto · 3 doneright-aligned in amber on row 2,auto 3 donein the status entry. Pass.clear: band and status entry gone. Pass.Found during the check: the engine prefixes a plugin's status with
⚠ trailhead:in its own warning colour, so the entry readtrailhead: trailhead map .... With the maintainer's agreement the entry's own prefix was dropped (ce8ba9f, tests updated) and the mockup updated to match (version 1791709261-979c). The glyph and colour are the engine's and cannot be changed by the mod.ToRvaLDz commentedon Oct 11, 2026
Resolved. The mod now shows the work state on screen.
$.ui.status):map #198 · #203 <title> · <step>, withwhiteboardfor a map-less ticket,· auto <n> doneduring an autonomous run,· orphaned, resume?when the file belongs to another session; the engine prefixes it with⚠ trailhead:(7b17720, ce8ba9f).AbovePrompt):#<n>+ title cut to the band width, thendiscuss › plan › execute › verifywith done✓, current▸in accent, pending dim;‖ paused,orphaned, resume?and the right-alignedauto · <n> donein amber; dims when orphaned, yields to a survey, draws nothing with no ticket in play (7b17720, fc13de6, 7192c93)..trailhead/work-state.jsononsession.start,turn.start,turn.completeand after a Bash call to the helper; a missing or unparseable file clears both, a transient read error keeps the last state; writes only on a real change;/clearmarks a state with a session orphaned at once (7b17720, 7192c93).autoDonecounter (--auto-done <n>, reset whenautois false), passed by the autonomous run; Codex projection pinned (0ca9566, 96b8257).hooks/mod/work-state-view.ts, contractautoDone+isOrphaned, module renamedregister.tsx(85bf404, 7b17720); mod README updated (8b5c2b4).Verification: VERIFY (review ledger clean, goal met) and acceptance (live check passed). Mockup: canvas page "Status entry and pipeline band (#203)".
feat: add the autoDone counter to the work state helper
docs: pass the autoDone counter during an autonomous run
feat: add the work state view logic and contract fields
feat: show the work state as a status entry and a pipeline band
docs: describe the status entry and the band in the mod note
fix: cut the band title by code point
fix: keep a state with no session un-orphaned on clear
fix: drop the status entry's own trailhead prefix