---
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
