Files
familysync/.planning/milestones/v1.0-phases/04-shared-lists-live-sync/04-DISCUSSION-LOG.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

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_shares model supports it; relevant at 3+ members.
  • List metadata (icons, per-list colors, max items) — not required; standard approaches fine.