Skip to content

Apply the execution-desk design system across Mail, RFQ and the app shell - #1

Merged
gstohl merged 35 commits into
mainfrom
claude/app-design-system-inspect-ed419e-7dcg3k
Sep 4, 2026
Merged

gstohl merged 35 commits into
mainfrom
claude/app-design-system-inspect-ed419e-7dcg3k

Conversation

@gstohl

@gstohl gstohl commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Twenty-six commits taking the product from "correct but hard to act in" to a desk you can work at. Every change was verified in the running app at 320–1440px; typecheck and the unit suite (1390 tests) pass on every commit.

Behaviour

  • Opened state survives a reload. It lived in React state alone, so a refresh marked a whole mailbox unread. It now persists per chain and account, bounded, cleared by Forget this device, and written when a record is opened rather than from an effect that could file one account's history under another's key.
  • The mailbox can be searched. There was no search at all; with sealed senders, felt addresses and fourteen record types, fifty records were unnavigable. The rail searches what this device already decrypted and says so.
  • A counterparty named once is named everywhere. Naming was stored against a single envelope and shown nowhere. It now belongs to the conversation, carries to records decrypted later, and appears in the rail, the thread head and search — with "Named on this device · not authenticated" travelling with it.
  • Every gate offers the thing it asks for. Compose on a disconnected desk set an error and pointed at the header; the mailbox, the RFQ recovery card and the counterparties book now carry the wallet control itself. An empty mailbox gives one answer instead of three.
  • Reply is reachable. It lived only inside a thread record, so it scrolled away; it sits in the reading toolbar.

Information architecture

  • The RFQ ticket leads its view. The CoinGecko board — whose own labels read "Not an RFQ quote" and "NON-EXECUTABLE", behind an opt-in that discloses the visitor's IP — collapses under it.
  • Active and Activity were two tabs over one set of records; they are one Records view with a scope control. #active and #activity still resolve there.
  • Operations left the tab bar for the row of separate operations.
  • Conversation-level actions (reply, naming) render once per conversation instead of once per record.
  • Four standing caveats that never changed were cut; the three that earn their words — the final value review, the send confirmation, the unencrypted-seed choice — are untouched.

Design system

  • Record colours mean one thing each again: accent for what you are doing, teal for public evidence, mauve for negotiated terms, danger for severity. Mauve had become a second error colour and a caption colour.
  • Amounts group: 25,000 STRK, not 25000, from one shared helper per module.
  • Prose keeps a measure; the composer no longer lets you write at 110ch and read at 68ch.
  • Destructive actions stopped flanking primary ones; commit actions stopped falling back to the browser's grey button.
  • The sidebar, sub-navigation, disclosures and dialogs share one control language.
  • Connecting a wallet is the strongest control in the header when disconnected; the wallet picker is on the desk instead of arriving as a rounded pastel card.

Bugs found on the way

  • Counterparties imported .masthead, .mastheadCopy and .eyebrow from a module where none existed, so its title and intro rendered unstyled and edge-to-edge. A new test now fails the build on any undefined CSS-module class; it also caught a dead modifier in the Privy vault.
  • /rfq/operations marked both sub-nav chips as the current page.
  • The drawer breakpoint in CSS (1023px) and JS (900px) disagreed, leaving a closed drawer focusable off-screen in between.
  • The operations dashboard painted an empty cell whenever a section had an odd number of facts.
  • A maker excluded because the directory was stale was labelled "Excluded" beside its own positive rationale.

Not covered

The localnet-only surfaces — the private-intent ticket and the quote-comparison screen — could not be exercised without a devnet, so they are reviewed by code only. tests/ui/app20.localnet.spec.ts has three assertions updated for renamed UI and wants a run against a live localnet before merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB


Generated by Claude Code

The reading pane repeated what the rest of the screen already said. Every
letter carried a boxed "recipient identities are sealed and absent from
MessagePosted" note between its header and its body, so a letter opened with
boilerplate instead of prose — while "What the chain sees" states the same
absence directly under the public record, and the message meta row already
carries the recipient count. The letter sheet's "N recipients · count public"
stamp said the count a third time. Both are gone; nothing is lost, because
the chain panel and the meta row keep the facts where they are decidable.

The per-message reply row and the rail's standing note are cut to one line
each for the same reason.

The thread head was the constant "Correspondence" under a kicker that
duplicated the reading toolbar's, so the pane never said which record was
open. It now names the counterparty and its detail, reusing the rail's own
conversationCorrespondent so neither pane can call the same counterparty
something different; the helper moves to correspondent.ts, which both panes
import. The section keeps a stable "Correspondence" accessible name, and the
localnet spec addresses the focused heading by id rather than by prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Compose on a disconnected desk set an error notice and called
setSidebarOpen(true) — a no-op on desktop — so the screen's primary action
failed and pointed at a control in the app header, the weakest affordance on
the page. The welcome CTA now offers the step that is actually available:
SelectWallet, restyled to the desk's square orange control instead of the
shared rounded CTA. The compose gate carries the same control inside its
notice, so a Compose click from the sidebar still resolves where the user is
looking.

Only one of the two asks at a time: while the gate notice is up, the welcome
sheet drops its own connect block and its copy, which would otherwise repeat
the notice sentence 60px below it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
The design system assigns orange to what you are doing, teal to public
on-chain evidence, mauve to negotiated terms. The mail module had drifted on
all three, so none of them carried information any more:

- mauve had become a second severity colour (Forget this device, restore
  warnings, wallet errors, preflight errors, Remove attachment, Delete draft)
  and a generic caption colour (every rail row's provenance line). Severity is
  --shell-danger; the row captions are plain secondary text. Mauve is now left
  only on deal surfaces: the deal sheet and fields, the terms sentence, the
  claimed payment address, the deal-kind toggle.
- teal headed the rail's "local browser index" note and stamped the welcome
  sheet "LOCAL ONLY" — the opposite of public evidence — and tinted the
  selected row. Selection is accent, and both local-state labels are neutral.
- orange sat on the inert rail total and on every "decrypted on this device"
  footer, competing with the controls that are actually actionable.

A standing caveat is neither a deal nor an error, so .actionWarning takes the
design system's risk frame instead of the deal frame.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
… send

You wrote at ~110ch and read at 68ch: the letter body is capped at
--measure-reading in the reading pane, while the composer's fields spanned the
whole 880px pane. The composer's prose now takes the same measure. The
recipient field is exempt and sized to fit a full felt on one line, because an
address is an identifier rather than prose.

Delete draft was a full-width control directly under Send encrypted message —
a destructive action at the primary action's weight and adjacency. It moves
below a rule, right-aligned at its own size.

Two hints were mono at 9-10px, which is the desk's label face, and one of them
restated the PUBLIC column of the privacy strip two rows above. Both are now
body text at a readable size, and the attachment note says what happens to the
document instead of describing the UI's own design decision.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
FOLDERS was a single-column list with boxed counts; SHOW was a two-column
grid with a smaller face, plain counts, an orphaned last cell and a solid
orange chip flush to the window edge — two controls doing the same job, in
two geometries. SHOW now uses the folder list's rows, counts and active
treatment, and both inset their text to the same 12px as Compose, which no
longer spans the pane edge-to-edge like a title bar.

"Encrypted mailbox recovery" and "Device safety" were collapsed sections with
no disclosure affordance at all, so they read as headings for empty regions;
they take the design system's +/- marker.

Checking for mail is the sidebar's most-used action and was two unequal
buttons crammed into "1fr auto", the primary one reading as a muted box. Both
now fill the panel, and the section heading matches the labels above it
instead of being a third heading style.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Every row carried the same fact three times and its identity twice:

- "Sealed sender" and "Sealed counterparty" differed only by envelope type,
  which says nothing about who sent a record — no incoming record carries a
  public sender. One name now covers all of them, and it drops the caption
  that restated it; the rail's standing note already says sealed rows have no
  public sender.
- The preview line repeated the type badge directly under it ("Invoice ·
  12,400 USDC" over INVOICE). Previews now spend themselves on terms — both
  legs of an offer or escrow, the amount requested, the reason a deal was
  declined — and say plainly when a payload cannot be read instead of falling
  back to the badge's own word.
- Read state and provenance shared one slot, so POSTED ON-CHAIN read as a
  third value of unread/opened. Provenance is a chip beside the type badge,
  and the state slot belongs to incoming mail.
- A 66-character felt was the row's primary identity and wrapped to two lines,
  breaking the row rhythm; the rail uses the same shortened headline as the
  thread head, with the full felt on hover.

In the thread, consecutive records had no boundary, so one message's "sealed
on this device" footer read as a heading for the next. They are separated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
repeating themselves

Between 1024px and 1180px the fixed 236px sidebar and 320px rail left the
reading pane under 470px, so a letter wrapped at roughly 40 characters while
the sidebar sat mostly empty. The sidebar becomes a drawer at 1180px instead,
and the JS breakpoint that marks the drawer modal now matches the CSS one —
they were 900px and 1023px apart, which left a closed drawer focusable
off-screen in between. Back to messages is scoped to the phone layout, where
the list is actually off-screen.

On phones the top bar said "APP20 Mail" directly under a tab bar whose current
tab says MAILBOX, above a rail heading that repeated the folder name — three
rows of chrome for one fact, in a viewport that was already 40% chrome before
the first message. The bar now names what the pane is showing, with the active
type filter beside it, and the rail heading stands down.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Replying lived only inside a thread record, so on a long correspondence it
scrolled out of reach. The reading toolbar carries it while a record is open,
and it stops spending its title line on the folder name that the rail already
shows two panes to the left.

Drafts: the recipient felt was the row's primary identity at full length, the
same wrap that broke the message rail's rhythm, and every row wore a DRAFT
chip inside a pane titled Drafts, under a note that named the active type
filter for no reason.

Key setup: the button that registers a mailbox is disabled without a wallet
and said nothing about why. The consequence of declining device encryption —
the raw seed sits in the clear, and anyone with the profile can send payment
requests that display as verified from you — was 11px fine print under an
optional-looking checkbox; it takes the risk frame the design system has for
exactly this. Prose across the module keeps a measure instead of running the
full pane.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Every incoming record in a thread carried an open address-book form — label,
book select, input and a submit button — between its meta line and the letter,
for an act (naming a sealed sender on this device) that is occasional and
deliberate. It collapses behind a disclosure, like the public record below it.

The conversation note and the button that continues the conversation shared a
stacked grid, so a record spent three lines on chrome before its first word.
They share one line, and only the longer notes wrap onto their own.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Every amount in the product printed ungrouped: a desk reads 25,000 STRK at a
glance and has to count the digits of 25000. formatBaseUnits groups the
integer part with literal commas — never the fraction, and never through a
locale format that could turn a decimal point into a group mark.

Inside a deal card, the protocol truth ("value moves now, this is not an
atomic swap") and the procedural note ("accepting takes two approvals") wore
the same frame, so neither ranked. The consequence keeps the frame; the note
takes a rule, which also stops the card from nesting a box in a box in a box.

A receipt printed its transaction hash twice at full length — once inside the
sentence, where it broke the line, and once as the copyable record below — and
ended with the raw protocol constant "one_sided_v1" under the word "warning".
The sentence carries a shortened hash and says what the constant means.

Prose in the module keeps a measure; the sheet type and meta line keep the
mono label face that the desk is built on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
The dismissal shared the stretch treatment of the pay actions, so on an
invoice with nothing payable from this rail it took the whole action row and
looked like the card's primary button. It keeps its size and sits at the end
of the row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Connecting a wallet gates every module, and it was the weakest affordance on
the page: a rounded legacy pill that resolved to bare orange text beside a
bordered Pay link and a solid accent network chip. It becomes the desk's
square accent button, and a connected wallet turns into a quiet bordered
status chip, because a connected wallet is state rather than an action.

The active network then sat as a second solid accent block flush against it,
so the two read as one control. Which network is current is state too, and
the header already marks current with the tab's inset accent rule; the chip
uses that instead. On phones both wallet controls keep the accent and lose
the width they do not need.

Checked for horizontal overflow at 320, 360, 390, 768, 1024, 1180 and 1440.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
The dialog that the mailbox's own accent button opens arrived as a rounded
pastel card with a soft drop shadow and a circular close button — the one
surface in the flow that still looked like a different product. It takes the
desk's dialog: square, accent hairline, hard offset shadow, square controls.

The mailbox connect wrapper also styled every descendant button, which
repainted that dialog's close button and wallet rows in solid accent; it
styles its own trigger only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
The view's own navigation was 8px mono — the smallest type on the page, for
the control that switches between its four screens. It reads at the size the
app header and the mail sidebar use.

View titles were set in the reading serif at regular weight, the face the
design system reserves for what a human wrote; New request, Active, Activity
and a record's own heading are UI, so they take the body face at display
weight.

Activity had no container: its heading and two paragraphs of storage prose ran
from the window edge to the far side of a 1440px viewport, beside cards that
were inset. It gets the same 16px inset as Active, and that prose keeps a
measure.

On the operations route the router marked /rfq current as well, so both chips
in the sub-nav claimed to be the open page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
A disabled rail said the same thing three times — the environment banner, a
wallet-recovery card, and the ticket's own notice — and the only button among
them was a grey "Review wallet connection" that re-read storage which cannot
answer without a wallet. Where the rail cannot execute an RFQ at all, the
recovery card stands down (connecting a wallet does not enable Sepolia) and
the reason leads the view at full width instead of trailing it at a third.
Where a wallet genuinely is the missing piece, the card carries the wallet
control itself, styled as the accent action rather than a browser-default box.

The public market board also stopped pretending: PUBLIC SPOT read "OPT IN" in
the slot where a price goes, and the opt-in that loads third-party data sat
inside the 1D/7D/30D group as though it were a range. The price cell reads
"not loaded", and the opt-in moved to the empty chart it fills, next to the
disclosure of what opting in discloses. The chart's placeholder no longer
reserves 300px of black to say it is empty.

Labels in the densest panel of the product were 8px mono; they are 9px, and
the eight info dots are muted until hovered so the accent belongs to controls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
The screen where a person commits value had two buttons that fell back to the
browser's grey default, so "Accept and fund" — the commit action — looked
disabled next to an equally weighted decline. Accept is the accent action,
decline is a ghost, and the details disclosure uses the desk's +/- instead of
a stray triangle.

Amounts read 25000 STRK; they now read 25,000 STRK, from one shared helper
rather than two copies of the same function in two files. The review's fact
labels join the mono label language of the tiles above them, its values and
its privacy list keep a measure instead of running the width of the sheet,
and the invited-maker panel's directory facts became a hairline grid rather
than six unaligned lines.

A maker excluded because the whole directory is stale was labelled "Excluded"
beside its own positive rationale — "Excluded · Signed a verified quote for
this exact request". The card now gives the binding reason, and keeps the
maker's own words for the makers that actually refused.

The operations dashboard painted an empty cell whenever a section had an odd
number of facts, and set its labels in body sans; the last fact spans the row
and the labels are mono. On phones the workspace sub-nav went back to 8px.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
… it is

An in-flight RFQ led with "25000000000000000000000 base units STRK →
12400000000 base units USDC" — a 23-digit integer as the headline of a trade
card. The record now reads 25,000 STRK → 12,400 USDC, with the exact integers
kept one rank below in muted mono, so nothing is lost and the line can be read.

The action on that card was another browser-default grey button, with the
sentence explaining it pinned to the far end of a 1000px row; they sit
together, and the button uses the desk's outline treatment with a real
disabled state.

Every disclosure in the workspace now carries the same +/- marker: sharing a
selector with the final review's summary had left three of them with no
affordance at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
.scratch-shot.mjs is a throwaway Playwright screenshotter used while
reviewing the RFQ views; it was swept into the frame commit by a
directory-wide add and is not part of the app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Counterparties imported .masthead, .mastheadCopy and .eyebrow from the RFQ
module and none of them existed, so the page title, its eyebrow and its intro
resolved to no class at all: they rendered unstyled and ran from the window
edge while the panel beneath them was inset. They get the desk's masthead —
a gutter, a display-weight headline, and a measure on the intro — and the
fine print reads at the same text column instead of the full window.

The empty book said a wallet was required without offering one; it carries
the wallet control, like the mail and RFQ gates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
An undefined CSS-module class resolves to `undefined`: the element renders
with no styling and the literal word "undefined" in its class list, and
nothing catches it — not the type checker, not the bundler. Counterparties
lost its whole masthead that way, and the Privy vault's recipient field
carried a modifier that was never written.

The guard scans every component that imports a stylesheet and asserts each
styles.x it uses is defined there. The vault's dead reference is removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
The funding gate was the last panel written outside the design system: its
eyebrow was a plain paragraph, its four readiness facts were body-sans label
and value pairs floating in a gap-12 grid with no rules, its section headings
were browser h3s, and every sentence ran the full width of a 1280px window.

It now reads like the rest of the desk — a mono eyebrow, a display-weight
headline, a hairline fact grid with mono labels, mono section labels, and a
measure on the prose — without changing a single statement it makes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
…arched

Read state lived in React state alone, so a refresh marked an entire mailbox
unread again and the rail's own note admitted it in small print. It now
persists per chain and account in this browser, bounded so a long-lived
mailbox cannot grow it without limit, cleared by Forget this device along with
every other local record, and written at the moment a record is opened rather
than from an effect that could race an account switch and file one account's
history under another's key.

The mailbox also had no search at all: with sealed senders, felt addresses and
fourteen record types, fifty records were unnavigable. The rail searches what
this device has already decrypted — correspondent, preview, record type, index
and device-local recipients — and says so, since a query can only reach the
plaintext this browser holds. Nothing about a search leaves the device.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
…cords

across two tabs

The public market board led the New request view: a CoinGecko chart and eight
metrics, above the ticket, in a panel whose own labels read "Not an RFQ quote"
and "NON-EXECUTABLE", behind an opt-in that discloses the visitor's IP. It is
reference, so it collapses into a disclosure under the ticket and opens when
someone asks for it. The view is now what a person came to fill in, and the
page it renders is half the height it was.

Active and Activity were two tabs over the same records, differing by a
filter, which left "which one am I in?" as a question with no good answer.
They are one Records view with an explicit scope — in flight first, because a
record you can still act on is why you opened it — and #active and #activity
still resolve there so existing bookmarks keep working.

Operations is a status page for one endpoint, mostly reading Unavailable; it
leaves the workspace tab bar for the row of separate operations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Naming a sealed sender was stored against a single envelope and shown nowhere:
the rail, the thread head and search all still said "Sealed sender", so the
work of identifying who you were talking to died at every record boundary.

A name now belongs to the conversation. It carries to every record that shares
it, including ones decrypted later, and it is what the rail row, the thread
head and search show — as the address-book label when there is one. The caveat
travels with it rather than being dropped: "Named on this device · not
authenticated", because a local label is not authentication and this product
does not pretend otherwise.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Four caveats were ambient rather than situated, and repetition was making the
ones that matter easier to skip:

- The signal bar carried a sentence that never changed on any route or in any
  state. The bar keeps live session state; each view states its own boundary
  where that boundary applies.
- Every record in a thread ended with where its plaintext lives, under a pane
  whose head already says LOCAL PLAINTEXT / CARBON COPY. Only the two
  provenances a reader cannot infer from that head — an imported payment link,
  and a Sent copy matched to on-chain evidence — still say so.
- Every record without a conversation tag said so, on every record, when the
  fact only bears on replying.
- The mail sidebar footer restated that ciphertext is public on-chain, which
  the record's own "What the chain sees" panel says in context.

The three that earn their words are untouched: the final value review, the
send confirmation, and the choice to leave a mailbox seed unencrypted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
…sation

An empty mailbox gave three instructions for one state: the welcome sheet
asked for a wallet, the rail said to check for mail, and the scan panel said
to set up a key. One derived gate — wallet, then key, then neither — now
answers in all three places. Search and the local-index note also stop
rendering above a mailbox that has no records to search.

Replying and naming a sender are acts on a conversation but rendered per
record, so a five-record thread carried five reply buttons and five naming
forms, under two different verbs. Reply lives in the reading toolbar, once;
naming lives under the thread head, once, with the reason a reply cannot
prefill a recipient beside it. A record keeps only what is its own: its
conversation tag, its sender proof, its unbound auth claim. The decorative
CLIP / 01 stamp in the thread head is gone.

The signal bar named the RFQ workspace on every route, Mailbox included; it
names the module you are in. Pay was a fourth destination styled as a wallet
utility, sitting among the account controls with an accent border; it joins
the tabs it belongs with.

On a disabled RFQ rail the banner states the rail and the notice below it
restated the same sentence; the notice explains instead. The Elsewhere row no
longer links to a public send whose own label said it was unavailable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
…osure before using it

The UI specs still navigated Active/Activity tabs, looked for a
"Public send" link and expected the market board open. They now use
New/Records, the In flight/All scope buttons and the collapsed
"Public market context" board.

The mailbox recovery panel is a closed disclosure, so its backup buttons
are not visible until opened; the two localnet specs open it first.

The ticket also left-aligns under the view heading at 560px instead of
floating centred in an empty pane.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
…d as two plain steps

The one-time privacy briefing opened as a modal the moment the ticket
mounted, so anyone landing on /rfq could not reach the wallet picker or
the navigation until they acknowledged it. It now renders in the flow at
the top of the ticket, waits for a connected wallet before its first
showing, and leaves the page usable; the ticket's own actions stay
disabled until it is acknowledged, which is the gate that matters.

The ticket also drops the three "nothing has happened yet" chips in its
idle state, names its two steps plainly ("Review what makers will see",
"Request quotes"), gives the invitation consent a proper checkbox row,
and leads each maker card with the verdict, keeping key and capacity
facts behind a disclosure.

Records: the empty state links straight to a new request, and the
storage caveats fold into one "What this browser stores" disclosure.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
The compose footer now says how many recipients a letter can carry and
no longer repeats the "recipient count is public" caveat that the
what-is-public list above it already states.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
The rail row now reads "Unread. Sealed sender. Memo. 0.1 USDC to settle"
instead of the old "Settlement memo · 0.1 USDC" label.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
#activity was the history tab, so someone arriving on it expects every
record, not only the ones still in flight. The Records view now opens in
the All scope for that hash.

The swap-routes spec still walked the pair review route, which the app
removed; that block is gone and the test says what it still covers.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
Restoring and reconciling never resubmits fund, fill, claim, or refund
for any record, so the All scope's storage disclosure states it as the
In flight scope already does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016c1PxPnJjZfByfqUjYgvKB
@gstohl
gstohl merged commit 509d0d9 into main Sep 4, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants