ci(coverage): drop client-project branches threshold 80 → 70 to unblock CI #556
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The client-project Istanbul coverage configuration in
frontend/vitest.client-coverage.config.tscurrently enforces an 80% threshold acrosslines,functions,branches, andstatements. The branch-coverage figure is 77% right now — below the 80% gate — and every CI run withtest:coverage(including thecoverage-flake-probe.ymlmatrix and the standardunit-testsjob) fails on this check alone, even when all tests are green.Per the prior incident captured in #496, reaching 80% branches on this codebase is a long-tail grind — the denominator grows as parents pull children into coverage, so each commit that improves a child's coverage often makes the parent worse. Expected cost to reach 80% from 77% is 30–100+ commits. That cost is not justified right now, especially while #553 / PR #555 is the active blocker for shipping the birpc-race fix.
Decision
Drop the branches threshold from
80→70invitest.client-coverage.config.ts. Leavelines,functions,statementsat80. Document the decision in a new ADR so the rationale is not lost.The 70% figure gives us ~7 percentage points of headroom (77% current → 70% gate) without effectively disabling the check. Lines/functions/statements stay at 80% because they don't suffer the same long-tail-grind problem (the parent/child accounting differs).
Acceptance
frontend/vitest.client-coverage.config.ts:47set tobranches: 70; lines/functions/statements remain at 80docs/adr/013-client-branches-coverage-threshold.md(or next free number — verify againstdocs/adr/) explaining:coverage-flake-probe.ymlrun (workflow_dispatch) succeeds end-to-end withbranches: 70Non-goals
vitest.config.ts) — only the client project hits the long-tail-grind pattern.References
frontend/vitest.client-coverage.config.ts:38-50— coverage block to modifydocs/adr/— naming convention for the new ADRMarkus Keller — Senior Application Architect
Observations
docs/adr/already contains BOTH012-browser-test-mocking-strategy.mdand012-nsenter-for-host-service-management-in-ci.md. The issue says "ADR 013-client-branches-coverage-threshold.md (or next free number — verify againstdocs/adr/)". The next free number is 013, but anyone reading the directory now sees a collision and will not know which 012 to cross-link from a new ADR. That is an architectural-memory smell.vitest.config.tsdoes not exist at that path — see below) is genuinely a different beast because its parent/child denominator behaviour differs. Leaving lines/functions/statements at 80% preserves the gate's signal.vitest.config.tsas the "server project" — that file does not exist atfrontend/vitest.config.ts. The split-config layout usesvitest.client-coverage.config.tsplus presumably a server-side companion. Worth verifying the issue's "Non-goals" wording before merge so the ADR doesn't reference a file that's been renamed.Recommendations
012-*.mdto014-*.md(e.g.014-nsenter-for-host-service-management-in-ci.md, since it landed later) and update any cross-references. Otherwise the next ADR author hits the same collision and the rationale chain breaks. This is exactly what ADRs exist to prevent.npm run test:coveragereports ≥ 73% branches sustained across 3 consecutive PRs, raise the gate to 73%. Never lower." That's the only mechanism that prevents 70% from quietly becoming the new permanent floor.vitest.client-coverage.config.ts:38-50(correct, I checked) but the "Non-goals" namesvitest.config.tsfor the server project — that path doesn't exist. Pin the actual filename before the ADR ships.Open Decisions
Felix Brandt — Senior Fullstack Developer
Observations
branches: 80→branches: 70infrontend/vitest.client-coverage.config.ts:47. Zero production code, zero test code, no failing-test-first cycle applies — this is a config gate adjustment, not a behavior change. TDD discipline isn't on the line here.npm run test:coverageagainst the client project must exit zero. That's verified by runningcoverage-flake-probe.ymlend-to-end (already listed in AC).vitest.client-coverage.config.ts:44-49, the thresholds block has four explicit fields. After the change, three are at 80 and one is at 70. That asymmetry deserves a one-line inline comment pointing at the ADR — exactly the kind of why comment that earns its keep.Recommendations
npm run test:coveragelocally before pushing to confirm the current branches number really is 77% (issue says 77%; I want the ADR to record the exact measured number with the date). The ADR's headroom argument hinges on this — if it's actually 73% the 70% gate has 3pp not 7pp.chore/issue-556-drop-client-branches-coverage-gate, commit referencing #556, PR with the ADR included.Open Decisions
(none — concrete, scoped, mechanical change)
Tobias Wendt — DevOps & Platform Engineer
Observations
unit-testsjob in.gitea/workflows/ci.ymlrunsnpm run test:coverage(line 63). Vitest exits non-zero on threshold failure, the step fails, and downstream jobs that depend onunit-testsnever run. So yes — this single threshold is currently a hard merge gate for #555 and every other PR.coverage-flake-probe.ymlis a workflow_dispatch matrix of 20 parallel runs. After the threshold drops to 70%, each cell of the matrix must now also pass the threshold. If a cell randomly comes in at 69.8% (small variance across runs is real with browser/playwright), the probe fails on coverage rather than on the birpc race it's meant to detect — defeating the probe's diagnostic purpose. This is the one infrastructure risk I'd flag.Recommendations
coverage-flake-probe.ymlonce onmainwith current settings and capture the per-cell branches percentage from the lcov output. If the spread is e.g. 76.8% – 78.2%, then 70% has plenty of headroom. If it's 70.5% – 78%, then 70% is too close and 65% is the safer pick. The issue assumes 7pp headroom — verify it's real before locking it in.next-review: 2026-11-13in the body, labelcleanup, milestone TBD. Otherwise this ADR slides off the radar like every other "revisit later" promise.coverage-flake-probe.ymlitself. Its job is to detect the birpc teardown race (ADR 012), not coverage thresholds. The threshold check happens implicitly insidenpm run test:coverage; the probe should keep its single responsibility.coverage-flake-probe.ymlrun (workflow_dispatch) succeeds end-to-end withbranches: 70". This is the right validation — but record the per-cell coverage numbers in the PR description (or a comment) so we have a baseline for the future ratchet decision.Open Decisions
77% current → 70% gate = 7pp, but that's a single measurement. If the 20-cell probe shows ±2% noise, 70% is fine. If it shows ±5% noise, the gate will start flaking and we'll be right back here in two weeks. Cost of 70% if variance is high: random CI failures every ~10 PRs. Cost of 65%: even less signal from the gate, but no flake. Measure first, pick second.Sara Holt — QA Engineer & Test Strategist
Observations
Recommendations
cd frontend && npm run test:coverageagainst commit<sha>" gives the next reviewer something to compare against when deciding whether to ratchet.branches: 70" — that's the actual quality test for this change. Capture the per-cell coverage from those 20 runs and paste it in the PR. That's our evidence the gate holds.Open Decisions
(none — the change is scoped, the ratchet path is documented, the only judgment call is the layer-distinction wording in the ADR)
Nora "NullX" Steiner — Application Security Engineer
Observations
src/**/*.svelteandsrc/**/*.svelte.ts(component templates and component-local logic). Auth flow components (login/+page.svelte,reset-password/+page.svelte,forgot-password/+page.svelte) are explicitly listed in #496 as 0% branch coverage today — meaning the 77% number already does not include them. So this change does not regress an already-tested security path; it acknowledges that several security-relevant components are simply untested today.Recommendations
login/+page.svelte,forgot-password/+page.svelte,reset-password/+page.svelte,register/+page.svelte, and any other auth/permission-boundary UI that has 0% branch coverage today. When the ratchet-up work begins, these jump to the top of the priority list — uncovered auth UI is where regression risk lives.@RequirePermissionenforcement tests,parseBackendErrorparsing, and CSRF/auth-cookie handling are tested at a different layer with different tooling. Their 80% gate stays.vitest.client-coverage.config.tsthresholds change — no test files removed, no exclude-list growth, noskipIfadded to existing security tests.Open Decisions
(none — this is a CI-gate change with no direct security surface)
Leonie Voss — UX Designer & Accessibility Strategist
Observations
Recommendations
DocumentTopBar,PersonCard,PersonChipRow, and the auth pages. These are where a11y regressions hide. (#496 already lists them — the prioritization should hold.)frontend/e2e/accessibility tests is being skipped or relaxed alongside this change.Open Decisions
(none — no UX/accessibility surface in this change)
"Elicit" — Requirements Engineer & Business Analyst
Observations (Brownfield mode)
npm run test:coveragerun passes on the branch where the gate is dropped. AC #3 says the flake-probe workflow passes, which proves the threshold-drop is enforceable. But the primary failure mode this issue exists to fix is:unit-testsjob inci.ymlcurrently fails. That needs its own AC bullet to close the loop.Recommendations
unit-testsjob passes." This is the actual business outcome — without it, AC measures "is the gate enforceable" but not "did we achieve the unblock."frontend/vitest.client-coverage.config.tslines 38–50 change; thebranchesvalue is set to 70; no other key in the thresholds block is modified." This prevents accidental scope drift on a P0-critical merge.<commit-sha>, (b) the trigger condition for raising the gate (e.g. 'when 3 consecutive PRs report ≥ 73% branches'), (c) a calendar review date no later than 2026-11-13." Without this, "when to revisit" is unenforceable and the 70% gate calcifies.Open Decisions
Decision Queue — Action Required
3 decisions need your input before implementation starts.
Backlog hygiene
The current state — closed-as-done with AC unmet — is the "zombie issue" pattern. Pick one.
Threshold value
coverage-flake-probe.ymlonce onmainwith the currentbranches: 80removed/relaxed to capture the per-cell branches percentage across the 20-cell matrix. If spread is small (e.g. 76.8% – 78.2%), 70% is fine. If wide (e.g. 70.5% – 78%), 65% is the safer pick and 70% will start flaking the same job in two weeks. Cost of guessing wrong: random CI failures every ~10 PRs and reopening this same conversation.ADR governance
next-review: 2026-11-13in a tracking issue): enforceable, surfacable via labels/queries.Pick one for the ADR. Calendar + trigger combined is fine; free-text-only is not.
Decisions on the review queue
Resolving the four cross-cutting themes from the persona review so the implementation can proceed without further blocking input.
1. ADR number → 013
docs/adr/confirmed: existing012-browser-test-mocking-strategy.mdand012-nsenter-for-host-service-management-in-ci.mdare a pre-existing collision (note: I will file a separate cleanup issue to renumber012-nsenter→013-nsenter). For this issue, use the next free numerical slot —013for the threshold ADR is acceptable; the duplicate-012 problem is out of scope and tracked separately.2. Concrete ratchet mechanism (not "vibes-based revisit")
The ADR must contain:
This is a concrete, observable trigger — not a calendar review.
3. Exact baseline recorded in the ADR
The ADR must include:
Capture the actual numbers from a fresh local run during the implementation (replace
xx.xxplaceholders).4. Variance handling
70% is the floor. If the
coverage-flake-probe.ymlmatrix produces any cell with branches < 70%, that is a separate signal worth investigating — open a follow-up issue rather than silently lowering the floor again. The 7-point headroom is intentional.Acceptance update
Add to the existing list:
Items closed by these decisions
Out of scope
Plan is pre-approved by the maintainer. The implementer should treat the items above as final, file the noted out-of-scope follow-ups as separate Gitea issues, and proceed.