Skip to content

tui/ has no test or typecheck gate — and typecheck fails today #725

Description

@lmorchard

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 only tui 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.tsxzero 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: the gate installs with npm ci, never npm install, and git diff --exit-code -- tui/package-lock.json is clean after a full gate run. Verified today that npm ci in tui/ does
    not 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 install
    rewriting src/decafclaw/web/static's lockfile, and install-js's own comment documents that
    history at length. Do not reintroduce it in a new directory.
  • 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.
  • Suggested shape, matching existing naming: install-tui / test-tui / check-tui.

What we're NOT doing

  • 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)

  1. 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.
  2. Should tui be in make check, or a separate make check-tui plus a CI job only?
    Decision: Create a separate make check-tui.
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-session:attempt-2Execution attempt counteragent-session:auto-okVerifiable criteria satisfied and ready for autonomous execution

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions