Status: living record. Reviewed against v0.7.22 (3660300); update it whenever the
checks or the vendored terminal change.
packages/tui is DSCODE's forked terminal, vendored as TypeScript source so it is
edited rather than patched as published output. Two paths erase types without
reading them — Node's type stripping at run time, ts.transpileModule in
scripts/build-tui.mjs at build time — and ESLint matches **/*.mjs, so until
2026-09-19 no compiler read any of its 28,506 lines. This records what is checked
now, what the numbers mean, and what is still unverified.
| Layer | Read by | At v0.7.22 |
|---|---|---|
First-party .mjs (plugins, launcher, bundle, scripts, bin) |
npm run test:coverage |
378 tests, 0 fail; 8969/10762 lines (83.34%); floor 75% |
Vendored terminal packages/tui/src (68 .ts, 28,506 lines) |
npm run typecheck, npm run typecheck:strict, tests/tui-typecheck.test.mjs, the component tests |
0 type errors; outside the coverage inventory |
docs/ and both READMEs |
scripts/check-docs.mjs (run as a unit test) |
links, index coverage, README agreement |
| Patch anchors against the pinned upstream | tests/patches.test.mjs, plus replaceOnce drift errors |
fails loudly on an upstream bump |
The component tests mount real panels against an in-memory TTY (tests/fixtures/tui-mount.mjs, extracted from tests/tui-render.test.mjs). tests/tui-render.test.mjs covers the effort bar, the status line, the /btw panel, the activity line and the model picker; tests/tui-panels.test.mjs covers three keyboard surfaces nothing mounted before — the Grok panel, the statusline picker and the provider panel. tests/tui-text.test.mjs pins the wrapping helper, and tests/dscode-paste.test.mjs pins the paste markers (atomic under one backspace, expandable on submit).
The coverage percentage never described the whole repository. scripts/checks.mjs
now prints the unmeasured TypeScript tree beside it, and the unmeasured block of
artifacts/local/coverage/summary.json carries the same split: 68 files, 28,506
source lines that the 83.34% says nothing about. Those counts use raw source lines — the
only basis for files no test loads — not the lcov line basis the percentage itself uses,
so the two numbers are reported side by side but not summed.
packages/tui/tsconfig.json plus npm run typecheck, wired into npm run check.
strictNullChecksis on: it is what makes the fork's result unions ({ok:true;value} | {ok:false;reason}) narrow at all. With it off, the compiler widens the literal discriminants and reports the failure branch as an error instead — three of the first 31 errors were that artifact, not code defects.noImplicitAnyis off in the base config, which reports roughly 420 errors across the vendored lines. It is on for the code DSCODE owns through the strict ratchet (packages/tui/tsconfig.strict.json,npm run typecheck:strict): the seed issrc/dscode/**, and TypeScript follows its imports, so first-party files get real inference without pretending the rest is ready. Grow thatinclude; the guardrail test fails if the seed or the flag disappears.@types/reactis a devDependency. React ships no types, and without them every hook and prop inapp.tsdegrades toany:useState(null),useRef(null)and whole prop objects stop being read, and generic calls such asrankByNamesilently fall back to their constraint.tests/tui-typecheck.test.mjsfails if that dependency or thesrcinclude is dropped.libis ES2023 sofindLastandfindLastIndexare typed; both run on the Node versions the manifest requires.inkneeds no extra types — it exportstypes: ./build/index.d.ts.
ESLint still never opens a .ts file. Extending it needs typescript-eslint,
which is not installed; until then tsc is the only static reader of the fork.
The first green run needed 31 changes. Four were defects on paths a user actually reaches, not type noise:
app.tscalledwrapText(...)for the email preview andformatTokens(...)for the compaction confirm panel without importing or defining either, so opening either surface threwReferenceError. The preview now wraps throughwrapTextinrender/text.ts, pinned bytests/tui-text.test.mjs.- Eight
useStableInput(handler)calls omitted the requiredactiveargument. Ink readsundefinedas active, so behaviour was already "always active"; the parameter now defaults totrueand the doc comment says so. - The
/provideraction union was missing{ kind: 'dscode-grok' }although two call sites set it. JavaScript stored the value regardless, so the Grok panel worked at run time; the type was wrong, and with React's types installed the compiler flagged both the setter and thekind === 'dscode-grok'comparison. DscodeProviderPaneland the credential panel typed their state asundefined, sodirectory.rowsand the credential status were never read by the compiler.
The rest is narrowing: RgbTriple parameters, new Set<StatusItemId>, explicit
map return types, a typed fetch JSON read, EditResult.killed, ModelPanel's
props, and a resumed prop the header never accepted (an upstream artifact).
Taking a new upstream dsh-code release is a manual merge of the local delta (16 files, +818/−151 at v0.7.22). The procedure, the provenance steps and what only a hand smoke test can catch live in vendored-tui-upgrade.md.
- Implicit
any(~420 errors) outside the strict ratchet, and theapp.tsvalues that flow through untyped helpers by design. - The composer's key routing has no direct test.
Inputtakes 83 required props and is not exported;Apptakes 61. Mounting either needs a fake application, so the branches a user actually presses are covered only through the panels. Extracting the routing into a pure(key, state) => actionfunction is the change that would fix it; see vendored-tui-upgrade.md. - Release and distribution scripts (
build-packages.mjs,publish-hub.mjs,distribute.mjs,doctor.mjs) count as zero lines in the inventory; the release path is covered by the Hub workflow, not by a unit test. DscodeEmailPanel,DscodeImapSetupandDscodeProviderPanelstill take untyped props, so their behaviour is covered only through the components that render them.- Plugin entry points (
grok,exec,clipboard-image,session-bridge,session-cards) are only partly loaded: their exported helpers are unit-tested, while the cordis mount path runs only inside a real session.grok/index.mjsandexec/index.mjstherefore sit near 30% in the inventory with no missing test behind that number.