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.
7.7 KiB
Phase 4: Shared Lists + Live Sync - 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-07 Phase: 4-Shared Lists + Live Sync Areas discussed: List & item behavior, Live feel & conflicts, Reconnect catch-up, Reordering behavior, Lists navigation
List & item behavior — Sharing
| Option | Description | Selected |
|---|---|---|
| All lists shared | Every list visible+editable by both; no permission model | |
| Shared + private | Lists can be private; owner/visibility model + permission checks | ✓ (refined below) |
User's choice: Shared + private — initially "default private, share to specific userdb members, optionally anonymous via unique URL."
Notes: Claude challenged three points: (1) anonymous URL = scope creep + unauthenticated security surface → deferred; (2) recipient picker = YAGNI for two members → boolean shared/private UI but list_shares join table underneath; (3) default-private fights the collaborative grocery use case → recommend default-shared. User accepted re-framing.
List & item behavior — Privacy (refined)
| Option | Description | Selected |
|---|---|---|
| Default Shared, toggle to Private | Family-hub default; flip individual list private; boolean model | ✓ |
| Default Private, toggle to Shared | Owner-only default; explicit share step | |
| All shared, no private | Drop private entirely |
User's choice: Default Shared, toggle to Private.
Notes: Anonymous URL → Defer it. Data model → list_shares join table, with the explicit instruction: "remember this project is intended to expand to other family members in the future... similar to treating fastmail like a generic provider helps set that framework." Captured as project-level principle D-18.
List & item behavior — Checked items & delete guard
| Option | Description | Selected |
|---|---|---|
| Strikethrough in place | Item stays, struck-through | |
| Sink to bottom | Checked move to completed section | ✓ |
| Disappear immediately | Removed from view | |
| Confirm list delete only | Dialog for lists; items delete instantly | ✓ |
| Confirm both | Dialog for lists and items | |
| No confirmation | Everything instant |
User's choice: Sink to bottom; confirm list-delete only. Notes: Private lists still live-sync across the owner's own devices (owner-only visibility, multi-device sync).
Live feel & conflicts
| Option | Description | Selected |
|---|---|---|
| Optimistic (instant local, reconcile) | Snappy; rollback on failure | ✓ |
| Server-confirmed | Wait for round-trip | |
| Last-write-wins | Later write wins per row | |
| Field-level merge | Merge non-conflicting fields | ✓ (bounded) |
| Delete wins | Deletion final, edit dropped | ✓ |
| Edit resurrects | Edit re-creates deleted item |
User's choice: Optimistic UI; field-level merge; delete wins. Notes: Claude bounded "field-level merge" to per-field PATCH + per-field last-write-wins (no CRDT) to prevent over-engineering. User context implied agreement (momentum).
Reconnect catch-up
| Option | Description | Selected |
|---|---|---|
| Full refetch on reconnect | React Query invalidate+refetch | ✓ |
| Last-Event-ID replay | Server replays missed events | |
| Hybrid | Replay, refetch fallback | |
| Silent + auto-recover | EventSource native reconnect, no UI | ✓ (refined) |
| Subtle indicator when offline | Show reconnecting hint | |
| Polling fallback (refetchInterval) | Periodic refetch if SSE drops | ✓ |
| SSE only, fix the proxy | Commit to SSE, no fallback |
User's choice: Full refetch; silent auto-recover with capped backoff then a "pause updates" indicator; polling fallback. Notes: User specified "set back off and then display an indicator to pause more updates." Claude flagged that raw EventSource has no backoff control → needs a manual reconnect wrapper or SSE client lib.
Reordering behavior
| Option | Description | Selected |
|---|---|---|
| Fractional rank | One-row write per move | ✓ (string-based) |
| Integer position + renumber | Many-row writes per move | |
| Animate to new order | Smooth remote reorder | ✓ |
| Update on next interaction | No animation | |
| Last-write-wins, brief settle | Optimistic, converge ~1s | ✓ |
| Lock during drag | Broadcast drag state |
User's choice: Fractional rank; animate to new order; last-write-wins settle.
Notes: Claude steered fractional rank to a string-based fractional index (e.g. fractional-indexing) rather than raw floats to avoid precision exhaustion on repeated mid-point inserts.
Lists navigation
| Option | Description | Selected |
|---|---|---|
| Bottom tab bar | Persistent Calendar | Lists tabs | ✓ |
| Hamburger drawer | Slide-out menu | |
| Top segmented control | Calendar/Lists toggle at top | |
| Add a router (real URLs) | react-router; deep-linkable lists | ✓ |
| Zustand view toggle (no router) | UI-state flag, no URLs |
User's choice: Bottom tab bar + react-router (real URLs).
Notes: No router installed today (App.tsx renders CalendarShell directly). Real URLs justified by Phase 5 push deep-linking to specific lists.
Claude's Discretion
- Fan-out mechanism (in-memory EventEmitter vs Redis pub/sub) — single Node process today; in-memory is YAGNI default; planner must address explicitly vs the N-member future.
- Position-rank column datatype, SSE auth/middleware wiring, React Query cache-key structure.
Deferred Ideas
- Anonymous list sharing via unique public URL (unauthenticated, bypasses Authelia) — own phase.
- Granular per-recipient sharing UI (member picker) —
list_sharesmodel supports it; relevant at 3+ members. - List metadata (icons, per-list colors, max items) — not required; standard approaches fine.