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
Goal: Put tui/ under an automated gate — its test suite and typecheck — so TUI work stops
landing unverified, and fix the type error that this immediately surfaces.
Source: agent-session:triage of the decafclaw backlog, 2026-07-29. Surfaced independently while
triaging #497 and #498, both of which cite npm test / npm run typecheck / make check in their
acceptance criteria — where none of those three grades tui/ today.
Current state
The TUI began as an exploratory spike (docs/dev-sessions/2026-05-13-1039-tui-spike/) and never
acquired a gate. Verified 2026-07-29:
make check does not touch tui/.check: depends on install-js check-message-types and
then runs uv run ruff check src/ tests/, uv run pyright, and cd src/decafclaw/web/static && npx tsc --noEmit. The onlytui string in the entire Makefile
is line 86 — a git diff --exit-code over tui/src/types.generated.ts inside check-message-types.
CI never mentions it.grep -rn "tui" .github/workflows/ → exit 1, zero matches. The
existing js-test job runs make test-js, which is src/decafclaw/web/static only.
No install path.install-js covers src/decafclaw/web/static only. tui/node_modules
contained zero installed packages on a working checkout, so npm run typecheck was not even
runnable without a manual install.
There are 4 test files, all pure-logic:conversationPicker.test.ts, dispatcher.test.ts, parseArgs.test.ts, wsClient.test.ts. No .test.tsx, no render harness, no ink-testing-library.
The good news, verified by running it:npm ci works cleanly in tui/ (there is a tui/package-lock.json), leaves the lockfile untouched, and all four suites pass — 4 files, 38
tests, 1.12s on vitest 2.1.9 (matching the ^2.1.2 pin). So wiring the test half in is mechanical
and fast.
The bad news, and the reason this issue exists:npx tsc --noEmit in tui/fails today,
exit 2, with exactly one error:
src/dispatcher.ts(174,19): error TS2345: Argument of type 'SrvStickyClear | SrvStickySet' is not
assignable to parameter of type 'never'.
Type 'SrvStickyClear' is not assignable to type 'never'.
dispatcher.ts:174 is an assertNever(m) exhaustiveness check in the switch's default: branch. SrvStickyClear and SrvStickySet were added to tui/src/types.generated.ts by #419/#642 (sticky
widget slot) and the dispatcher never got matching cases. The codegen's exhaustiveness check caught
a real drift and no gate ever ran it.
It has a live functional consequence: assertNever has an empty body (dispatcher.ts:52-55) and
does not throw, so at runtime sticky_set / sticky_clear fall to default:, no-op, and return
state unchanged. grep -n sticky tui/src/dispatcher.ts tui/src/App.tsx → zero hits. The sticky
widget slot is silently non-functional in the TUI.
Verifiable acceptance criteria
CRITERION: The tui package SHALL typecheck clean.
CHECK: cd tui && npm ci && npx tsc --noEmit exits 0.
VERIFIED DISCRIMINATING: exits 2 today with the single TS2345 error quoted above (error count
confirmed: grep -c "error TS" → 1).
CRITERION: WHEN a tui test fails, THEN make check-tui SHALL fail.
CHECK (mutation, not inspection): append a deliberately failing assertion to a file under tui/src/*.test.ts, run make check-tui, confirm a nonzero exit, then revert. A grep for tui in
the Makefile is not an acceptable check — it is satisfied by adding a line that runs nothing.
VERIFIED DISCRIMINATING: make check-tui does not exist today (see Current state),
so a failing tui test provably cannot affect its exit code.
CRITERION: WHEN a tui typecheck error is present, THEN make check-tui SHALL fail.
CHECK (mutation): with the assertNever fix in place, remove one case from dispatch's
switch to re-open the exhaustiveness gap, run make check-tui, confirm nonzero exit, then revert.
VERIFIED DISCRIMINATING: same — no execution path today.
CRITERION: CI SHALL run the tui test suite and typecheck on pull requests via a separate check-tui job.
CHECK: partly human. The honest oracle is "a PR containing a failing tui test gets a red
check", which cannot be verified locally. Locally checkable half: the workflow declares a separate check-tui job that
installs tui deps with npm ci and runs both the vitest suite and tsc --noEmit. The grep half
is a proxy (satisfiable by a step that runs nothing), which is why the mutation criteria above are
the load-bearing ones and why this criterion contributes to the tier.
Regression guards
Pass today; must keep passing. These do not affect the tier.
GUARD: cd tui && npm ci && npm test — invariant: no test lost, newly skipped, or newly
failing. Observed 2026-07-29: Test Files 4 passed (4) / Tests 38 passed (38). Stated as an
invariant deliberately, not as 38 — a pinned count goes stale the moment a test is added.
GUARD: assertNever(m) remains in dispatch's default: branch. This is the guard that
matters most: the cheapest way to make the typecheck criterion green is to delete the
exhaustiveness check or silence it, which would discard the mechanism that found this bug.
GUARD: no @ts-expect-error / @ts-ignore added to tui/src/dispatcher.ts, and tui/tsconfig.json
strictness is not reduced. Same reasoning — the criterion must be satisfied by handling the cases,
not by suppressing the diagnostic.
GUARD: make check-message-types stays green (Makefile:83-86) — this work must not hand-edit tui/src/types.generated.ts to make the typecheck pass. The generated file is an output; the fix
belongs in dispatcher.ts.
GUARD: make test-js (web/static) unaffected.
Tier: auto-ok (Upgraded from needs-review after human approval 2026-08-19)
Trigger 2 — deploy/infra/CI config. The deliverable edits Makefile (make check-tui) and .github/workflows/ci.yml (separate check-tui job).
Approved by human (lmorchard) on 2026-08-19.
Patterns to follow
js-test in .github/workflows/ci.yml:36-48 is the job to mirror: actions/checkout@v6, actions/setup-node@v4 with node-version: '22', then run: make <target>. Pin node 22 to
match .nvmrc and the existing job — not the local machine's node (this checkout is on v26, and
npm-major skew across that boundary is exactly what make check fails at install-js: a check target must not run npm install #716 was about).
install-js (Makefile:49+) is the install pattern, including its npm ci rationale. A tui-side equivalent (install-tui) should follow it rather than inventing a second approach.
Building a rendered-output test harness for Ink components. There is none today (no .test.tsx,
no ink-testing-library), and adding one is a dependency decision that TUI: render markdown in assistant text #497 and TUI: multi-line composer + persistent input history #498 both depend
on. This issue gates the tests that already exist; it does not create new testing capability.
That is a separate, larger issue and should be filed as its own.
Implementing the sticky widget in the TUI. Handled via explicit case "sticky_set": case "sticky_clear": falling through/returning state unchanged with a not-yet-implemented comment, per human decision More flexible config (JSON/YAML) #1 (with a separate issue filed for sticky widget feature).
Broadening the gate to lint/format for tui/ (no ESLint/Prettier config exists there today).
Open questions / Resolutions (Human Approved 2026-08-19)
How should the sticky cases be resolved? Decision: File a new issue for the sticky widget handling and make the honest not-yet-implemented comment now in dispatcher.ts.
Should tui be in make check, or a separate make check-tui plus a CI job only? Decision: Create a separate make check-tui.
Does the tui gate get its own CI job, or extend the existing js-test job? Decision: Create a separate CI job for check-tui in GitHub.
Goal: Put
tui/under an automated gate — its test suite and typecheck — so TUI work stopslanding unverified, and fix the type error that this immediately surfaces.
Source: agent-session:triage of the decafclaw backlog, 2026-07-29. Surfaced independently while
triaging #497 and #498, both of which cite
npm test/npm run typecheck/make checkin theiracceptance criteria — where none of those three grades
tui/today.Current state
The TUI began as an exploratory spike (
docs/dev-sessions/2026-05-13-1039-tui-spike/) and neveracquired a gate. Verified 2026-07-29:
make checkdoes not touchtui/.check:depends oninstall-js check-message-typesandthen runs
uv run ruff check src/ tests/,uv run pyright, andcd src/decafclaw/web/static && npx tsc --noEmit. The onlytuistring in the entire Makefileis line 86 — a
git diff --exit-codeovertui/src/types.generated.tsinsidecheck-message-types.grep -rn "tui" .github/workflows/→ exit 1, zero matches. Theexisting
js-testjob runsmake test-js, which issrc/decafclaw/web/staticonly.install-jscoverssrc/decafclaw/web/staticonly.tui/node_modulescontained zero installed packages on a working checkout, so
npm run typecheckwas not evenrunnable without a manual install.
conversationPicker.test.ts,dispatcher.test.ts,parseArgs.test.ts,wsClient.test.ts. No.test.tsx, no render harness, noink-testing-library.The good news, verified by running it:
npm ciworks cleanly intui/(there is atui/package-lock.json), leaves the lockfile untouched, and all four suites pass — 4 files, 38tests, 1.12s on vitest 2.1.9 (matching the
^2.1.2pin). So wiring the test half in is mechanicaland fast.
The bad news, and the reason this issue exists:
npx tsc --noEmitintui/fails today,exit 2, with exactly one error:
dispatcher.ts:174is anassertNever(m)exhaustiveness check in theswitch'sdefault:branch.SrvStickyClearandSrvStickySetwere added totui/src/types.generated.tsby #419/#642 (stickywidget slot) and the dispatcher never got matching cases. The codegen's exhaustiveness check caught
a real drift and no gate ever ran it.
It has a live functional consequence:
assertNeverhas an empty body (dispatcher.ts:52-55) anddoes not throw, so at runtime
sticky_set/sticky_clearfall todefault:, no-op, and returnstate unchanged.
grep -n sticky tui/src/dispatcher.ts tui/src/App.tsx→ zero hits. The stickywidget slot is silently non-functional in the TUI.
Verifiable acceptance criteria
CRITERION: The
tuipackage SHALL typecheck clean.CHECK:
cd tui && npm ci && npx tsc --noEmitexits 0.VERIFIED DISCRIMINATING: exits 2 today with the single
TS2345error quoted above (error countconfirmed:
grep -c "error TS"→ 1).CRITERION: WHEN a
tuitest fails, THENmake check-tuiSHALL fail.CHECK (mutation, not inspection): append a deliberately failing assertion to a file under
tui/src/*.test.ts, runmake check-tui, confirm a nonzero exit, then revert. Agrepfortuiinthe Makefile is not an acceptable check — it is satisfied by adding a line that runs nothing.
VERIFIED DISCRIMINATING:
make check-tuidoes not exist today (see Current state),so a failing
tuitest provably cannot affect its exit code.CRITERION: WHEN a
tuitypecheck error is present, THENmake check-tuiSHALL fail.CHECK (mutation): with the
assertNeverfix in place, remove onecasefromdispatch'sswitch to re-open the exhaustiveness gap, run
make check-tui, confirm nonzero exit, then revert.VERIFIED DISCRIMINATING: same — no execution path today.
CRITERION: CI SHALL run the
tuitest suite and typecheck on pull requests via a separatecheck-tuijob.CHECK: partly human. The honest oracle is "a PR containing a failing
tuitest gets a redcheck", which cannot be verified locally. Locally checkable half: the workflow declares a separate
check-tuijob thatinstalls
tuideps withnpm ciand runs both the vitest suite andtsc --noEmit. The grep halfis a proxy (satisfiable by a step that runs nothing), which is why the mutation criteria above are
the load-bearing ones and why this criterion contributes to the tier.
Regression guards
Pass today; must keep passing. These do not affect the tier.
cd tui && npm ci && npm test— invariant: no test lost, newly skipped, or newlyfailing. Observed 2026-07-29:
Test Files 4 passed (4) / Tests 38 passed (38). Stated as aninvariant deliberately, not as
38— a pinned count goes stale the moment a test is added.assertNever(m)remains indispatch'sdefault:branch. This is the guard thatmatters most: the cheapest way to make the typecheck criterion green is to delete the
exhaustiveness check or silence it, which would discard the mechanism that found this bug.
@ts-expect-error/@ts-ignoreadded totui/src/dispatcher.ts, andtui/tsconfig.jsonstrictness is not reduced. Same reasoning — the criterion must be satisfied by handling the cases,
not by suppressing the diagnostic.
npm ci, nevernpm install, andgit diff --exit-code -- tui/package-lock.jsonis clean after a full gate run. Verified today thatnpm ciintui/doesnot write the lockfile. This is not hypothetical: make check silently rewrites package-lock.json (512 deletions), undoing #703 #706 and make check fails at install-js: a check target must not run npm install #716 were both caused by
npm installrewriting
src/decafclaw/web/static's lockfile, andinstall-js's own comment documents thathistory at length. Do not reintroduce it in a new directory.
make check-message-typesstays green (Makefile:83-86) — this work must not hand-edittui/src/types.generated.tsto make the typecheck pass. The generated file is an output; the fixbelongs in
dispatcher.ts.make test-js(web/static) unaffected.Tier:
auto-ok(Upgraded fromneeds-reviewafter human approval 2026-08-19)Trigger 2 — deploy/infra/CI config. The deliverable edits
Makefile(make check-tui) and.github/workflows/ci.yml(separatecheck-tuijob).Approved by human (
lmorchard) on 2026-08-19.Patterns to follow
js-testin.github/workflows/ci.yml:36-48is the job to mirror:actions/checkout@v6,actions/setup-node@v4withnode-version: '22', thenrun: make <target>. Pin node 22 tomatch
.nvmrcand the existing job — not the local machine's node (this checkout is on v26, andnpm-major skew across that boundary is exactly what make check fails at install-js: a check target must not run npm install #716 was about).
install-js(Makefile:49+) is the install pattern, including itsnpm cirationale. Atui-side equivalent (install-tui) should follow it rather than inventing a second approach.install-tui/test-tui/check-tui.What we're NOT doing
.test.tsx,no
ink-testing-library), and adding one is a dependency decision that TUI: render markdown in assistant text #497 and TUI: multi-line composer + persistent input history #498 both dependon. This issue gates the tests that already exist; it does not create new testing capability.
That is a separate, larger issue and should be filed as its own.
case "sticky_set": case "sticky_clear":falling through/returning state unchanged with a not-yet-implemented comment, per human decision More flexible config (JSON/YAML) #1 (with a separate issue filed for sticky widget feature).tui/(no ESLint/Prettier config exists there today).Open questions / Resolutions (Human Approved 2026-08-19)
Decision: File a new issue for the sticky widget handling and make the honest not-yet-implemented comment now in
dispatcher.ts.tuibe inmake check, or a separatemake check-tuiplus a CI job only?Decision: Create a separate
make check-tui.tuigate get its own CI job, or extend the existingjs-testjob?Decision: Create a separate CI job for
check-tuiin GitHub.