feat(web): tsc --noEmit typecheck CI gate for the dashboard client (JEF-399)#270
Merged
thejefflarson merged 2 commits intoJul 26, 2026
Conversation
…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
…typechecklint-ci-step-tsc-noemit-for
thejefflarson
deleted the
thejefflarson/jef-399-dashboard-v4-add-a-js-typechecklint-ci-step-tsc-noemit-for
branch
July 26, 2026 23:01
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
JEF-499 already merged eslint + eslint-plugin-jsx-a11y for the dashboard client, and eslint's
no-unused-varsalready 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.typescript(pinned exact version7.0.2) as a devDependency, installed via the existingnpm ci --ignore-scripts(ADR-0025/JEF-396 — build-from-source, no install-hook egress).engine/web/tsconfig.json:allowJs,checkJs: true,noEmit: true,jsx: "react-jsx"/jsxImportSource: "preact",noUnusedLocals: true,skipLibCheck: true,strict: false, scoped toinclude: ["src"].npm run typecheckscript (tsc --noEmit) and wired a new "Typecheck dashboard client (tsc --noEmit)" CI step next to the existinglintstep in.github/workflows/rust.yml'slintjob.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, acrosssigning.jsx,findings/row.jsx,findings/evidence.jsx) — numeric prop, not a numeric string.signing.jsx'sBannerRow'sdetailprop 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.roleprop's JSDoc narrowed fromstringto the literal"alert"it's actually ever passed.engine/web/jsconfig.json(editor-hint only, never enforced) withtsconfig.json— one config, not two.Strictness tradeoff (DECISION): a fully
strict: truecheckJspass over this Preact tree produced ~450 mostly-noise implicit-anyand strict-null-check errors (untyped serde-shaped props,document.querySelectornarrowing in tests). Per the ticket's own fallback guidance ("better a lean green typecheck than a sea of suppressions"), I tunedstrictdown tofalseand scopedincludetosrconly (the ticket's own acceptance target), which brought it to 8 real, fixable errors with zero@ts-*suppressions needed.test/andbuild.mjsare out oftsconfig.json'sinclude— they weren't previously enforced (jsconfig.json was editor-hint only) and typing them (DOM narrowing casts in every test,@types/nodeforbuild.mjs) is out of this ticket's stated scope (engine/web/src).Test plan
From
engine/web/:npm ci— installs cleanly,package-lock.jsondiff 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 greennpm run build— still greennpm 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 unmodifiedmaintip viagit stash; not introduced by this change, out of this ticket's scope.engine/web/srcfile exceeds 1000 lines./soundcheck:pr-reviewself-review of the diff: no Critical/High findings (devDependency + CI config + JSX attribute-type fixes; no new input surface, secrets, or endpoints)./simplifyself-review (single-pass, Agent tool unavailable in this context): diff is clean;jsconfig.json→tsconfig.jsonconsolidation removes a duplicate config rather than leaving two to drift.Closes JEF-399
🤖 Generated with Claude Code
https://claude.ai/code/session_01VtjoJttCvBY4dzCoE4f9vP