Skip to content

fix(#725): gate tui/ with its own typecheck + test job, and handle shell_approval - #855

Merged
lmorchard merged 1 commit into
mainfrom
fix/725-tui-gate
Sep 16, 2026
Merged

lmorchard merged 1 commit into
mainfrom
fix/725-tui-gate

Conversation

@lmorchard

Copy link
Copy Markdown
Owner

Closes #725.

tui/ had no automated gate. The only tui string in the Makefile was the
check-message-types diff, and grep -rn tui .github/workflows/ returned zero
matches. Its typecheck had been failing on main as a result.

The bug this is really about

dispatcher.ts's assertNever() exhaustiveness check caught a real drift that
no gate ever ran. #725 was filed when the unhandled variants were
SrvStickySet / SrvStickyClear; those have since been handled, and today
it is SrvShellApproval
, added by #850. Same bug, second instance, because
the gate still didn't exist. Since assertNever has an empty body and doesn't
throw, shell_approval messages fell to default: and silently no-opped.

Changes

install-tui / check-tui / test-tui mirror install-js / check-js /
test-js, including install-js's npm ci lockfile guard and its #706/#716
rationale. Deliberately not part of make check (issue decision #2):
tui/ has its own package-lock.json and node_modules, and nothing in the
Python gate needs them.

A separate check-tui CI job (decision #3) runs both goals in one make
invocation, so install-tui's npm ci happens once rather than per target
(verified: exactly one npm ci in the combined run). Node version comes from
.nvmrc, consistent with the other two jobs after #854 — that satisfies the
issue's "pin node 22" requirement with one source of truth instead of a third
hardcoded '22'.

shell_approval is surfaced as a system transcript line, not added to the
spike-deferred no-op group. Auto-approved shell commands are security-relevant
and the web UI already shows them, so silently dropping them in the TUI is the
worse failure — and it needs no new rendering capability, unlike the
sticky/canvas widgets. Rendered as
[shell auto-approval: ALLOWED] <command> (risk: <risk>), matching the
server's own shell auto-approval: ALLOWED/DECLINED phrasing.

Guards from the issue, all verified

Guard Status
assertNever(m) remains in default: unchanged (dispatcher.ts:190)
no @ts-expect-error / @ts-ignore in dispatcher.ts count 0
tui/tsconfig.json strictness not reduced file unmodified
types.generated.ts not hand-edited file unmodified
gate uses npm ci, lockfile clean after a full run unmodified, on npm 11
no test lost, newly skipped, or newly failing 41 -> 44, 0 skipped
make test-js unaffected 20 files, 178 tests, pass
make check-message-types green passes inside make check

Verification

  • Tests written first and observed failingtranscript came back
    undefined because shell_approval fell through to the no-op default.
    Three cases: ALLOWED, DECLINED, and empty risk. That last one is reachable
    (the server publishes data.get("risk", "")) and must not render a dangling
    (risk: ).
  • Gate teeth-checked, not assumed: a deliberate TS2345 under tui/src/
    makes make check-tui exit 2 and name the error. The probe was a newly
    created file, deleted afterwards, so no existing file was touched.
  • make check, make test-js, make check-tui test-tui all exit 0;
    make test is 3894 passed / 2 skipped.

Not included

Per the issue's explicit scope: no Ink render-test harness (this gates the
tests that already exist), no sticky widget implementation, no lint/format for
tui/.

One thing I noticed but left alone: the spike-deferred case group in
dispatcher.ts has no pointer to where that work lives. #507 (TUI: widget
inputs + canvas mirror
) is the natural home for the sticky/canvas entries, and
adding that reference would help the next reader — but it's outside this
issue's diff.

…ell_approval

tui/ had no automated gate: the only 'tui' string in the Makefile was the
check-message-types diff, and .github/workflows/ grepped to zero matches. Its
typecheck had been failing on main as a result.

The dispatcher's assertNever() exhaustiveness check had caught a real drift
that no gate ever ran. #725 was filed when the missing variants were
SrvStickySet/SrvStickyClear; those have since been handled, and today it is
SrvShellApproval, added by #850. Same bug, second instance, because the gate
still did not exist.

- install-tui / check-tui / test-tui mirror install-js / check-js / test-js,
  including install-js's npm ci lockfile guard and its #706/#716 rationale.
  Deliberately NOT part of 'make check': tui/ has its own package-lock and
  node_modules, and nothing in the Python gate needs them.
- A separate check-tui CI job runs both goals in one make invocation, so
  install-tui's npm ci happens once rather than per target. Node version comes
  from .nvmrc, consistent with the other two jobs after #854.
- shell_approval is now surfaced as a system transcript line rather than added
  to the spike-deferred no-op group. Auto-approved shell commands are
  security-relevant and the web UI already shows them, so silently dropping
  them in the TUI is the worse failure. It needs no new rendering capability,
  unlike the sticky/canvas widgets (#507).

assertNever stays in the default branch, no @ts-ignore was added, tsconfig
strictness is unchanged, and types.generated.ts was not hand-edited - the fix
belongs in dispatcher.ts, not in generated output.

Tests written first and observed failing (transcript undefined, since
shell_approval fell through to the no-op default). Covers ALLOWED, DECLINED,
and empty risk, which is reachable because the server publishes
data.get('risk', '') and must not render a dangling '(risk: )'.

Verified: tui 41 -> 44 tests, none skipped or lost; tui typecheck clean;
make check, make test-js (178 tests) and make test (3894 passed, 2 skipped)
all unaffected; tui/package-lock.json unchanged by npm ci on npm 11. Gate
teeth-checked: a deliberate TS2345 under tui/src/ makes make check-tui exit 2.
Copilot AI lite review requested due to automatic review settings September 16, 2026 00:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

check-tui does not run tests, and archived shell approval history is not restored.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds dedicated TUI typecheck/test gates and shell approval transcript handling.

Changes:

  • Adds TUI Make targets and a dedicated CI job.
  • Renders and tests shell_approval events.
File summaries
File Summary
tui/src/dispatcher.ts Handles live shell approval transcript events.
tui/src/dispatcher.test.ts Tests shell approval rendering cases.
Makefile Adds TUI dependency, typecheck, and test targets.
.github/workflows/ci.yml Adds the TUI CI gate.
Review details
  • Files reviewed: 4/4 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Makefile
fi

# Type check the TUI (mirrors check-js)
check-tui: install-tui
Comment thread tui/src/dispatcher.ts
Comment on lines +163 to +169
case "shell_approval": {
const verdict = m.approved ? "ALLOWED" : "DECLINED";
const risk = m.risk ? ` (risk: ${m.risk})` : "";
return appendTranscript(s, {
kind: "system",
text: `[shell auto-approval: ${verdict}] ${m.command}${risk}`,
});
@lmorchard
lmorchard merged commit e3836c1 into main Sep 16, 2026
4 checks passed
@lmorchard
lmorchard deleted the fix/725-tui-gate branch September 16, 2026 01:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants