docs(03): revise gap plans 03-10/11/12 per checker feedback
This commit is contained in:
@@ -10,11 +10,12 @@ requirements: [CAL-05, CAL-07, PWA-01, PWA-02]
|
||||
files_modified:
|
||||
- apps/pwa/src/components/EventForm.tsx
|
||||
- apps/pwa/src/components/EventForm.test.tsx
|
||||
- apps/pwa/src/store/calendarStore.ts
|
||||
must_haves:
|
||||
truths:
|
||||
- "Opening the form in edit mode populates Title/Start/End from the cached occurrence even when the form opens before the occurrence is resolved (no blank edit form)"
|
||||
- "Editing a recurring event preselects its existing recurrence preset instead of resetting to 'none'"
|
||||
- "The edit form shows the event's original date/time consistently (no UTC-date / local-time mismatch that shifts the day)"
|
||||
- "The edit form shows the event's original date/time consistently (no UTC-date / local-time mismatch that shifts the day), proven by a test that pins TZ so it cannot pass by coincidence on an EDT runner"
|
||||
- "Tab and Shift+Tab cycle focus within the open dialog and never reach background controls"
|
||||
- "The PWA install assets (icon-192/512, apple-touch-icon) exist so Add-to-Home-Screen installs with a real icon (PWA-01/PWA-02)"
|
||||
artifacts:
|
||||
@@ -56,47 +57,50 @@ Output: an EventForm that round-trips an existing event's fields and traps focus
|
||||
</context>
|
||||
|
||||
<artifacts_this_phase_produces>
|
||||
No new exported symbols. Internal changes to EventForm: reset effect deps gain
|
||||
`occurrence`, a recurrence-deriving initializer, a zone-consistent `parseDateTime`,
|
||||
and a real Tab/Shift+Tab focus-cycle handler. IN-03 (collapse the duplicate
|
||||
getDefaultStartDate/getDefaultEndDate to one helper) is folded in as a cheap adjacent cleanup.
|
||||
No new exported symbols beyond exporting the existing `todayIso` from calendarStore.ts
|
||||
(see IN-03 below — it is currently a private module function, NOT yet exported). Internal
|
||||
changes to EventForm: reset effect deps gain `occurrence`, a recurrence-deriving initializer,
|
||||
a zone-consistent `parseDateTime`, and a real Tab/Shift+Tab focus-cycle handler.
|
||||
</artifacts_this_phase_produces>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="tdd" tdd="true">
|
||||
<name>Task 1: RED+GREEN — edit-mode population, recurrence derivation, zone-consistent dates (WR-03, WR-05, IN-03)</name>
|
||||
<files>apps/pwa/src/components/EventForm.tsx, apps/pwa/src/components/EventForm.test.tsx</files>
|
||||
<files>apps/pwa/src/components/EventForm.tsx, apps/pwa/src/components/EventForm.test.tsx, apps/pwa/src/store/calendarStore.ts</files>
|
||||
<read_first>
|
||||
- apps/pwa/src/components/EventForm.tsx (occurrence IIFE lines 113-124; reset effect lines 167-181 with deps [eventFormOpen,eventFormMode,eventFormUid]; parseDateTime lines 84-101; getDefaultStartDate/EndDate lines 47-53)
|
||||
- apps/pwa/src/components/EventForm.tsx (occurrence IIFE lines 113-124; reset effect deps `[eventFormOpen,eventFormMode,eventFormUid]`; parseDateTime lines 84-101 — note it mixes `d.toISOString().slice(0,10)` (UTC date) with `d.getHours()/getMinutes()` (local time): THIS is the WR-05 bug; getDefaultStartDate/getDefaultEndDate lines 47-53)
|
||||
- apps/pwa/src/api/client.ts (CalendarOccurrence.start/end format note lines 69-73: 'YYYY-MM-DD' for allDay, ISO 8601 with IANA tz for timed)
|
||||
- apps/pwa/src/store/calendarStore.ts (setEventForm/eventFormMode/eventFormUid lines 43-44,162-163; a todayIso helper exists per IN-03)
|
||||
- apps/pwa/src/store/calendarStore.ts (todayIso at lines 121-124 is a PRIVATE module function — it is NOT currently exported; IN-03 requires adding `export` to it before EventForm can import it)
|
||||
- .planning/phases/03-event-write-back-pwa-install/03-REVIEW.md (WR-03, WR-05, IN-03)
|
||||
</read_first>
|
||||
<behavior>
|
||||
- RED (WR-03 blank): render EventForm in edit mode where the occurrence becomes available in the ['events'] cache AFTER the form opens; assert the Title input value equals the occurrence title (not empty). Fails today because the reset effect deps exclude `occurrence`.
|
||||
- RED (WR-03 recurrence): edit an occurrence whose recurrence is 'weekly'; assert the Repeat select value is 'weekly', not 'none'.
|
||||
- RED (WR-05 zone): a timed occurrence start of '2026-06-10T23:30:00-04:00[America/Toronto]' renders Start date '2026-06-10' and time '23:30' (the event's own wall time), not a UTC-shifted '2026-06-11'/'03:30'.
|
||||
- RED (WR-05 zone — DETERMINISTIC, TZ-pinned so it cannot pass by coincidence): pin the test runner timezone to UTC for this test file. Use the top-of-file `// @vitest-environment jsdom` already in place, and add `process.env.TZ = 'UTC'` in a `beforeAll` (set BEFORE any Date is constructed in the test) — OR, preferred, add `env: { TZ: 'UTC' }` to the pwa vitest config's `test` block so the runner zone is fixed for the whole suite. State which approach you used in a comment. With TZ pinned to UTC, feed a timed occurrence start of `'2026-06-10T23:30:00-04:00'` (i.e. UTC instant `2026-06-11T03:30:00Z`) and assert the rendered Start date and time equal the event's OWN wall-clock as derived by the fixed extraction rule (see <action>): the test must assert the exact strings the corrected `parseDateTime` produces for that input under TZ=UTC, and document why those values are correct regardless of the developer's machine zone. The point: the assertion is stable on a UTC CI runner AND would fail loudly if `parseDateTime` reverted to the toISOString/getHours mismatch.
|
||||
</behavior>
|
||||
<action>
|
||||
WR-03: add `occurrence` (or `occurrence?.uid` plus `occurrence?.start`) to the reset effect dependency
|
||||
array so the form re-initializes when the occurrence resolves after open. In the reset effect, derive
|
||||
the initial recurrence from the occurrence instead of always `setRecurrence('none')` — if the
|
||||
CalendarOccurrence carries a recurrence preset use it; if the occurrence shape does not expose one,
|
||||
add the preset to the occurrence/expand contract is OUT OF SCOPE — instead read it from the cached
|
||||
raw recurrence if present and default to 'none' only when genuinely absent (document the limitation in
|
||||
a comment citing WR-03). Guard against opening edit mode before the cache is populated: keep fields
|
||||
blank-safe but re-run on arrival.
|
||||
extending the occurrence/expand contract is OUT OF SCOPE — read it from the cached raw recurrence if
|
||||
present and default to 'none' only when genuinely absent (add a comment citing WR-03 documenting that
|
||||
occurrence edits whose recurrence is not present in the cache default to 'none' in v1). Guard against
|
||||
opening edit mode before the cache is populated: keep fields blank-safe but re-run on arrival.
|
||||
|
||||
WR-05: rewrite `parseDateTime` so date and time are derived in ONE consistent frame. For a timed ISO
|
||||
with an offset/IANA suffix, compute the local wall-clock components with `getFullYear/getMonth/getDate/
|
||||
getHours/getMinutes` together (or reuse the Temporal-based conversion the calendar render path already
|
||||
uses) — never mix `toISOString().slice(0,10)` (UTC date) with `getHours()` (local time). The all-day
|
||||
`^\d{4}-\d{2}-\d{2}$` branch is unchanged.
|
||||
WR-05 (the owning fix): rewrite `parseDateTime` so date and time are derived in ONE consistent frame.
|
||||
For a timed ISO with an offset/IANA suffix, build the JS Date, then extract BOTH the date and time from
|
||||
the SAME accessor family — use local accessors together (`getFullYear/getMonth/getDate/getHours/getMinutes`,
|
||||
zero-padded) so the date string and the time string describe the same wall clock. NEVER mix
|
||||
`toISOString().slice(0,10)` (UTC date) with `getHours()` (local time). Because the WR-05 test pins TZ=UTC,
|
||||
"local" == UTC in the test and the extracted wall clock is deterministic; in production the user's own
|
||||
zone yields their own wall clock consistently. The all-day `^\d{4}-\d{2}-\d{2}$` branch is unchanged.
|
||||
|
||||
IN-03: collapse `getDefaultStartDate`/`getDefaultEndDate` into one `todayIso()` helper (reuse the
|
||||
existing one in calendarStore.ts if exported); keep the separate '09:00'/'10:00' default times at the
|
||||
call sites.
|
||||
IN-03: export the existing `todayIso` from calendarStore.ts (add the `export` keyword to the function at
|
||||
lines 121-124 — it is currently private), then import it into EventForm and collapse
|
||||
`getDefaultStartDate`/`getDefaultEndDate` into calls to `todayIso()`; keep the separate '09:00'/'10:00'
|
||||
default times at the call sites. Do not duplicate the helper — there must be exactly one `todayIso`.
|
||||
|
||||
Commit RED then GREEN.
|
||||
</action>
|
||||
@@ -106,11 +110,13 @@ getDefaultStartDate/getDefaultEndDate to one helper) is folded in as a cheap adj
|
||||
<acceptance_criteria>
|
||||
- behavior: edit form Title is populated even when occurrence resolves after open.
|
||||
- behavior: editing a recurring event preselects its recurrence preset.
|
||||
- behavior: a timed occurrence renders its own wall-clock date and time (no UTC/local day shift).
|
||||
- behavior (deterministic): with the runner TZ pinned to UTC, a timed occurrence `'2026-06-10T23:30:00-04:00'` renders the wall-clock date/time the corrected parseDateTime yields under UTC, and the assertion is hard-coded to those exact strings (cannot pass by a coincidentally-EDT runner).
|
||||
- source: the reset effect dependency array in EventForm.tsx includes occurrence (grep for occurrence in the deps line).
|
||||
- source: `grep -c 'export function todayIso' apps/pwa/src/store/calendarStore.ts` returns 1 (todayIso is now exported; IN-03).
|
||||
- source: parseDateTime no longer mixes UTC and local accessors — `grep -c 'toISOString' apps/pwa/src/components/EventForm.tsx` does not appear inside parseDateTime's timed branch (verify by reading the function).
|
||||
- test-command: `cd apps/pwa && npx vitest run src/components/EventForm.test.tsx` passes.
|
||||
</acceptance_criteria>
|
||||
<done>Edit mode pre-populates correctly (fields, recurrence, correct zone); duplicate date helpers collapsed.</done>
|
||||
<done>Edit mode pre-populates correctly (fields, recurrence, correct zone proven by a TZ-pinned deterministic test); duplicate date helpers collapsed to one exported todayIso.</done>
|
||||
</task>
|
||||
|
||||
<task type="tdd" tdd="true">
|
||||
@@ -160,11 +166,12 @@ getDefaultStartDate/getDefaultEndDate to one helper) is folded in as a cheap adj
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
Edit mode pre-populates fields/recurrence in the correct zone, the dialog traps focus,
|
||||
and the PWA install assets are confirmed present. WR-03, WR-05, WR-07, IN-03, IN-04 closed;
|
||||
Edit mode pre-populates fields/recurrence in the correct zone (proven by a TZ-pinned deterministic test),
|
||||
the dialog traps focus, and the PWA install assets are confirmed present. WR-03, WR-05, WR-07, IN-03, IN-04 closed;
|
||||
PWA-01/PWA-02 verified.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/03-event-write-back-pwa-install/03-12-SUMMARY.md` when done.
|
||||
</output>
|
||||
</content>
|
||||
|
||||
Reference in New Issue
Block a user