Kulcare Docs
Developer

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).

StageWho runs CIHow
Feature PR → docs/new-schema-designYou, locally, before opening the PRcd 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) and master is backstopped by the workflow.

Before opening a feature PR

cd frontend
pnpm run ci      # lint + typecheck + test, each ratcheted — must pass

Every 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/:

FileMeaningCurrent
lint.txtmax tolerated ESLint errors0
typecheck.txtmax tolerated TypeScript errors254
test.txtmax tolerated failing tests0

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 0 is 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 test

The 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.

On this page