--- phase: 06-ux-polish plan: "01" subsystem: pwa/lib tags: [tdd, date-math, event-form, d-04] dependency_graph: requires: [] provides: - computeNewTimedEnd (apps/pwa/src/lib/eventDateTime.ts) - computeNewAllDayEnd (apps/pwa/src/lib/eventDateTime.ts) affects: - Plan 06-06 (EventForm wires these helpers into onChange handlers) tech_stack: added: [] patterns: - Local Date accessors (WR-05): getFullYear/getMonth/getDate/getHours/getMinutes — never toISOString().slice - TDD RED→GREEN with Vitest in apps/pwa key_files: created: [] modified: - apps/pwa/src/lib/eventDateTime.ts - apps/pwa/src/lib/eventDateTime.test.ts decisions: - WR-05 enforced: all new date formatting uses local accessors; toISOString().slice banned for date strings - Private helpers (localDateISO, localTimeHHMM, dateDiffDays, addDaysISO) added to eventDateTime.ts to support the two exports metrics: duration_minutes: 2 completed_date: "2026-06-10" tasks_completed: 2 files_changed: 2 --- # Phase 06 Plan 01: End-Tracking Duration Math (D-04) Summary **One-liner:** Pure duration-preservation helpers (`computeNewTimedEnd` + `computeNewAllDayEnd`) with 1h/same-day floor rules, TDD RED→GREEN in eventDateTime.ts. ## Tasks Completed | Task | Name | Commit | Files | |------|------|--------|-------| | 1 (RED) | Failing tests for computeNewTimedEnd / computeNewAllDayEnd | `16cdbf3` | eventDateTime.test.ts | | 2 (GREEN) | Implement the two end-tracking helpers | `605f543` | eventDateTime.ts | ## What Was Built Two exported pure functions added to `apps/pwa/src/lib/eventDateTime.ts`: - **`computeNewTimedEnd(newStartDate, newStartTime, oldStartDate, oldStartTime, oldEndDate, oldEndTime)`** — preserves a timed event's duration when the start moves. Returns `{ endDate: 'YYYY-MM-DD', endTime: 'HH:MM' }`. If the old end was already behind the old start (stale state), floors to newStart + 1 hour. - **`computeNewAllDayEnd(newStartDate, oldStartDate, oldEndDate)`** — preserves an all-day event's inclusive day-span when the start moves. Returns `'YYYY-MM-DD'`. If the old span was negative (stale state), floors to 0 days (same day as newStart). Four private helpers added to the same file: `localDateISO`, `localTimeHHMM`, `dateDiffDays`, `addDaysISO`. All use local Date accessors (WR-05 compliance). ## Test Coverage Six new unit tests in `apps/pwa/src/lib/eventDateTime.test.ts`: | Test | Behavior | |------|----------| | preserves a 1-hour timed delta | old 09:00→10:00; new start 11:00 → new end 12:00 | | preserves a multi-day timed delta (26h) | old 08:00→+26h; new start same offset → correct | | floors to 1h when old end was behind start | stale end → snaps to newStart+1h | | preserves a 0-day span (single day all-day) | oldStart=oldEnd → newEnd=newStart | | preserves a 3-day span | newEnd = newStart + 3 days | | floors to same day when old end behind start | negative span → 0 → same day | Full suite: **166/166 tests pass**. Pre-existing `serializeEventDateTime` / `localWallClockToUtcIso` tests unaffected. ## TDD Gate Compliance - RED commit (`test(06-01): ...`): `16cdbf3` — 6 tests failing with `TypeError: computeNewTimedEnd is not a function` - GREEN commit (`feat(06-01): ...`): `605f543` — all 166 tests pass - RED→GREEN order confirmed via `git log` ## Deviations from Plan None — plan executed exactly as written. ## Threat Flags None. Pure client-side date arithmetic on already-trusted local form state. No network boundary, no new attack surface. T-06-01 accepted per threat model. ## Self-Check: PASSED - `apps/pwa/src/lib/eventDateTime.ts` — FOUND (modified) - `apps/pwa/src/lib/eventDateTime.test.ts` — FOUND (modified) - Commit `16cdbf3` — FOUND (RED: test(06-01)) - Commit `605f543` — FOUND (GREEN: feat(06-01)) - `computeNewTimedEnd` export — FOUND in eventDateTime.ts - `computeNewAllDayEnd` export — FOUND in eventDateTime.ts - No `toISOString().slice` in helper code — CONFIRMED