BRIEF.code-presence.md — Code presence as display state (DEC-025)#
Project: relay-creative / display direction for DEC-025 identity-and-codes Status: direction proposal — nothing here binds app work until EC ratifies Live concepts: /concepts/code-presence-v1.html (app surfaces) · /mcp/code-presence-display.html (MCP rich + plain tiers) Upstream decision: relay-platform docs/DESIGN-NOTE.identity-and-codes.md, docs/GUIDE.identity-and-codes-rollout.md (branch claude/relay-code-access-control-ydwgbj) · Linear RCTX-1066 · relay AJA9TN Date: 2026-08-01 · v3.5 neutral-first
Why#
Today every object mints a Code at creation, shared or not. That makes the Code wallpaper — the most distinctive thing in the product, rendered on things nobody ever handed to anyone.
DEC-025 fixes it: a Code exists if and only if an actor deliberately elevated context for handoff — human, agent, or system alike. Presence of a Code is evidence of that act. Absence means the object never left.
The consequence for design is blunt: most objects will have no Code. Every surface built on the assumption that a Code is always there now has a second state it was never drawn for.
The question this brief answers#
Not "what replaces the Code." That was tried and rejected — see Rejected drafts.
How does a surface look when the hero artifact is legitimately absent, without the absence reading as an error or a missing field?
The answer the design must express, in one line a person could say out loud:
A Code means someone chose to share this. No Code means it never left.
Both states are correct and normal. The difference should be readable at a glance, and neither should look like the other one broken.
The four laws#
L1 · Mark presence, never absence#
No placeholder. No em-dash in the Code slot. No dashed outline, no ghost lozenge, no "Not shared" stamp on the row. **A symbol that stands for nothing happened is exactly what reads as an error** — a person scanning a list cannot tell your deliberate em-dash from a failed fetch, and neither can a screen reader.
Shared objects are marked. Unshared objects are simply quiet.
L2 · One teal moment per object#
The accent tracks where the intent lives:
| State | The one teal moment |
|---|---|
| Shared | the Code — --accent-text, mono, the brightest thing in the card |
| Unshared | the invitation — the share action, --accent-fill + --on-accent |
So the surface never loses its accent and never doubles it. On an unshared object the brightest element is the act that would create a Code — which is the honest hierarchy, because that act is the only thing of consequence available on that object.
This also gives the two states a shape-level difference (a mono token vs. a filled button) rather than a colour-level one, which survives greyscale, low-vision, and the plain-text MCP tier.
L3 · The title never changes rank#
Same slot, same size, same weight, same colour in both states. Nothing is promoted to fill the gap.
The title's job is recall — finding your own work again. The Code's job is sharing. Different jobs; they do not compete and they do not substitute. On an unshared object the title is simply a label, which is what it always was.
This law is the direct lesson of the rejected draft, and it is the one most likely to be violated by accident: a designer sees empty space where the Code was and reaches for the nearest text field.
L4 · Absence is addressable, not annotated#
On list surfaces, "never shared" is a filter you can select, not a badge stamped on every row. Make the set selectable and absence becomes legible as a population — "41 of 68 never left this workspace" — instead of a mark repeated 41 times that each individually looks like missing data.
This is what satisfies "absence of a Code is information and should be legible in the UI" without violating L1.
Surface-by-surface#
| Surface | Shared | Unshared |
|---|---|---|
| Relay detail | Code in the hero slot (.relay-code-hero), copy affordance, provenance line | Hero slot absent — the layout closes up, it is not left empty. Title in its normal label slot. Share is the primary action. |
| Share / elevation | Code hero at full weight; this is the surface the Code was designed for | The panel is the act — the Code slot is occupied by the invitation until the moment of elevation, then by the Code, arriving once |
| List / inbox | Code in the leading rail, mono, --accent-text | Rail carries nothing. Row is otherwise identical. Segment control (All · In circulation · Never shared) carries the state |
| Claim / notification / onboarding | unchanged — sharing is the subject on these surfaces, the Code stays hero | n/a — a codeless object cannot reach these surfaces |
The old canon line — "the Code is the product — hero artifact on every relay surface" — is narrowed, not replaced:
The Code is the hero artifact on every surface where sharing is the subject: share panel, handoff, claim, notifications, onboarding.
The elevation moment#
This is where the Code is most hero, and lazy mint makes it better: minting on every object makes the Code wallpaper; minting on intent makes it earned. The Code's first appearance is now an event with a cause, so it gets the one event animation on the surface — .rly-materialize (pulse family #7, characters land top-down, 55ms stagger, once, system teal) and nothing else moving.
Copy stays at capability level, never mechanism — RCTX-1073. "A Code means someone chose to share this" clears the gate. Describing how minting works does not. UI labels name the act ("Share this relay"), never the machinery.
Rejected drafts#
Kept here so they are not re-proposed.
| # | Draft | Why it fails |
|---|---|---|
| 1 | Title as hero on codeless objects | Rejected by EC. Title is the most generic field in the schema — every competing tool has one. It cannot carry a verb surface, and promoting it teaches that the hero slot is a container for whatever is available rather than a claim about identity. |
| 2 | Title in Code costume — mono, uppercase, letterspaced in the Code slot | A costume. It fakes the artifact with a field that has none of its properties: not short, not shareable, not unique, not speakable over a phone. Worse than #1 because it is #1 wearing a disguise. |
| 3 | Show the ID (r7f3a91c…) where the Code would be | IDs are lowercase hex machine identifiers and are not a display surface (invariant C2). Their narrow alphabet is deliberate — it keeps them shape-disjoint from uppercase Codes, which is what lets one resolver dispatch on shape alone. Rendering one to a person leaks a machine handle and invites the demand to prettify it, which is identity churn. |
| 4 | Em-dash / "—" or "Not shared" chip in the Code slot | Violates L1. Indistinguishable from a failure state at a glance, and it spends the row's attention budget on a non-event. |
Accessibility (non-negotiable)#
- Entity prefix glyphs are visual-only and must never reach the accessible tree as raw text:
aria-hidden="true"on the symbol plus a visually-hidden plain-English type label. A screen reader reads "Relay A3BF72KQ", never the Unicode character name. - The absence of a Code produces no announcement. There is nothing to say; saying "no code" would be narrating a non-event to the people least able to ignore it. State reaches assistive tech through the list filter and through the share action's own label.
- Codes as text use
--accent-text(5.5:1 light / 10.4:1 dark). Teal fills use--accent-fill+--on-accent; dark mode takes the#0d1514ink-flip — white-on-teal is 1.9:1 and is never used. - The one event animation disables under
prefers-reduced-motion; the Code still arrives, it just arrives instantly. - Finding, needs a call: the ratified light-mode button fill
#0d9488is 3.74:1 with a white label, which fails AA at the 13px/600 used on product buttons.relay-tokens-v35.cssalready names the alternate inline (swap to #0f766e for strict AA, 5.5:1), and both concept pages use it, because under L2 that button is the unshared state's one accent and carries the law. Dark is unaffected:#2dd4bfwith the#0d1514ink flip is 9.9:1. Either ratify#0f766eas--rly-accent-fillin light, or accept that primary button labels must be ≥18.66px bold to qualify as large text.
MCP tiers#
Per-harness rich/plain — relay-platform docs/DESIGN-NOTE.mcp-rich-display.md, rui-* kit in src/relay/mcp/ui/.
- Rich: the same four laws, expressed in
rui-*components. Code hero in the card head when shared; head closes up when not, and the card's action row carries the invitation. - Plain: bracketed type labels, never Unicode glyphs —
[Relay] A3BF72KQ. An unshared object renders its title alone with no bracket-and-token line at all. L1 holds hardest here: in a plain-text transcript, any placeholder character is indistinguishable from a truncation artifact.
What ratification unblocks#
relay-app REFERENCE.product.md:47 and REFERENCE.screens.md:19 both still carry the old "hero artifact on every relay surface" line. Rewriting them is gated on this direction, and they in turn gate all Train 3 app work.
Note for sizing: the rollout guide's touchpoint #1 counts 52 code references in relay-detail.ts. That is the mechanical part. The real work is the visual hierarchy of every relay surface, and it is not counted anywhere yet.
Open questions → EC#
- Ratify the four laws (or amend). L3 is the load-bearing one.
- List default filter — proposed
All. Alternative: default toIn circulationon the inbox surface, where the shared set is the subject. - Does an unshared object show any provenance at all (created-by, stream, time), or is the quiet state fully quiet? Proposed: yes, provenance stays — it is recall metadata, and recall is the unshared object's whole job.