fa1fe00c51
The /api/portal/appointments contract returns ISO `startTime`/`endTime`
and no `date`/`time` fields. `isUpcoming()` read `appt.date`/`appt.time`
and called `parseTimeTo24Hour(undefined)` → `undefined.split(' ')` →
TypeError. The throw was swallowed by the fetch `try/catch`, surfacing
"Failed to load appointments" and making "Book New" unreachable for
every signed-in customer.
- Add `getAppointmentStart()` helper: prefers ISO `startTime`, falls
back to legacy `date` + `time`, returns null on missing/unparseable
input so callers never throw.
- Rewrite `isUpcoming()` on top of the helper.
- Add `formatAppointmentDate()` / `formatAppointmentTime()` and use them
at all date/time display sites (list row + RescheduleFlow header).
- Guard `parseTimeTo24Hour(undefined)`.
- Mark `date`/`time` optional and add `startTime`/`endTime` to the
`Appointment` type to match the API contract.
- Tests: API-shape fixtures + regression guards (no throw on startTime
shape, undefined-safe parse, helper resolution/formatting).
- Update UAT_PLAYBOOK.md §5.12 (customer portal appointments).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>