# Phase 18 — Plan Index & Coverage Audit

**Created:** 2026-06-15
**Plans:** 4 (3 waves)
**Mode:** standard · TDD mode ON · MVP off

---

## Wave Structure

| Wave | Plans | Autonomous | Files (disjoint per wave) |
|------|-------|------------|----------------------------|
| 1 | 18-01 (accessor + IANA validator, TDD) | yes | `lib/householdTimezone.ts` (+test) |
| 2 | 18-02 (admin endpoints, TDD), 18-03 (broker wiring, TDD) | yes, yes | 02: `routes/admin.ts` (+test); 03: `broker/reminderScheduler.ts` + `broker/outboxWorker.ts` (+tests) — no overlap with 02 |
| 3 | 18-04 (PWA picker UI) | no (human-verify checkpoint) | `pwa/api/client.ts`, `pwa/routes/AdminPage.tsx` |

Dependency rationale: 02 and 03 both depend only on the Wave-1 accessor and touch disjoint files →
parallel in Wave 2. 04 depends on the 02 endpoint contract → Wave 3.

---

## Artifacts This Phase Produces (MANDATORY)

| Symbol / Artifact | Kind | File | Plan |
|-------------------|------|------|------|
| `getHouseholdTimezone(db)` | function (async, → Promise<string>) | `apps/api/src/lib/householdTimezone.ts` | 01 |
| `isValidIanaTimezone(tz)` | function (→ boolean) | `apps/api/src/lib/householdTimezone.ts` | 01 |
| `'household_timezone'` | app_config key string (new additive row, no migration) | `apps/api/src/db/schema.ts` appConfig (existing table) | 01/02 |
| `GET /api/admin/config/timezone` | route | `apps/api/src/routes/admin.ts` | 02 |
| `PUT /api/admin/config/timezone` | route | `apps/api/src/routes/admin.ts` | 02 |
| `POST /api/admin/config/timezone/seed` | route (no-overwrite seed, D-02/D-03) | `apps/api/src/routes/admin.ts` | 02 |
| `timezoneSchema` | Zod schema | `apps/api/src/routes/admin.ts` | 02 |
| `fetchAdminTimezone()` | client fn | `apps/pwa/src/api/client.ts` | 04 |
| `setAdminTimezone(timezone)` | client fn | `apps/pwa/src/api/client.ts` | 04 |
| `AdminTimezoneResponse` | interface | `apps/pwa/src/api/client.ts` | 04 |
| Timezone section (`aria-label="Timezone"`) | UI component/section | `apps/pwa/src/routes/AdminPage.tsx` | 04 |
| `apps/api/tests/lib/householdTimezone.test.ts` | new test file | — | 01 |

Test files extended (not new): `apps/api/tests/routes/admin.test.ts` (02),
`apps/api/tests/broker/reminderScheduler.test.ts` + `apps/api/tests/broker/outboxWorker.test.ts` (03).

---

## Multi-Source Coverage Audit

### GOAL (ROADMAP Phase 18 goal)
"Auto-detect the household timezone and allow changing it" → COVERED: detection seed (D-02, Plans 02 seed
endpoint + 04 detected-zone affordance); change (D-04, Plans 02 PUT + 04 picker); behavioral wiring
(D-05, Plan 03).

### REQ (phase_req_ids from REQUIREMENTS.md)
No REQ-IDs are mapped to Phase 18 in ROADMAP.md (confirmed: "Requirements: TBD"). Per the planning
context, the D-01..D-07 decision set is the coverage contract — see CONTEXT below. Not a gap.

### RESEARCH (18-RESEARCH.md features/constraints)
- Single accessor `getHouseholdTimezone` + IANA validator → Plan 01.
- GET/PUT endpoints on adminRouter, no new router → Plan 02.
- Seed endpoint, additive/non-blocking (Phase 12 not yet executed) → Plan 02.
- Rewire reminderScheduler:247 + outboxWorker:501,607 → Plan 03.
- PWA picker, no new package, Intl-based → Plan 04.
- No new npm installs (Package Legitimacy Audit: zero) → honored across all plans; T-18-SC = no-op.

### CONTEXT (D-01..D-07 — the trackable decision contract)

| Decision | Covered by | Where |
|----------|-----------|-------|
| D-01 single household app_config key `household_timezone` | Plan 01 (key literal), Plan 02 (upsert) | accessor + endpoints |
| D-02 browser-detect + seed at first run | Plan 02 (seed endpoint), Plan 04 (detected-zone affordance) | endpoints + UI |
| D-03 no auto-overwrite after seed | Plan 02 (seed writes only if unset) | seed endpoint |
| D-04 change via role-gated /admin Settings | Plan 02 (requireAdmin endpoints), Plan 04 (Timezone section) | endpoints + UI |
| D-05 stored TZ = source of truth, single accessor at both scheduler sites | Plan 01 (accessor), Plan 03 (both sites route through it) | accessor + broker |
| D-06 fallback chain when unset | Plan 01 (verbatim fallback), Plan 03 (existing tests stay green) | accessor + broker |
| D-07 do not touch display/timed-write path | Plan 03 + Plan 04 (boundary as acceptance criterion) | broker + UI |

**No unplanned items.** Deferred Ideas (per-member timezones; driving display/timed off stored TZ) are
correctly absent from all plans.
