Repository navigation
Batch: v0.10.6 feature PRs (16) - #642
Merged
derek-flossman merged 67 commits intoOct 13, 2026
Merged
Conversation
The Send amount step let the user compose a transfer without saying what it would cost; the fee only appeared once the review was built, after the amount had been committed and a preparation had run. The amount screen now quotes the same figure the review will bind. A plain public transfer shows the network estimate from the review's own estimator, deployment-inclusive total included; a pool route states the pool's per-batch charge in the same sentence the review's Cost box uses and invents no network fee, because a deposit's calldata does not exist before its preparation. The preview is debounced, token-guarded against a superseded estimate, and clears when the step or the route moves on. The audit #248 recipient-Enter guard encoded "no request from this step"; it now encodes what that request must carry, which is the address on screen and never the previous choice. The UI Lab gains an amount-and-fee checkpoint and a shield pool-fee checkpoint, with stories that assert the rendered line. No existing checkpoint showed the amount screen with an amount entered, so the state could not be rendered or guarded.
The amount step refused an unaffordable private send with "Not enough private STRK for this amount plus the pool fee" and named no figure, so a user could not tell what to change. The shared send-route resolver now appends the minimum the amount needs. - send-routes: shortfall() takes the needed amount and appends "You need at least <amount> <symbol>."; the pool-fee-only refusal names the fee minimum and its token. - wallet-model networks: export STRK_TOKEN_DECIMALS and reuse it for the STRK entries instead of a literal 18. - transfer-route: pass the asset's decimals into the resolver. - ui-lab: a send checkpoint renders the refused unshield from a one-STRK private balance, and a story asserts both refusal sites carry the minimum.
A typed Starknet ID name (for example altaga.braavos.stark) is resolved to the account address it points at instead of failing recipient validation. - new pure @bramble/starknet/stark-name module: normalizeStarkName plus resolveStarkName over the library's Starknet ID naming contract, with a discriminated result (resolved / not-found / unsupported-network / unreachable) and transport failover; an unregistered name is the naming contract's own zero answer, so it never resolves to 0x0. - background wallet.resolveStarkName (privileged by default), readStarkNameInput parser, response type and guard, sidepanel client method. - Send recipient step: a valid name enables Continue and is resolved before it is committed; only the resolved address is reviewed and the typed name stays as its label, with resolving/not-found/unreachable notes under the field. - focused tests for the resolver, the background handler and the UI path, plus UI Lab checkpoints for the resolved and not-found states.
Address the review findings: the note must not let an unbreakable name widen the panel (overflow-wrap: anywhere), the detached/edit-during-flight commit guard gains tests, and the multi-label calldata is pinned. - .send-recipient-note wraps with overflow-wrap: anywhere; a 40+40 character name previously set the stage's min-content width and pushed the panel to 710px on a 400px surface, now it wraps and the panel stays inside its width. - transfer-compose-ui: drop a resolution whose field was edited since the read started, not only one whose form detached; both guarded branches now have tests. - resolver test pins the four-felt calldata for altaga.braavos.stark.
Address derek-flossman's review of #528. A bare word is no longer a name: normalizeStarkName requires the .stark suffix instead of adding it, so an ordinary word can never become a lookup. A typed text that names a listed contact or account commits that row and is never resolved. The resolution is bound to a network: the wallet.resolveStarkName request and response both carry the networkId the read was made on, the panel names the network it shows, and a reply read on another network is dropped rather than committed. The recipient is labelled with the canonical name the wallet resolved, never the typed spelling. The review shows the full address with "Resolved from Starknet ID", and warns when the resolved recipient matches no row on the screen. Row lookups move to recipient-rows.ts so the compose binding stays within the file-length cap. New tests cover the bare word, contact shadowing, the canonical label, the network carried on the request and the response, and a reply read on another network. The UI Lab gains a review checkpoint for a name-resolved recipient that no row lists.
…view
Review asked for four changes on the composed-fee preview:
- Every path back to the amount step from the review (header Back, preparation
failure, review failure) now goes through goTo("details") so the fee lines are
re-quoted, rather than left blank or stale until the next input event.
- The preview stops estimating against a detached form: paint(), the target read
and the debounced callback all bail when the form is no longer connected.
- The amount gate shares send-model's finitePositive, so a comma decimal such as
1,5 is refused at the amount step the same way the submit path refuses it.
- The recipients Enter guard distinguishes the amount step's debounced preview
from a submit-driven review by holding the injected schedule: nothing is
reviewed until the debounce runs, and the one request then names the address
on screen.
Test fixtures move to tests/support/compose-fee-fixtures.ts to keep the suite
under the file-length cap.
The submit path keeps its own inline strictly-positive-finite check, so the comment no longer claims one shared gate across every Send surface.
The first-run recovery scan decided an undeployed account was worth importing by probing ETH and STRK balances alone. An account holding only native USDC — which a counterfactual address can receive before deployment — read as empty and was skipped, so a real account was invisible to import (the D3 "USDC-only" gap in the vendor-import dataset). Add a discovery-only RECOVERY_PROBE_TOKENS set (the display defaults plus native mainnet USDC) and probe it in the scan via recoveryProbeTokens(). The user's displayed token set is unchanged — USDC is an activity signal, never shown by default — so detection widens without touching the portfolio. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Braavos lets a Ledger stark key own a Braavos account, deriving it at Ready's `argentx` EIP-2645 path (confirmed from Braavos's shipped client bundle and a live mainnet account). This wallet already scans that path for the "Restore a Ready Ledger account" route, so discovery is a small extension: also derive a Braavos candidate at each device key, at the Braavos deploy class with salt = the owner public key. The same key yields both a Ready Argent and a Braavos address; the on-chain probe reveals which is live. - Admit Braavos as a Ledger family: isLedgerAccountContractType (all three families) replaces the creatable guard at the Ledger message boundary. The live account validator already accepts the record, so no schema change; create, preview, and raw-key import still refuse Braavos. - Default the Braavos deploy class on the Ledger derivation path only, since a device scan probes the vendor's current convention rather than naming a class the way an import does. - Signing composes unchanged: compatibility is family-dispatched (the Braavos signer check reads chain) and the signer is keyless-dispatched (the Ledger blind-signs the stark hash). A ground-truth test pins a real mainnet Braavos+Ledger account's address. Deploying a funded-but-undeployed Braavos Ledger account follows next. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The explicit Braavos deployment is key-source-agnostic: it routes the passed signHash through the family's extended-deploy assembly (aux/params first, deploy hash last, captured as the one broadcastable), so a keyless device signer composes with no change. This pins it — a Ledger-owned Braavos account deploys through the identical extended signature, the deploy hash signed last and accepted by the node, with no keyless guard rejecting the one family a device can own here but not create. Braavos validates its deploy signature even on query versions, so unlike OpenZeppelin and Argent (which estimate against a mock) a Ledger Braavos deploy signs for real and prompts the device during estimate and submit alike. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The terminal's HTTP and RPC clients enforced neither a deadline nor a response-size limit. The shared request cache also stripped the caller's signal before the deduplicated fetch, so a request that never settled stayed in the in-flight map for the lifetime of the page, and a caller that detached could not release it. Route every request through one reusable boundary: - Add protocols/support/bounded-network.ts: boundedFetch composes the caller's signal with an internal deadline (so a caller that detaches no longer strands the request), rejects an oversized response before it is parsed or stored, and passes a caller-cancellation through as an AbortError rather than mislabelling it as a timeout. - data/core/client.ts: fetchJson and the HttpError body now use the boundary; error bodies are read up to a fixed prefix. - data/core/cache.ts: give a shared request its own total budget that is independent of any one consumer, and derive each attempt's deadline from the remaining budget so retries cannot outlive it. - protocols/support/provider.ts: the default RpcProvider is built on the bounded fetch with a wider RPC deadline and byte cap. - Migrate the remaining direct fetch/RpcProvider call sites (avnu swap, private-swap client, vesu, privacy pool, NFT punk runtime). Tests: tests/network-boundary.test.ts covers the internal deadline firing on caller detach, the byte cap before parse and before error storage, the total request budget, a null body, and a source guard that every production fetch/RpcProvider goes through the boundary. Existing fetch stubs now return real Response objects, as production does. Refs #271
The Activity screen scrolled as one block, so the heading and its export and refresh controls left the panel with the list; neither the screen's identity nor its actions stayed reachable while reading history. The title bar is the screen's first grid item, so it now sticks to the top of the panel while the rows pass beneath it. A pseudo element bleeds the panel fill past the text column and over the shell's top inset, so a row never shows through the bar and the resting layout is unchanged: the bleed and its negative inset cancel. The UI Lab gains a long-feed checkpoint and a story that asserts the bar rests below the panel edge and pins at zero on scroll. The existing fixture never overflowed the surface, so there was no state in which the behavior could be seen or guarded. The account bar above the screen is shared shell chrome on every dock screen and is left as it was; this change is scoped to the Activity screen the issue names.
The Gallery checkpoint's `data-lab-ready` signal is set only after the wallet frame's `load` event, and that frame's initial HTML loads one eager fixture image. A slow response for that image pushed readiness past the 5s budget in `assertCheckpointReady`, which is the intermittent timeout reported for the story. Inline the fixture artwork as a data URI (Vite `?inline`) so the checkpoint's readiness path stays local and deterministic, and drop the now-unused public asset. Refs #464
Add a 320 x 760 surface preset to the review bench and a set of journey stories that render each main screen at that width through the bench's own surface control. Each story fails if the panel scrolls horizontally or a named amount or address is clipped, so the layout cannot regress below the width the project documents (320px, and 256px under zoom). Refs #482
The narrow-width guard checked only the first element matching a selector, so a selector aimed at a list (`.token-amount`) would pass while a clipped later row went unnoticed. Both helpers now walk every match with `querySelectorAll` and report the offending index; an empty match is still an error. A negative control confirms the old helper passed a clipped fifth row that the new helper rejects.
…lations The review frame is script-free (sandbox without allow-scripts), so its realm never fires setTimeout and axe-core's run never settles there; the lab recorded that as a manual limitation instead of a violations readout. New runtime/a11y-scan.ts renders the live wallet document once more in an ephemeral, offscreen, same-origin twin frame that is allowed to run the engine, scans that twin's realm, and removes the twin in a finally. scanWalletFrame serializes the frame's current DOM (checkpoint prepare hooks mutate it after load) and sizes the twin to the frame's rendered box so contrast and layout agree; scanWalletDocument is the underlying helper. axe-core 4.13.0 becomes a ui-lab devDependency, imported with Vite's ?raw suffix. The wallet frame document gains a title element, so the lab no longer raises the serious document-title finding against its own document. New Wallet UI Lab/Accessibility scan story drives the helper: a positive control (image-alt on a crafted document), the real readout resolving, every flagged node resolving inside the reviewed document, and no leftover twin with the review frame's sandbox unchanged.
Split out of #533 so the approval and review copy can go through the reviewed-group pass on its own (AGENTS.md, docs/COPY_STYLE.md). No behaviour change beyond the copy and the review surface it describes. - Connect card: the consent action reads "Connect account" and its escape hatch "Cancel", and the shared account's familiar display name leads, stacked over the address it shares. - Send review: the confirm face names the reviewed network between the parties and Amount, and each party's complete address is one native <details> disclosure away, closed by default and keyboard reachable. The network label is read from the form's own data-send-network-label, so it stays correct after edits and repaint.
The deploy flow no longer hides the threshold and signer behind a separate check step. The approval stage quotes the deployment itself, debounced, and renders the predicted address and fee next to the fields it was computed from. One button, always labelled "Deploy multisig", submits the reviewed deployment.
While a deployment quote was in flight or had failed, the single action still read "Deploy multisig" although pressing it only re-quoted. The action now reads "Check fee and address" until a matching review is held and switches to "Deploy multisig" only then. It is marked busy for the whole quote so a fast second press cannot deploy a review the reader has not seen, and the failure toast is reserved for an explicit submit rather than every debounced auto-quote settle.
…or failed The revision changes the deploy action's label and busy state, so both affected states need to be directly inspectable. The multisig scenario gains a checkpoint whose fixture quote never resolves and one whose quote rejects, so the action's busy label and its failure label can be reviewed on the real surface. Recount the lab's checkpoint total.
The approval and review copy (the connect card labels and the send review summary) is split out into its own PR so it can go through the reviewed copy group on its own. This PR keeps the remaining review fixes. - New-wallet setup: present the sole derived account as the account being created (a static read-out with its check mark, no click-to-select) behind an outcome-named "Finish setup", gated on walletReady && previewMatchesInput. - Dock: each root shows its icon plus a visible caption (Home, Activity, Settings) in a labelled capsule, replacing the icon-only disc and the hover-only title. Accessible names, selected state, keyboard behaviour and badge placement are unchanged. - Receive: one name, "Wallet card", for the card object and its copy action. - UI Lab: give the activation review a realistic fee spelled the way production spells it, and represent a fee-estimate failure through the real preparation failure path as the activation-fee-failed checkpoint.
The Activity dock listed every record the account held with no way to narrow it. Add a toolbar above the feed: a search box that matches a full or partial address or a saved name, a type, status and token filter offering only the values the account's own records carry, and a date/amount sort. Searching a single counterparty adds a summary of what was sent and received there. Filtering happens over the rendered rows, so no extra transport or storage is involved; the view state is ephemeral, panel-local, and cleared when the account or network scope changes or the vault locks. Sorting re-renders through the status read, because the amount order drops the day headings. Two UI Lab checkpoints render the toolbar and a collapsed search, since the lab had no way to show this screen.
… token Address the review requests on the Activity feed controls: - Reconcile the stored filter against the options the current feed offers before the toolbar renders, so an axis whose value has left the feed (a pending transaction settling between paints) resets to "all" instead of emptying the feed behind a control that reads "All". - Group the amount sort by token so a raw amount is never compared across tokens (1 ETH is not below 2 USDC); each token's rows order by amount. - Repaint the sort locally from the data already on screen instead of reading the wallet status, so a failed status read can no longer leave the pressed control and the stored sort disagreeing.
The hero percentage and each token row's change are 24h price moves at the user's current balances, so sends and receives do not count and the row figures do not sum to the hero. One muted caption under the hero says this once, rather than repeating a label on every row, which the copy style discourages (say the subject once per screen). The caption renders only when a priced baseline exists.
Each token row now carries a quiet window tag beside its percentage, so the row figure says it is a price move rather than restating the caption. The hero line and its one caption move into a shared heroChange module that both the account Home hero and the watched multisig hero render, so the two surfaces cannot name the same figure two ways; the caption still renders only when a priced baseline exists. The caption gate test now drives a priced-but-unmeasured baseline (prices present, no balance priced), so removing the gate fails it; the old empty fixture also passed on base, so it did not guard the gate.
Replace accountId with the plain accountAddress (the network is already its own column), rename hash to transactionHash, add an explorer URL, and render the timestamp as 'YYYY-MM-DD HH:MM:SS' UTC with the zone named in the header and one precision on every row. Rebuild a transfer row's summary from the structured fields so it names the counterparty in full and reads with one verb, keeping the recorded summary for every other row and for rows missing a detail. Formula-injection escaping is unchanged. The fee and fiat columns depend on issue 463 and are deferred; the fee-as-transfer rows travel with them.
derek-flossman
requested review from
Ryun1,
adrienlacombe,
l-henri,
omarespejel,
robertkodra and
teddyjfpender
as code owners
October 13, 2026 12:22
DOMParser never enters a template's content, so the sanitiser left an `on*` handler inside one untouched. The twin's srcdoc parser does attach a declarative shadow root from `<template shadowrootmode>`, so that handler ran in the script-enabled, same-origin twin. Templates are now removed with the other active elements. The embedded-code story gains a shadow-root control case; it fails against the previous sanitiser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#521 routed the two async failure paths (review and private preparation) through `goTo("details")`, which is bound to the form mounted when the submit started. A re-render while a preparation ran (it can take about a minute) replaced that form, so the failure repainted the detached copy and the live screen stayed on "reviewing" with its button locked and no reason shown. The failures now repaint the active binding and re-quote its Amount-screen fee, keeping #521's intent. Both paths have a regression test that fails against the previous code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The per-counterparty summary added every visible in or out row to its totals whatever its status, so a reverted or rejected send read as "Sent". One accepted 1 STRK beside a reverted 5 and a rejected 7 showed "Sent 13 STRK". The summary row now carries the status, and only an accepted or pre-confirmed transfer adds to the totals; every visible row is still counted. No user-facing wording changes. The new test fails against the previous code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A non-STRK pool fee is bounded at twice an independent price for the STRK charge, and the relay's quote, known only at review, may be lower. The Amount screen now says "Pool fee: up to X" for it. STRK's exact fee is unchanged. Copy approved by Derek, 2026-10-13. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
l-henri
approved these changes
Oct 13, 2026
This was referenced Oct 13, 2026
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.
Lands sixteen feature PRs into
release/v0.10.6in one merge, the same way as #635–#641. Merging them one at a time would need a fresh approval after each one.Left out: #554 (privacy SDK rc.8) is held until the deployed contracts are confirmed (see its PR comments). #568 (vendor dataset tooling) will land separately: it excludes its folder from the repository gates and derives keys outside Krusty. #450 is not part of this batch.
What this contains
Each PR head is merged with
--no-ffontorelease/v0.10.6atc1aab546. Fifteen of the sixteen are Moss's fork PRs, which have never run CI; this branch is their first CI run.name.stark) as recipientsCode that is only in this PR
Conflict resolutions (in merge commits; each keeps release's audit behaviour):
221f0188feat(send): state the minimum a private send needs #518f8c057acfix(terminal): bound HTTP and RPC requests with one shared network boundary #510 (keeps the Sherlock 147 route check)73bcf86cfeat(send): resolve Starknet name recipients #528fc667f70feat(multisig): quote the deployment beside the approval controls #532 (keepsparticipantContactSaverandonUnselect)632ae8a0feat(ux): finish the remaining Don't Make Me Think review fixes #533d1a8d6f4feat(activity): filter, sort and search the activity feed #541 (keepsledgerSign.dismisson lock and theerrorDetailsexport toast)Fixes from review. Each has a test that fails without it.
992423b5feat(send): state the minimum a private send needs #518: the minimum uses the configured pool-fee token, amount and decimals. Release's fee-token behaviour is unchanged.e9f4a6abfeat(send): quote the composed fee on the amount screen #521: the Amount-screen pool-fee line names the token that pays it.854994dbfeat(activity): filter, sort and search the activity feed #541: the token filter, amount sort and totals are keyed by token address, not symbol. Counterparties are compared canonically.30c5d530feat(activity): rebuild the CSV export columns and summary #573: the CSV direction comes from the storeddirection, never from the summary text.0ac8832cfeat(multisig): quote the deployment beside the approval controls #532: an auto-quote can't enable a Deploy button that the class or signer gate disabled.5842bee8feat(ledger): recover and deploy a Braavos account owned by a Ledger key #570: idle Braavos Ledger rows aren't selectable, and the background refuses Braavos at any scheme but argentx.ec9634bctest(ui-lab): scan the embedded wallet document for accessibility violations #527: the scanned markup is made inert before it renders in a script-enabled frame, and the scan is labelled a readout.e699b6bbfeat(send): resolve Starknet name recipients #528: a name is resolved only by an RPC on the expected chain, otherwise it fails closed.Combination commits:
69af3ef4,515b8375,b163d186: splits to stay under the 500-line cap.77b4daaa: error copy for name resolution.4d774bf4: test fixture.28b4e4d0: knip.c9eadb8a: UI Lab send fixture split.Fixes from the second review round (Codex gpt-6-astra, high effort, on the fix commits):
896f4ee9: the pinned Activity bar story measures the rows against where they rest. feat(activity): filter, sort and search the activity feed #541's filter toolbar now sits between the bar and the list. The bar still pins.6e7bde86,4b71e638test(ui-lab): scan the embedded wallet document for accessibility violations #527: the scanner also strips embedded frames, objects, embeds,srcdocandjavascript:URLs. A new story fails against the old sanitiser.ec8cedae,1aff5b11feat(send): state the minimum a private send needs #518: a converted pool fee is a ceiling, not an exact charge, so both blockers say how much to keep for the fee instead of a minimum. The copy is approved.Fixes from the independent audit (three reviewers split by area, plus Codex on the core diff):
d67db4f6feat(send): quote the composed fee on the amount screen #521: when a review or proof preparation fails after the panel re-renders, the live Send form now returns to the Amount screen with the error. Before, it stayed stuck on "reviewing".33c0eb7dfeat(activity): filter, sort and search the activity feed #541: the counterparty summary totals only accepted and pre-confirmed transfers. A reverted, rejected or pending send no longer counts as sent.89ef29e7test(ui-lab): scan the embedded wallet document for accessibility violations #527: the scanner also strips<template>, so a declarative shadow root can't run code.4038e6f0feat(send): quote the composed fee on the amount screen #521: a pool fee in a token other than STRK reads "Pool fee: up to X" on the Amount screen, because the figure is the ceiling. The copy is approved.Checks run locally (Linux, Bun 1.3.3)
bun run check4038e6f0)bun run check:coverage33c0eb7d)bun run check:auditbun run buildbun run test:e2e33c0eb7d)4038e6f0)Independent reviews: three first-round reviewers (one per group of PRs), and a second-round review of the six conflict resolutions, which found nothing actionable, and a Codex review of the fix commits (above). All copy is approved on the copy review page.
🤖 Generated with Claude Code