# REVIEW_02 — Simplification & maintainability audit Date: 2026-05-08 Scope: full repo at `main` (commit `1dba877`), against SPEC.md as of the same SHA. Sister document to `doc/REVIEW_01.md` — that one captures security and "fishy patterns"; this one captures **overcomplicated solutions that could be rewritten more simply with no functional change.** This document is meant to be referenced on each refactor loop alongside SPEC.md and REVIEW_01.md. Findings are ordered by severity (highest first) and each carries: - **ID**: stable handle (`R02-Nxx`) — quote when committing a fix. - **Severity**: HIGH / MEDIUM / LOW. Severity here measures _maintenance pain_, not security. HIGH = a real bug surface (already caused regressions, or two-place edits routinely diverge); MEDIUM = meaningful churn / readability / tested-once-then-forgotten; LOW = cosmetic / churn for churn's sake unless the area is being touched anyway. - **Status**: `open` / `accepted-by-design` / `fixed-in-`. - **Where**: file paths + line ranges (current; will shift after edits). - **What**: the pattern that's overcomplicated. - **Why it's worth fixing**: what breaks today, or what the next contributor will trip over. - **Suggested rewrite**: shape of the change, not a full diff. Keep the spec invariants intact: vanilla JS, strict CSP, Twig auto-escape, audit-row-per-mutation, capacity math under §5. When a finding is closed, set Status to `fixed-in-` and append a short note. Do not delete entries — they're history, same contract as REVIEW_01. --- ## How to use this with /loop > **Before starting on any finding, read `SPEC.md` end-to-end.** It is > the contract these refactors must preserve — vanilla JS, strict CSP, > Twig auto-escape, audit-row-per-mutation, the capacity math in §5, the > audit logging rules in §7, and the build-phase log in §9 (which often > records *why* the "complicated" pattern landed that way). A > simplification that silently breaks any of these is a regression, not > a fix. Most findings here are pure internals so SPEC.md will not need > editing — but you can only know that after reading it. 1. Read `SPEC.md` in full (at minimum §5, §7, §9). Skip this step and you will land R02-N02-style hotfixes a week later. 2. Pick the lowest-numbered `open` finding whose severity ≥ MEDIUM. 3. Apply the rewrite, add/adjust tests, update SPEC.md if a public contract changes. 4. Commit; flip Status to `fixed-in-` and add a one-line note. 5. Commit the doc update separately. --- ## HIGH ### R02-N01 — Capacity formula duplicated in PHP and JS - **Severity**: HIGH (already a documented two-place-edit hazard). - **Status**: open. - **Where**: - `src/Services/CapacityCalculator.php` — authoritative implementation. - `public/assets/js/sprint-planner.js` lines 172–222 — `recomputeRow()` + `applyServerCapacity()` reproduce the same `round_half`, `after_reserves`, `committed_p1`, `available` chain on the client. - SPEC.md §5 calls this out explicitly: _"Runs identically in `App\Services\CapacityCalculator` (PHP) and in `sprint-planner.js` (JS). Any edit must touch both."_ - **What**: The capacity rule (the core domain invariant of the app) lives in two places. The JS copy exists so the on-screen totals update during typing without waiting for the server round-trip. Every PATCH response already returns the freshly computed `per_worker` values though, and `applyServerCapacity()` is called immediately after each save (lines 249, 559, 612). - **Why it's worth fixing**: any future change to capacity (a new reserve type, a different rounding rule, priority-3 tasks, anything) must be made twice. The invariant is the kind of thing that gets changed during product evolution; missing one side has produced subtle display bugs in similar projects historically. - **Suggested rewrite**: drop `recomputeRow()`'s arithmetic. Two paths: - **Path A (preferred):** keep typing-responsive feel by relying on the existing 400 ms debounce — the user usually pauses before expecting Available to update anyway. Render `Available` from server values only; on edit, optionally show a "saving…" indicator. - **Path B:** keep an immediate visual update but make it a *plain sum* (no reserve arithmetic) so the formula stays single-source. Server PATCH response is authoritative for Reserves/Available. Either way the JS file shrinks by ~30 lines and the spec's "any edit must touch both" line in §5 goes away. ### R02-N02 — `buildTaskRow()` mirrors the Twig partial by hand - **Severity**: HIGH (already the source of two phase-bound hotfixes). - **Status**: fixed-in-d5a09ff. Row markup extracted to `views/sprints/_task_row.twig`; `_task_list.twig` includes it once per task and once more inside a hidden `