Skip to content

squad: UI/UX polish — 375px stranger drill of the aha surfaces + entity-space sweep - #653

Open
nategeier wants to merge 7 commits into
feat/squad-securityfrom
feat/squad-uiux
Open

squad: UI/UX polish — 375px stranger drill of the aha surfaces + entity-space sweep#653
nategeier wants to merge 7 commits into
feat/squad-securityfrom
feat/squad-uiux

Conversation

@nategeier

@nategeier nategeier commented Aug 18, 2026

Copy link
Copy Markdown
Member

STACKED on #650 — merge #650 first; GitHub retargets this to main when #650's branch is deleted (or retarget by hand). Rule: retarget before merging, top-down.

Squad 2026-08-18 — UI/UX lane (round 1; round 2 accumulates on this PR)

Stranger drill at 375px + 1280px, both themes, on a prod build (next start -p 3801) of the new aha surfaces: /links mint stage (U3 recipient field), /i splash + runtime, /inbox/

, /agents/, /docs/desk, the rail "For you" inbox (mock-EIP-6963 connect-only), and the unified sign-in modal on /chat + /dashboard.

What broke → what was fixed

Break (where) Fix
Guest banner ("Ask anything — no wallet needed") sat on top of the composer at 375px on /chat — both themes (bottom-28 ignored the 48px mobile tab bar) ChatSignInGate: max-lg:bottom-[calc(7.25rem+48px+safe)], desktop untouched
Sign-in modal: the Google lane was a 346px-wide glyph-only button (no visible label) CreateAccountButton: renders Continue with Google beside the glyph
/inbox rows: "Review & sign" crushed the ask into a word-per-line column at 375 text block min-w-[14rem] → CTA wraps under; 1280 unchanged (inline)
Splash "What Pantessa noticed" tile header: title stacked word-per-line beside a clipped subtitle at 375 header wraps; title whitespace-nowrap, subtitle drops to its own line
/links fine-print toggle wrapped centered at 375 text-left + non-shrinking chevron
Rail inbox row read "from Risk Bot**·** tap to review" (SWC entity-space) JS string, not JSX text
27 glued sites of the SWC entity-space bug in rendered HTML: scan_walletreads, Sign caps—, Pantessa Inc.(“, /NATIVE VENUES, $idle —, newmints, … across /docs/{desk,embed,links,jobs,trust,privacy,terms,spend-policy,transactions,creator-earnings,first-five-minutes,host-buttons,embedded-wallet}, /pricing, /activity {' '} after the inline element at every site

Clean at both widths/themes: /i splash + runtime, /agents/, /docs/desk (pre blocks scroll internally), /links mint stage incl. the recipient field, sign-in modal (all three lanes full-width, no crush).

Harness

  • New entity-space fence in test:api over 23 prose routes (</em|strong|a|code> glued to a letter / ( / / spaced ) — the sweep is now un-regressable.
  • Gates: tsc + build + test:api 1483 passed / 1 known-red (router-select) vs :3801.

Not changed (findings for the coordinator)

  • The mint stage's recipient field sits below the Mint CTA — reads as an afterthought; moving it above is a design call, left as-is.
  • The guest banner now covers the last example chip on very short mobile viewports instead of the composer (the lesser evil; the empty state scrolls).
  • /l/nate 404s locally (no claimed handle in this DB) — not a bug.

🤖 Generated with Claude Code


Round 2 — the /i sign path (ideation premortem of the ten-strangers drill)

Lead ask: Swap $20 of USDC to ETH on Base on an /i link, MetaMask/Rabby EOA → Uniswap v3 approve→swap as a SendTxChain.

  • Wallet-refusal beacon wired into SendTxButton (pre-broadcast errors + refused network switch; artifact tx / tx-chain w/ build path) and SignOrderButton (typed-data refusal, cow-order) — the added postgres #1 predicted failure (approve fails: USDC present, zero ETH for gas) now lands in /dashboard/failures as wallet-refused with the wallet's words + connector + chain. Fire-and-forget; human "no" dropped in the beacon; post-hash reverts stay the chain's.
  • SendTxChain: "Once this confirms, the next step (Swap …) opens in your wallet automatically — no button hunt" under the current step; the done receipt line wraps (it carries the amount out — the nearest honest "you now hold ~X ETH" without a balance RPC; none added).
  • /i splash carries Pantessa · formerly Yeetful → /rebrand (the page has no site footer).
  • Post-receipt save bar now says Optional — "you're done here; the money moved…". The /i door already leads with Connect a wallet (Google, then email) — no prop change.
  • Harness: /i rebrand pin + a wallet-refusal wiring audit over every signTypedDataAsync/sendTransactionAsync caller (11 senders; the queued ones — DCA/spot arms, grants, NFT listing, x402 payment sigs, votes — allowlisted by name with a reason).

Round 3 — watchable /dashboard/failures + links funnel

  • /dashboard/failures reads ?funded=1 / ?kind=wallet-refused / ?internal=1 on mount and writes them back on every toggle (replaceState); new wallet refused + internal toggles.
  • components/LivePill.tsx: useLivePoll (visible-only interval, pauses hidden, refires on return) + the "live · updated Xs ago" pill. Failures page polls quietly every 25s; useIntentLinks re-reads every 30s → pill on the rail Links tab + /dashboard/links.
  • /api/admin/ask-failures: ?kind= filter, ?internal=1 opt-in; rows carry internal read from ask_failures.is_internal via a raw query that tolerates the column's absence (squad: security review + honest arrival denominator #650 adds it) — stamped rows hidden by default, counted as internalHidden, tagged internal when shown.
  • Pins: ?kind slice + internal tag + ?internal=1 zeroes the hidden count; page-source pin for the URL params + pill.
  • Live-proven on :3801 as the burner (real SIWE via mock EIP-6963 + local signer): toggles write the URL; router.push with the query lights the toggles on mount; pill ticks on failures + /dashboard/links; a stamped drill row wears the tag only under ?internal=1.

Gates after r3: tsc + build + test:api 1487 passed / 1 known-red vs :3801.

Round 4 — small polish off QA + my own finding

  • Guest banner never hides a chip: EmptyState reserves the banner height below the gallery on mobile (guestBannerPad) — last chip scrolls clear (375 proven).
  • HL delegated door: the venue's raw "Must deposit before performing actions" → honest line + the chat's own deposit ask (deposit 10 usdc to hyperliquid round-trips parseHlIntent). Copy-only — organic traffic hits has-collateral (lib/hyperliquid-exec.ts:558) + planHlCollateralShortfall (:670) before any card; the drive reached the card only because the artifact was canned from a funded wallet.
  • SendTxButton: refused chain switch → inline line names the chain and the button becomes "Switch to & retry" (re-runs switch + sign).
  • Gates: tsc + build + test:api 1488 / 1 known-red vs :3801.

Round 5 — the moment of truth on /i (Visuals' H1 storyboard)

  • The sign button is the primary CTA: lib/sign-cta.ts SIGN_CTA_CLASS (accent pill, both themes) on SendTxButton (→ SendTxChain), SignOrderButton, SignHlActionButton — was a ghost text button; everything else on the card stays quiet.
  • /i leads with the human lineSwap 20 USDC → ~0.010555 ETH · on Base · fee 0.5% · your wallet signs — router/pool/slippage folded behind "Details" (lib/simple-reply.ts + components/chat/SimpleArtifactReply.tsx; simple surface only, chat/embed unchanged).
  • /i door (walletConnectOnly): title "Connect a wallet" + wallet icon; email submit is a real loading state while CDP boots — no "Starting…" stall.
  • /i splash ≤sm: CTA above the fold, the three cards follow.
  • Mint-stage eyebrow → INTENT LINK · YOUR WALLET SIGNS (matches the OG card).
  • Frames: squad-2026-08-18/shots-uiux/h1-r5/{dark-1280,light-1280,dark-375}/04-built.png (cdp.mjs recipe; fixture link deleted after).
  • Gates: tsc + build + test:api 1491 / 1 known-red vs :3801.

Round 6 — merge hygiene + stranger-eye copy

  • Rebased onto feat/squad-security (4a222c0); the /api/admin/ask-failures + /dashboard/failures overlap resolved as the integration branch did (8a91da0): squad: UI/UX polish — 375px stranger drill of the aha surfaces + entity-space sweep #653 structure on squad: security review + honest arrival denominator #650's typed is_internal, raw reader retired.
  • walletErrorWords: refusal rows store the wallet's real words (viem .details/.data.message/.cause chain), not "An internal error was received." — pinned.
  • N1 "Confirm in your wallet…" on every sign/arm button · N2 /i exit ramps hidden mid-flow, back on receipt · N3 /i eyebrow = the OG card (From @handle · your wallet signs; linkEyebrow pinned, page passes the claimed handle).
  • Gates on the rebased branch: tsc + build + test:api 1513 / 1 known-red vs :3801.

Round 7 — last: the wallet's words, for real

  • walletErrorWords now prefers the deepest data.message over any shallower generic (Internal JSON-RPC error. / An internal error was received. / … added to GENERIC) — QA's exact MetaMask -32603 wrapper pinned; the card copy on SendTxButton / SignOrderButton / SignHlActionButton uses the same helper.
  • Gates: tsc + build + test:api 1513 / 1 known-red vs :3801.

@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
website Ready Ready Preview Aug 18, 2026 10:51am

Request Review

nategeier added a commit that referenced this pull request Aug 18, 2026
…p on the ask-failures feed

Both lanes added ?internal=1 to /api/admin/ask-failures + /dashboard/failures:
#650 via the new typed is_internal column (hidden by default in the WHERE),
#653 via a raw-SQL id read tolerant of the missing column + ?kind= slicing.
Merged: #653's structure (kind filter, page toggles/tag/poll) on #650's typed
column; raw reader retired; internalHidden counted server-side; response
carries kind + internal + per-row internal. test-api: both appended check
blocks kept. Duplicate 'internal' field in the page type dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
nategeier and others added 5 commits August 18, 2026 11:23
…ty-space sweep

Round-1 drill (375px + 1280px, both themes) of /links mint stage, /i,
/inbox, /agents, /docs/desk, the rail inbox and the unified sign-in modal
on a prod build. Fixes, all surgical:

- ChatSignInGate: the guest banner sat ON the composer below lg (bottom-28
  ignored the 48px bottom tab bar) — rides higher on mobile only.
- CreateAccountButton: the Google lane was a wide glyph-only button; it now
  carries its visible label like the wallet + email lanes.
- /inbox rows: 'Review & sign' crushed the ask into a word-per-line column
  at 375 — min-w on the text block wraps the CTA under instead.
- SplashDashboard tile header: 'WHAT / PANTESSA / NOTICED' beside a clipped
  subtitle — title stays one line, subtitle wraps to its own line.
- MintLinkForm fine-print toggle: text-left + non-shrinking chevron when it
  wraps at 375.
- JobsRailTab inbox row: 'from Risk Bot· tap' (entity-space bug) fixed.
- SWC entity-space sweep across rendered public HTML: 27 glued sites in
  /docs/* (desk, embed, links, jobs, trust, privacy, terms, spend-policy,
  transactions, creator-earnings, first-five-minutes, host-buttons,
  embedded-wallet), /pricing, /activity feed + trace terminal.
- test:api: entity-space fence over 23 prose routes (letters, opening
  paren/quote, spaced em dash glued to a closing inline tag).

Gates: tsc + build + test:api 1483/1 known-red vs :3801.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…advance hint, wrapping receipt, /rebrand pointer, optional-explicit save bar

Ideation's premortem of the ten-strangers drill (lead ask: Swap $20 USDC→ETH
on Base via an /i link, MetaMask/Rabby EOA, Uniswap v3 approve→swap as a
SendTxChain) named these /i-runtime fixes:

- reportWalletRefusal (lib/wallet-refusal.ts) now fires from SendTxButton
  (pre-broadcast errors + refused network switch; artifact 'tx', or
  'tx-chain' with the refresh kind as build path when inside SendTxChain)
  and SignOrderButton (typed-data refusal, 'cow-order'). The #1 predicted
  failure — approve fails, USDC present, zero ETH for gas — now lands in
  /dashboard/failures as kind wallet-refused with the wallet's words,
  connector and chain. Fire-and-forget; human rejections dropped in the
  beacon; reverts after a hash stay the chain's.
- SendTxChain: one-line hint under the current step — "once this confirms,
  the next step (<title>) opens in your wallet automatically" — so the ~2s
  auto-advance stops reading as "why twice".
- SendTxChain done receipt: the summary line WRAPS instead of truncating
  (it carries the amount out — the closest thing to "you now hold X ETH"
  without adding a balance RPC; none added, by design).
- /i splash: "Pantessa · formerly Yeetful" → /rebrand pointer (the page has
  no site footer; every other page carries it).
- Post-receipt save bar: "Optional — you're done here; the money moved…"
  (it read as a third signature). The /i door already leads with "Connect
  a wallet" (Google, then email below) — no prop change needed.
- test:api: /i rebrand pointer pin + a wallet-refusal wiring audit over
  every signTypedDataAsync/sendTransactionAsync caller (11 senders; queued
  ones allowlisted BY NAME with a reason).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s, live poll pill, internal rows hidden by default

The ten-strangers drill has Nate watching /dashboard/failures and the
per-link funnel while a recruit signs; GTM verified ?funded=1 was not read,
nothing auto-refreshed, and useIntentLinks loaded once.

- /dashboard/failures reads ?funded=1 / ?kind=wallet-refused / ?internal=1
  on mount and writes them back on every toggle (replaceState) — a watch
  link opens on the right slice. New "wallet refused" + "internal" toggles.
- components/LivePill.tsx: useLivePoll (visible-only interval, pauses when
  the tab is hidden, refires on return) + the "live · updated Xs ago" pill.
  Failures page polls quietly every 25s; useIntentLinks re-reads every 30s
  and exposes updatedAt — pill on the rail Links tab + /dashboard/links.
- /api/admin/ask-failures: ?kind= filter, ?internal=1 opt-in; rows carry
  internal:boolean read from ask_failures.is_internal via a raw query that
  tolerates the column's absence (#650 adds it) — never a 500; stamped rows
  hidden by default, counted as internalHidden, tagged "internal" when shown.
- test:api pins: ?kind slice + internal tag + ?internal=1 zeroes the hidden
  count; page source pin for the URL params + pill.

Live-proven on :3801 as the burner (real SIWE via mock EIP-6963 + local
signer): toggles write the URL, router.push with the query lights the
toggles on mount, pill ticks on failures + /dashboard/links, a stamped
drill row wears the tag only under ?internal=1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d, switch-refusal = named chain + switch-and-retry button

- EmptyState reserves the guest banner's height below the example gallery
  (guestBannerPad, below lg only) — the last chip scrolls clear of the
  banner instead of sitting under it (375 proven: chip bottom 296 < banner
  top 542 at scroll end).
- SignHlActionButton: the venue's raw "Must deposit before performing
  actions" (fresh wallet on the delegated door — even approveAgent gets it)
  → honest line + the chat's own deposit ask ("deposit 10 usdc to
  hyperliquid", round-trips parseHlIntent → guarded Bridge2 transfer).
  Copy-only: in organic traffic the build's has-collateral guard
  (lib/hyperliquid-exec.ts:558) + planHlCollateralShortfall (:670) offer
  the deposit before any card renders; the drive reached the card only
  because the artifact was canned from a funded wallet.
- SendTxButton: a refused network switch names the chain inline and the
  button itself becomes "Switch to <chain> & retry" (re-runs the switch +
  sign) — never just red text.
- test:api: source pin for both card copies. 1488/1 known-red.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ply, wallet door, CTA above the fold, eyebrow aligned

From Visuals' frame-by-frame H1 storyboard (what a recruit sees when it
counts):

- lib/sign-cta.ts SIGN_CTA_CLASS: the current step's sign/send button is
  THE primary accent CTA on SendTxButton (→ SendTxChain), SignOrderButton
  and SignHlActionButton — one class, both themes (.text-black light remap);
  everything else on the card stays quiet. Was a ghost text button.
- /i (simple) leads with the human line — "Swap 20 USDC → ~0.010555 ETH ·
  on Base · fee 0.5% · your wallet signs" — and folds venue/pool/slippage
  behind a details disclosure (lib/simple-reply.ts pure splitter, three
  builder shapes; components/chat/SimpleArtifactReply.tsx). Only
  artifact-bearing replies split; chat + embed print the reply unchanged.
- The /i door (walletConnectOnly) is titled "Connect a wallet" with the
  wallet icon (no "sign in", rule 6); the email submit is a real loading
  state ("Preparing the email lane…" + spinner) while CDP boots, never a
  disabled button stuck on "Starting…".
- /i splash ≤sm: the CTA leads and the three contract cards follow (flex
  order) — the door was below the fold on a phone.
- MintLinkForm mint-stage eyebrow: INTENT LINK · YOUR WALLET SIGNS (mirrors
  the r2 OG card; no more TAP TO RUN).
- test:api: splitter pinned on the Uniswap + LiFi shapes and the plain
  pass-through; source pins for the CTA class on all three buttons, the door
  title, the ≤sm order, the eyebrow. 1491/1 known-red vs :3801.

Frames on the r5 build (squad-2026-08-18/shots-uiux/h1-r5/, cdp.mjs recipe,
mock wallet impersonating nategeier.eth read-only, fixture link deleted):
dark-1280 / light-1280 / dark-375 04-built = lead line + details toggle +
accent "Sign & send approve" + the auto-advance hint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… refusal rows, "Confirm in your wallet…", /i exit ramps gated, eyebrow = the OG card

Merge hygiene (QA r3): rebased onto feat/squad-security 4a222c0 and resolved
the ask-failures overlap exactly as the integration branch did (8a91da0):
#653's structure (?kind= slice, page toggles/tag/poll) on #650's typed
is_internal column; the raw-SQL id reader is retired.

- lib/wallet-refusal.ts walletErrorWords: the row's reply carries the
  wallet's ACTUAL words — viem wraps RPC failures so e.message leads with
  "An internal error was received."; the diagnosable line lives in
  .details / .data.message / .cause — first specific line wins. Used at
  every beacon site (SendTxButton, SignOrderButton, SignHlActionButton).
- N1: the in-flight label is "Confirm in your wallet…" on every sign/arm
  button (was "Sign in wallet…" — read as a login).
- N2: the /i header exit ramps (MAKE A LINK / OPEN THE APP) render only on
  receipt or flow-nudge — never above the card mid-flow.
- N3: linkEyebrow (lib/intent-links) mirrors the OG card: From @handle ·
  your wallet signs / Call by <agent> · … / Intent link · from <sender> · …
  (branded splash keeps · powered by Pantessa); the /i page passes the
  creator's claimed handle. No more "CALL · BY @handle".
- Pins: walletErrorWords on viem/node/plain shapes; linkEyebrow pure +
  rendered creator/house eyebrows; N1/N2 source pins. 1513/1 known-red vs
  :3801 on the rebased branch (Security's pins included).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@nategeier
nategeier changed the base branch from main to feat/squad-security August 18, 2026 10:37
…der's generic details (MetaMask -32603), card copy uses the same helper

QA's final smoke: a -32603 stored "Internal JSON-RPC error." — viem's OUTER
details (the provider's generic envelope) beat the wallet's words nested in
cause.data.message, and the card showed "An internal error was received."
for the same reason. Now: candidates carry depth + fromData; the deepest
data.message wins, then the deepest specific line; GENERIC covers "Internal
JSON-RPC error.", "An internal error was received.", "An unknown RPC error
occurred.", "Request failed.", bare "Internal error"/"RPC error". SendTxButton,
SignOrderButton and SignHlActionButton show the same words on the card.
Pinned on QA's exact MetaMask -32603 wrapper shape. 1513/1 known-red vs
:3801.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant