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>
20 KiB
20 KiB
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 2–3 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
- Viewing a directory-discoverable peer,
noneconnection (default) — full layout as drawn. Footer:[ Follow ▾ ]+[ Request colleague ]. Both actions live. - 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. - Viewing a connected colleague — footer:
[ Following ▾ ]+[ Connected ▾ ](drop: disconnect / mute / start peer-DM). New section appears above the footer: "Threads" — 1–3 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. - 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." - 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 endorsebookings-trystlisting optimization." (Sample-endorsement line is generated from peer's accepted endorsements, not from the request itself.) - 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." - 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. - 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. - Viewed-by-non-discoverable-peer (mutual-coop-only view) — the peer's posture is
incognitoordiscoverableand the viewer has no platform-wide visibility, but ≥1 shared coop exists. Renders a stripped layout: handle + posture badge (incognitorendered as "—" / "private") + mutual coops chip row + footer with[ Request colleague ]only if the peer isdiscoverableAND 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." - 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". - 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.
- 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.
- 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."
- 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.mdfor 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-trystlisting 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_atis 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.mdprivacy 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-berlinisincognito, 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
open→incognitowhile 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.mdstate 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
Requestedstate. 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 1–3 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.
Related
- Brief AE §AE5 — profile schema source.
- Brief AE §AE2 — connection state machine driving footer controls.
- Brief AE §AE7 — endorsements section + endorse affordance.
- Brief AE §AE10 — moderation menu, under-review state, block invariants.
- Brief AE §AE11 — posture-derived rendering (states 7, 8, 9, 12).
peer-directory.screen.md— primary entrypoint.chat-home.screen.md— peer-DM thread row pattern reused in state 3.coop-drawer.screen.md— destination of mutual-coop chip tap.- Brief N — mutual-coop intersection source.
- Brief K §K3 — PII gate on bio + render-time filter.
- Brief AD — bio render in viewer's locale (per AD3 re-authoring).
- Brief I — every connection action + endorsement + block is an
agent_actionsrow. - Brief V — erasure-vs-block indistinguishability in state 8 + 12.
- Brief X — VoiceOver order + reduced motion.
- Brief O — surface_kind vocabulary.
00-system-voice.md§V2b working / §V2c plain.
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.mdif 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 forincognitopeers 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).