Skip to content

Monorepo Testing / CI / DX Review — 2026-06-25

Section titled “Monorepo Testing / CI / DX Review — 2026-06-25”

Ground-truth run of every test/lint/typecheck command at HEAD 9d9430f. Toolchain: node v24.12.0, pnpm 10.22.0, uv 0.9.24, Python 3.12.3. node_modules present (no install needed).


RESULTS — green/red at HEAD (real numbers)

Section titled “RESULTS — green/red at HEAD (real numbers)”
CommandStatusNumbers / evidence
pnpm test (design-tokens vitest)GREEN1 file, 26/26 passed (473ms)
pnpm test:sd-app (vitest)GREEN1 file, 3/3 passed (655ms) — still only 1 test file
pnpm test:py → sd-math pytestGREEN218 passed in 0.67s
pnpm test:py → sd-api pytestREDdependency resolution fails — 0 tests run. Chain aborts.
pnpm lintRED10 errors, 7 warnings, exit 1
pnpm check (typecheck, recursive)RED61 errors in sd-app (missing paraglide keys)
template astro checkGREEN0 errors, 0 warnings, 9 hints (150 files)
mbr astro checkGREEN0 errors, 0 warnings, 1 hint (24 files)

Bottom line: Of the 4 commands in pnpm test:all (test && test:sd-app && test:py && lint), two are RED (test:py via sd-api, and lint). pnpm test:all does NOT pass at HEAD, and the .husky/pre-push hook would fail (it runs pnpm lint, pnpm test:sd-app, and sd-api pytest). Pushes currently only succeed via --no-verify or because the repo is “local-only” (never pushed).

1. pnpm lint (10 errors):

sites/mbr/src/pages/focus.astro
298:9 error Unexpected var, use let or const instead no-var
298:13 error 'GAP' is assigned a value but never used @typescript-eslint/no-unused-vars
306:9 / 308:9 / 315:9 / 324:9 / 428:5 / 432:5 error Unexpected var, use let or const
sites/sdc/src/pages/focus.astro
385:5 / 389:5 error Unexpected var, use let or const instead no-var
✖ 17 problems (10 errors, 7 warnings) — 9 auto-fixable with --fix

Note GAP (focus.astro:298 var GAP = 6;) is genuine dead code — assigned, never referenced by positionTooltip. Both offending files (focus.astro on mbr + sdc) are part of the 61-commit delta (Tooltip/focus-page work).

2. pnpm test:py → sd-api:

× No solution found when resolving dependencies:
╰─▶ Because sd-math was not found in the package registry and your project
depends on sd-math, we can conclude that your project's requirements
are unsatisfiable.
ELIFECYCLE Command failed with exit code 1.

3. pnpm check (sd-app svelte-check, 61 errors, sample):

src/routes/[country]/[lang]/term-loan/+page.svelte 35:13
"Property 'tl_snapshot_title' does not exist on type 'typeof import("$paraglide/messages")'."
... (61 ERRORS, 2 FILES_WITH_PROBLEMS: int-only + term-loan +page.svelte)

Prior-review (2026-05-14) resolution status

Section titled “Prior-review (2026-05-14) resolution status”

Strong progress — most 2026-05-14 findings are FIXED:

Prior #FindingStatus now
1CI case-mismatch sites/TemplateFIXEDtest.yml rewritten, single 3-min pipeline, lowercase paths
2pnpm lint broken (brands.astro no-var)Regressed elsewhere — original fixed, but new no-var errors in focus.astro (see RED #1)
3Python tests orphaned from pipelineFIXED (partly) — test:py script added + in pre-push; but sd-api leg is broken (see RED #2)
4/test-all incompleteImproved — test:all now spans tokens+sd-app+py+lint
5pre-push only ran Template E2EFIXED — pre-push now runs test, test:sd-app, lint, template check+E2E, sd-math+sd-api pytest
9sd-app only 1 test fileNOT resolved — still exactly 1 test file (calculators/ca/index.test.ts, 3 tests)

[CRITICAL] [effort: 0.5h] sd-api pytest cannot resolve sd-mathtest:py + pre-push are RED

Section titled “[CRITICAL] [effort: 0.5h] sd-api pytest cannot resolve sd-math — test:py + pre-push are RED”
  • Path: packages/sd-api/pyproject.toml
  • What: dependencies = [... "sd-math"] is a bare name with no [tool.uv.sources] entry, and packages/sd-api/ has no uv.lock (sd-math has one; sd-api doesn’t). So uv run pytest aborts at resolution before any test runs. This kills the second half of pnpm test:py and the sd-api step in .husky/pre-push and test.yml.
  • Evidence: Reproduced the failure (RED #2). Then confirmed the 31 sd-api tests actually PASS when sd-math is path-resolvable: uv run --no-project --with ../sd-math --with fastapi --with "uvicorn[standard]" --with httpx --with pytest python -m pytest -q31 passed, 1 warning in 1.53s. So it’s purely a packaging/resolution defect, not test failures.
  • Why it matters: sd-api wraps the financial-math engine as HTTP endpoints. 31 working tests are invisible to the pipeline; the whole test:py chain reports failure, masking sd-math’s green result behind a red exit. Anyone running test:all or pushing sees a confusing dependency error, not a test report.
  • Fix: Add to packages/sd-api/pyproject.toml:
    [tool.uv.sources]
    sd-math = { path = "../sd-math", editable = true }
    Then cd packages/sd-api && uv lock && uv sync and commit the resulting uv.lock (matches the SSoT/lockfile-committed dev rule). Verify uv run pytest → 31 passed.

[HIGH] [effort: 0.25h] pnpm lint RED — 10 errors in new focus pages (blocks pre-push + test:all)

Section titled “[HIGH] [effort: 0.25h] pnpm lint RED — 10 errors in new focus pages (blocks pre-push + test:all)”
  • Path: sites/mbr/src/pages/focus.astro, sites/sdc/src/pages/focus.astro
  • What: 8 no-var + 1 unused-var (GAP) + 1 more no-var (see RED #1). 9 of 10 auto-fixable.
  • Why: pnpm lint is a step in both pnpm test:all and .husky/pre-push; this makes both fail. The GAP unused var is a real (if harmless) dead-code smell shipped in the Tooltip positioning logic — the tooltip GAP offset is defined but never applied, so tooltips may be mispositioned vs intent.
  • Fix: pnpm lint:fix clears the 8 varconst. Manually resolve GAP: either wire it into positionTooltip (if a 6px offset was intended) or delete it. This is repeated inline-script code in two focus.astro files — consider extracting the tooltip JS to a shared .ts module so it lints once and is testable (it currently can’t be unit-tested as inline Astro script).

[HIGH] [effort: 2h] MBR rates helper — 14 exported functions incl. financial math, ZERO unit tests

Section titled “[HIGH] [effort: 2h] MBR rates helper — 14 exported functions incl. financial math, ZERO unit tests”
  • Path: sites/mbr/src/lib/rates.ts (98 lines, delta surface from commits 4cb2f63 / fafce34 / cdd11c3)
  • What: Pure, trivially-testable exports with no .test.ts: annualGain(principal, candidate, baseline), annualGainPer100k(...), getTopProducts(limit) (sort+filter), getFullTableProducts(), getBestSavingsRate(), getStandardCreditCardRate(), getBestLowRateCard(), formatCurrency, formatRate, getBaselineRate/getPrimeRate/getPublicProducts (reference_only filter).
  • Why it matters: This is the math behind the active commercial site’s rate comparison and “annual gain” claims — exactly the FSS-mission correctness surface. The single mbr E2E (smoke.spec.ts) checks that the gain cell changes when the bank rate changes, but nothing pins the value of annualGain/annualGainPer100k, the sort order of getTopProducts, the reference_only exclusion, or currency/rate formatting. A regression that flips a sign or mis-sorts would ship green.
  • Fix: Add sites/mbr/src/lib/rates.test.ts (vitest — no DOM needed; these are pure functions importing JSON). Wire mbr into the test pipeline: mbr currently has no vitest config and no test (unit) script — add one and include in test:all. Target: every exported fn in rates.ts has an assertion, especially the 3 financial-math fns with known fixtures.

[MEDIUM] [effort: 1.5h] New Reverse Mortgages / Investment Loans sections have no E2E assertions

Section titled “[MEDIUM] [effort: 1.5h] New Reverse Mortgages / Investment Loans sections have no E2E assertions”
  • Path: sites/mbr/src/pages/rates.astro, sites/mbr/tests/smoke.spec.ts
  • What: Commits cdd11c3 (Reverse Mortgages — 4 lenders), fafce34 (Investment Loans), d5cfece (mortgage Term/Type cols, Dealerhop auto loans), 4cb2f63 (dashboard overhaul) added substantial new rates-page surface. smoke.spec.ts only asserts the original HISA “annual gain” interaction. No test renders or asserts the new sections exist / show their lenders.
  • Why: These are the most-changed user-facing additions in the delta and are entirely unguarded by E2E. A broken data shape or removed section would not be caught.
  • Fix: Extend smoke.spec.ts with section-presence assertions (heading + at least one lender row per new section). Cheap; rides the existing mbr playwright config.

[MEDIUM] [effort: 0.5h] pnpm check RED — 61 sd-app typecheck errors (paraglide keys)

Section titled “[MEDIUM] [effort: 0.5h] pnpm check RED — 61 sd-app typecheck errors (paraglide keys)”
  • Path: apps/sd-app/src/routes/[country]/[lang]/{int-only,term-loan}/+page.svelte
  • What: 61 svelte-check errors: snapshot_* / tl_snapshot_* message keys referenced but absent from $paraglide/messages. CI already comments-out the sd-app check step (acknowledged in UPGRADES.md), but pnpm check (the documented DX command) is fully red.
  • Why: These pages render at runtime (paraglide falls back to the key string), but the typecheck gate is permanently disabled, so any real type regression in sd-app is now invisible. DX rot: developers learn to ignore pnpm check.
  • Fix: Reconcile the message catalogue — add the ~30 missing keys to messages/en.json (+ locales), regenerate, re-enable the CI Typecheck — sd-app step. Until then pnpm check will mask new errors among the 61 noise lines.

[MEDIUM] [effort: 1h] sd-app still has only 1 test file despite being the calculator product

Section titled “[MEDIUM] [effort: 1h] sd-app still has only 1 test file despite being the calculator product”
  • Path: apps/sd-app/src/ — only src/lib/calculators/ca/index.test.ts (3 tests)
  • What: Unchanged since 2026-05-14 (prior #9). No tests for the US calculator path, formatters, i18n locale/currency, or the int-only/term-loan route logic. Note the Python sd-math engine is well-covered (218 tests), but the TS-side calculators/formatters that feed the PWA UI are not.
  • Fix: Add sibling .test.ts for each file under src/lib/calculators/ and formatters; one playwright smoke over the /[country]/[lang]/ route matrix.

[LOW] [effort: 0.25h] CI exists but is documented as “never runs” — sd-api step would fail if it did

Section titled “[LOW] [effort: 0.25h] CI exists but is documented as “never runs” — sd-api step would fail if it did”
  • Path: .github/workflows/test.yml
  • What: Well-structured single pipeline (good — prior #1 fixed). But the file itself says “this repo is local-only (no GitHub push), so CI never runs,” while containing a Pytest — sd-api step (uv run pytest in packages/sd-api) that would fail exactly as reproduced (CRITICAL above) plus a Format check and Lint step that are currently red. So CI is simultaneously (a) configured and (b) declared inert. If the repo is ever pushed to GitHub, CI goes red on day one.
  • Fix: Resolve the sd-api/lint reds (above) so the workflow is actually green-able, then either enable push-based CI or delete .github/workflows/ to match the “local-only” story. Don’t keep a red, inert workflow.

[LOW] [effort: 0.25h] Tooltip / Glossary components have no test

Section titled “[LOW] [effort: 0.25h] Tooltip / Glossary components have no test”
  • Path: packages/components/src/ui/Tooltip.astro, sites/template/src/components/showcase/TooltipShowcase.astro, glossary via sites/mbr/src/utils/load-focus-content.ts
  • What: New Tooltip component (commits 1ee1a5f / a741aa2) has a showcase page but no spec; the only “tooltip” reference in any test is incidental (search.spec.ts). Glossary() tooltip syntax and focus-page bfcache/cache-control handlers (focus.astro pageshow/visibilitychange reload logic, commit 42d76a3) are untested.
  • Why: Lower stakes than rates math, but the bfcache reload handler is exactly the kind of browser-behavior logic that benefits from a playwright assertion (it caused the stale-PWA bug it fixes).
  • Fix: Add a tooltip showcase spec in sites/template/tests/ (hover → tooltip visible + positioned in-viewport, matching the viewport-clamp fix 3b54a12). Optionally a focus-page reload assertion.

  • Lint: RED (10 errors) — but 9 auto-fixable; debt is small and localized to 2 new files.
  • Typecheck: astro check GREEN for both shipping sites (template, mbr). pnpm check RED only via sd-app paraglide keys (known, CI-suppressed). Root tsconfig still lacks noUncheckedIndexedAccess (prior #6, unaddressed) — relevant for TS-side calculators.
  • Pipeline integrity: pnpm test:all and .husky/pre-push both currently fail (sd-api resolution + lint). The hooks are good in design (comprehensive, changed-aware would be even better) but a red HEAD means they’re being bypassed.
  • Coverage of correctness-critical math: sd-math (Python) is excellent — 218 tests across CA/US, term/interest-only, historical/monthly. The TS-side financial math (sites/mbr/src/lib/rates.ts, sd-app calculators/formatters) is the gap — that’s where the active-site claims live and it’s untested.
  • Flaky/slow: All ran fast (<1s each for unit suites). Did not run Playwright E2E this pass (browser-driven, time-boxed out); template E2E is the historically flaky surface (visual snapshots, WSL-vs-CI rendering — prior #13 still undocumented).
  • 1 CRITICAL (sd-api pytest resolution — breaks test:py + pre-push)
  • 2 HIGH (lint red in focus pages; mbr rates.ts financial math untested)
  • 3 MEDIUM (new rates sections no E2E; pnpm check 61 errors; sd-app 1-test-file)
  • 3 LOW (inert-but-red CI; Tooltip/Glossary untested; — )