cocottetech/@platform/codebase/@features/ai-copilot/docs/peer-directory.screen.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

17 KiB
Raw Blame History

peer-directory.screen

Implementation breakdown of Brief AE §AE5 — the opt-in, platform-wide peer-discoverable directory. Mobile-narrow list with a filter bar at the top, searchable by surface, language, region, and endorsement scope-claim text. Rows reveal handle, bio excerpt, surface chips, and endorsement-count badge; tap routes to peer-profile.screen.md. The drawer is reached from chat-home's top-bar overflow ("Peers") and from the coop drawer's peer-roster sub-tab when a coop member opts in to platform-wide discoverability.

Voice register: working by default (per 00-system-voice.md §V2b — peers are colleagues, decisions are deliberate); plain on empty / blocked / posture-related states (per §V2c — the cost of leaking that someone is incognito is non-trivial). Hearth lexicon stays out — this is a directory, not a hearth.

Layout (iPhone 17 logical 393×852)

┌─────────────────────────────────────────────────┐
│ ◄ status bar (system)                           │ 47pt
├─────────────────────────────────────────────────┤
│ [◄]  Peers                          [🎤]  [⌥]   │ 56pt — top bar
│                                                 │   ⌥ = posture + filters sheet
├─────────────────────────────────────────────────┤
│  ╭─────────────────────────────────────────╮    │ 44pt — searchbar
│  │ 🔍  Search handle, bio, scope-claim…    │    │   voice mic on right of bar
│  ╰─────────────────────────────────────────╯    │
├─────────────────────────────────────────────────┤
│ [ surface ▾ ] [ language ▾ ] [ region ▾ ]      │ 36pt — filter chip row
│ [ endorses ▾ ]                       [ reset ] │   horizontal scroll
├─────────────────────────────────────────────────┤
│                                                 │
│ ┌───────────────────────────────────────────┐   │ result row
│ │ @sarah-k                          ★ 12    │   │ handle · endorsement badge
│ │ Berlin · DE/EN · she/her                  │   │ region + langs line
│ │ "Two years on Tryst, three on OF.         │   │ bio excerpt (2 lines max)
│ │  Pricing-study group regular."            │   │
│ │ [tryst] [of] [threads]   [ Connect ]      │   │ surface chips · primary CTA
│ └───────────────────────────────────────────┘   │
│                                                 │
│ ┌───────────────────────────────────────────┐   │
│ │ @q-berlin                         ★ 31    │   │
│ │ Berlin · DE/EN/中文                       │   │
│ │ "Mandarin-speaking subject handling."     │   │
│ │ [tryst] [of]            [ Requested ]     │   │ pending-state CTA
│ └───────────────────────────────────────────┘   │
│                                                 │
│ ┌───────────────────────────────────────────┐   │
│ │ @mira-pdx                         ★ 4     │   │
│ │ Portland · EN                             │   │
│ │ "PPV pricing for content-onlyfans."       │   │
│ │ [of]                    [ Connected ]     │   │ post-accept state
│ └───────────────────────────────────────────┘   │
│      …                                          │
│      [ load more · 23 results ]                 │
│                                                 │
├─────────────────────────────────────────────────┤
│                                                 │ 34pt — home indicator
└─────────────────────────────────────────────────┘

Components

Component Brief ref Notes
Top bar AE §AE5 Back to chat. [🎤] = voice query (routes through ai-copilot → directory filter set; see Interactions). [⌥] opens posture + advanced-filters sheet (see Interactions).
Searchbar AE §AE5 Full-width, debounced 250ms. Searches handle, bio substring, and endorsement.scope_claim text. Never searches region literal beyond chip filter (avoids precise-location leak per K3f-2).
Filter chip row AE §AE5 + F §F5 Four canonical chips: surface (multi-select per O surface_kind), language (ISO 639-1 multi), region (country/metro coarse only — never lat/long or city precision), endorses (free-text scope-claim picker). [ reset ] clears all. Active chips show count badge.
Result row AE §AE5 + AE §AE7 Handle line + endorsement-count badge (★N — count of accepted public endorsements per AE7). Region + langs line (coarse only). Bio excerpt (2 lines, ellipsis). Surface chips per O. Right-aligned connection CTA whose label reflects current peer_connections.state for this pair (per AE2).
Connection CTA AE §AE2 Polymorphic per state: none → "Connect", requested-by-me → "Requested" (disabled), requested-by-them → "Accept ▾" (drop = accept/decline), connected → "Connected" (tap → row context menu), muted → "Muted", blocked → row hidden.
Posture banner AE §AE11 Renders above results when viewer's posture ≠ open and they navigated here directly. Plain register: "Your posture is discoverable. You can browse, but you won't appear in this directory until you switch to open." Tap → settings posture sheet.
Voice query mic A §multimodal + AD §AD3 Push-to-talk; produces a structured filter set ("show me Berlin peers who endorse Tryst optimization") which surfaces as auto-filled chips + searchbar; never auto-executes a connection request.
PII-filter receipt AE §AE5 + K §K3 Invisible-by-default footer line at very bottom of list: "Directory hides government names + precise location." Plain register. Tap → AE11 posture sheet.

States

  1. Browse, populated (default) — opt-in posture is discoverable or open; ≥1 result rows matching filters. Default sort: endorsement-count desc, then last_active_at desc.
  2. Searching / debouncing — searchbar non-empty, results re-rendering. Subtle top progress bar (1.5pt) under the searchbar; existing rows dim to 50%. No skeleton — too noisy.
  3. No matches — filters narrow to zero. Working register: "No peers match these filters. Try widening region or removing a surface chip." Includes a single-tap "reset filters" button.
  4. Empty — incognito posture — viewer's posture is incognito (AE11 default). Plain register full-screen takeover: "You're incognito. The directory is hidden until you opt in." Single CTA "Open posture sheet" → AE11 sheet. No peer rows render; this is the privacy floor — incognito viewers should not even see the directory exists is populated.
  5. Empty — posture discoverable but no platform-wide rows opted in to surface to you — populated but coop-only; banner: "Coop-mediated peers only. Switch to open for the platform-wide directory." Result rows that exist via shared-coop opt-in still render.
  6. Voice query active[🎤] long-pressed. Composer-like mic-pulse replaces filter bar; on release, the recognized intent fills chips + searchbar with a 2s "review filters" toast before applying. Mishears: tap the toast to undo.
  7. Filtered to one endorsement scope-claim — chip "endorses: bookings-tryst listing optimization" pinned. Result rows reorder by endorsements-on-this-claim-desc. Subtle helper line below filter row: "Sorted by endorsements on this claim."
  8. Degraded (M §M2b) — directory backend unreachable. Top banner (yellow, plain): "Peer directory can't reach the server. Showing cached rows from {time}." Cached rows are read-only; Connect CTAs are disabled with hint "back online to send a request."
  9. Offline (M §M2c) — no cached data: empty state with grey banner "Offline. Directory needs a connection." If cache exists: behave as state 8.
  10. Rate-limited / posture-rate-cap (AE §AE10) — viewer has sent too many connection requests in the rolling window (anti-spam per AE10). Connect buttons globally disabled with plain-register tooltip: "You've sent 12 requests today. Cooldown until tomorrow morning." Browsing remains live.
  11. Viewer is shadowbanned from directory (AE §AE10 sanction) — viewer can browse but their own posture is forcibly demoted; banner: "You can't appear in the directory while a moderation review is open." Browsing is unaffected; appearing is.

Interactions / gestures

  • Tap result row → routes to peer-profile.screen.md.
  • Tap Connect CTA → opens a one-line "context note" sheet (per AE1b referral pattern but for self-introductions); confirm to write a peer_connections row in requested state. Haptic on commit. CTA flips to Requested (disabled).
  • Long-press result row → row context menu: "View profile", "Mute", "Hide from this directory view" (per-viewer hide; doesn't block), "Report". Block is intentionally NOT here — block lives on the profile screen so the viewer reads the bio first.
  • Tap surface chip in row → adds that surface to the filter set; brief flash on the chip row to show the new filter.
  • Pull-to-refresh → re-pulls directory page. Surfaces newly-opt-in peers at the top with a "new" pip for 2s.
  • Swipe-down on searchbar from idle → dismisses keyboard.
  • [🎤] long-press → voice query mode (state 6).
  • [⌥] tap → posture + advanced-filters sheet (toggle posture, save filter preset, manage muted-peers list).
  • VoiceOver order per row — handle → endorsement-count → region → langs → bio excerpt → surfaces → connection CTA. Most-identifying first.

In-the-wild copy

  • (working, empty-no-matches) "No peers match these filters. Try widening region or removing a surface chip."
  • (plain, incognito empty) "You're incognito. The directory is hidden until you opt in."
  • (plain, posture-discoverable) "Your posture is discoverable. You can browse, but you won't appear in this directory until you switch to open."
  • (working, voice-query toast) "Filtering: Berlin · Tryst · endorsing listing optimization. Tap to undo."
  • (plain, degraded) "Peer directory can't reach the server. Showing cached rows from 14:02."
  • (plain, rate-limited) "You've sent 12 requests today. Cooldown until tomorrow morning."
  • (working, PII filter footer) "Directory hides government names + precise location."
  • (plain, shadowbanned) "You can't appear in the directory while a moderation review is open."
  • (working, connect-context-sheet placeholder) "One line so they know why you're reaching out. Optional."

Privacy invariants

  • No government name field ever rendered. peer_profiles.handle is the only identity; bio passes K3 PII gate at publish time, never at render.
  • No precise location. Region chip is country / metro at coarsest; the renderer ignores any free-text region beyond a known coarse vocabulary.
  • No "viewed by" telemetry. Tapping a row does not write a visible-to-target event. (Audit row exists per I, but the target peer is never notified of directory views — only of connection requests.)
  • Blocked peers are invisible to the blocker and the blocked. A row that exists for the blocker's view will not render for the blocked viewer, and vice versa. Hash-based row filter at query time, not at render — never round-trip blocked rows to client.
  • Incognito peers never appear, even with shared coops. The directory honors peer_profiles.posture strictly.
  • Voice query never echoes raw recognized text to other peers. The structured filter set is what travels; the spoken bytes stay on-device + the audit row.

Edge cases

  • Filter chips combine to a single-peer result who happens to be the viewer → viewer-self row is suppressed; helper line: "Showing 0 matches (excluding you)."
  • Bio contains a flagged phrase post-publish (K3 list updated after publish) → row renders with bio replaced by plain stub: "Bio under review." Peer is notified separately via chat-home receipt.
  • Endorsement count includes withdrawn endorsements? → No. Badge counts only public=true AND withdrawn_at IS NULL. Withdrawal updates the count within ~1 vigil per AE9 aggregator cadence.
  • Two peers with the same handle → schema-enforced unique; this state cannot occur. If it ever does (data corruption), render both with a (disputed) chip and block any connection action.
  • Searchbar query that K3-PII-trips (viewer types a govt name as query) → query is dropped client-side with a plain-register inline notice: "We don't search by names like that. Try a handle or scope-claim."
  • Reduced motion — replace top progress bar with a static dim of the result list during debounce.
  • Dynamic Type XXL — result row reflows: surface chips wrap to a second line below bio; CTA stays right-aligned but full-width on row when wrapped.
  • VoiceOver + voice query — voice query goes through VoiceOver's mic path, not the directory mic, to avoid double-narration.
  • Right-to-left locale (per AD-Q4) — searchbar mic-icon flips to left; chip row scroll direction mirrors; CTA stays at the row's trailing edge.
  • Peer just-opted-in mid-scroll → "new" pip on insertion; list does not jump-scroll.
  • Peer in shared coop has opt-in to discoverable but not open → renders only when viewer is also in that coop and viewer's posture ≥ discoverable. Filter chip "in coop ▾" is conditionally available if viewer is in ≥1 shared coop.
  • [open] Bio length limit on the row excerpt — currently 2 lines truncated. Open whether tap-to-expand-inline is worth the affordance vs. routing to profile. Lean: route to profile; an excerpt is a teaser.
  • [open] Default sort tie-breakers — endorsement-count-desc, then last_active_at desc. Open whether mutual-coop count should outrank last_active_at.

Out of scope

  • The posture + advanced-filters sheet interior (its own .screen.md if it grows beyond toggles).
  • Coop-mediated peer-roster sub-tab (lives inside coop-drawer.screen.md).
  • The introduction flow from AE1b referral (separate screen — a referral-introduce sheet).
  • Cross-org directory rows for org-attributed peer connections (deferred to AE-Q6 P5+).
  • iPad / web variants.

Open questions

  • Default sort tie-breaker — endorsement-count-desc → last_active_at desc → mutual-coop count? Or mutual-coop count second? [nice-to-have] (lean: last_active_at second; mutual-coop count is its own filter chip).
  • Voice query review-toast window — 2s feels short for non-native-language voice input. [exploratory] (lean: 2s for confident parses, 4s for any chip below 0.75 confidence per AD3 threshold).
  • [open] Can endorsement-count badge be tapped to surface which scope-claims drive the count? [exploratory] (lean: yes, but on the profile screen, not the directory row — keep rows scannable).