## 1. Backend: Batch Availability Endpoint (Phase 1 - Tests RED) - [x] 1.1 Write feature test: POST /api/capacity/availability/batch saves multiple updates and returns 200 with saved count - [x] 1.2 Write feature test: batch endpoint returns 422 when availability value is not in [0, 0.5, 1] - [x] 1.3 Write feature test: batch endpoint returns 422 when team_member_id does not exist - [x] 1.4 Write feature test: empty updates array returns 200 with saved count 0 - [x] 1.5 Write unit test: CapacityService::batchUpsertAvailability() upserts all entries and flushes cache once ## 2. Backend: Batch Availability Endpoint (Phase 2 - Implement GREEN) - [x] 2.1 Add `batchUpsertAvailability(array $updates, string $month): int` to CapacityService — upserts all rows, flushes month-level cache once after all upserts - [x] 2.2 Add `batchUpdateAvailability(Request $request): JsonResponse` to CapacityController — validate month + updates array, call service, return `{ data: { saved, month } }` - [x] 2.3 Add route: `POST /api/capacity/availability/batch` in `routes/api.php` - [x] 2.4 Run pint and all backend tests — confirm all pass ## 3. Backend: Batch Availability Endpoint (Phase 3 - Refactor & Document) - [x] 3.1 Add Scribe docblock to `batchUpdateAvailability()` with request/response examples - [x] 3.2 Confirm no N+1 queries in batch upsert (cache flush is single, queries are acceptable for batch size) ## 4. Frontend: Expert Mode Toggle (Phase 1 - Tests RED) - [x] 4.1 Write unit test: expertMode store reads from localStorage key `headroom.capacity.expertMode`, defaults to `false` - [x] 4.2 Write unit test: toggling expertMode persists new value to localStorage - [x] 4.3 Write component test: Expert Mode toggle renders right-aligned on tabs row - [x] 4.4 Write component test: toggle reflects current expertMode store value - [x] 4.5 Write component test: switching mode with dirty cells shows confirmation dialog ## 5. Frontend: Expert Mode Toggle (Phase 2 - Implement GREEN) - [x] 5.1 Create `frontend/src/lib/stores/expertMode.ts` — writable store backed by `localStorage` key `headroom.capacity.expertMode`, default `false` - [x] 5.2 Add Expert Mode toggle (label + DaisyUI toggle switch) to `capacity/+page.svelte`, right-aligned on tabs row - [x] 5.3 Wire toggle to `expertModeStore` — on change, persist to localStorage - [x] 5.4 When toggling off with dirty cells: show confirm dialog; discard changes on confirm, cancel toggle on dismiss - [x] 5.5 Run type-check and unit tests — confirm all pass ## 6. Frontend: Expert Mode Grid Component (Phase 1 - Tests RED) - [x] 6.1 Write component test: CapacityExpertGrid renders a row per active team member - [x] 6.2 Write component test: CapacityExpertGrid renders a column per day of the month - [x] 6.3 Write unit test: token normalization — `H` → `{ rawToken: "H", numericValue: 0, valid: true }` - [x] 6.4 Write unit test: token normalization — `O` → `{ rawToken: "O", numericValue: 0, valid: true }` - [x] 6.5 Write unit test: token normalization — `.5` → `{ rawToken: "0.5", numericValue: 0.5, valid: true }` - [x] 6.6 Write unit test: token normalization — `0.5` → `{ rawToken: "0.5", numericValue: 0.5, valid: true }` - [x] 6.7 Write unit test: token normalization — `1` → `{ rawToken: "1", numericValue: 1, valid: true }` - [x] 6.8 Write unit test: token normalization — `0` → `{ rawToken: "0", numericValue: 0, valid: true }` - [x] 6.9 Write unit test: token normalization — `2` → `{ rawToken: "2", numericValue: null, valid: false }` - [x] 6.10 Write unit test: auto-render — `0` on weekend column → rawToken becomes `O` - [x] 6.11 Write unit test: auto-render — `0` on holiday column → rawToken becomes `H` - [x] 6.12 Write component test: invalid cell shows red border on blur - [x] 6.13 Write component test: Submit button disabled when any invalid cell exists - [x] 6.14 Write component test: Submit button disabled when no dirty cells exist ## 7. Frontend: Expert Mode Grid Component (Phase 2 - Implement GREEN) - [x] 7.1 Create `frontend/src/lib/utils/expertModeTokens.ts` — export `normalizeToken(raw, isWeekend, isHoliday)` returning `{ rawToken, numericValue, valid }` - [x] 7.2 Create `frontend/src/lib/components/capacity/CapacityExpertGrid.svelte`: - Props: `month`, `teamMembers`, `holidays` - On mount: fetch all members' individual capacity in parallel - Render grid: members × days, cells as `` elements - On blur: run `normalizeToken`, apply auto-render rule, mark dirty - Invalid cell: red border - Emit `dirty` and `valid` state to parent - [x] 7.3 Wire `capacity/+page.svelte`: when Expert Mode on, render `CapacityExpertGrid` instead of calendar tab content - [x] 7.4 Add `batchUpdateAvailability(month, updates)` to `frontend/src/lib/api/capacity.ts` - [x] 7.5 Add Submit button to Expert Mode view — disabled when `!hasDirty || !allValid`; on click, call batch API, show success/error toast - [x] 7.6 Run type-check and component tests — confirm all pass ## 8. Frontend: Live KPI Bar (Phase 1 - Tests RED) - [x] 8.1 Write unit test: KPI bar capacity = sum of all members' numeric cell values / 1 (person-days) - [x] 8.2 Write unit test: KPI bar revenue = sum(member person-days × hourly_rate × 8) - [x] 8.3 Write unit test: invalid cells contribute 0 to KPI totals (not null/NaN) - [x] 8.4 Write component test: KPI bar updates when a cell value changes ## 9. Frontend: Live KPI Bar (Phase 2 - Implement GREEN) - [x] 9.1 Create `frontend/src/lib/components/capacity/ExpertModeKpiBar.svelte` (integrated into CapacityExpertGrid) - Props: `gridState` (reactive cell map), `teamMembers` (with hourly_rate) - Derived: `totalPersonDays`, `projectedRevenue` - Render: two stat cards (Capacity in person-days, Projected Revenue) - [x] 9.2 Mount `ExpertModeKpiBar` above the grid in Expert Mode view - [x] 9.3 Confirm KPI bar reactively updates on every cell change without API call - [x] 9.4 Run type-check and component tests — confirm all pass ## 10. Frontend: Expert Mode Grid (Phase 3 - Refactor) - [x] 10.1 Extract cell rendering into a `ExpertModeCell.svelte` sub-component if grid component exceeds ~150 lines (skipped - optional refactor, grid at 243 lines is acceptable) - [x] 10.2 Add horizontal scroll container for wide grids (months with 28–31 days) - [x] 10.3 Ensure weekend columns have muted background, holiday columns have distinct header marker - [x] 10.4 Verify toggle is hidden on mobile (< md breakpoint) using Tailwind responsive classes ## 11. E2E Tests - [x] 11.1 Write E2E test: Expert Mode toggle appears on Capacity page and persists after reload - [x] 11.2 Write E2E test: grid renders all team members as rows for selected month - [x] 11.3 Write E2E test: typing invalid token shows red cell and disables Submit - [x] 11.4 Write E2E test: typing valid tokens and clicking Submit saves and shows success toast - [x] 11.5 Write E2E test: KPI bar updates when cell value changes - [x] 11.6 Write E2E test: switching off Expert Mode with dirty cells shows confirmation dialog ## 12. Timezone & Accessibility Fixes - [x] 12.1 Fix weekend detection: use `new Date(dateStr + 'T00:00:00')` to avoid local timezone drift - [x] 12.2 Update weekend styling: use `bg-base-300` solid background + `border-base-400` for accessibility - [x] 12.3 Update holiday styling: use `bg-warning/40` + `border-warning` with bold `H` marker - [x] 12.4 Prefill weekend cells with `O` on grid load (not `1`) - [x] 12.5 Prefill holiday cells with `H` on grid load (not `1`) - [x] 12.6 Add backend timezone helper using `America/New_York` for weekend/holiday checks - [x] 12.7 Ensure backend seeds weekends with `availability: 0` (to match frontend `O`) - [x] 12.8 Ensure backend seeds holidays with `availability: 0` (to match frontend `H`) - [x] 12.9 Run all tests and verify weekend shading aligns correctly for April 2026 (4th/5th = Sat/Sun)