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