Skip to content

ADR-0079: Dashboard strict typecheck + JSDoc enforcement (phased)

  • Status: Accepted
  • Date: 2026-05-23
  • Driver: 2026-05-23 best-practices review §2 (noUncheckedIndexedAccess not enabled) + §4.3 (TS comment density 4.7% vs Go 24%)

Context

Two gaps in the dashboard codebase quality, both flagged by the 2026-05-23 best-practices review:

  1. §2 — TypeScript safety: tsconfig.json enables strict: true, noUnusedLocals, noUnusedParameters, noImplicitReturns, noFallthroughCasesInSwitch — solid. But noUncheckedIndexedAccess is not enabled, so:
const userById = new Map<string, User>();
const u = userById.get(id);  // type: User (not User | undefined!)
u.name;                      // RUNTIME ERROR if id was unknown

The same shape applies to array indexing (arr[i]), record indexing (obj[key]), and destructuring with index access.

  1. §4.3 — Documentation density: Go ratio is 24% (excellent); TS ratio is 4.7%. Drilling in, src/lib/find/registry.ts (787 LoC), src/lib/dut-catalog/loader.ts (685 LoC) are sparse on JSDoc.

The straightforward fixes (enable the flag, enforce JSDoc on lib/) would land hundreds of new errors in a single PR — unsafe to ship without staging.

Decision

Adopt phased rollout for both:

Part A — noUncheckedIndexedAccess

  1. New dashboard/tsconfig.strict.json extends tsconfig.json and adds the flag.
  2. New dashboard/package.json script typecheck:strict runs tsc -p tsconfig.strict.json --noEmit.
  3. New .github/workflows/dashboard-typecheck-strict.yml runs typecheck:strict on every PR, warn-only (continue-on-error: true).
  4. The workflow comments the current finding count + delta-vs-baseline on the PR so ratcheting is visible.
  5. Existing dashboard — typecheck, lint, test, build job continues to use the non-strict tsconfig.json — no behavior change to the required checks.

Baseline at landing: 430 strict findings. Most are Map.get() / arr[i] patterns in lib/ and app/ pages.

Part B — JSDoc enforcement

  1. New devDependency eslint-plugin-jsdoc.
  2. New eslint.config.mjs rule block scoped to src/lib/**/*.{ts,tsx}:
Rule Phase
jsdoc/check-alignment WARN (active now)
jsdoc/check-syntax WARN (active now)
jsdoc/require-jsdoc OFF (Phase 2 ratchet target)
jsdoc/require-description OFF (Phase 2)
jsdoc/require-param-description OFF (Phase 2)
jsdoc/require-returns-description OFF (Phase 2)
jsdoc/no-undefined-types OFF (TS covers it)
jsdoc/valid-types OFF (confused by TS-style annotations)

Phase 1 enforces structural correctness of existing JSDoc (alignment, syntax) without requiring new headers. This makes sloppy existing comments fail the lint while not penalizing files that have no JSDoc.

Part C — Ratchet plan

Wave When Action
W0 This PR Land strict-typecheck workflow (warn-only) + JSDoc plugin (alignment+syntax only).
W1 +4 weeks Fix all Map.get() patterns in src/lib/ (about 80 findings).
W2 +8 weeks Fix all array-index patterns in src/app/ API routes.
W3 +12 weeks Flip jsdoc/require-jsdoc from off to warn for files with > 200 LoC.
W4 +16 weeks When strict-typecheck baseline is < 50, flip the workflow to fail-gate + promote to required_status_checks.

Each ratchet wave is its own PR with the corresponding fixes.

Consequences

Positive

  • First visibility into the strict-typecheck baseline. Without measurement we can't manage it.
  • Trend tracking — the workflow's comment shows whether each PR adds or removes findings. New PRs adding strict findings will be visible (though not blocking) on the PR UI.
  • JSDoc structural correctness active immediately — bad existing JSDoc now fails the lint.

Negative

  • One more CI job (~30s on a hot cache). Mitigated by paths: filter — only runs when TS files / config / package change.
  • Warn-only initially means findings can still land. Discipline required to actually look at the workflow comment. Mitigated by the W3-W4 ratchet timeline.

Alternatives considered

  • Enable in main tsconfig + fix all 430 findings in this PR — rejected as too-large blast radius. A future ratchet wave can do this when the baseline is < 50.
  • tsconfig.lib.json strict overlay only on src/lib/** — rejected initially because src/lib/ interacts with src/app/ types; if a function in lib/ takes User | undefined then the caller in app/ needs to handle that too, which means the strict mode needs to be consistent across files. Wave 1 may revisit this.
  • @ts-expect-error baseline file — rejected: too noisy in every-day code reading. The separate strict tsconfig is cleaner.

See also

  • dashboard/tsconfig.strict.json — the strict overlay
  • .github/workflows/dashboard-typecheck-strict.yml — the warn gate
  • dashboard/eslint.config.mjs — the JSDoc plugin block
  • ADR-0074 — sibling pattern (file-size budget with baseline file)