Frontend CI: local-first, GitHub only on the trunk
Frontend CI runs the canonical gate — frontend/bin/ci (also pnpm run ci) —
which checks lint, typecheck, and test across the whole workspace.
Each dimension is ratcheted: a baseline count that only moves DOWN, so the
existing red backlog is tolerated but any NEW failure fails the build (the same
pattern as backend's domains ratchet).
| Stage | Who runs CI | How |
|---|---|---|
Feature PR → docs/new-schema-design | You, locally, before opening the PR | cd frontend && pnpm run ci |
PR → master (reaching the trunk) | GitHub Actions (final safety net) | .github/workflows/frontend-ci.yml |
Everyday development spends zero GitHub-hosted minutes — the runner only fires
on PRs into master. Run the suite on your own machine (it's the same bin/ci),
and only pay for cloud CI at the trunk.
Why not a required status check /
gh signoff? Both need GitHub branch protection, which this repo's plan doesn't offer (HTTP 403). So the feature-PR gate is local (pnpm run ci) andmasteris backstopped by the workflow.
Before opening a feature PR
cd frontend
pnpm run ci # lint + typecheck + test, each ratcheted — must passEvery dimension runs even if an earlier one fails, so you see all of them in one pass. It exits non-zero only if a dimension exceeded its baseline (i.e. you added a NEW lint error / type error / test failure).
The ratchet
Baselines live in frontend/.ci-baselines/:
| File | Meaning | Current |
|---|---|---|
lint.txt | max tolerated ESLint errors | 0 |
typecheck.txt | max tolerated TypeScript errors | 254 |
test.txt | max tolerated failing tests | 0 |
scripts/ci-ratchet.mjs <dim> runs turbo run <dim> --continue, counts the
failures across the workspace, and compares to the baseline:
- count > baseline → ❌ fail ("you introduced new failures — fix them").
- count < baseline → ✅ pass, and it prints "ratchet the baseline down to N".
Do that in the same PR that fixed the red, so the ceiling never drifts back
up. Driving all three to
0is the goal.
Burning down the backlog
Lint and test baselines permit no errors. Type errors remain in cms, connect,
growth, shell, and website. A baseline is a limit, not a current test result.
Use the output from the latest CI run to check the current result.
Fix some, run pnpm run ci, and ratchet the relevant baseline down to the new
number it reports.
Website hover checks
The website uses the existing frontend Vitest dependencies. No separate test install is required. Run its focused checks from the repository root:
pnpm --dir website/app testThe dedicated Vitest workspace checks all 17 sections that used mapped Hooks
and the shared HoverMotionDiv component. It tests independent hover state, pointer leave,
keyed list changes, Strict Mode, and the DOM structure. The website test task
does not use Turbo caching because its test files and runner live in frontend/.
Call Hooks in components or custom Hooks, not in list callbacks. Each mapped card must have its own component instance and a stable key. Escape quotes in JSX text without changing the displayed text. Keep the Hook lint rule enabled.
For a browser check, open a website page with these cards. Move the pointer over one card, then another, then outside the grid. Only the hovered card must show its hover style. Check the page in light and dark mode. There must be no Hook errors in the browser console.
The full website browser check still reports an icon hydration mismatch in the
unchanged clinics/revenue-management.tsx section. The same mismatch occurs at
pre-fix commit 0d85967223. This is separate from the mapped Hook fixes. A
passing lint check does not mean that the full website has no runtime errors.
The master gate
.github/workflows/frontend-ci.yml triggers on pull_request into master
(paths frontend/**) and runs the identical pnpm run ci on a GitHub-hosted
runner. If your local run was green, this will be too.
Prereqs (same as any local frontend dev): Node 20 + pnpm 9, pnpm install.