160 lines
17 KiB
Markdown
160 lines
17 KiB
Markdown
|
|
# peer-directory.screen
|
|||
|
|
|
|||
|
|
Implementation breakdown of [Brief AE §AE5](./AE-provider-social-network.brief.md) — 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`](./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`](./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`](./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`.
|
|||
|
|
|
|||
|
|
## Related
|
|||
|
|
|
|||
|
|
- [Brief AE §AE5](./AE-provider-social-network.brief.md) — directory parent + opt-in posture.
|
|||
|
|
- [Brief AE §AE2](./AE-provider-social-network.brief.md) — connection state machine driving CTA labels.
|
|||
|
|
- [Brief AE §AE7](./AE-provider-social-network.brief.md) — endorsement-count badge source.
|
|||
|
|
- [Brief AE §AE10](./AE-provider-social-network.brief.md) — rate-limit + shadowban states.
|
|||
|
|
- [Brief AE §AE11](./AE-provider-social-network.brief.md) — posture floor + privacy invariants.
|
|||
|
|
- [`peer-profile.screen.md`](./peer-profile.screen.md) — destination of row tap.
|
|||
|
|
- [`chat-home.screen.md`](./chat-home.screen.md) — top-bar overflow entrypoint.
|
|||
|
|
- [`coop-drawer.screen.md`](./coop-drawer.screen.md) — sibling drawer; complementary plane.
|
|||
|
|
- [Brief K §K3](./K-safety-blocklist.brief.md) — PII gate on bio + search query.
|
|||
|
|
- [Brief AD](./AD-multilingual-opaque.brief.md) — locale-invariant filter labels + voice query.
|
|||
|
|
- [Brief X](./X-accessibility.brief.md) — VoiceOver row order + reduced motion.
|
|||
|
|
- [Brief V](./V-data-portability-erasure.brief.md) — erasure redacts profile + endorsements without deleting connection-state history.
|
|||
|
|
- [Brief O](./O-surface-kinds.brief.md) — `surface_kind` vocabulary for chips.
|
|||
|
|
- [Brief F §F5](./00-system-visual-system.md) — surface-chip iconography.
|
|||
|
|
- [`00-system-voice.md`](./00-system-voice.md) §V2b working / §V2c plain.
|
|||
|
|
|
|||
|
|
## 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).
|