Skip to content

UX foundation: DESIGN.md v2 + W1 (fonts + widgets.rs + slop kill) - #120

Merged
hellno merged 12 commits into
mainfrom
hellno/ux-foundation-overhaul
Jun 22, 2026
Merged

UX foundation: DESIGN.md v2 + W1 (fonts + widgets.rs + slop kill)#120
hellno merged 12 commits into
mainfrom
hellno/ux-foundation-overhaul

Conversation

@hellno

@hellno hellno commented Jun 20, 2026

Copy link
Copy Markdown
Owner

The first landable chunk of the UX foundation overhaul (epic #117).

DESIGN.md v2 — the enforceable foundation (a92fb0f)

Re-grounds the visual language as editorial (type + whitespace + hairlines, no card-default; oversized mono hero; cockpit rows) and makes it enforceable: bundled fonts, a shared widgets.rs vocabulary, the ⌘↵ key-cap confirm spec (no hold), transaction-as-hero clear-signing, the agent interaction model (first-class standalone agents + a dedicated agent surface + compact home presence + the data-driven expandability contract), the lean Activity scope, and a visual definition-of-done checklist. Decisions log records every call.

Golden references: deckard-editorial-v3.html + deckard-agent-v4.html (in ~/.gstack/projects/hellno-deckard/designs/).

W1 implementation — Closes #114

  • Fonts (the C1 fix): General Sans + JetBrains Mono are bundled and registered (add_fonts). The app rendered in the OS system font before this; verified live that it now renders in General Sans on the onboarding screen.
  • widgets.rs: one shared vocabulary (short_addr 6+4, caution_line/error_line, section_label, identity_mark) replacing the 3-4 divergent per-file copies that were the root cause of drift.
  • Slop kill: keyboard-hint chip row removed; caution boxes → inline icon+text; raw provider error → humanize_read_error; orphan → "Syncing…"; em-dash sweep; "Welcome to Deckard" → the promise; honest settings copy; "broadcast" → "sent"; stale starter doc comment.

Verification

  • cargo fmt --all --check clean
  • just check green on both default and --features tray
  • cargo test -p deckard-app -p deckard-core green (53 + 55 pass)
  • Built + ran the app; onboarding renders in the bundled font with the new promise copy (was "Welcome to Deckard").

Follow-up: wallet-home screenshot (monograms + no hint chips + mono money) needs the qa-vault unlock path; capturing next. W2 (editorial layout) #115 and W3 (agent surface + swap + onboarding) #116 build on this.

hellno added 12 commits June 20, 2026 20:02
Re-grounds the visual language (editorial: type + whitespace + hairlines,
no card-default; oversized mono hero; cockpit row layout) and makes the
foundation enforceable so agents stop drifting:

- Enforcement model: bundle the fonts (they were never embedded, the app
  ran in the OS system font), one shared widgets.rs vocabulary, a visual
  definition-of-done checklist.
- Confirm pattern: cmd+enter key-cap (no hold-to-confirm) + arm-delay,
  tiered by risk.
- Clear-signing redesigned transaction-as-hero.
- Agent interaction model: agents first-class standalone in the sidebar,
  a dedicated agent surface owning editable policy + controls + activity,
  a compact agent presence on the home, the data-driven expandability
  contract (agent = policy + activity).
- Activity lean scope (audit log + STOP) with triage/keyboard/drill-in
  documented for later.

Grounded in an 8-agent source audit + golden-reference HTML
(deckard-editorial-v3 / deckard-agent-v4).
Make the foundation real and consistent (DESIGN.md v2 §Enforcement).

Fonts (the C1 fix): embed General Sans + JetBrains Mono in assets/fonts/
and uncomment add_fonts in main.rs. The app rendered in the OS system font
before this; now it uses Deckard's real type (mono-for-money + weight
hierarchy actually apply). Verified live on the onboarding screen.

widgets.rs (the shared vocabulary): short_addr (one first-6+last-4 rule),
caution_line / error_line (one Lucide-icon caution language, kills the
`⚠` emoji), section_label (one tracked-label treatment), identity_mark
(monogram, never a blank square). Routed all views through it, deleting the
3-4 divergent copies of each helper that were the root cause of drift.

Slop kill: removed the on-canvas keyboard-hint chip row; caution boxes ->
inline icon+text lines; raw provider error -> humanize_read_error; orphan
`—` hero placeholder -> "Syncing…"; em-dash sweep in UI copy;
"Welcome to Deckard" -> the product promise; honest settings copy
(dead display_name, dropped tray-feature leak); "broadcast" -> "sent";
stale GPUI-starter doc comment.

DoD: cargo fmt --all --check clean; just check green (default + tray);
cargo test -p deckard-app -p deckard-core green (53 + 55); no new deps.
Apply the editorial language (DESIGN.md v2: type + whitespace + hairlines,
no card-default; oversized mono hero; cockpit rows). Verified live on the
wallet home (10,000 ETH hero, monograms, Shield-primary action row,
de-carded agent section, de-jargoned captions, in the bundled fonts).

- widgets.rs: add budget_gauge (4px threshold gauge, neutral->amber>=90->red>=100)
  and truncated_address (identicon + ENS + short_addr).
- welcome.rs: oversized balance hero (64px mono); agent fence DE-CARDED to a
  frameless section + budget_gauge under the spent/daily rows; action row =
  Shield primary + divider + Send/Receive/Swap ghost cluster; section_label
  for Holdings + the fence header.
- activity_view.rs: editorial dense rows, per-row hairlines (not card boxes),
  left cluster min_w_0 + truncate so nothing overflows the pane (the historical
  horizontal-overflow bug), amber NEEDS YOU band, section_label day bands.
- commit_view.rs: transaction-as-hero review (amount as the big mono hero,
  recipient via truncated_address, quiet supporting facts), hold_to_confirm
  wiring preserved (the cmd+enter swap is a focused follow-up).
- swap_view.rs: editorial compose (sell/receive large mono + asset chips +
  quote/route strip) + transaction-as-hero review; fixed a borrow-across-mut-cx.

DoD: cargo fmt --all --check clean; just check green (default + tray);
cargo test -p deckard-app -p deckard-core green (53 + 55); no new deps.
…ibsted Grotesk (#116)

Make the agent first-class (DESIGN.md v2 §The agent interaction model).
Verified live: sidebar AGENTS group + Atlas, the agent surface (Personal ›
Atlas), in the bundled font.

Agent model:
- New Selection::Agent + (Selection::Agent, Surface::Home) dispatch + a
  standalone sidebar AGENTS group (Atlas: cyan squircle + name + status dot)
  that select()s the agent and kicks the live policy.
- New agent_view.rs render_agent_surface: identity + acting status + Revoke &
  STOP (the existing two-step brake) + a plain-language autonomy statement
  (cap derived from policy) + LIMITS (the live policy rows) + budget_gauge +
  controls (Pause / Rotate / Adjust shown disabled until wired; honest, not
  no-ops) — built entirely from agent_policy (the expandability contract).
- welcome.rs: the home's full policy fence -> a COMPACT agent presence row
  (squircle + Atlas + status + thin budget_gauge + chevron) that links to the
  surface. Deleted render_agent_fence; agent_policy_rows + fraction are now
  pub(crate) and reused by the surface.
- palette.rs + shell_chrome breadcrumb: Selection::Agent arms ('Atlas').

Font: swap the UI face General Sans -> Schibsted Grotesk (OFL-1.1, ships in a
public repo); JetBrains Mono unchanged. theme.rs + main.rs + assets + DESIGN.md.

Scope: the ⌘↵ confirm interaction + onboarding split are the next wave.
DoD: cargo fmt --all --check clean; just check green (default + tray);
cargo test -p deckard-app -p deckard-core green (53 + 55); no new deps.
The hold-to-confirm gesture was an anti-pattern (user feedback). Replace it
with a keyboard-first key-cap confirm (DESIGN.md v2 §The confirm pattern): a
deliberate click — or ⌘↵ — confirms, never a press-and-hold.

- commit_view hold_to_confirm now renders a key-cap button (label + ⌘↵), not
  the amber hold-sweep. Its click routes to the existing confirm handlers.
- A COMMIT_ARM_DELAY (450ms): the confirm is gated until the review has been on
  screen long enough, so a keypress/click carried over from compose can't
  approve (commit_review_at set on proposal install; commit_armed() checks it).
- ⌘↵ is a ConfirmCommit action scoped to a  key context on the focused
  Send/Shield/Swap review (focused only on the review step, so compose inputs
  still type). It never shadows Activity's own ⌘⏎ approve.
- The signing handlers (confirm_send/shield/swap) are UNTOUCHED — only the
  trigger changed. Removed the dead hold machinery (begin_hold,
  hold_still_valid, the *_hold_start timers + *_hold_cancel handlers/adapters,
  the hold-label/fill-id descriptor fields, SHIELD_HOLD).

Verified LIVE on the qa fork: composed a 0.01 ETH send, the review shows the
'Send  ⌘↵' key-cap button, a click signed + broadcast it (Transfer sent, tx
0xb8ab…4a29, balance 10,000 -> 9,999.9899, block mined). (Synthetic ⌘↵ keys
don't register with this GPUI app, same as synthetic Enter on unlock; the
keybinding is wired for a real keyboard, the click path is the proof.)

Known follow-ups (for code-review): the now-vestigial CommitFlow hold fields
(holding/hold_epoch/cancel_hold + the open() leave-guards) and a couple stale
'hold-to-confirm' doc comments.

DoD: cargo fmt --all --check clean; just check green (default + tray);
cargo test -p deckard-app -p deckard-core green (52 + 55); no new deps.
DESIGN.md v2 Required states: amount > balance disables the action with an
inline error, never a late raw provider string. can_review now gates on the
parsed amount <= the wallet's native balance (self.portfolio.native_wei); an
over-balance amount shows 'More than your wallet holds.' under the input and
keeps Review disabled. Covers Send and Shield (both spend native ETH); a None
balance (pre-sync) leaves the gate open. DoD: fmt clean, just check green both
configs, tests green, no new deps.

Remaining for /code-review: humanize_deny noun (send still says 'deposit'),
the vestigial CommitFlow hold state, the onboarding backup/verify split + a
strength meter, and a couple stale hold-to-confirm doc comments.
The high-effort review of the foundation PR caught real bugs; fixed:

- Arm-delay silent dead-zone: a click within 450ms of a review no-opped with
  no feedback (nothing re-rendered at the boundary). New arm_commit() stamps
  the time AND schedules a re-render at the 450ms mark; the key-cap is dimmed
  (muted) while arming and brightens (amber) when live, so an early press reads
  as 'not ready yet' instead of a dead button.
- Arm gate fail-open: commit_review_at was never cleared. open() now clears it
  on every nav and re-arms (fresh delay) only when landing on a review that
  already has a proposal, so a stale-but-elapsed timestamp can never fire.
- Duplicate STOP on the agent surface: the footer 'Revoke' shared the header
  STOP's arming flag but its label wasn't state-aware, so a double-click could
  fire the irreversible key-zeroize with no visible confirm. Removed it; the
  header 'Revoke & STOP' is the one two-step brake.
- Stale policy on back-nav: on_go_back re-kicked the live policy only for
  Selection::Wallet; now also for Selection::Agent (the agent surface shows the
  same STOP-brake / budget fence).
- Recipient address on the Send/Shield confirm was the dimmest text (muted) and
  6+4-truncated. It is the moment-of-authorization string: now the FULL address
  in fg (wraps for a long 0zk), with the identicon.
- Honesty severity was substring-sniffed ('lost'/'can't be undone'); a reword
  could silently downgrade a danger line. Now an explicit HonestyLine.danger.
- Softened the over-balance gate comment: the daemon's gas-aware check is the
  backstop for the gas-leaves-nothing edge (no up-front gas estimate here).

DoD: fmt clean, just check green (default + tray), tests 52 + 55, no new deps.
…y screen

Rework the create flow into the DESIGN.md §Onboarding stepped shape:

- Secure: a live passphrase strength meter (length-dominant, dictionary-free
  so no new dep), an amber "if you forget it, no one can reset it" consequence,
  and the Argon2id/XChaCha20 algorithm names demoted to a muted line.
- Back up: now reveal-only — hold-to-reveal grid + a "nobody can see your
  screen" caution + a demoted explicit-only Copy; the reveal auto-hides after
  10s (epoch-guarded against stale timers).
- Verify (new, separate step): the grid is hidden, confirm by position, and the
  primary stays disabled until the typed words match. backup_words_match is the
  single fail-closed predicate the button and the submit share.
- Ready (new): a real "you're ready" interstitial showing the new address;
  finish_unlock is deferred to the explicit "Enter Deckard" click.

Secret-lifecycle hardening (from an adversarial review of this change):
- create_epoch guards the async Vault::create — a KDF that lands after the user
  backed out drops its freshly-derived secrets instead of staging them.
- abandon_create() (Back / Import) invalidates the in-flight KDF, clears the
  busy flag, and wipes any staged phrase/pass/vault.
- confirm_backup keeps the pending secrets on a write error on purpose (retry
  needs the same phrase); cleared on success, bounded by Zeroizing on exit.

6 new passphrase_strength unit tests. fmt + clippy (both configs) + workspace
tests green.
Code-review (5 finder angles + verify) of the W4b change surfaced secret-hygiene
and consistency gaps; verified-clean fixes:

- Wipe the verify field (confirm_words) on leaving the verify step. It holds 3
  real seed words the user typed, and InputState isn't Zeroizing — they no longer
  linger in memory after success/Back.
- Disable the Verify "Back" button during the seal/unlock write, closing the
  "navigate away mid-flight then get force-jumped when the result lands" race.
- Wrap BOTH passphrase reads in Zeroizing in do_create — the confirm-field copy
  (p2) was a bare String left un-zeroized in freed heap.
- start_create() now calls abandon_create() (epoch bump + busy clear + secret
  wipe) instead of a bare epoch bump, so it's self-sufficient, not caller-reliant.
- back_to_backup() bumps reveal_epoch, symmetric with advance_to_verify.

Cleanup / design conformance:
- Ready screen uses widgets::truncated_address (the canonical identicon + mono
  short address) instead of a hand-rolled copy (DESIGN §Trust).
- passphrase_strength returns a PassBand enum that owns both the word and the
  colour, so the meter's label and bar colour share one set of cut-points and
  can't drift. Unit tests updated to assert bands.

fmt + clippy (both configs) + workspace tests green; the fix delta re-reviewed
clean (0 findings).
…iner

Per design feedback:
- Welcome headline: "Deckard, your new favorite wallet." (drop the "money on
  autopilot" framing).
- Trim implementation/feature descriptions across the auth screens — remove the
  "Encrypted at rest with Argon2id + XChaCha20-Poly1305." line, the "encrypted /
  decrypt on this device" clauses, and the "length matters more than symbols" tip.
- Keep the "Open source · AGPL-3.0 · no telemetry" subline on the first screen only.
- De-em-dash every user-facing string (matches the W1 em-dash sweep).
…ocked type scale

From /design-review (cross-model: codex + screenshot audit) of the onboarding flow:
- Back up screen: demote Copy to a centered ghost action BELOW the full-width
  "Hold to reveal" row, per DESIGN.md §Seed reveal ("Copy demoted below reveal").
  It was sitting beside reveal in an h_flex.
- auth_heading: use the locked DESIGN §Typography scale — H1 22px/600 and
  subtitle 13px/400, not gpui's text_2xl (24) / text_sm (14), which ran large.

Verified live (before/after screenshots). fmt + clippy (both configs) + tests green.
…s, steady meter

From the scope clarification on this PR:
- Step progress rail: "Secure · Back up · Verify · Ready" above the four create
  steps, active step in amber, the rest muted (labels only, no chrome). The user
  always knows where they are in the flow.
- Ready screen: the new wallet address is now one-click copy with inline
  "Copied ✓" (DESIGN §Trust: every address is copyable).
- Reserve the strength-meter's height so nothing below shifts when it appears on
  the first keystroke.
- Remove the "Touch ID … (Phase 2)" note (roadmap language in production auth UI).

All verified live (rail active-step on each screen, no meter shift, copy → Copied ✓
with the full address on the clipboard). fmt + clippy (both configs) + tests green.
@hellno
hellno merged commit 27da898 into main Jun 22, 2026
5 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.

UX foundation W1 — bundle fonts + widgets.rs + slop kill

1 participant