cocottetech/@platform/codebase/@features/ai-copilot/docs/U-global-search.brief.md
natalie 1b719e1fd7 chore(bootstrap): initial V4 commit
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>
2026-05-18 08:11:41 -07:00

11 KiB
Raw Blame History

U — Global search

Goal

Every drawer has its own filter/search — audit (brief I), unified inbox (brief P), surfaces settings, audit log on web. Quinn shouldn't have to remember which drawer to open to find "what did Felix say last week" or "show me every action from content-onlyfans in October." This brief defines the one search affordance that spans the corpus and returns results grouped by where they live.

Search is a cross-cutting access layer, not a separate surface. It's reachable from chat-home, from any drawer, and via voice ("hey copilot, find Felix"). Results are typed and grouped; tap a result to land in the originating drawer scoped to that item.

Designer skim

  • Headline UX: One search input, reachable everywhere. Returns typed grouped results (Prospects · Conversations · Audit · Content plans · Posts · Settings · Tour legs · Hotels · Specialists · Briefs). Tap a result → land in the right drawer at the right scope.
  • Index scope: everything in platform.db that has user_id = Quinn. Plus the briefs themselves (for Quinn-as-power-user).
  • Voice: register adapts to what was found — hearth on rich result, plain on no-match.
  • Pair-with: existing per-drawer search affordances (audit, inbox, surfaces) reuse this brief's component.
  • Blocking Qs: see OPEN-DECISIONS.md → U-Q1 voice-query default, U-Q2 indexing-cost cadence.

States to design

  • Search empty (Quinn just opened the input).
  • Typing — recent queries + suggestions appear below.
  • Loading — partial results streaming in per group.
  • Result groups expanded — typical mix.
  • Single-group filter — Quinn tapped "Audit (47)" to scope to one group.
  • No results found.
  • Search degraded (index stale; banner: "showing best-effort").
  • Voice query in progress.
  • Long result (>200 in a group) with "load more" affordance.

U1 — Result types and grouping

Results are typed; the type drives where tapping lands.

Type Indexed fields Lands at
Prospect display name, handle, phone hash, email hash, surface aliases, free-text notes brief B3 prospect drawer
Message DM body, channel, prospect_id, timestamp brief P unified inbox scoped to thread
Audit row action_type, specialist_id, target preview, outcome_json keys brief I §I3 row detail sheet
Content plan title, surface, body draft, scheduled_for brief B1 calendar drawer at this plan
Content post body, surface, status, prospects-mentioned brief I audit (it's already published) OR B1 (scheduled)
Asset tags, persona facet, surface labels, filename brief B2 asset library at this asset
Tour leg city, dates, state, journal mentions brief R R2b leg-detail drawer
Hotel property name, neighborhood, state, anonymity grade hotel detail card under tour leg
Setting setting name, current value, category brief S category drawer at this setting
Specialist specialist_id, role, recent action types specialist drawer (B6)
Brief brief title, section headers, in-the-wild copy the brief file itself in a doc reader
Journal entry content, tags, mood, mentions, vigil date brief Q journal detail drawer

Brief results are last in the rank (Quinn power-user use); message + prospect + audit are first.

U2 — Query syntax (lightweight)

Default is fuzzy free-text across all types. Two lightweight operators for power use:

Operator Example Effect
type: type:audit content-onlyfans Scope to one result type.
@ @felix Scope to prospects + their related messages/audit.
# #berlin Scope to journal tags + tour legs tagged with that string.
surface: surface:tryst Scope to one surface across all result types.
since: since:7d Time window (7d, 30d, today, yesterday, or ISO date).

No boolean operators (no AND/OR/NOT) — search should be conversational and forgiving, not a query language. Power-user queries that exceed this should be conversation with strategist or ai-copilot, not search syntax.

U3 — Result presentation

┌────────────────────────────────────────┐
│  ⌕  felix berlin                       │
│  recent: "tryst this week" · "k3"      │
├────────────────────────────────────────┤
│  Prospects (1)                    [▾]  │
│  • @felix · OF + Tryst · last 2h ago   │
├────────────────────────────────────────┤
│  Messages (8)                     [▾]  │
│  • @felix · OF · "thinking Berlin"     │
│  • @felix · iMessage · "is Adina…"     │
│  +6 more                               │
├────────────────────────────────────────┤
│  Audit (4)                        [▾]  │
│  • content-onlyfans drafted DM Apr 14  │
│  +3 more                               │
├────────────────────────────────────────┤
│  Journal (2)                      [▾]  │
│  • Apr 14 — "Felix re-subbed, asking…" │
├────────────────────────────────────────┤
│  Tour legs (1)                    [▾]  │
│  • Berlin · Oct 38 · closed           │
└────────────────────────────────────────┘
  • Groups collapse if count > 5; "expand" replaces the +N.
  • Order of groups is by relevance score (top), within a group by recency.
  • Each row is tappable; tap → land in the originating drawer.
  • Long-press a row → quick actions appropriate to the type (e.g. on a prospect: "block", "open last message", "open audit slice").

"Hey copilot, find Felix" → ai-copilot interprets, runs the same search, speaks the top result group summary:

Three places match Felix. One prospect with eight messages, four audit rows, and a journal entry from April 14. Tap to open or say which.

Quinn can then say "open the journal entry" or "show me the messages." If the result is unambiguous (one prospect with exact-name match), ai-copilot may surface the prospect drawer directly without enumeration.

U5 — Indexing model

  • Primary index lives on platform.db (PostgreSQL pg_trgm + full-text search per-table).
  • Heavy fields (audit outcome_json, content-post body, journal content) get tokenized columns kept in lock-step with their canonical column via triggers.
  • Index refresh: real-time on insert/update; no batch lag for user-touchable fields.
  • Per-tenant scoping: every query is RLS-scoped to user_id + optional org_id (per platform DESIGN §5). Cross-user leakage is impossible at the query layer.
  • Brief search runs against a separate small static index of .md files (rebuilt on deploy); doesn't share platform.db.

U6 — Where the input lives

  • Chat-home top-bar has a ⌕ icon next to the settings gear. Tap or two-finger-swipe-down opens the search overlay.
  • Every drawer with its own filter (audit, inbox, surfaces, settings) keeps its local filter, but also exposes a "search everywhere" affordance that opens the global search overlay pre-seeded with current context (e.g. opening from a prospect drawer pre-fills @prospect-name).
  • Voice: any time, any surface — "find X" is recognized.
  • Web companion: ⌘K / ctrl-K keyboard shortcut (desktop convention).

U7 — Privacy invariants

  • Search never indexes coop intel reports from other coops (per brief N §N1) — Quinn's local search shows only her own coop reports + reports about subjects she's interacted with.
  • Search never reveals raw hashed PII (phone, email) in result snippets — shows the hashed identifier or the display name, never both at full resolution.
  • Voice search responses never read aloud hotel addresses (brief K §K3f-2), prospect phone numbers, govt names — speech results paraphrase.
  • Brief search results don't include _engineering-* annex content by default (operator-vs-engineer separation); add an include:engineering opt-in for power users.

In-the-wild copy

Empty input (hearth — gentle):

What are you looking for? Try a name, a city, an action, or "what did Cocotte do yesterday."

Typing-with-recent-queries (hearth):

Recent: "tryst this week" · "k3" · "amsterdam refund"

Typical result header (working):

16 matches across 5 places.

No results (plain — no metaphor on a flat answer):

Nothing matches. Try a different spelling or a related term.

Voice top-result summary (hearth, dialed for conversation):

Three places match Felix. One prospect with eight messages, four audit rows, and a journal entry from April 14. Tap to open or say which.

Degraded index banner (plain):

Index is rebuilding. Showing best-effort. Results in ~30s.

Out of scope

  • Cross-org search (single-Quinn for P0).
  • Semantic / embedding-based search — pg_trgm + FTS suffice for the data volumes at P0P4. Embedding-based "find similar prospects to Felix" is a P5 feature (and probably lives in strategist, not search).
  • Saved searches / search alerts — defer until usage proves the need.
  • Export search results (privacy-cautious; never default).

Open questions

  • U-Q1 Voice query default — enumerate result groups or surface the top one directly? Lean: enumerate when ambiguous (multiple types match), top-one direct when unambiguous. [blocking]
  • U-Q2 Indexing cost cadence — re-tokenize heavy fields on every write, or batch every minute? Lean: real-time for user-touchable fields, batched for outcome_json blobs. [engineering]
  • U-Q3 Should typing into chat that looks like a search query (e.g. Quinn types "find felix") auto-route to search vs treat as a chat turn? Lean: chat takes the turn (per brief A debounced turn model), but ai-copilot recognizes search intent and offers "search instead" affordance inline. [blocking]
  • U-Q4 Recent queries list — per-device or per-account? Lean: per-account (Quinn shouldn't lose her search history switching to web). [nice-to-have]
  • U-Q5 When a search result lands in a drawer, should the drawer remember it came from search (breadcrumb back)? Lean: yes, soft breadcrumb at top "from search ⌕"; tap returns to results. [nice-to-have]
  • brief A — search opens from chat-home top-bar.
  • brief B — every drawer reuses the search component as their local filter.
  • brief I §I2 — audit-drawer search is a scoped variant of this.
  • brief P — inbox filter is a scoped variant of this.
  • brief S §S10 — settings search shares the component.
  • brief N — coop intel privacy invariants apply at the index layer.
  • brief K §K3c — identity invariants apply at the result snippet layer (no govt-name reveal).
  • voice — voice search invocation patterns.