# Flow — cross-device handoff (iPhone ⇄ laptop / web) ## Goal Quinn does most of her work on iPhone (chat-driven), but bulk operations and calendar work feel better on a laptop. The handoff between **CocotteAI on iOS** and the **web FEs on `ai.cocotte.maison`** (brief G) must feel like one continuous session, not two apps to keep in sync. ## Constraints - Same auth on both surfaces (SSO device-link → shared session). - platform.api is the single source of truth; both clients read from it. - Cache invalidation arrives via Redis pub/sub on vps-0 → SSE / WebSocket to clients (brief G open question; resolve here as: WebSocket from vps-0 cache layer). - No conflict-resolution UI for P0 — last-write-wins via platform.api row `updated_at`. Concurrent edits to the same resource are extremely unlikely (single Quinn). ## Three handoff modes ### Mode 1 — Pick-up-where-you-left-off Quinn is editing a caption draft on iPhone; her phone dies / she switches to laptop. - Web companion at `ai.cocotte.maison` opens → ai-copilot greets her. - A subtle banner at the top of chat: "Picking up — last edit on iPhone 2 min ago: caption for OF post #abc..." - Tap → opens the same edit drawer the iPhone was showing. - Pending mutations from iPhone (queued in SyncEngine if offline) reconcile in the background; if any failed, a notification surfaces in chat. ### Mode 2 — Sidekick-on-laptop Quinn is on iPhone but wants the bigger screen for a specific task (calendar review, asset library batch operation). - In iPhone chat, she types: "**Open the calendar on my laptop.**" - ai-copilot returns a card: "Calendar opened on Desktop · transquinnftw-MBP." - (Detected by: device-link list shows active web sessions.) - The card on iPhone shows what the laptop is doing in real time (current month, scroll position). - Laptop opens to the calendar drawer directly (deeplink). - Mutations on either device propagate via cache.invalidate → both UIs update. ### Mode 3 — Notification chase Push lands on iPhone; Quinn is mid-something on laptop and doesn't want to switch. - iOS notification arrives normally (brief C / notification-rich-preview). - Quinn presses ⌘+T on laptop (or clicks the notification badge in `ai.cocotte.maison`) → the same approval card appears in the web companion's chat. - Action taken on laptop → push notification on iPhone auto-clears. - Audit row records *which device* approved (in `outcome_json.device_id`). ## State sync model - **Authoritative state**: platform.api on black. Both clients always defer to it. - **Optimistic state**: each client's local cache. Mutations are committed locally + queued for platform.api. - **Sync channel**: WebSocket from vps-0 (subscribing to Redis cache.invalidate); each connected client gets push notifications of every change. iOS uses APNs for backgrounded delivery; web uses native WebSocket while tab is open. - **Reconciliation**: on reconnect, client fetches `updated_at > last_seen` deltas from platform.api. - **Conflict policy** (rare): last-write-wins by `updated_at`. If a mutation's `if-match` fails, show a non-blocking toast "Refreshed from server — your version overwrote a newer one. View diff?" — tapping opens diff card. ## Visible affordances ### On iPhone chat - **"Send to laptop"** action on any drawer (sheet header overflow menu). Opens the same drawer on the laptop session. - **Active sessions indicator** in settings: which devices are currently connected, last seen, "sign out everywhere else" affordance. - **Banner** when iPhone resumes from background and detects activity from another device. ### On web companion - **"Send to phone"** action on any drawer — pushes a notification to iPhone with deeplink. - **Device list dropdown** in the top-right (mirrors iOS settings session list). ## States to design - Pick-up banner (iPhone → web). - Pick-up banner dismissable / persistent. - Sidekick-on-laptop card (iPhone view of laptop activity). - Notification cleared cross-device confirmation (subtle toast on iPhone after web takes action). - Conflict toast + diff card. - Active sessions list (settings). - Offline-on-one-device-online-on-other state (e.g. iPhone offline, laptop edits) — laptop edits succeed, iPhone catches up on reconnect. ## Out of scope - iPad as a third active device (P5+). - Concurrent collaboration with another operator (still single-Quinn P0). - macOS Catalyst (the web companion is the laptop story for P0). ## Open questions - WebSocket vs Server-Sent Events from vps-0? WebSocket is bidirectional but heavier; SSE is one-way and fine for cache invalidation but needs a second channel for client→server pings. - "Send to laptop" — should it always open the *same* drawer, or open at the parent feature (e.g. calendar root, not specific tour edit)? - Audit log device attribution — show always or only on demand?