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

20 KiB
Raw Blame History

peer-profile.screen

Implementation breakdown of Brief AE §AE5 profile rendering + Brief AE §AE2 connection-state controls — what a provider sees when viewing another peer. Reached from peer-directory.screen.md, from coop-drawer peer-roster rows, from a referral-introduction sheet (AE1b), from peer-DM thread headers, and from endorsement / mentorship notification rows in chat-home.

Voice register: working for the bio, surfaces, endorsements, mutual-coop chips, and connection controls (per 00-system-voice.md §V2b — peer relationships are deliberate). Plain for moderation actions, blocked / 404 states, and posture-derived empty views (per §V2c — block + moderation cannot be softened).

Layout (iPhone 17 logical 393×852)

┌─────────────────────────────────────────────────┐
│ ◄ status bar (system)                           │ 47pt
├─────────────────────────────────────────────────┤
│ [◄]  Profile                              [⋯]   │ 56pt — top bar
│                                                 │   ⋯ = moderation menu (long-press anywhere)
├─────────────────────────────────────────────────┤
│                                                 │
│   @sarah-k                              ★ 12    │  header band
│   Berlin · DE / EN · she/her                    │  region · langs · pronouns
│   posture: open · last active 2h ago            │  posture badge + freshness
│                                                 │
│   ──── Bio ────────────────────────────────     │
│   "Two years on Tryst, three on OF. Pricing-    │  bio body — passes K3 + V6 gate
│    study group regular. Berlin-area tours       │
│    monthly. Mandarin OK, German native."        │
│                                                 │
│   ──── Surfaces operated ──────────────────     │
│   [tryst]  [of]  [threads]                      │  surface chips per O
│                                                 │
│   ──── Endorsements · 12 ──────────────────     │
│   "bookings-tryst listing optimization"    · 5  │  scope-claim · endorser count
│       endorsed by @q-berlin · @mira-pdx · +3    │  small-text endorser sample
│   "Mandarin-speaking subject handling"     · 4  │
│       endorsed by @q-berlin · @lin-sf · +2      │
│   "Tour pricing for Berlin"                · 3  │
│       endorsed by @mira-pdx · @rosa-de · +1     │
│   [ See all 12 → ]                              │
│                                                 │
│   ──── Mutual coops · 2 ───────────────────     │
│   [Berlin escort coop]  [Pricing study]         │  chip row; tap → coop drawer
│                                                 │
│                                                 │  (scroll padding)
│                                                 │
├─────────────────────────────────────────────────┤
│  [ Follow ▾ ]   [ Request colleague ]           │ 64pt — footer control bar
└─────────────────────────────────────────────────┘
                                                  │ 34pt — home indicator

Components

Component Brief ref Notes
Top bar AE §AE5 Back to source surface. [⋯] opens the moderation + utility menu: "Mute", "Block", "Report to coop moderators", "Report to platform-admin", "Share profile via referral" (AE1b), "Copy handle" (handle only, never any other field).
Header band AE §AE5 + AE7 Handle · endorsement-count badge (★N). Second line: region (coarse) · languages · pronouns. Third line: posture badge + freshness (last active ~Nh ago, rounded to vigil; never precise).
Posture badge AE §AE11 One of open / discoverable / incognito. Color-neutral; this is information, not status. Only rendered if the viewer has visibility into the posture (i.e. the peer is in discoverable for a shared-coop view, or open for a directory view).
Bio section AE §AE5 Free-text, passed through K3 + voice §V6 gate at publish-time. Renders as quoted text in working register. Long bios expand with "See more"; never auto-truncate mid-sentence.
Surfaces operated chips AE §AE5 + O One chip per peer_profiles.surfaces_operated entry. Claim-only at P0 (no verification — per AE5). Tappable: opens a sheet describing the surface kind, NOT a deep-link into the peer's content (cross-surface content links across providers are out-of-scope at P0).
Endorsements list AE §AE7 Grouped by scope_claim; per group: claim text + endorser count + first 23 endorser handles + overflow. "See all" routes to a full endorsements sheet. Renders only public=true AND withdrawn_at IS NULL.
Mutual coops chip row AE §AE5 + N Coops the viewer and the peer both belong to (intersection per N membership). Tap → coop drawer for that coop. If 0: section hidden entirely (don't render an empty mutual-coops zone — signals nothing useful).
Connection control footer AE §AE2 Polymorphic per peer_connections.(kind, state) for the viewer↔peer pair. Two slots: a follow control (lightweight) and a colleague control (gated). Labels per State table below.
Moderation menu AE §AE10 Long-press anywhere on the profile body (or [⋯]) opens it. Items in plain register: "Mute (per AE2)", "Block (per AE2)", "Report to coop moderators", "Report to platform-admin", separated visually from utility items.

States

  1. Viewing a directory-discoverable peer, none connection (default) — full layout as drawn. Footer: [ Follow ▾ ] + [ Request colleague ]. Both actions live.
  2. Viewing a follow-only peer (you follow them, they don't follow back) — footer: [ Following ▾ ] (tap = unfollow / request colleague) + [ Request colleague ]. Endorsements + bio fully rendered.
  3. Viewing a connected colleague — footer: [ Following ▾ ] + [ Connected ▾ ] (drop: disconnect / mute / start peer-DM). New section appears above the footer: "Threads" — 13 recent peer-DM thread snippets (rendered per chat-home thread row pattern). Endorsement-offer affordance: [ Endorse @sarah-k ] inline at the bottom of the endorsements section.
  4. Connection requested by viewer — footer: [ Follow ▾ ] + [ Requested ] (disabled). Header has small plain-register stamp: "Requested 2h ago. They'll see this when they next open Peers."
  5. Connection requested by peer (incoming) — footer: [ Follow ▾ ] + [ Accept ▾ ] (drop: accept / decline / decline-with-note). Header has working-register banner: "Sarah-K asked to be colleagues. They endorse bookings-tryst listing optimization." (Sample-endorsement line is generated from peer's accepted endorsements, not from the request itself.)
  6. Muted by viewer — footer: [ Following ▾ ] + [ Muted ▾ ] (drop: unmute / block / disconnect). Endorsements + bio render normally; this is muting, not blocking. Header has working-register chip: "You muted Sarah-K. They don't know."
  7. Blocked by viewer — full-screen plain-register takeover: "You blocked Sarah-K. They won't see your profile, feed, or any AE surface." Single CTA [ Unblock ]. No bio / endorsements / mutual-coops visible — blocker has chosen to not see, honor that.
  8. Viewed-by-blocked-peer (404-style) — viewer was blocked BY this peer. Plain register full-screen takeover: "This profile isn't available." No bio, no endorsements, no posture, no last-active, no mutual coops, no footer controls — only [ ← Back ]. The copy is intentionally ambiguous: does not confirm the peer exists, does not confirm a block. Mirrors HTTP 404 semantics so blocking is non-distinguishable from "deleted profile" or "never existed". Per AE §AE10: a block must not leak its existence to the blocked peer.
  9. Viewed-by-non-discoverable-peer (mutual-coop-only view) — the peer's posture is incognito or discoverable and the viewer has no platform-wide visibility, but ≥1 shared coop exists. Renders a stripped layout: handle + posture badge (incognito rendered as "—" / "private") + mutual coops chip row + footer with [ Request colleague ] only if the peer is discoverable AND a shared-coop opt-in exists (AE1a); otherwise footer is suppressed. Bio, endorsements, surfaces all hidden. Plain register helper line: "This peer keeps their profile to coops you share."
  10. Viewing your own profile — redirects to the self-edit profile screen (peer-profile-edit.screen.md, out-of-scope here). If somehow rendered: read-only with "This is you — edit in Settings".
  11. Peer is under moderation review (AE §AE10) — header has plain-register banner: "Profile under review. Endorsements paused." Endorsement section renders with a "paused" overlay; bio + surfaces render normally. Connection actions still live — moderation is on their profile claims, not on contactability.
  12. Peer just dissolved their account / erased (Brief V) — full-screen plain takeover: "This profile has been erased." Identical visual to state 8 (no leakage about whether it's erasure vs block). Past peer-DM threads still visible in viewer's chat-home audit; this screen only governs profile rendering.
  13. Degraded (M §M2b) — backend unreachable. Top banner (yellow, plain): "Peer profile can't refresh. Showing cached data from {time}." Connection actions disabled with hint "back online to act."
  14. Offline (M §M2c) — no cached data: plain register full-screen "Offline. This profile needs a connection." Cached: behave as state 13.

Interactions / gestures

  • Tap surface chip → surface-kind info sheet (not a deep link; AE doesn't bridge content across providers at P0).
  • Tap endorsement claim row → full endorsement detail sheet (claim text, evidence if any, endorser handles, accepted-at timestamps).
  • Tap mutual-coop chip → opens coop-drawer.screen.md for that coop.
  • Long-press anywhere on profile body → moderation menu (same as [⋯]).
  • Tap footer Follow / Request colleague → confirmation sheet ("one-line context" for request; follow is one-tap with subtle haptic).
  • Tap Connected ▾ drop → context menu: "Start peer-DM", "Endorse @handle", "Disconnect", "Mute", "Block".
  • Swipe-down → dismiss to source surface.
  • VoiceOver order — handle → endorsement-count → posture → freshness → bio → surfaces → endorsements (claim-by-claim) → mutual coops → footer controls. Identity-and-posture-first, action-last.
  • Pull-to-refresh → re-pulls profile; surfaces any state changes (new endorsements, posture changes, connection state advances) with a subtle "updated" pip.
  • Tap header endorsement-count badge ★N → scrolls to endorsements section + expands all groups.

In-the-wild copy

  • (working, default empty bio) "No bio yet." — never invent one.
  • (working, connection request received header banner) "Sarah-K asked to be colleagues. They endorse bookings-tryst listing optimization."
  • (plain, blocked-by-viewer) "You blocked Sarah-K. They won't see your profile, feed, or any AE surface."
  • (plain, 404 / blocked-by-peer / erased) "This profile isn't available."
  • (plain, non-discoverable mutual-coop-only) "This peer keeps their profile to coops you share."
  • (plain, under-review banner) "Profile under review. Endorsements paused."
  • (working, endorse affordance) "Endorse @sarah-k for…" — sheet opens with scope-claim entry.
  • (plain, degraded) "Peer profile can't refresh. Showing cached data from 14:02."
  • (working, requested-stamp) "Requested 2h ago. They'll see this when they next open Peers."
  • (plain, mute-receipt) "You muted Sarah-K. They don't know."
  • (plain, moderation menu — block confirmation) "Block Sarah-K? They won't see your profile, your feed, or that you exist on this platform. You can unblock later."

Privacy invariants

  • Blocked peers see state-8 takeover — never anything else. No leakage of bio, endorsements, posture, or mutual coops. The 404 copy is identical to erased-profile to remove the block signal.
  • last_active_at is rounded to vigil (~6h buckets) — never precise. Per AE11 + I append-only audit, precise activity is internal-only.
  • No screenshot affordance + iOS screen-capture detection raises a plain banner (per coop-drawer.screen.md privacy pattern): "Screenshots of peer profiles are discouraged. This is a private directory."
  • Handle is the only copyable identifier. Bio, endorsement text, region, langs — none are tap-to-copy. Long-press shows a privacy reminder, not a copy menu.
  • Endorser handles in the endorsement list respect the endorser's posture. If @q-berlin is incognito, their endorsement still counts toward the public count but their handle renders as (private peer) — the endorsement is real, the endorser is hidden.
  • Mutual-coop chips never reveal coops the viewer is not in. The intersection is computed server-side; rows that fail RLS never reach the client.
  • No "viewed your profile" telemetry to the peer. Profile view is audit-only (I); never push-notified to the peer. Only connection requests, endorsement offers, and DM sends generate peer-visible events.
  • PII filter applies at render time too, not only at publish. If K3 list updates post-publish, the bio renders as "Bio under review" stub (per directory screen state 7 pattern).

Edge cases

  • Peer changes posture from openincognito while viewer is on the profile → screen live-transitions to state 9 (or state 8 if no shared coop) with a subtle fade; no error. Connection state (if any) persists per AE11 — past data doesn't disappear, surfacing stops.
  • Peer-DM thread exists but peer just blocked viewer → profile renders as state 8 (404), BUT the past thread in chat-home remains visible to viewer (it's audit-preserved). Viewer cannot send new messages; composer disabled in that thread with plain copy "Thread paused."
  • Endorsement count includes an endorsement from a peer who has since been suspended (AE10) → count holds (it was valid when made); the suspended endorser's handle renders as (peer suspended) in the endorser sample.
  • Mutual-coop chip for a coop the viewer just left mid-session → chip removed on next refresh; if tapped mid-stale, opens the coop drawer in "left" state (per coop-drawer.screen.md state 6).
  • Peer's handle changed since the viewer last loaded → new handle renders; small footer hint on first load post-change: "@old-handle is now @new-handle." (One-time, not persistent.) Handle history is audit-only.
  • Request-colleague with a peer who has rate-limit-capped incoming requests (AE10 anti-spam) → request still submits; peer's queue holds it; viewer sees Requested state. No leakage that the peer is at cap.
  • Long-press moderation menu opened on viewer's own self-profile → menu hides Mute / Block / Report items (those are not self-actionable); shows "Edit profile" + "Posture settings".
  • VoiceOver + endorser handle that includes punctuation → handles render character-by-character on first encounter, then by token; matches handle-screen convention.
  • Reduced motion — replace section-expand animations on "See more" / "See all" with instant reveal.
  • Dynamic Type XXL — header band wraps to 4 lines; endorsements list renders one claim per visible viewport at a time; footer controls stack vertically (Follow on top, Request colleague below).
  • Right-to-left locale (per AD-Q4) — header alignment mirrors; endorsement counts stay numerically rendered; footer button order flips per RTL action precedence.
  • Endorsement from a peer in a coop the viewer is NOT in → endorser handle renders normally; mutual coops section is unaffected. The endorsement's existence is per-peer profile data, not per-coop data.
  • Counter-action target (per I) — viewer who blocked-by-mistake can unblock; unblock writes a counter-action audit row but does NOT notify the formerly-blocked peer. Block + unblock both stay silent from the peer's side.
  • [open] Endorsement freshness decay (AE-Q5) — should the endorsement count visually weight recent endorsements higher (e.g. ★ 12 with subtle dim on >6mo-old)? Open. Lean: dim, but keep the count integer.
  • [open] Peer-DM "Threads" section in state 3 — currently sketched as 13 recent thread snippets inline. Open whether this is too aggressive a surface (might leak thread context to a shoulder-surfer). Lean: behind a tap-to-reveal toggle off by default.

Out of scope

  • The self-edit profile screen (peer-profile-edit.screen.md — separate file).
  • The full endorsements sheet (state 3 "See all" destination).
  • The endorse compose sheet (own .screen.md if it grows).
  • Peer-DM thread interior (lives in chat-home thread variant).
  • The referral-introduction compose sheet (AE1b — own .screen.md).
  • Org-attributed peer profiles (deferred to AE-Q6 P5+).
  • iPad / web variants.

Open questions

  • Endorsement freshness decay surface treatment (AE-Q5) — dim old endorser handles vs. visually re-rank vs. footnote a "freshness" line. [exploratory] (lean: dim handles older than 6mo; keep count integer; surface decay only on tap-to-detail).
  • State 3 Threads section default visibility — show inline (current sketch) or hide behind tap-to-reveal? [blocking] (lean: hide-by-default to avoid shoulder-surf leak; show on tap with subtle haptic).
  • [open] Posture badge rendering for incognito peers in state 9 — currently "—" / "private". Open whether posture should be hidden entirely from incognito-peer views (so even the existence of a posture is not signaled). [nice-to-have] (lean: hide entirely; the mutual-coop-only stripped layout is already a posture signal in itself).