Skip to content

feat(web): tsc --noEmit typecheck CI gate for the dashboard client (JEF-399)#270

Merged
thejefflarson merged 2 commits into
mainfrom
thejefflarson/jef-399-dashboard-v4-add-a-js-typechecklint-ci-step-tsc-noemit-for
Jul 26, 2026
Merged

feat(web): tsc --noEmit typecheck CI gate for the dashboard client (JEF-399)#270
thejefflarson merged 2 commits into
mainfrom
thejefflarson/jef-399-dashboard-v4-add-a-js-typechecklint-ci-step-tsc-noemit-for

Conversation

@thejefflarson

Copy link
Copy Markdown
Owner

Summary

JEF-499 already merged eslint + eslint-plugin-jsx-a11y for the dashboard client, and eslint's no-unused-vars already catches the unused-import gap this ticket originally cited. This PR closes the ticket's remaining, non-duplicated value: type-checking, which eslint does not do.

  • Added typescript (pinned exact version 7.0.2) as a devDependency, installed via the existing npm ci --ignore-scripts (ADR-0025/JEF-396 — build-from-source, no install-hook egress).
  • Added engine/web/tsconfig.json: allowJs, checkJs: true, noEmit: true, jsx: "react-jsx" / jsxImportSource: "preact", noUnusedLocals: true, skipLibCheck: true, strict: false, scoped to include: ["src"].
  • Added an npm run typecheck script (tsc --noEmit) and wired a new "Typecheck dashboard client (tsc --noEmit)" CI step next to the existing lint step in .github/workflows/rust.yml's lint job.
  • Fixed the 8 real errors the clean-config pass surfaced in engine/web/src (all genuine, in-scope bugs, not suppressions):
    • aria-expanded={String(open)}aria-expanded={open} (×2, signing.jsx / findings/row.jsx) — Preact's JSX types want a boolean; Preact already serializes it to the correct "true"/"false" DOM attribute string, so behavior is unchanged.
    • colspan="6" / colspan="7"colspan={6} / colspan={7} (×4, across signing.jsx, findings/row.jsx, findings/evidence.jsx) — numeric prop, not a numeric string.
    • signing.jsx's BannerRow's detail prop had a stale JSDoc signature ((open: boolean) => any) that didn't match any of its three call sites (all () => <...Detail .../>, none consuming the arg); corrected the doc to () => any.
    • role prop's JSDoc narrowed from string to the literal "alert" it's actually ever passed.
  • Replaced engine/web/jsconfig.json (editor-hint only, never enforced) with tsconfig.json — one config, not two.

Strictness tradeoff (DECISION): a fully strict: true checkJs pass over this Preact tree produced ~450 mostly-noise implicit-any and strict-null-check errors (untyped serde-shaped props, document.querySelector narrowing in tests). Per the ticket's own fallback guidance ("better a lean green typecheck than a sea of suppressions"), I tuned strict down to false and scoped include to src only (the ticket's own acceptance target), which brought it to 8 real, fixable errors with zero @ts-* suppressions needed. test/ and build.mjs are out of tsconfig.json's include — they weren't previously enforced (jsconfig.json was editor-hint only) and typing them (DOM narrowing casts in every test, @types/node for build.mjs) is out of this ticket's stated scope (engine/web/src).

Test plan

From engine/web/:

  • npm ci — installs cleanly, package-lock.json diff is additive-only (typescript has zero deps)
  • npm run typecheck (tsc --noEmit) — green on the current tree; verified it has teeth (a deliberately-injected unused local and a deliberately-injected type error both correctly failed before being reverted)
  • npm run lint — still green
  • npm run build — still green
  • npm run test — 1 pre-existing failing file (test/a11y-routes.test.jsx, "Axe is already running" — a vitest-axe concurrency flake), confirmed to reproduce identically on the unmodified main tip via git stash; not introduced by this change, out of this ticket's scope.
  • No engine/web/src file exceeds 1000 lines.
  • /soundcheck:pr-review self-review of the diff: no Critical/High findings (devDependency + CI config + JSX attribute-type fixes; no new input surface, secrets, or endpoints).
  • /simplify self-review (single-pass, Agent tool unavailable in this context): diff is clean; jsconfig.jsontsconfig.json consolidation removes a duplicate config rather than leaving two to drift.

Closes JEF-399

🤖 Generated with Claude Code

https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP

thejefflarson and others added 2 commits July 26, 2026 15:18
…EF-399)

JEF-499 already added eslint's no-unused-vars, so this closes the
remaining gap: real type errors (a JSX prop of the wrong type, a stale
JSDoc signature) that eslint doesn't catch. tsc --noEmit runs over
engine/web/src only (checkJs + noUnusedLocals, non-strict — strict
checkJs against this untyped Preact tree produced ~450 implicit-any
errors, so per the ticket's own fallback guidance the config is tuned
down to stay lean and green rather than paper over the tree with
@ts-* suppressions). Fixed the 8 real errors it surfaced: aria-expanded
needs a boolean not a stringified one, colspan needs a number not a
numeric string, and one BannerRow's `detail` JSDoc claimed a param none
of its three call sites pass. Replaces jsconfig.json (editor-hint only,
never enforced) with tsconfig.json so there's one config, not two.

Wired as a new `typecheck` step next to the existing `lint` step in the
lint CI job. typescript is a pinned exact-version devDependency,
installed via the existing --ignore-scripts npm ci (ADR-0025/JEF-396).

Closes JEF-399

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP
@thejefflarson
thejefflarson merged commit 46d65ef into main Jul 26, 2026
5 checks passed
@thejefflarson
thejefflarson deleted the thejefflarson/jef-399-dashboard-v4-add-a-js-typechecklint-ci-step-tsc-noemit-for branch July 26, 2026 23:01
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.

1 participant