docs(06): capture phase context

This commit is contained in:
Lucas Berger
2026-06-10 09:33:36 -04:00
parent 497daf6add
commit afdc8d124d
2 changed files with 218 additions and 0 deletions
+133
View File
@@ -0,0 +1,133 @@
# Phase 6: UX Polish - Context
**Gathered:** 2026-06-10
**Status:** Ready for planning
<domain>
## Phase Boundary
Smooth the rough edges surfaced during Phase 3 Gate 2 live use so the app feels slick for the non-technical Apple member (hard UX constraint). **Polish only — no new capabilities.**
Scope is the six backlog polish items promoted into this phase:
| Item | What it fixes | Success criterion |
|---|---|---|
| 999.6 | All-day events visually distinct from timed events | #1 |
| 999.7 | Event end auto-tracks start (any event); all-day-edit off-by-one | #2 |
| 999.8 | Recurrence can be bounded (repeat-until / count); FREQ-persistence check | #2 |
| 999.9 | A recurring series can be edited whole | #3 |
| 999.3 | Session expiry redirects cleanly to sign-in instead of hanging | #4 |
| 999.2 | No calendar/"sign-in required" flash before Authelia on cold load | #5 |
**Explicitly OUT of this phase** (promoted to milestone 1.1, see Deferred): 999.4 (reminder/VALARM selector) and 999.5 (first-login provider setup). The roadmap flagged both as "more feature than polish"; the user confirmed they belong in the next milestone, not here.
</domain>
<decisions>
## Implementation Decisions
### Scope (the backlog pull)
- **D-01:** Phase 6 = the six polish items only (999.2/3/6/7/8/9). Do NOT pull in 999.4 or 999.5.
- **D-02:** 999.4 (reminders/VALARM) and 999.5 (provider setup) move to a new **milestone 1.1**, started via the proper GSD milestone flow (`/gsd-new-milestone`). 999.1 (provider-abstraction) also remains backlog/1.1 candidate.
### Event-form end-tracking (999.7) — applies to ALL events, not just recurring
- **D-03:** This is a general bug: when the user moves the **start** date/time forward, the **end** selector does not follow — for one-time, single-day, and timed events alike. Today an end left behind the start produces a stale/absurd span.
- **D-04:** On any start change, **preserve the current duration** — timed: keep the start→end delta; all-day: keep the day-span — so the end moves with the start automatically. **Floor requirement** (the user's explicit minimum): the end must never strand behind the start day; at worst it snaps to the same day as the new start, and the user extends forward from there for genuine multi-day events.
- **D-05:** The all-day-edit off-by-one part of 999.7 is **already fixed** (commit `f645644`, CR-03: exclusive `DTEND` → inclusive on edit pre-fill, `EventForm.tsx:199`). Verify it still holds; don't re-implement. Remaining 999.7 work = the start→end auto-advance.
### Recurrence bounding (999.8)
- **D-06:** Add a bound to the recurrence control so users stop misusing the event end-date as "repeat until." Primary control: **"repeat until <date>"** (RRULE `UNTIL`) — matches the misuse pattern most directly. **"for N occurrences"** (RRULE `COUNT`) is acceptable to ship alongside or as the alternative; exact control set is a planning/UI-phase call. Each occurrence's duration must stay tied to start→end, NOT the recurrence span.
- **D-07:** Verify/fix the FREQ-persistence bug noted in 999.8 (a daily selection reportedly persisted as weekly). Confirm the dropdown writes the selected `FREQ`.
### Recurring-series edit (999.9)
- **D-08:** Behavior is **whole-series edit**: editing a recurring occurrence edits the master VEVENT (title / time / RRULE) for all occurrences. Per-occurrence (`RECURRENCE-ID`) and "this and following" edits stay **deferred to v1.x** (carried from Phase 3 D-03 / Deferred Items — not reopened here).
- **D-09:** The confirmation/prompt UX for "this changes the whole series" is **delegated to `/gsd-ui-phase`** — lock the behavior, defer the wording/placement.
### Visual feel & sync feedback (user-added, 2026-06-10)
- **D-12:** Beyond fixing individual rough edges, the user wants the app to **feel more distinct and modern** as a whole. The visual refresh is delegated to `/gsd-ui-phase`, which **should invoke the `frontend-design` skill** to drive a polished, non-generic aesthetic across the touched surfaces. Scope note: this widens "polish the rough edges" toward a light visual refresh — keep it to elevating the existing surfaces (calendar, event form, lists, sync feedback, auth splash), not a ground-up redesign; a full redesign would be its own phase. The user owns this call.
- **D-13:** **Sync indicators must actually spin.** Today the spinner styles reference `animation: spin …` but the `@keyframes spin` is defined locally inside `PushPermissionPrompt.tsx` (`:359`) rather than globally — so `SyncStateToast` (`:158`) and `LiveSyncIndicator` likely don't animate when that component isn't mounted. Hoist `@keyframes spin` to a global stylesheet (or per-component) so every sync indicator animates. Planner/researcher to confirm the exact failure.
### Auth-flow polish (999.2 + 999.3)
- **D-10:** 999.2 — gate the app render on auth state so no calendar shell / skeleton / "Sign-in required" alert paints before Authelia. While unauthenticated and redirecting, show a single neutral full-screen "Signing you in…" splash. Reserve the "Sign-in required" dead-end only for the one-shot-guard fall-through. Root cause + proposed fix are in ROADMAP 999.2 (CalendarShell optimistic render).
- **D-11:** 999.3 — detect session expiry (401 / opaqueredirect) from ANY query or mutation (not just the initial `/api/me`) and drive a clear re-auth via top-level navigation to `/api/login`, ideally behind a brief "Your session expired — signing you back in…" interstitial. Centralize detection in `apps/pwa/src/api/client.ts` (typed `SessionExpiredError`, consistent `redirect:'manual'`) with a single TanStack Query/Mutation error handler re-arming `maybeRedirectToLogin()`. In-flight-write preservation/replay is a *nice-to-have*, not a hard requirement — acceptable to surface a clear re-auth rather than silently losing a write; planner to decide effort.
### Claude's Discretion / delegated to UI-phase
- **All-day visual treatment (999.6):** behavior locked (must be distinguishable at a glance); the concrete treatment — full-width pill/bar vs background band vs distinct shape — is **delegated to `/gsd-ui-phase`** against the design system.
- **Series-edit prompt UX (999.9):** delegated to `/gsd-ui-phase` (see D-09).
- **Splash/interstitial copy (999.2/999.3):** exact wording open; "Signing you in…" / "Your session expired — signing you back in…" are starting points.
</decisions>
<canonical_refs>
## Canonical References
**Downstream agents MUST read these before planning or implementing.**
### Phase scope & backlog source
- `.planning/ROADMAP.md` §"Phase 6: UX Polish" — goal + 5 success criteria (the locked WHAT)
- `.planning/ROADMAP.md` §"Backlog" 999.2/999.3/999.6/999.7/999.8/999.9 — each item carries a diagnosed root cause (from Phase 3 Gate 2, 2026-06-07) and a proposed fix. These are the authoritative problem statements.
- `.planning/ROADMAP.md` §"Backlog" 999.1/999.4/999.5 — deferred to milestone 1.1 (do NOT implement here)
- `.planning/PROJECT.md` — hard UX constraint (wife adoption), recurring "create + display only" v1 stance
### Event form / write-back (999.6/7/8/9)
- `apps/pwa/src/components/EventForm.tsx` — start/end/all-day/recurrence state; `:199` exclusive→inclusive all-day fix (D-05); `:303` current all-day toggle clamp; `RecurrencePreset` usage
- `apps/pwa/src/api/client.ts` §`RecurrencePreset` (`:130`) — frequency-only today; UNTIL/COUNT to be added (D-06)
- `apps/api/src/broker/vevent.ts` — WR-04 inclusive→exclusive `DTEND` on write-back (symmetry partner for D-05)
- `apps/api/src/broker/expand.ts` — recurrence expansion (per-occurrence duration must follow start→end, D-06)
- `.planning/phases/03-event-write-back-pwa-install/03-CONTEXT.md` — D-03 recurrence create+display-only, WR-01..04 write-back contract, per-occurrence/"this-and-following" deferral (carried into D-08)
### Visual feel & sync indicators (D-12/D-13)
- `apps/pwa/src/components/SyncStateToast.tsx` `:158` — uses `animation: 'spin …'` (D-13)
- `apps/pwa/src/components/LiveSyncIndicator.tsx` — sync-state indicator (D-13)
- `apps/pwa/src/components/PushPermissionPrompt.tsx` `:359` — where `@keyframes spin` is currently (locally) defined; needs hoisting to global (D-13)
- `frontend-design` skill — to be invoked by `/gsd-ui-phase` for the modern/distinct visual refresh (D-12)
### Auth flow (999.2/999.3)
- `apps/pwa/src/components/CalendarShell.tsx` — optimistic render that causes the flash (999.2 root cause, D-10)
- `apps/pwa/src/api/client.ts``fetchMe` `redirect:'manual'`, opaqueredirect/401 detection (`:3844`); centralization point for D-11
- `apps/pwa/src/lib/loginRedirect.ts``maybeRedirectToLogin()` / one-shot `familysync.loginRedirectAttempted` guard to be re-armed (D-11)
</canonical_refs>
<code_context>
## Existing Code Insights
### Reusable Assets
- `EventForm.tsx`: already has start/end/all-day/recurrence state and the inclusive/exclusive all-day conversion helper (`exclusiveEndToInclusiveDate`). Extend it for end-tracking (D-04) and the recurrence bound control (D-06) rather than rebuilding.
- `client.ts`: already detects `opaqueredirect`/401 for `fetchMe`; the typed error + single error-handler pattern (D-11) generalizes the existing one-shot detection.
- `loginRedirect.ts` `maybeRedirectToLogin()`: reuse for the splash/redirect on both 999.2 and 999.3.
### Established Patterns
- Write-back keeps an inclusive(form)↔exclusive(`DTEND`) convention split across PWA (`EventForm.tsx`) and API (`vevent.ts` WR-04). Any end-tracking change (D-04) must preserve this; all-day day-span is inclusive in the form.
- Recurrence is "create + display only" in v1 (Phase 3 D-03). 999.9 adds whole-series edit on top; do NOT add per-occurrence edit.
- Auth redirect is intentionally `redirect:'manual'` + document navigation (XHR can't follow cross-origin IdP redirects); keep that mechanism for D-10/D-11.
### Integration Points
- 999.8 spans PWA (`EventForm` UNTIL/COUNT control) → API write (`vevent.ts` RRULE serialization) → expansion (`expand.ts`). Per-occurrence duration must derive from start→end, independent of the recurrence span.
- 999.2/999.3 both center on `client.ts` + `CalendarShell.tsx` + `loginRedirect.ts` — a single auth-gating refactor likely serves both; plan them together.
</code_context>
<specifics>
## Specific Ideas
- The motivating bug for 999.7/999.8: a "recurring event" created start 2026-06-11, end 2026-08-13 (~2-month span) with a weekly RRULE rendered as overlapping bars across the calendar (looked duplicated). The end didn't track the start AND the series was unbounded — two distinct fixes (D-04 + D-06).
- User's framing of 999.7 (verbatim intent): end-not-tracking-start is "not just for recurrence events — it's for any event… at the very least the end selector should move to the same day as the start time and the user can move it further forward for multi-day events from there."
- Splash copy starting points: "Signing you in…" (999.2 cold load), "Your session expired — signing you back in…" (999.3 mid-use).
</specifics>
<deferred>
## Deferred Ideas
- **999.4 — Event reminder/VALARM options** → milestone 1.1. The reminder-input half of the now-shipped Phase 5 push system; a real write-back feature, not polish.
- **999.5 — First-login provider setup (Fastmail app password)** → milestone 1.1. Multi-member onboarding; security + UX surface, not polish.
- **999.1 — Calendar provider abstraction** → backlog / milestone 1.1 candidate.
- **Per-occurrence (`RECURRENCE-ID`) and "this and following" recurring edits** → v1.x (carried from Phase 3; 999.9 delivers whole-series only).
</deferred>
---
*Phase: 6-ux-polish*
*Context gathered: 2026-06-10*
@@ -0,0 +1,85 @@
# Phase 6: UX Polish - Discussion Log
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
**Date:** 2026-06-10
**Phase:** 06-ux-polish
**Areas discussed:** Backlog scope (999.4/999.5), All-day visual, Event-form behavior (999.7/999.8), Recurring-series edit (999.9)
---
## Backlog scope — pull in 999.4 / 999.5?
| Option | Description | Selected |
|--------|-------------|----------|
| Neither — keep 6-item polish scope | Phase 6 stays 999.2/3/6/7/8/9; 999.4/999.5 stay backlog | ✓ |
| Pull in 999.4 (reminders) | Add VALARM selector | |
| Pull in 999.5 (provider setup) | Add first-login app-password onboarding | |
**User's choice:** Keep Phase 6 as polish; move 999.4 and 999.5 to a new milestone 1.1 and start it via the proper GSD milestone flow.
**Notes:** User wants the deferred items parked under milestone 1.1 explicitly, kicked off with `/gsd-new-milestone`. Sequencing (start 1.1 before vs after v1.0 ships) raised as a follow-up.
---
## All-day visual treatment (999.6)
| Option | Description | Selected |
|--------|-------------|----------|
| Full-width pill/bar | Google/Apple-style all-day row bar | |
| Background band / tint | Subtle full-day band | |
| Distinct shape/border | Border/icon marker only | |
| Let UI-phase decide | Capture intent, defer treatment to /gsd-ui-phase | ✓ |
**User's choice:** Let UI-phase decide.
**Notes:** Intent locked (must be distinguishable at a glance); concrete treatment delegated to /gsd-ui-phase.
---
## Event-form behavior (999.7 end-tracking + 999.8 recurrence bound)
| Option | Description | Selected |
|--------|-------------|----------|
| Preserve duration on start change | End auto-advances to keep duration | ✓ |
| Bound by "repeat until <date>" | RRULE UNTIL | ✓ (primary) |
| Bound by "for N occurrences" | RRULE COUNT | (acceptable alongside/alt) |
**User's choice:** Preserve duration; recurrence bound in scope (UNTIL primary, COUNT acceptable).
**Notes:** IMPORTANT correction — end-not-tracking-start is NOT recurrence-specific; it affects every event (one-time, single-day, timed). Floor requirement: end must at least snap to the same day as the new start; user extends forward for multi-day. Off-by-one portion of 999.7 already fixed (commit f645644).
---
## Recurring-series edit (999.9)
| Option | Description | Selected |
|--------|-------------|----------|
| Whole series, with confirm | Edit master VEVENT after a confirmation | |
| Whole series, no extra confirm | Edit series with helper note only | |
| Let UI-phase decide the prompt UX | Lock whole-series behavior, defer prompt UX | ✓ |
**User's choice:** Let UI-phase decide the prompt UX.
**Notes:** Behavior locked (whole-series edit of master VEVENT). Per-occurrence / "this and following" stay deferred to v1.x. Confirmation/prompt wording delegated to /gsd-ui-phase.
---
## Visual feel & sync feedback (user-added, post-questions)
**User's input (verbatim intent):** "For the UI polish, I want the whole thing to feel more distinct and modern. You can invoke the claude frontend-design skill for this to make it feel better. I want the sync indicators to actually spin too."
- Modern/distinct visual refresh → /gsd-ui-phase to invoke the `frontend-design` skill (D-12). Flagged as widening "polish" toward a light refresh; kept to elevating existing surfaces, not a ground-up redesign.
- Sync indicators must actually spin (D-13) — likely root cause: `@keyframes spin` defined locally in `PushPermissionPrompt.tsx`, not global, so `SyncStateToast` / `LiveSyncIndicator` don't animate.
## Claude's Discretion
- All-day visual treatment (999.6) — delegated to /gsd-ui-phase
- Series-edit confirmation/prompt UX (999.9) — delegated to /gsd-ui-phase
- Splash/interstitial copy (999.2/999.3) — open; starting points provided
- Recurrence bound exact control set (UNTIL only vs +COUNT) — planning/UI call
- In-flight-write preservation on session expiry (999.3) — nice-to-have, planner decides effort
## Deferred Ideas
- 999.4 (reminders/VALARM) → milestone 1.1
- 999.5 (first-login provider setup) → milestone 1.1
- 999.1 (provider abstraction) → backlog / 1.1 candidate
- Per-occurrence & "this and following" recurring edits → v1.x