Why this document exists
The authoritative mobile PRD has been living outside git as relay 8NHT5P since June. Every mobile decision cites it; no repo contains it. This document brings the PRD into the estate, folds v1 and v2, and records the scope change EC made on 2026-08-17. From here the board copy is the source of record and the relay is a snapshot of it, not the other way round.
v1 scoped mobile as a five-screen companion: Inbox, Claim, Approve/Deny, Stream glance, Settings. v2 kept that shape, named the three jobs (Act · Aware · Capture), and drew a hard line — "companion, not a port" — explicitly excluding the composer, search, series management and anything admin-shaped.
Two things have since made that line false.
The build already crossed it. Twelve screens ship today, and three of v1's named exclusions are among them — send.tsx is a composer, search.tsx is search, plan.tsx is billing. The largest file in the app, at 449 lines, is requests.tsx: the governance surface. The scope boundary stopped describing the product some time ago and nobody retired it.
The estate's own decisions entail more. DEC-023 rules that the console does not reimplement the app's own-account screens — Profile, Security, Organization, Connect, onboarding and recovery belong to the app, with the console deep-linking in. Experience 4.0 Pillar 5 rules that mobile is a first-class rendering of the same grammar, not a reduced port. Together those already oblige mobile to carry account and governance. Nobody had drawn the consequence.
Mobile is a companion surface for the work and a primary surface for the account. Depth belongs where the user chose to go, not to whichever device they happened to pick up.
v2's three jobs stay intact and stay first. v3 adds the fourth that the build and the decisions already imply.
| Job | What it means | Posture |
|---|---|---|
| Act | Approve or deny what's waiting on you, in seconds, from the notification where the OS allows. | Ambient · notification-first |
| Aware | A 5-second read on where things are, with useful context resurfaced through the day. | Ambient · glanceable |
| Capture | Speak or share a thought on the move. The phone is the sticky note. | Ambient · one gesture |
| Attend | Handle things about Relay — your profile, your hats, your library, your people, your plan, your support requests. | Deliberate · you navigated here on purpose |
Attend is the new one, and the distinction that makes it safe is posture, not depth. Act/Aware/Capture are things that happen to you and must be resolvable in seconds. Attend is what you opened the app to do. A user who taps into workspace management has declared intent, and serving them a deliberately impoverished screen because they are on a phone is the failure GUIDE.mobile-composition.md exists to prevent.
Experience 4.0 §The 4.0 bar, rule 3 currently reads:
taste (MCP) → excerpt (mobile) → full (web app)
As written this makes every Attend screen off-canon. Proposed amendment: depth is a property of the view, not of the surface.
This preserves the rule's real intent (a response that reproduces the app page is at the wrong tier) while removing the reading that mobile is permanently the shallow tier. Ratification required — see §8, D3.
Honest state of relay-app/mobile/, so planning starts from the code rather than the PRD.
| Screen | Lines | What it actually is | Job |
|---|---|---|---|
index.tsx | 346 | Home — status glance + Today strip | Aware |
notifications.tsx | 317 | In-app Needs-You feed + activity. Not push. | Act · Aware |
requests.tsx | 449 | Approve/deny. Largest screen in the app. | Act |
capture.tsx | 297 | Capture inbox + structured draft | Capture |
send.tsx | 347 | Composer with recipient picker | Capture |
search.tsx | 146 | Global search + Code lookup | Aware |
settings.tsx | 340 | Settings; profile edits name only | Attend |
plan.tsx | 154 | Plan / billing | Attend |
graph.tsx | 491 | Graph — ego view | Aware |
connect.tsx | 176 | Loop referral programme — see §5 | — |
claim.tsx | 96 | Code entry → claim | Act |
relay/[pin].tsx, transfer/[code].tsx | — | Relay detail | Aware |
(auth)/, (gate)/ | — | OTP sign-in; NDA/EAPA legal gate | — |
Not present: push notifications (expo-notifications is not in mobile/package.json — the dependency is not installed), any files/library/shelf surface, any workspace or org switching, any people-connections surface, any support surface.
Design-system state: mobile/lib/theme.ts is a third design system — GitHub-Primer dark (#0d1117, #010409, #30363d), accent #00D9C8 (motion-only under v3.5, and the upstream source of the rogue teal that leaked into web status dots), System/Courier New fonts behind a TODO. Accessibility: one accessibilityLabel across 194 Pressable/TouchableOpacity references. Tracked as RCTX-443 and RCTX-763.
The single most useful finding in this rewrite: mobile is not uniformly behind. Three of the surfaces EC named have no implementation on any platform, and their concepts are already drawn at 390px. For those, "port from web" is not a slower path — it is a more expensive one, because composing at desktop width and reducing is exactly what GUIDE.mobile-composition.md forbids.
| Surface | Web today | Mobile today | Direction |
|---|---|---|---|
| Files / Library / Shelf | None — catalog.ts has zero shelf/library references | None | Mobile-first |
| Workspace (hats) | Functions only, no shell switcher | None | Mobile-first |
| Capture | Follows | Shipped, 297 lines | Mobile-first (already) |
| Governance / requests | requests.ts | Shipped, 449 lines | Parity — mobile is strong |
| Profile | profile.ts, handle-profile.ts | Name only | Port to mobile |
| Connections (people) | connections.ts, network.ts | None | Port to mobile |
| Relay quick-info | Partial | Detail screen only | Blocked on a ruling |
| Support | 3 report sinks + Freshdesk | None | New on both |
The nouns settled 2026-08-08 — Library (container) / Shelf (set), display-layer only. The schema landed. PRD — Cloud Files is written and awaiting ratification. Concepts exist for desktop (files-v3.html, app-v35/files.html), for governance (console-v35/org-ia.html) — and for the phone (shelves-mobile-v1.html). What does not exist is a single line of surface code, on either platform.
Build the 390px composition first and let desktop earn the extra space. This is the law as written, not an exception to it.
Hard problem, stated honestly: browsing is table-shaped, and tables are the worst case at 390px against a law that bans chip walls, caps chrome at two rows, and demands content hold ≥70% of the fold. shelves-mobile-v1.html and the console's shared DataTable are the two pieces of prior art; neither has been proven at volume. The regime table (BRIEF.context-map-density.md, ~50 / ~500 / ~5,000 objects, phone column first) is a dependency, not a nicety.
Precise state:
orgs.ts:164 exports switchWorkspace() → /api/user/v1/workspace/switch, and switchOrg() → /org/switch (which reissues the JWT). Both are functions on the Orgs screen. There is no shell-level hat switcher, and 4.0 requires the switcher be the one place a user changes hats and that it re-gate the shell.Do not mistake account switching for hat switching
Mobile shipped multi-account switching (mobile/lib/session.ts, context/SessionContext.tsx, SecureStore — see relay-app docs/DESIGN-NOTE.mobile-account-switching.md). That moves between your Relay accounts. A hat is a workspace boundary inside one account. The two are different axes and the shipped one is easily read as covering both. It does not.
v2 put multi-workspace switching out of scope, reasoning: "tied to unresolved user/org architecture issues; revisit when that lands." That blocker has cleared — workspace custody is live on the backend, feature-gated, with zero app surfacing. The deferral was correct in June and is stale now.
Because no surface exists anywhere, mobile can ship the first hat switcher, and the touch target is the honest test of the concept: a hat that seals rather than filters, where positions never move (hats-and-lenses-v1.html; the v40 app concept already makes hats touchable).
Mobile has no people-connections surface. connect.tsx is the Loop founding-member referral programme (loopApi, referral code, short URL, sponsor email).
"Connect" names three unrelated products across the estate:
| Meaning | Where it lives |
|---|---|
| Connector setup (MCP host handshake) | web connect-wizard.ts, connect-panel.ts, connect-catalog.ts; Connect × Activation |
| Loop referral programme | mobile connect.tsx |
| Connections / people | web connections.ts, network.ts |
This is worse than the "lens" drift the lens glossary is meant to fix: there, one word carried seven shades of one concept; here one word names three different products, and the phone shows the least common of them.
Proposed resolution — folds into the lens-glossary ratification (Wave 0 #3), enforced by relay-creative scripts/check_vocab.py:
connect.tsx → loop.tsx)Gating collision
relay-app gating.ts's streamlined preset hides network, team, contract and refer for curated cohorts. If Network becomes a core mobile job it lands on a surface switched off for exactly the users being onboarded most carefully. Resolve when Network ships on mobile — either Network leaves the streamlined hide-list, or mobile respects it and the job is scoped to non-streamlined users.
"See useful information about a relay you have access to, without claiming it" is the document reading of the view tier. DEC-027 shipped enforcement (view = read-only at the claim gate, with grandfathering), but Sharing Direction — document vs handoff — is decision pending, gate RCTX-1259, with the groups UI and share-dialog tracks (RCTX-1260/1262) behind it.
Any mobile preview surface built before that ruling is building on an unratified model. No mobile work starts here until RCTX-1259 lands.
Mobile Settings shows email display-only and edits name alone; UpdateProfileRequest carries no email field. Bring over the additional-emails list with Verified / Pending / Backup state, add-email → OTP verify, handle management. Tracked as RCTX-373, spec at specs/profile-page-spec.md. Well-understood; no decisions required.
Act/Aware/Capture never had a support leg. The platform has a Freshdesk integration (src/relay/freshdesk.py) and web has three report sinks — faults → frontend-error, refusals → gate-denied, wrong content → /feedback. None of it reaches the phone.
That is backwards. The phone is where you are when something breaks and you are away from the desk, and a support request raised there can carry device, build, session and the object you were looking at without asking the user for any of it. Scope for v3: raise a request with automatic context attachment, see its status, respond. Not a helpdesk.
Every ambient job depends on one unbuilt system, and it is the smallest genuinely-new platform piece in the programme.
Already live. The nudge engine runs and emits typed, prioritised, deep-linked signals — closing_relay (p1), expiring_series (p1), unclaimed (p2), unread_inbound (p2), each with text and a cta_url. specs/notification-layer.md has the right architecture: one signal layer, N delivery channels, with nudges.py computing identically across channels and only delivery differing. Sources are already unified — nudges, fired Cues, maintenance, inbox/claim-approval, A2A push. The payload exists; only transport is missing.
Absent. expo-notifications is not installed. No device-token registry, no APNs/FCM wiring. specs/notification-layer.md has been Draft since 2026-06-21.
Triple-filed. Three overlapping Linear issues must collapse to one before anyone starts, or the work forks:
| Issue | Title | State |
|---|---|---|
| RCTX-341 | Mobile V1 P0: push infrastructure (APNs/FCM + device token registry) | Urgent · Mobile App |
| RCTX-459 | Epic · Notification layer — push + nudge delivery (web/mobile/MCP) | High · Phase A milestone |
| RCTX-467 | Epic: Notification layer — APNs/FCM + nudge delivery, one pipe | High · no project |
RCTX-459 and RCTX-467 were filed 2026-07-10 seven minutes apart and say the same thing. Recommendation: keep RCTX-459 (it carries the Phase A milestone and the project), close RCTX-467 as duplicate, and make RCTX-341 a child of RCTX-459.
Downstream dependency: the number-match push 2FA in the concept (RCTX-64 / RCTX-134) cannot function without this layer. It is not a parallel track.
Three artefacts currently disagree while DEC-022 reads as settled:
concepts/app-mobile-v2.html, tagged current: ships a .tabbar, against shell law 5 (mobile folds, never forks).When mobile did three jobs, a companion-posture exception was defensible. The v3 scope removes that argument arithmetically. Destinations under v3: Home · Activity · Capture · Search · Files/Library · Explore/Graph · Requests · Network · Profile · Workspace · Plan · Support. That is twelve. The concept already had to demote "You" into a Menu sheet to avoid a fifth screen with only five destinations competing.
A drawer scales; five tabs do not. The exception was justified by narrow scope, and the scope just widened.
Land it in one PR or it just moves
Whichever way this rules, it must change DEC-022, this PRD, and concepts/app-mobile-v2.html in the same PR. Three artefacts disagreeing is the current failure; fixing two of three reproduces it.
Retained from the concept regardless of ruling: Menu-as-sheet (slides over the current screen, never navigates away), the persistent bell sharing Activity's badge, and Activity as one unified space for notifications and feed — which is also the two-inbox ruling (Wave 0 #6) already answered correctly on mobile.
Resequenced from v2. The change: Files/Shelves and the hat switcher leave 4.0's Wave 3 ("port the native app"), because there is no web surface to port and the concepts are already drawn at 390px.
| Phase | Scope | Gate to advance |
|---|---|---|
| A — The spine | Notification layer end-to-end (registry, APNs/FCM, delivery rules, per-category prefs, quiet hours); inline approve/deny from the shade; resurface_at scheduled delivery | Notification → decision under 15s, measured. Mute rate instrumented. |
| A′ — Shell + system (parallel, no dependency) | Nav ruling applied; v3.5 token port (RCTX-443); accessibility labelling pass (RCTX-763) | Zero unratified nav forks; every control labelled; dual theme correct |
| B — Attend I: account | Profile parity (RCTX-373); Security (passkey, sessions, device management); Plan; Support intake with auto-context | Self-serve account management with no web round-trip |
| C — Attend II: hats | Workspace/hat switcher — the estate's first; shell re-gating on switch; org switching | A hat switch re-gates the shell and positions never move |
| D — Attend III: library | Files / Library / Shelf browsing at 390px, desktop following | Regime table passed at ~50 / ~500 / ~5,000; first-time viewer finds a tap target within 5s |
| E — Network + depth | Network (people) after the naming resolution; provenance timeline; relay quick-info if RCTX-1259 has ruled; analytics cards | Naming enforced by check_vocab.py; no surface on an unratified model |
| F — Store | Polish, a11y audit pass, App Store review | See hard gates below |
(gate)/Nothing in §7 past phase A′ can be scheduled until these land. All are ratifications.
| # | Decision | Gates | Recommendation |
|---|---|---|---|
| D1 | Mobile nav — DEC-022 exception, or drawer adoption | The native shell. Phase A′ is otherwise build-ready; this is the only hold. | Drawer. See §6. |
| D2 | Scope — is Attend accepted as a fourth job? | This entire document | Accept; it ratifies what shipped |
| D3 | Depth rule — depth per view, not per surface | Every Attend screen's canon-compliance | Accept the amendment (§2) |
| D4 | Connect naming — Connect / Network / Loop | Mobile Network work; folds into the lens glossary | Accept; rename connect.tsx → loop.tsx |
| D5 | RCTX-1259 — document vs handoff for the view tier | Relay quick-info on every surface | Out of scope here — Sharing Direction owns it |
| D6 | Notification epics — collapse 341 / 459 / 467 | Phase A | Keep 459, close 467, 341 becomes a child |
| D7 | Streamlined × Network — does Network leave the hide-list? | Phase E | Decide when Network ships |
| D8 | Patent provisional — assign counsel, file | Phase F | Needs an owner and a date |
Carried from v2 where still valid, extended for Attend.
HNH24P, U3YYN5)X43WPZ), platform admins only — mobile is the priority surfaceCODE_MAX_LENGTH). Neither "ID" nor "PIN" appears in mobile copy.Found while reconciling; each needs a fix in its own repo.
| Defect | Where | Fix |
|---|---|---|
| Copy states "Codes are 6–8 characters" — canon is 4–16 accepted, 10 minted, never hardcode a length | relay-creative concepts/app-mobile-v2.html | Correct the string. This rule already cost a bug — relay-app a43470a (#707) |
Rollout-plan pointer resolves to nothing (product/Relay - Mobile V1 - Rollout Concept.md; no product/ dir exists) | relay-creative briefs/BRIEF.mobile-v1.md | Repoint at this PRD |
| Mobile absent from the capability roll-up | docs/product/feature-index.md | Fixed in this change — §3.8 added |
| Stale: "Beta · Apr 30 2026", mobile v2 parked in a Sep 30 2026 roadmap row; untouched since the 2026-04-29 migration | docs/PRODUCT.md | Partially fixed in this change — mobile row corrected; the rest needs its own pass |
Retired vocabulary in live concepts: app-mobile-v2.html ("Relay Code" ×1, "pulse" ×2), mobile-v1.html ("PIN" ×1), map-mobile-concept.html ("Little Rocket" ×1) | relay-creative | In vocab-baseline.txt, so CI is green. Burn down and re-baseline |
| Concept header badges "Design system v3.5"; the build is Primer-dark v3.4-era | concepts/app-mobile-v2.html | Correct as a specification; misleading as a status label |
Confidential — Relay Context Inc.