Clean successor to V3 (forge: lilith/atlilith). Seeded from local Mac working tree at ~/Code/@projects/@cocottetech/. node_modules and build artifacts excluded via .gitignore. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
76 lines
4.8 KiB
Markdown
76 lines
4.8 KiB
Markdown
# 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?
|