Files
familysync/.planning/milestones/v1.0-phases/06-ux-polish/06-CONTEXT.md
T
Lucas Berger 982438dc10 style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125
insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc
(singleQuote:true, semi:true, tabWidth:2, trailingComma:all,
printWidth:100). Isolated per D-13-08 for reviewability.
2026-06-11 20:35:18 -04:00

12 KiB
Raw Blame History

Phase 6: UX Polish - Context

Gathered: 2026-06-10 Status: Ready for planning

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

## 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 " (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.

<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.tsfetchMe redirect:'manual', opaqueredirect/401 detection (:3844); centralization point for D-11
  • apps/pwa/src/lib/loginRedirect.tsmaybeRedirectToLogin() / 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>

## 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).
## 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).

Phase: 6-ux-polish Context gathered: 2026-06-10