12 KiB
12 KiB
Phase 6: UX Polish - Context
Gathered: 2026-06-10 Status: Ready for planning
## Phase BoundarySmooth 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.
## Implementation DecisionsScope (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: exclusiveDTEND→ 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 " (RRULE
UNTIL) — matches the misuse pattern most directly. "for N occurrences" (RRULECOUNT) 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 thefrontend-designskill 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 spinis defined locally insidePushPermissionPrompt.tsx(:359) rather than globally — soSyncStateToast(:158) andLiveSyncIndicatorlikely don't animate when that component isn't mounted. Hoist@keyframes spinto 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 inapps/pwa/src/api/client.ts(typedSessionExpiredError, consistentredirect:'manual') with a single TanStack Query/Mutation error handler re-armingmaybeRedirectToLogin(). 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-phaseagainst 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.
<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;:199exclusive→inclusive all-day fix (D-05);:303current all-day toggle clamp;RecurrencePresetusageapps/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→exclusiveDTENDon 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— usesanimation: 'spin …'(D-13)apps/pwa/src/components/LiveSyncIndicator.tsx— sync-state indicator (D-13)apps/pwa/src/components/PushPermissionPrompt.tsx:359— where@keyframes spinis currently (locally) defined; needs hoisting to global (D-13)frontend-designskill — to be invoked by/gsd-ui-phasefor 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—fetchMeredirect:'manual', opaqueredirect/401 detection (:38–44); centralization point for D-11apps/pwa/src/lib/loginRedirect.ts—maybeRedirectToLogin()/ one-shotfamilysync.loginRedirectAttemptedguard 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 detectsopaqueredirect/401 forfetchMe; the typed error + single error-handler pattern (D-11) generalizes the existing one-shot detection.loginRedirect.tsmaybeRedirectToLogin(): 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.tsWR-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 (
EventFormUNTIL/COUNT control) → API write (vevent.tsRRULE 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>
## 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).
- 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).
Phase: 6-ux-polish Context gathered: 2026-06-10