RelayCTX™ Relay for Chrome · as built · concept v1 · 2026-09-24
Concept · Relay for Chrome · the MVP, as merged

Every surface of Relay for Chrome, and every state it can be in.

The first-party Chrome extension shipped as Aware only: a toolbar count, desktop notifications for what needs you, and a read-only popup. This page is the design record of that build. Each state is drawn as HTML from the code that renders it, with the real strings, so it can be checked against the product line by line.

Everything on a mock is quoted from relay-app extension/src/ or from the platform's notification templates. Names, emails and Codes are fictional. Theme (top right) flips every mock between light and dark; the popup follows the OS theme the same way.

Design record of what shipped · §6 is marked not built
00 · What shipped

One job of four, and the smallest permission set that does it.

The PRD maps Mobile v3's four jobs onto the browser. The MVP is the first job only. “From a MVP, just get notifications moving. We can deal with interactions later.” (EC, 2026-09-22). No new server surface was needed and none was changed.

P1 · built
Aware

Badge with the unified count, desktop notifications for p0/p1, a popup with the latest ten.

P2 · not built
Act

Approve / deny, snooze, mark read, from the notification or the popup.

P3 · not built
Capture

Right-click Send to Relay; save a tab to a Stream or the library.

P3 · not built
Carry

Package an AI chat, paste a Code into a composer, Codes on any page.

Permissions, pinned by a test so they cannot creep:

alarmsnotificationsstoragehttps://app.relayctx.com/* content scriptstabsidentityread all sites

The one host permission is what lets the extension's requests carry the app's own session cookie. That is the whole sign-in: the extension is whoever is signed in to app.relayctx.com in that Chrome profile. It stores no credential and never refreshes the session.

01 · The toolbar icon and badge

A number, a colour and a tooltip. Seven states.

Every minute an alarm asks the app for the count. The number is unread notifications plus open nudges, the same number the app's bell shows. Above 99 it reads 99+. The colour is amber when anything p0 is waiting, teal otherwise, and grey when the extension can't vouch for the number. The tooltip is the explanation.

app.relayctx.com
Relay
A · signed in, nothing waiting

No badge. Tooltip Relay. Badge text is empty when unread + nudges is 0.

claude.ai4
Relay
B · a count

Teal, Relay. 3 unread + 1 open nudge = 4. Nothing p0.

mail.example.com2
Relay
C · p0 waiting

Amber, the app's p0 “must action” colour. Any p0 in the count turns the whole badge amber: a request to open your relay, a connection request, a relay expiring within 2h.

docs.example.com99+
Relay
D · 99+

Anything over 99 reads 99+. Chrome fits about four characters in a badge; the cap keeps it legible. Amber if any of them is p0.

claude.ai
Relay — sign in to Relay in this browser to get notifications
E · signed out

Badge cleared, tone muted. Also what an expired session and a 403 (a legal or consent gate the app has to show) look like. Every notification on screen is taken down.

claude.ai4
Relay — offline, will retry
F · offline

Network failure. The last number is kept and turns grey: it may be stale, so it stops claiming urgency. Retries on the next tick.

claude.ai4
Relay — having trouble reaching Relay, will retry
G · having trouble

A 5xx or maintenance page. Same treatment as offline: number kept, grey, nothing else changes.

urgent · amber #d97706 · any p0 normal · the light accent teal, both themes muted · grey #71717a · signed out, offline, error

Badge colours are fixed in background.ts because the toolbar has no theme; they are the extension's values, not brand tokens. The badge text colour is not set by the extension, so Chrome picks it for contrast; white is drawn here. The icon is public/icons/icon-*.png: the Open Signal mark on a dark tile in every theme.

02 · Desktop notifications

Plain text in the operating system's frame.

When the count moves, the extension fetches the latest 20 unread items and toasts the ones it has never surfaced before, and only p0 and p1; p2 and p3 stay on the badge. Oldest first, at most three a minute, then one summary. The OS draws the frame, so these are drawn as a neutral card: only the icon, the title and the message are Relay's. Titles clip at 80 characters, messages at 180, and markup in a title shows as literal text.

Relaynow
Priya Nair sent you a relay
‘Onboarding copy pass’ is ready for you to receive
p1 · transfer.sent

Title and body from the payload. Times out like any notification. Click → /transfer/K7QX2M4TPR.

stays until dealt with
Relaynow
Claim request for your relay
Maya Chen wants to claim ‘Q3 pricing review’
p0 · claim.request

Raised at high priority with requireInteraction: it stays on screen until clicked or dismissed. Click → /transfer/<Code>, where the request is decided.

The title and body are the platform's template, quoted as shipped. They still say “claim” in copy, which _vocab/_VOCAB.md retires; that is relay-platform's string to change, not the extension's.

Relaynow
A relay arrived for you
Open Relay to see it.
fallback title

When the payload carries no title, the event type picks one; when it carries no body, context title or Code, the message is Open Relay to see it.

claim.request* → Someone asked to open your relay
relay.received* → A relay arrived for you
contains connection → Connection request
anything else → Something in Relay needs you

Relaynow
Sam Okafor wants to connect on Relay
Relaynow
Relay expiring in 2h
Relaynow
Priya Nair sent you a relay
Relaynow
2 more waiting in Relay
Open Relay to see everything that needs you.
flood · 3 + the summary

Five new p0/p1 items in one tick: the three oldest toast, the rest fold into N more waiting in Relay. The summary click opens /inbox. Collapsed here to titles.

Platform frames differ. macOS, Windows and ChromeOS each draw their own card, order and timeout; Do Not Disturb applies. The MVP has no notification buttons, so nothing here is affected by macOS folding buttons under Options; that matters only for P2 Act (§6).

04 · Where a click lands

Exactly where the same row lands in the app.

Notification rows carry no URL, so routing.ts mirrors the web app's notifAction() from two fields: cta_action and the Code. It never dead-ends: anything unrecognised opens /inbox, which holds every notification. A click reuses an open app tab (navigating it and bringing its window forward) or opens one, and takes the clicked notification down.

cta_actionWith a CodeWithout a CodeTypical event
approve_claim · claim_relay · confirm_recipient/transfer/<Code>/requestsa request to open your relay; a relay sent to you; confirm the recipient
view_relay, or no action but a Code present/transfer/<Code>/inboxrelay received, updated, expiring
view_pulse/streamStream contribution, mention, weight (the wire still says pulse)
review_connection · view_connection · review_org_connection/networkconnection requests, reminders, accepted
view_requests/requeststhe request digest
anything else/inboxview_session, view_profile_links, view_org, claim_series, dismiss …
“N more waiting in Relay” summary/inboxthe flood fold (§2)
Open Relay / Sign in to Relay (popup)/the popup's one button

All destinations are on https://app.relayctx.com; the popup refuses to open anything outside it. Codes are URL-encoded and no length is assumed (legacy six-character Codes and ten-character Codes route the same). Worth a look later: claim_series and view_session fall through to /inbox, which is safe but less direct than the app could be.

05 · Behaviour rules that shape what people see

Four rules you only notice when they're missing.

01 · no backlog replay

The first look badges; it never floods.

The first check after sign-in, after signing back in following a gap, or after an account switch marks everything present as seen and toasts nothing. The badge still shows the full count.

If that first list fails, it stays a first run, so the retry can't flood either.

E1 · E9 · E10 · E15
02 · quiet while you're in the app

The app's bell already told you.

When an app.relayctx.com tab is the focused tab of the focused window, no notifications are raised. Those items still count as seen, so they don't pop up later when you leave the tab.

E19 · D2
03 · read means gone

A notification leaves when its item does.

Read or act on an item in the app, or anywhere else, and its desktop notification is taken down on the next tick. Signing out takes every one down; switching accounts takes the old account's down.

E7 · E10 · E18
04 · follows the app's account

No separate sign-in.

The extension is whoever is signed in to the app in this Chrome profile. Switch accounts in the app and it switches; sign out and it signs out. It never refreshes the session, so an expired one reads as signed out until the app is opened again.

E7 · E8 · E11 · E12 · D5

Also true, and visible: the same item never notifies twice (E4); when nothing moved, the list isn't fetched at all, one small request a minute (E6); p2 and p3 only ever reach the badge (E3); open nudges count, matching the bell (E20).

05a · Edge-case register, as the user meets it

Twenty-two cases, each a named test.

From PRD §5a; each E-row is a test in extension/src/tick.test.ts. The right-hand column is what a person sees, which is the part this page records.

#CaseWhat the person sees
E1First check after sign-inBadge with the backlog; no notifications
E2New p0 / p1One notification; click opens the item
E3New p2 / p3Badge only
E4Same item across checksNever notified twice
E5FloodThree notifications, oldest first, then N more waiting in Relay → Inbox
E6Nothing movedNothing changes
E7Signed out / session expiredBadge cleared and grey, notifications taken down, tooltip says how to fix it
E8403 legal or consent gateSame as signed out; the app shows the gate
E9Signed back in after a gapWhat arrived meanwhile is badged, not toasted
E10Account switched in the appOld account's notifications gone; new account starts clean
E11Two accounts with identical countsThe switch is still noticed
E12Account flips between two requestsThat list is dropped; nothing from the wrong account appears
E13OfflineLast number kept, grey; tooltip says offline
E145xxNothing changes; tooltip says it's having trouble
E15List fails during a first runStill no flood on the retry
E16List fails laterCaught up on the next tick
E17Malformed responseTreated as zero; nothing breaks
E18Item read or actioned in the appIts notification is taken down
E19App is the focused tabNo notifications; they don't replay later
E20Open nudgesCounted in the badge, same as the bell
E21Missing title or body; long textFallback title (§2); clipped at 80 / 180
E22Markup in a titleShown as literal text, in the notification and the popup

Not yet verified at build time: the live session cookie against production (PRD D6). The first unpacked load checks it: sign in to the app, then open the popup.

06 · What's next

Not built. Sketches, so the MVP's shapes leave room for them.

Nothing in this section exists in the extension. Screenshots and store art must not show any of it (the marketing kit's rule). Each sketch reuses a shape the MVP already has, which is the point of drawing it now.

Not built · P2 Act · RCTX-1433

Decide from where you're told.

Approve or deny a request, snooze, mark read, from the notification or the popup, on endpoints that already exist (claim-approval/{id}/approve|deny, notifications/read, notifications/snooze). Target: notification to decision in under 15 seconds. The popup is the dependable surface, because on macOS Chrome can fold notification buttons under Options.

Relaynow
Maya Chen asked to open ‘Q3 pricing review’
Code K7QX2M4TPR
sketch · notification with buttons

Two buttons, the OS limit. On macOS these may sit behind Options, so the notification can't be the only place to decide. The copy is a proposal and drops “claim”.

sketch · popup row with actions

Actions inline on p0 rows only; everything else keeps the MVP row. Org-membership approvals route separately and would need adding.

Not built · P3 Capture & Carry

Everything Relay does around AI chats that have no MCP.

Both need content scripts, so both are gated on PRD D4: optional host permissions requested per site at first use, never at install. Carry is the strategic one: an extension reaches every AI chat in the browser, regardless of vendor.

Capture · right-click

Selection, page or link into Relay; the current tab into a Stream or the library (the second-brain Pipe).

Carry · on an AI chat page

Package this chat seals the conversation as a relay; Paste Code relays one in and loads it into the composer.

Carry · Codes anywhere

A Code found on any page (mail, chat, docs) gets a hover card and a one-click receive; the omnibox takes relay <Code>.

Also ahead, outside the four jobs: P1b Web Push (RCTX-1431) replaces the one-minute poll and brings quiet hours and per-event push preferences to the extension (PRD D1); a cross-profile badge and an account switcher; the extension as a second device for sign-in approval. Each needs its own ruling.