Repository navigation
feat(send): pay a person: the person first, the fee line pinned over review send - #1795
Open
alejoamiras wants to merge 27 commits into
Open
alejoamiras wants to merge 27 commits into
alejoamiras wants to merge 27 commits into
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
nulo-landing | 03027e0 | Commit Preview URL Branch Preview URL |
Nov 12 2026, 08:58 PM |
Extension preview build —
|
…9, 131 and 133) Three stacked arcs: the form (person step, person block, token row, amount, native radios, publish line, fee line pinned over review send, every send through the review), the review (to, amount, privacy strip, fee row), and the close-out. Audited by Codex (reject, then approve after revision and a fresh pass) and Opus (conditional approve); every finding recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
addressHalves, the journal's past sends (parse, recent recipients, last send, known address, age words), the fee line's figure, the publish sentence and the one fee warning, and the button label that names what is missing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… juice words PayeeBlock, SendAgainRow, SideChoice and PublishLine; the person step and the pinned fee line. The fee card hands a closed host its notices and the one payer that stops a private send's fee naming the account, and picks it on applyRemedy; its payer names the method. The review re-arms when its disclosure changes. Private gas, the fee contract and Private FPC read as Fee Juice and FPC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ee line and its sheet Send asks who is paid first (a contact, a pasted address, a recent recipient or an own account), then shows the form: the payee whole in two lines of 33 with bold ends, Same as last time, the token, the amount with Max on its label line, two native radio groups, and the publish line or the warning with its fix. The footer pins the fee line over Review send; Change opens the fee card in a sheet, the card itself staying mounted for the estimate. Every send goes through the review. RecipientCard and SendTypesCard retire with their tests: Send was the card's one host, and the field now emits its pick once a held press ends, so the step never swaps out under the press's own click. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- With nothing to spend the amount takes no focus, so the form opens on the token row instead of dropping focus to the page. - A query-string contact goes through the person step's own choice: an address that is no point stays in the field, which says so; a valid one opens the form with its focus. - "Pay this address" stays while a held press defers the field's match, so the rows under the press never move. - At 240 px a recent row's age takes its own line, so the address is never split; each payee half carries its mono role. - The fee line's notes are paragraphs, read apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e review Every Send flow chooses the person first, sets the sides through the radios, picks a fee inside the Fee sheet and passes the review. The publish invariant reads the card's tag, the form's warning and the publish line's `you`. The released build's Send keeps today's steps in `fixtures/update/release-ui.ts`. Smoke covers the person step, the M3 Tab order, the radios' arrows, the fee sheet's trap and a held press over a suggestion; fee-methods reopens Send after a real send for Recent and Same as last time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ange refuses a repeat - The field's held pick no longer captures the click its release delivers, so a press on a Recent or own-account row while the field holds a contact's whole address pays the pressed row. - An off-curve candidate is never chosen, so no form opens that can never send. - Back from the form ends USD mode on the amount it settled on, and drops one still converting. - The fee line's Change says it opens a dialog and whether it is open, refuses a repeat Enter, and takes the keyboard when a focused Try again is replaced; its dead `pending` prop goes. - The queued cap on the fee line is the card's own figure, never a second price read; the amount sentence keeps every decimal of the token amount. - E2E: `chooseRecipient` proves the payee; the fee sheet's state is read from Change; a regression for the held press on a Recent row; the authwit leg types an amount within the rig's balance; the backup-import send answers the trust prompt a restore raises; a covered click names what covers it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y place - A tap's compatibility mousedown blurs the field after its pointerup, and its click comes in the same task: every blur with no tracked press now holds the field's pick for that task, so what the tap landed on chooses first. - A test holds Same as last time and Recent to every place of an 18-decimal amount. - E2E: send-keyboard seeds its contacts at addresses on the curve, which Send can pay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the person step The Terms banner stands over both steps and the form's button stays off under it; the flagged token's banner is the form's, so the token-list walk chooses a person first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ox's layout unit Max now sits on the label line, flush with the row's right edge, and Firefox reports that edge through float32 as 0.00002 px past the row. The failure names each part's span. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd transfers no longer cycle `fixtures/fee-method.ts` holds the shared FeeSettingsCard picker the Send sheet, the dApp windows and the release's Send use; `helpers.ts` re-exports it. The plan records the a1 gates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Committed for the PR body to link and removed in the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r the sponsor The sponsor exists on some networks only. The fix offers the account's private Fee Juice when it holds the estimated fee, and the bridge link otherwise, on every network. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The row pads itself to the rules around it, so the tint spans the 64 px band and the row's full width; a banner or caveat in the band keeps its spacing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… then done Send's card shows who pays as native radios, each with its glossary definition and balance; priority follows, and Done closes the sheet with the pick kept. The dApp windows keep the menu; the tag the menu drew for Send alone goes with it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… can pay A pick the card's rules refuse left the browser's check on it, since Vue patches `checked` only when the bound value changes: every check is put back to the card's choice after a change. The Fee sheet asks the radios for its first focus (the chosen payer, else the first that can pay), so `Popup`'s `initialFocus` also takes a function. A sponsor added by hand carries the glossary's FPC definition, not Sponsored's, since it may charge. Comments corrected where they still named a dropdown or claimed the payer-in-use estimate bounds the PrivateFPC's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ved pick's preview The radios preview a saved pick while balances load, so a tag derived from them fired before the pick paid; the tag now reads the fee line's payer, the source the warning shares, and a checked radio must be the card's payer. The unfunded-sponsor spec waits on that payer, not on the browser's check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ighteenth owner ask Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
alejoamiras
force-pushed
the
send-build-1-a1
branch
from
November 12, 2026 20:28
6352718 to
246e6bd
Compare
…random CodeQL reads `Math.random()` behind the group's label id and radio `name` as insecure randomness. The id needs only to be unique on the page, which `useId()` gives, as the other components that label by id use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Send becomes B, "pay a person": a person step asks who is paid first, then one form for that person, with the fee line pinned over Review send. Every send now passes the review sheet (its body is unchanged in this arc; arc a2 rebuilds it). Plan:
implementations-plan/send-build-1/plan.md; owner asks with what ships now:implementations-plan/send-build-1/OWNER-ASKS.md. Part of the three-arcsend-build-1stack (the form, the review, the close-out). No issue is linked on pages 129, 131 or 133, so this PR closes none.Changed after page 138
One answer on page 138's notes reaches this PR's form. It ships in #1797, the stack's review arc, which builds every page-138 answer, so this PR stays the form as signed on page 137.
In #1797, "FPC" in "Nulo can't tell what this FPC publishes about you." and "Nulo can't tell what this FPC charges you." is a dotted term keyed to the glossary's existing
fpcentry, on the form and the review; #1797's body states the dotted terms per screen.Changed after page 137
The owner did not approve this PR as built on page 137 (signed 2026-11-12 14:44:49 UTC); this round makes the four changes asked there. Each item quotes the page verbatim.
PayeeBlock.vue). P137-01 (14:42:35 UTC): "I don't like "check it's start and it's end" => we should just say to review carefully." It now reads "You have not paid this address before. Review it carefully before you send." (D21)PublishLine.vue,FeeSettingsCard.vue). P137-01 (14:42:35 UTC): "We shouldn't rely on the "use sponsored" since that's only for Testnet. On mainnet, they should get private fee juice." P137-06 = b (14:44:37 UTC): "Today's review remedy, the bridge link Get private Fee Juice, as the warning's fix." On a private send paid from public Fee Juice the fix is Pay it from private Fee Juice when the account's private Fee Juice covers the fee (the PrivateFPC row can pay and its balance holds the current estimate's max fee; no estimate yet, a balance above zero), otherwise the link Get private Fee Juice toFEE_JUICE_BRIDGE_URL, new tab,noopener noreferrer, on every network. The fix only ever picks the PrivateFPC the wallet derives; the sponsor stays one press away through Change. "Covers" is an approximation of the PrivateFPC's own estimate, put to the owner as OA-18. (D22)SelectTokenCard.vue,send.vue). P137-03 (14:43:46 UTC): ""A" but the hover state looks pretty awful because it does not change it's color till the top and bottom of the component? it looks like a very slim highlighted thing, which looks really bad." The row now pads itself to its section's rules, so its hover, focus-visible and new press tints fill the whole 64 px band, both themes; a flagged banner or the caveat in the section keeps its spacing. Proven insend-keyboard's smoke spec (the box's edges against the rules, 64 px, tinted at hover, focus and press). (D23)FeeMethodRadios.vue, new;FeeSettingsCard.vue;send.vue). P137-04 = b (14:44:03 UTC): "The prototype's sheet: the payers as radios with definitions and balances, priority, Done." On Send the card draws "Who pays the fee" as one native radio per payer (private Fee Juice, public Fee Juice, then the sponsors in the menu's order), each with its glossary definition and what it can spend or why it cannot, then priority, then the card's notices and You pay, then Done. The sheet opens on the chosen payer, else the first that can pay; Escape, the X and Done return to Change with the pick kept. A pick the card's rules refuse has its browser check put back on the card's payer. The menu's "NAMES YOUR ADDRESS" tag and its prop leave (only Send drew them; the form's warning says it). The dApp windows keep the menu, unchanged. What the prototype never drew ships as listed in OA-17. (D24)No change, as answered: P137-02, OA-2 = a (14:40:46 UTC); P137-05, OA-8 = a (14:44:23 UTC); P137-07, OA-14 = a (14:44:47 UTC).
UI impact, before and after
Owner asks opened this round: OA-17 (the sheet's parts the prototype never drew) and OA-18 (what the reviews surfaced: the "covers" approximation, an arrow through public Fee Juice is a pick, the tint with a banner or caveat, the bridge link's network), each with what ships now in
implementations-plan/send-build-1/OWNER-ASKS.md.Testids
send-fee-payers(the radio group,data-fee-method),send-fee-payer-{private,public,sponsored}(each radio,data-fpc-idon sponsors) and their-choicelabels,send-fee-done,send-fee-warning-bridge.send-fee-privacy-notice(the tag; only Send drew it). Off Send, kept by the dApp windows:send-fee-method-trigger,send-fee-method-*. The specs that read them moved to the radios:send-page.ts,send-burst.ts,fee-methods,fee-sponsor-funding,send-chaos,send-fee-privacy,send-keyboard.Reviews
Codex (GPT-6.1 Sol, high, read-only) and Opus 5.5 beside it, both conditional approve; the resumed Codex round approved the fixes. Adopted: the native radio check kept on a pick the card refused (reproduced in memory, now put back after every change, with a component test red without it); the e2e invariant read the radios' preview of a saved pick, which reddened
fee-methodson a real run (now the fee line's payer in effect); the sheet opening on no control when nothing is checked; a hand-added sponsor's row carried Sponsored's definition (now the glossary's FPC definition). Records:plan.md§ Audit verdicts.Validation of this round
Every run at retry 0 on head
477d1f040(the capture commits after it change no code), on a shared host at load 85 to 380 on 188 cores.After these gates
devmoved three commits (#1792, #1800, #1803) and this PR could no longer merge, so its CI never started; the stack was rebased ontodevwith no conflict in code (two plan files only). On the rebased head246e6bdfb:bun run lint,bun run typecheck:allandbun run test:allgreen at retry 0. The e2e suites run again in CI here and locally on #1797's head, which carries this one. CI then failed one non-required check, CodeQL, on one new alert: "insecure randomness" for theMath.random()that named the payer radios' group (FeeMethodRadios.vue). The id only has to be unique on the page, so it now comes from Vue'suseId(), as the other components that label by id do (one commit,03027e005; its component, page and integration tests andtypecheckgreen). On03027e005every check here is green, CodeQL included, and #1797's head, which carries it, passed the full local gates (smoke and the 24 network specs on both browsers).bun run lint,bun run typecheck:all,bun run test:ci-gating(755 pass),bun run check:plans: green.bun run test:all: the first run had one red,price/convert.test.tstiming out at 5 s at load 278 (its 21 tests take 143 ms alone; nothing here touches it); rerun green (extension 18,384 passed).sealed-storage(the unlock's sealing pass outran its 120 s wait at load ~330; nothing here touches sealing, and it passed in 31 s in the arc's earlier Firefox gate); that file rerun green.send-chaos,store-captures,stale-network-a10-capture): 3 of 3.The host load differed between the runs (far higher on this round's Firefox run), so the rise is load, not cost. Network, the 24 specs, after: Chrome 2663 s, Firefox 3394 s.
Changed e2e scenarios (none added):
send-keyboard, "the token row is the whole band between its rules, and focus, hover and press tint all of it" (was "the token card draws the accent ring when focused"). The failure mode is a tint that covers the row's content box but not the band between the section's rules, which only real layout shows; jsdom has none. Estimated +4 s over the old scenario; measured 24.1 s on Chrome and 12.6 s on Firefox (was 8.3 s and 15.2 s; Chrome ran at higher load).send-keyboard, "the fee sheet's payers: Change opens it on the chosen radio with the ring, the arrows pick, Tab leaves, Escape and Done return" (was the fee method picker's). The failure mode is native radios' arrow keys and the teleported card inside the sheet's focus trap, which differ by engine and jsdom has neither. Estimated as the old scenario; measured 26.7 s on Chrome and 14.5 s on Firefox (was 9.7 s and 15.8 s).Captures of this round
From the real popup over the local sandbox (never Testnet), 360 x 600 unless marked, light theme unless marked; every frame also exists in the other theme.
The check line on a pasted address, at 360 and 240 px; dark
The warning's fix: private Fee Juice covers the fee (Pay it from private Fee Juice); none held (Get private Fee Juice, on a network whose sponsor could pay); dark
The token row at rest, hovered and pressed: the tint fills the band between its rules
The token row hovered and pressed, dark
The Fee sheet: at rest (private Fee Juice chosen), public Fee Juice picked, at 240 px
The Fee sheet, dark
What changed and why (arc 1 as first built)
SendPersonStep.vue, aroundRecipientField): "Who are you paying?", the field, "Pay this address" for a valid paste, Recent (this account's own popup sends the node took, newest first, up to four, own accounts left out), Your accounts. The field emitschooseonce its pick may be shown; a press held on any row, or a tap, chooses what it landed on, never the contact the field's blur matched. An off-curve candidate is never chosen.PayeeBlock("To", the name, the whole address in two lines of 33, the check line for an address with no record),SendAgainRow(Same as last time, from the operation journal,recent-sends.ts), the token row with no label,AmountCard(Max on the label line, the refusal under the field, no balance segment), two native radio groups (SideChoice.vue),PublishLine.vue(one sentence, or the one warning with its fix).SendFeeLine.vue(fee-line-words.ts) and Review send, whose off label names what is missing (send-labels.ts). Change opens the fee card, whole, in aFeesheet; the card stays mounted in a hidden holder and is teleported into the sheet, so its estimate and picks live across opens.publish-facts.ts, Send and today's review sheet.RecipientCardandSendTypesCardare deleted;CLAUDE.md§ Keyboard admits a native radio group beside the roving tablist.UI impact (the owner's sign-offs, verbatim)
Owner asks this arc ships in their "what ships now" form: OA-2 (refusal under the field, caveat under the token row), OA-3 (64 px band), OA-4 (today's fee card in the sheet), OA-5 (the line's other states in today's words), OA-6 (other off causes keep "Review send"), OA-7 (the FX2 sentences listed there), OA-8 (every send through the review), OA-9 (the line's figures), OA-11 (four lines at 240 px), OA-12 (the warning without a fix when nothing can pay), OA-13, OA-14.
Captures
From the real popup over the local sandbox (never Testnet), 360 x 600 unless marked, light theme unless marked; every frame also exists in the other theme. Today's review sheet is unchanged in this arc (a2 rebuilds it).
The person step, before any send and after one (Recent: Ana, Last: 1 TST, publicly)
The person step at 240 px; the form for a named payee at 360 and 240 px, Same as last time showing
Dark theme; Same as last time pressed (the row goes, the fee line reads Sponsored with the sponsor's cost); the token row
A refused amount (Fix the amount); the fee line sponsored at 360 and 240 px
The fee line paid from public and from private Fee Juice (no price on the local network, so FJ only); the fee sheet
A pasted address nobody has paid, with its check line, at 360 and 240 px, and scrolled to the fee line
The publish sentence: all hidden on the sponsor, a public send; the one warning (private to private)
The warning's second wording (private to public)
Accessibility calls (under the 2026-10-19 delegation)
aria-haspopup="dialog"andaria-expanded, refuses a repeat Enter, and receives the keyboard when a focused Try again is replaced by the estimate it started.:focus-within).peoplecontainer query) rather than splitting the name; the address halves wrap anywhere (OA-11).Testids
send-page,send-person-step,send-pay-address,send-recent,send-recent-address,send-own-account,send-own-account-address,send-payee,send-payee-name,send-payee-address,send-payee-half-first,send-payee-half-second,send-payee-check,send-again,send-{from,to}-{private,public}and their-choicelabels,send-publish,send-publish-line,send-fee-warning,send-fee-warning-fix,send-fee-line,send-fee-line-value,send-fee-line-note,send-fee-line-retry,send-fee-change,send-fee-sheet.recipient-card*(RecipientCarddeleted;fixtures/send-page.ts,send-keyboard,type-roles,send-amount-exact,store-captures),send-amount-balanceandsend-amount-balance-loading(fiat-send,send-amount-exact,send-picker,store-captures; the From hint carries the balance),send-publish-strip(fixtures/send-page.ts,fixtures/shots.ts,fee-methods,send-amount-exact,type-roles-windows,send-fee-privacy; the component stays for a2's review), and the fixtures'data-action(fixtures/send-page.ts,snack-placement,send-fee-privacy;submitSendalways passes the review).send-from-typeandsend-to-typeare kept, now on the radio groups.Validation (arc 1 as first built)
Every run at retry 0 on this head, on a shared host (load 150 to 840 on 188 cores).
bun run lint,bun run typecheck:all,bun run test:all(extension 18,378 passed),bun run test:ci-gating(755 pass),bun run check:plans: green.legal-acceptanceS4 and S7,token-list: both read Send's form without the person step); fixed and rerun green (16/16). Firefox 295 passed, 0 red.send-amount-exact: Max's right edge read 336.0000153 px against the row's 336 (Firefox reports its 1/60 px layout unit through float32). The spec now compares edges at that unit; green on both browsers. The three opt-in specs among them (send-chaos,store-captures,stale-network-a10-capture) skip in a plain run, so they ran armed on Chrome: green.The host load differed between the runs, so the fall is noise, not a saving. Network, the 24 touched specs, after: Chrome 2309 s, Firefox 2711 s.
One added e2e scenario:
send-keyboard, "a mouse press held on a Recent row while the field holds a contact's whole address pays the pressed row". The failure mode is the browser's own dispatch of a held press, its blur, release and click, choosing the field's contact over the pressed row. Component tests fake that order, so only a real browser establishes it. Estimated 10 s; measured 9.1 s on Chrome and 16.1 s on Firefox.Found on the way and filed: #1794 (the execute window's type-roles sweep reads its "Reading arguments…" line as it settles; the twin of #1763, in a window this PR does not touch).
🤖 Generated with Claude Code