The one place a person lands when Relay tells them to talk to a human — relayctx.com/support. Public, no session, arrived-at-from-a-failure: a textbook public view. This concept puts it on the shared .shell and works through the states the shipped page has to survive — including the ones nobody designs for, like the ticket system being down.
Tell us what you need and we'll follow up by email. You don't need to be signed in.
The form's real job isn't the form. It's that any URL can pre-compose a ticket: the app hands someone a link that's already filled in, and all they do is read it and press send. Build one and apply it to the demo above.
| Param | Behaviour |
|---|---|
topic | Selects the dropdown. Known set: account-merge · billing · access · bug · other. An unknown value falls back to "other" and the raw string rides along in a hidden field, so a topic we invent later still reaches the ticket instead of being silently dropped. |
ref | The 4-hex correlation code minted when the server logged the error that sent them here. Rendered read-only — it's evidence, not input — and carried into the ticket body. Support resolves it in the console Error Log by [ref=XXXX]. |
email | Prefills reply-to, still editable. The person may want a different inbox than the one that failed. |
subject · message | Optional full compose. This is what makes a link "a ticket waiting for a signature". |
Every one of these is reflected into HTML from an attacker-controlled query string on an unauthenticated page, so all five are escaped on render — the shipped implementation has a test per param asserting it.
The .shell was drawn for terminal states: a message, at most two buttons, done. This is the category's first surface that asks the person for something, so it needs one addition — and only one:
| Slot | Contract |
|---|---|
.form | New. Sits between .body and .actions. Left-aligns (centred labels are unreadable); inherits every token otherwise. Mono micro-labels, one field per row, prefilled fields ring in --rly-accent-ui so a person can see at a glance what the link filled in for them. |
.code | Omitted. Empty ⇒ hidden, per the anchor's rule — there's no HTTP status to shout. |
.status-strip | Neutral on entry (support · no sign-in needed), accent on success, --rly-error on a rejected submit. Same tint rules as every other member. |
.support | Empty on entry and on a clean send — there's nothing to quote. It earns its place on the degraded outcome, where the ref is the only thread the person has back to us. Same slot, same mono treatment, same lookup vocabulary as the error members. |
This person already hit one failure; that's why they're here. If Freshdesk is unreachable when they press send, showing them an error is a second dead end in a row, and they leave. So the degraded path still thanks them, still gives them a ref, and tells them what to do if it goes quiet — while the failure itself lands in the console Error Log for us. Flip to ticket system down above: the person's experience barely changes, and that's deliberate.
/support went live to kill a 404 that the app was already linking into, and it was styled from the /feedback page's inline token block — so it reads as Relay, but it's a bespoke chrome, not a member of this category. This concept is the proposal to fold it in. Adopting it is a reskin of the render layer only: the routing, prefill parsing, escaping, Turnstile, rate limit, and Freshdesk call all stay exactly as they are.
.form addition.data-theme stamp — every state reads in both.public-* reports on a working form would poison the spike thresholds. The ref it displays comes from the server, not from a client-minted PB-… code.