cocottetech/@platform/codebase/@features/ai-copilot/docs/prospect-detail.screen.md

150 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Prospect detail · screen
Implementation breakdown of [brief B](./B-drawers.brief.md) §B3 — the cross-surface message-history drawer for one resolved `prospect_id`, reached from chat citations, the unified-inbox row tap, audit-row links, and global-search prospect hits.
## Layout (iPhone)
```
┌────────────────────────────────────────┐
│ ← @felix ⋮ share │ ← header: back, overflow, share-with-org (W)
├────────────────────────────────────────┤
│ @felix · OF + Tryst + SMS │ ← identity line (working register)
│ first DM Mar 12 · last contact 2h ago │
│ [ funnel: PPV ▾ ] value: $1,420 ytd │ ← funnel chip + value-to-date strip
├────────────────────────────────────────┤
│ Touchpoint chain · [ Time-decay ▾ ] │ ← attribution model picker (per T-attr-model)
│ ┌──────────────────────────────────┐ │
│ │ Tryst (Apr 8 14:02) │ │
│ │ ↓ 19h │ │
│ │ iMessage (Apr 9 09:14) │ │
│ │ ↓ 13h │ │
│ │ OF subscription (Apr 9 22:11) │ │
│ │ ↓ ongoing · 3 tips $47 │ │
│ │ │ │
│ │ Credit: Tryst 60% · X 0% · OF40%│ │
│ └──────────────────────────────────┘ │
├────────────────────────────────────────┤
│ ⚠ Coop intel · 2 peer reports │ ← coop callout (plain register)
│ Berlin Providers · UNSAFE │
│ [ open report ] │
├────────────────────────────────────────┤
│ Timeline · cross-surface │
│ ┌──────────────────────────────────┐ │
│ │ Tryst · 2h ago │ │
│ │ him: "thinking about Berlin..." │ │
│ │ Cocotte drafted: "Berlin Oct…" │ │
│ │ [ approve · edit · set aside ] │ │
│ ├──────────────────────────────────┤ │
│ │ OF DM · yesterday 9:14pm │ │
│ │ him: "subbing again" │ │
│ │ you: "welcome back —" · sent │ │
│ ├──────────────────────────────────┤ │
│ │ SMS · Mar 28 · tip $40 │ │
│ │ event · funnel: free → tip │ │
│ └──────────────────────────────────┘ │
├────────────────────────────────────────┤
│ Reply │
│ via: [ Tryst DM ▾ ] │
│ ┌──────────────────────────────────┐ │
│ │ Berlin Oct 37 — want a slot? │ │
│ └──────────────────────────────────┘ │
│ [ send ] [ ask Cocotte to draft ] │
└────────────────────────────────────────┘
```
## Component table
| Component | Source | Notes |
|---|---|---|
| `ProspectHeader` | new (B3) | Identity line + surfaces list + first/last-contact stamps. Working register. |
| `FunnelChip` | new (B3 + T2) | Stage selector: free / DM / tip / PPV / subscribe / booking. Tap opens stage history sheet. |
| `ValueToDateStrip` | new (B3 + T2) | YTD revenue attributed to this `prospect_id`. Reads `engagement_events` rollup. |
| `CoopIntelCallout` | shared (coop-intel-detail) | Inline summary if peer reports match this subject. Plain register. Tap opens [`coop-intel-detail.screen.md`](./coop-intel-detail.screen.md). |
| `CrossSurfaceTimelineRow` | new (B3) | One row per `engagement_events` entry: source chip (per F5d) + body + Cocotte draft inline if pending. |
| `InlineApprovalStrip` | shared (A, approval-card) | Same swipe + button trio: approve / edit / set aside. |
| `ReplyComposer` | new (B3 + P) | Channel picker (defaults to last-contact channel) + text + "ask Cocotte to draft" — routes through `mcp__quinn-prospector__draft_message`. |
| `ShareWithOrgButton` | new (W) | Cross-context handoff affordance; gated on org membership + share scope. |
| `UnresolvedIdentifierBanner` | new (B3 + P §prospect-resolver) | Renders when `prospect-resolver` has unmerged candidate identifiers for this prospect. |
| `TouchpointChain` | new (B3 + T §T-attribution) | Reads `prospect_touchpoints` for this `prospect_id`, oldest-first. Each touchpoint shows surface + kind + occurred_at; arrows show the elapsed time between consecutive touchpoints. Anonymous touchpoints never appear here (per [_engineering-surface-metrics.md](./_engineering-surface-metrics.md) §9 invariant). |
| `AttributionModelPicker` | shared (T §T-attribution-model) | Dropdown: First-touch / Last-touch / Time-decay (default) / Position-based. Same component used in T2 funnel `Top paths`. Persists to `users.attribution_model_pref`. |
| `AttributionCreditStrip` | new (B3 + T §T-attribution-model) | Per-surface credit weights for this prospect under the selected model. Shows `—` (unmeasured) for surfaces with tier-gated analytics off, never `0%` (which would falsely imply "no influence" rather than "we can't tell"). |
## States to render
1. **Typical** — known prospect, mixed-surface history, funnel-stage PPV, no coop reports.
2. **New prospect, no history** — single inbound event. Timeline shows one row + hearth-leaning empty hint above the composer: "First contact. Cocotte hasn't drafted yet — ask for a draft, or write your own."
3. **Funnel-stage paying** (PPV / subscribe / booking) — value strip foregrounded, accent-green; strategist insight chip appears: "Warm cohort — worth a follow-up before the heat fades."
4. **Funnel-stage cold** — value strip muted, hearth chip suggests "On the back burner. Want strategist to draft a re-engagement?" Reply composer still available; no auto-draft pre-filled.
5. **Peer-coop reports present** (per brief N) — `CoopIntelCallout` rendered above the timeline in plain register. Reply composer **soft-warns** but is not disabled; Quinn judges.
6. **Blocked in K1** — drawer renders for replay/audit purposes only. Composer + approval strips are disabled with a plain-register notice: "This prospect is on your blocklist. Reply disabled. Manage in safety settings."
7. **Cross-surface unresolved**`prospect-resolver` hasn't merged yet. Banner above the timeline: "2 unmerged identifiers — Cocotte is still confirming this is the same person. Showing what's matched so far." Identity line shows resolved channels only. **Touchpoint chain stays empty** until merge resolves — anonymous touchpoints never render per-prospect (surface-metrics §9 invariant).
9. **Single-touch prospect** — only one resolved touchpoint exists. `TouchpointChain` renders the single node without arrows; `AttributionCreditStrip` shows 100% credit to that surface under any model. Strategist insight chip (working): "Single-touch prospect. Worth nudging onto a second surface — typical Felix-types convert at 3× after the second touchpoint."
10. **Chain-confidence hedge**`prospect-resolver` linked touchpoints via `identifier_hash` match but didn't confirm. Chain renders with dotted arrows + plain-register hint: "Linked by identifier hash — Cocotte's 78% confident this is the same person." Quinn can confirm/break the link from a long-press menu on any arrow.
8. **Share-with-org pending** (per brief W) — header `share` button shows a confirmation sheet before any data crosses scopes. Org-context voice shift applies once confirmed.
## Gestures
- **Swipe right on a timeline row**: approve a pending draft (when the row carries one).
- **Swipe left on a timeline row**: set aside the draft (same semantics as A's chat cards).
- **Tap a timeline row**: opens the source's raw thread (audit-shadow view).
- **Long-press the funnel chip**: opens funnel-stage history sheet (when did this prospect move from free → tip, etc.).
- **Tap the attribution-model picker**: switches model; credit strip + insight chip recompute instantly (all 4 models pre-computed per [T §T-attribution-model](./T-analytics-dashboard.brief.md)).
- **Long-press a chain arrow**: opens "confirm / break link" sheet for hedged identifier-hash links (state 10).
- **Tap the coop callout**: opens [`coop-intel-detail.screen.md`](./coop-intel-detail.screen.md).
- **Swipe down**: dismiss to the surface that opened this drawer (chat / inbox / search / audit).
## In-the-wild copy
**Header identity line** (working):
> @felix · OF + Tryst · first DM Mar 12 · last contact 2h ago
**Funnel chip, paying** (working):
> PPV · $1,420 ytd · last buy 4 days ago
**Coop intel callout** (plain):
> 2 peer reports on this subject. Berlin Providers · UNSAFE. Read before you reply.
**Unresolved-identifier banner** (working):
> 2 unmerged identifiers — Cocotte is still confirming this is the same person. Showing what's matched so far.
**Blocked-in-K1 notice** (plain):
> This prospect is on your blocklist. Reply disabled. Manage in safety settings.
**Share-with-org confirmation** (plain, per brief W):
> Share this prospect with Demimonde org? Members with prospect-scope access will see this history and funnel state. Continue / cancel.
**Auto-conversation live indicator** (working, per Q3):
> Cocotte is on a thread with @felix right now — last turn 12s ago.
## Privacy invariants
- Subject phone / email / handle identifiers in the header and timeline are **never copyable to clipboard** (same rule as [coop-intel-detail](./coop-intel-detail.screen.md) §privacy). Tap-and-hold opens a privacy reminder, not a copy menu.
- Cross-channel identifier guards (per brief K3h): the reply composer's channel picker warns when the chosen channel uses an identifier the prospect hasn't seen — "They messaged via Tryst. You're replying via SMS, which uses a number they may not have. Continue / switch back."
- No screenshot affordance. iOS screen-capture detection raises a banner per coop-intel-detail pattern.
- Coop intel callout obeys per-coop visibility rules — reports from coops Quinn isn't in never surface here.
- Share-with-org never echoes identifiers outside the org scope; the org receives the resolved `prospect_id` view, not raw cross-channel identifiers from coops Quinn-only is in.
## Edge cases
- **Govt-name draft reply** (K3c-1 hard rule) — if a Cocotte-proposed draft contains what the safety pass flags as a government / legal name, the draft is **withheld**; row shows plain-register stub: "Draft held — contains a name we don't put in writing. Ask Cocotte to redraft." Hard rule; no override here.
- **Search lands here** — brief U query routes to this screen with the matched timeline row scrolled into view and highlighted for ~2s; back-swipe returns to search results, not chat.
- **Auto-conversation in progress** (per brief Q3) — when triage is mid-thread with this prospect, a live indicator pulses next to the header and the composer is dimmed with copy: "Cocotte is replying — interrupt to take over?" Tap "interrupt" snapshots the conversation and hands control back.
- **Two phone numbers, same prospect, not yet merged** — timeline renders both as separate source chips with the `UnresolvedIdentifierBanner`; once `prospect-resolver` (P4) merges, banner clears and rows recompose without a reload.
- **Outbound failure on reply** — failed row enters `pending retry` (per brief M §M3). Approval strip becomes "retry / set aside" until reconciled.
- **Counter-action target** (per brief I) — a row sent by mistake can be tapped → "Counter-action: retract / revert / follow-up" sheet opens; counter-actions land in audit.
## Related
- [brief B](./B-drawers.brief.md) §B3 — drawer parent.
- [brief P](./P-inboxes.brief.md) — unified inbox routes here on row tap; channel posture from P drives the composer's default `via:`.
- [brief N](./N-provider-coop.brief.md) — coop intel callout source; opens [`coop-intel-detail.screen.md`](./coop-intel-detail.screen.md).
- [brief Q](./Q-vigil-journal-auto-conversations.brief.md) §Q3 — auto-conversation live indicator.
- [brief U](./U-global-search.brief.md) — search routes land here.
- [brief I](./I-audit-trust-replay.brief.md) — counter-action target; row-level retract / revert / follow-up.
- [brief W](./W-org-overlay.brief.md) — share-with-org cross-context handoff.
- [brief K](./K-safety-blocklist.brief.md) §K1 (blocked-in-drawer), §K3c-1 (govt-name hard rule), §K3h (cross-channel identifier guard).
- [voice](./00-system-voice.md) §V2b working register · §V2c plain on coop callout + safety stubs.
- [T-analytics-dashboard.brief.md §T-attribution](./T-analytics-dashboard.brief.md) — per-prospect chain is the drilldown surface from T2 `Top paths`.
- [T-analytics-dashboard.brief.md §T-attribution-model](./T-analytics-dashboard.brief.md) — model picker shared with this screen.
- [specialist-prospect-resolver.contract.md](./specialist-prospect-resolver.contract.md) — writes the `prospect_touchpoints` rows this screen renders; handles state-10 hedged links.
- [_engineering-surface-metrics.md](./_engineering-surface-metrics.md) §4 (touchpoints schema) + §9 (privacy: anonymous-aggregate-only invariant).