# ADR 013 — Client-Project Branch Coverage Threshold **Status:** Accepted **Date:** 2026-05-14 **Issues:** [#556 — threshold drop](https://git.raddatz.cloud/marcel/familienarchiv/issues/556) · [#496 — long-tail-grind tracking](https://git.raddatz.cloud/marcel/familienarchiv/issues/496) --- ## Context The browser-mode component test suite (`vitest.client-coverage.config.ts`) enforces Istanbul coverage thresholds across `lines`, `functions`, `branches`, and `statements`. The `branches` metric was set to 80%, but the codebase sits at **75%** — below the gate — causing every CI run of `unit-tests` and `coverage-flake-probe` to fail on this check alone, even when all tests are green. **Measured baseline (2026-05-14, branch `feat/issue-553-birpc-async-mock-factory`, head `2e6cc346`):** ``` branches: 75% (below the 80% gate — reason for this ADR) lines: ≥ 80% functions: ≥ 80% statements: ≥ 80% ``` Reproducer: ```bash cd frontend && npm ci && npx vitest run -c vitest.client-coverage.config.ts --coverage ``` ### The long-tail-grind problem In Istanbul's branch accounting, when a child component gains test coverage its branches are added to the parent's denominator. A child moving from 40% → 80% coverage can drag a parent from 78% → 72% because more branches in the call graph become reachable and must be covered. This is not a bug — it is how branch accounting works — but it means that on a large SvelteKit application the denominator grows with every coverage improvement, making an arbitrary 80% ceiling a constant grind. Per #496, the expected cost to reach 80% branches from 75% is 30–100+ commits with no guarantee of stability. ### Why this layer is different The 80% branch floor used for backend unit/integration tests is appropriate for Java service code and permission logic. Browser-mode component coverage measures Svelte template branches: conditional class bindings, `{#if}` blocks, empty/loaded/error state guards. These branches have a fundamentally different accounting model and a higher inherent denominator. This ADR **only** lowers the browser-mode component gate; the backend test coverage gates are unaffected. ### Security-relevant uncovered components The following auth/permission-boundary components currently have low or zero branch coverage. When ratchet-up work begins (see below), these are the highest-priority targets: - `src/routes/login/+page.svelte` - `src/routes/forgot-password/+page.svelte` - `src/routes/reset-password/+page.svelte` - `src/routes/register/+page.svelte` Note: the 75% figure already reflects the absence of coverage on these files. Lowering the gate does not create this gap — it makes the existing state legible. --- ## Decision Drop the `branches` threshold from `80` → `75` in `frontend/vitest.client-coverage.config.ts`. Leave `lines`, `functions`, and `statements` at `80`. The 75% figure matches the measured current state, allowing CI to pass while deliberate coverage improvement work (tracked in #496) continues without blocking other PRs. The asymmetry in the thresholds block is intentional and documented with an inline comment pointing here. --- ## Ratchet Rule The branches threshold ratchets **up by 3 percentage points** when the rolling 3-PR-average client-project branches figure on `main` stays at or above `threshold + 3pp` for ≥ 30 consecutive days. Direction is **up-only** — never lower the floor below 75 without a new ADR superseding this one. Manual today (verify before any `vitest.client-coverage.config.ts` edit); a future automation issue may codify the check. Concretely: - When `main` sustains ≥ 78% branches across 3 consecutive PRs for 30 days → raise gate to 78% - When `main` sustains ≥ 81% branches across 3 consecutive PRs for 30 days → raise gate back to 80% --- ## Non-goals - **Not** raising actual branch coverage — that is #496's job, tracked separately. - **Not** touching the server-project coverage configuration (`vitest.config.ts`) — only the client project hits the long-tail-grind pattern. - **Not** removing or relaxing any existing test files, `skipIf` guards, or axe-playwright accessibility runs. --- ## Consequences **Easier:** - CI unblocked — `unit-tests` and `coverage-flake-probe` jobs pass when all tests are green - The ratchet rule creates a concrete, observable path back to 80% **Harder:** - The gate now has near-zero headroom — any branch regression that drops below 75% will fail CI immediately - The 75% floor must not be treated as a permanent ceiling; the ratchet discipline requires active attention --- ## References - [#496 — Branch coverage long-tail grind](https://git.raddatz.cloud/marcel/familienarchiv/issues/496) - [#556 — This threshold drop](https://git.raddatz.cloud/marcel/familienarchiv/issues/556) - [ADR 012 — Browser-Mode Test Mocking Strategy](./012-browser-test-mocking-strategy.md) - `frontend/vitest.client-coverage.config.ts` — thresholds block (lines 44–51)