## 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 `<input>` 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)
