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>
9.2 KiB
9.2 KiB
notification-rich-preview.screen
Single-screen breakdown for the rich push notification Cocotte surfaces on the iOS lock screen, Notification Center, and banner. Before Quinn taps in, she should be able to act from the lock screen when stakes allow — and when she can't act, she should at least know what's waiting. Brief C established stakes-aware deeplinks (where tapping goes); this screen-doc covers the content of the notification itself plus the rich-content extension.
Constraints
- iOS rich notification content extension (
UNNotificationContentExtension), iOS 17+. - Notification payload limited to ~4 KB; rich media via category + attachment URL.
- At most 2 inline quick actions (
UNNotificationAction). - Must work without app launch (silent push update path) and with foreground delivery.
- Reuses
lilith-messenger-ios/Core/Push/PushNotificationHandler.swiftas the foundation. - Speaker in body copy is Cocotte (matches chat-home, daily-digest); iOS sender row shows the app bundle name.
Layout (lock-screen banner, default)
┌─────────────────────────────────────────────────┐
│ ▢ Cocotte now ⌃ │ 28pt — system header (sender + timestamp + expand)
│ ┃ Approve PPV pricing — $48 for new sub │ ┃ = stakes accent stripe (4pt left edge, F §F1)
│ ┃ │
│ ┃ content-onlyfans · conf 0.82 │ chip row: specialist + confidence
│ ┃ "Buyer profile matches paid-tier signals; │ body excerpt (≤ 90 chars, ellipsis after)
│ ┃ $48 is 1σ above your median…" │
│ ┃ │
│ ┃ [ Reject ] [ Approve ] │ 2 quick actions — primary right
└─────────────────────────────────────────────────┘
Long-press → expanded UNNotificationContentExtension view: same skeleton but body fully unfurled (no ellipsis), Why: line per approval-card pattern, both quick actions visible.
Components
| Component | Notes |
|---|---|
| Stakes accent stripe | 4pt left edge, color per F §F1 (low neutral, medium amber, high rose). Renders as solid bar; haptic on delivery for high. |
| System header | iOS-rendered — bundle name as sender, timestamp, expand chevron. Not customizable. |
| Title | One line, ≤ 50 chars. Plain register for failures, working for decisions, hearth for digests. No banned phrases per voice §V6. |
| Chip row | Specialist name (per F §F5) + confidence value (0.00–1.00). Single line; truncates from right. |
| Body excerpt | ≤ 90 chars on banner, full on expand. Falls back to deterministic summary if specialist's draft is missing. |
| Media attachment (optional) | Small avatar (prospect surface profile pic) or surface chip glyph; pre-fetched on push receipt with 2s timeout, falls back to no-avatar variant. |
| Quick actions | Max 2 UNNotificationAction buttons. authenticationRequired = true for high-stakes per N-Q1. |
States
- Low stakes — digest summary (hearth) — Title: "Last night with Cocotte"; body: "Bumped Tryst 6×, TS4Rent 6×. 3 OF DMs queued."; no quick actions; tap → daily-digest in chat. No stripe color, no haptic, system-default sound (or silent per S6).
- Medium stakes — engagement awaiting (working) — Title: "OF: likely PPV buyer"; body: triage's draft reply first ~90 chars; chips: triage · conf X; quick actions
Approve draft·Open in app; amber stripe; soft chime + light haptic. - High stakes — approval required (working dialed firm) — Title: specialist recommendation; body: reason + confidence; quick actions
Reject·Approve(Face ID gated per N-Q1); rose stripe; priority chime + heavy haptic; bypasses DND if S6 toggle is on. - Failure / needs-attention (plain) — Title: "Tryst stopped accepting bumps"; body: "Last successful: 04:14. 2 retries failed. Listing may be invisible."; quick actions
Retry now·Escalate to me; amber stripe + SF Symbols warning glyph; priority haptic. - Grouped (5+ from Cocotte in 1h) — iOS native grouping; group summary header reads "Cocotte · 7 updates"; most-recent expanded; tapping group routes to a filtered chat-home view (last 1h of receipts + cards).
- Compact (Notification Center) — title + first 50 chars of body; chips suppressed; no quick actions visible until long-press.
- Expanded (long-press) — full body, both quick actions,
Why:line, surface chip + stakes label. - Silent-push update — content extension re-renders an already-delivered notification (e.g. a card was resolved on another device); banner replaced with "✓ resolved on Mac" toast within the existing notification.
- Quick-action result toast — fires inside the extension (no app launch); shows ✓/✗ for 1.2s then dismisses; commits via background
URLSessionConfiguration.background.
Interactions / gestures
- Tap body → stakes-aware deeplink per brief C (high → focused approval modal; medium/low → chat scrolled to the card).
- Long-press → expanded content-extension view; both quick actions visible.
- Swipe left → Clear / View — system; "View" opens expanded.
- Swipe right (lock screen) — system unlock + deeplink.
- Quick action tap → fires
UNNotificationAction; ifauthenticationRequired, Face ID prompt first; commits via background URLSession; result toast inline. - Drag down on group → expand into per-notification list (system).
In-the-wild copy
- (hearth, low) "Last night with Cocotte" / "Bumped Tryst 6×, TS4Rent 6×. 3 OF DMs queued."
- (working, medium) "OF: likely PPV buyer" / "Subject opened your last 3 posts. Triage drafted a $24 nudge."
- (working, high) "Approve PPV pricing — $48 for new sub" / "Buyer matches paid-tier signals. $48 is 1σ above your median."
- (plain, failure) "Tryst stopped accepting bumps" / "Last successful: 04:14. 2 retries failed. Listing may be invisible."
- (grouped) "Cocotte · 7 updates"
Edge cases
- Media attachment fetch timeout (>2s on push receipt) — render no-avatar variant; surface chip glyph stands in.
- Card resolved on another device while notification still live — silent push triggers content-extension re-render to "✓ resolved on Mac" toast; quick actions disabled. Audit row links via
turn_idso the resolution is replayable per brief I. - High-stakes notification while device is in Cook-mode (chat-home state 7) — bypasses banner; spoken via existing voice path (no double-narration) per chat-home accessibility rule.
- Quick-action commit fails offline — queues via background URLSession; toast shows "Queued — will commit on reconnect." Aligns with brief M §M2c offline behavior.
- VoiceOver active — reading order: stakes label → title → chip row → body → quick actions last. Most-important-context-first per brief X.
- Reduced motion — suppress haptic stripe pulse; static color band.
- K3c-1 / K3f-2 / K3h — body excerpt is PII-scanned before delivery; govt name, hotel address, or channel-vs-surface leak triggers a fallback opaque title ("New approval waiting · open to view") rather than embedding the leaky string.
- Notification arrives during Vacation mode (brief H1) — low/medium suppressed; only high + failure pass through. Filtered at notifier per S6.
- DND override — only state 3 (high) and state 4 (failure) can request DND bypass; gated by S6 per-stakes toggle.
Related
- Brief C — stakes-aware deeplink semantics + per-channel routing.
- Brief F §F1, §F5 — stakes color band, surface chip iconography.
- Brief I —
turn_id+ audit rows for quick-action commits. - Brief M §M2c — offline commit queueing.
- Brief K §K3 — PII scan before delivery (K3c-1 / K3f-2 / K3h).
- Brief X — VoiceOver reading order + reduced motion.
- 00-system-voice §V2 — register copy + banned phrases.
- approval-card.screen — in-app destination for tapped high-stakes notifications.
- daily-digest.screen — in-app destination for tapped low-stakes notifications.
Out of scope
- Apple Watch complications / notifications (brief AB).
- Critical alerts API (requires Apple entitlement; not for P0).
- iPad / macOS notification variants (brief E + G).
Open questions
- NRP-Q1 Should approve/reject via quick action require Face ID for high-stakes? iOS supports
UNNotificationAction.Options.authenticationRequired. Lean: yes for high; no for medium.[blocking] - NRP-Q2 Avatar / media attachment latency — pre-fetch with notification, or fall back to no-avatar if attachment fails within 2s? Lean: 2s timeout + fallback (current spec).
[exploratory] - NRP-Q3 Grouping threshold — group at 5 / 10 / never? Lean: 5 in any rolling 1h window.
[nice-to-have]