Restructure src around what needs the extension and what does not - #249
Merged
Conversation
utils/ was 139 files and 32k lines - 42% of the codebase and every
security-critical module in it. The name said "small helpers"; the contents
were the domain. That mis-naming is not cosmetic: ranking files by directory
or import count put an icon barrel at the top and buried the modules
everything depends on.
Split by measurement, not taste. Every file was checked for whether it or
anything it transitively imports touches chrome.*, browser.*, wxt or
webext-bridge. blockchain (77 files), validation, encryption and qr-code
came back at 0% coupling; auth at 100%, storage at 90%. 100 of 139 files
were already portable, and exactly one crossed the line - walletManager,
which reaches for session storage. Moving it alone took core's dependencies
on platform from three to zero.
core/ cannot reach the extension runtime: packing and unpacking
messages, signing, key derivation, encryption, validation,
formatting, the API client and its cache
platform/ is the extension: chrome storage, the session keychain, the
dapp bridge, analytics, popup and proxy plumbing
core/__tests__/layering.test.ts asserts the direction rather than
documenting it: platform may call into core, core may not reach back, and
nothing under core may touch chrome.* or import webext-bridge, #imports or
wxt. An auto-import would otherwise undo this silently.
Inside core: api/ groups the client, its health check and its cache, which
existed as three loose files serving each other. blockchain/ is gone - it
held bitcoin, counterparty and counterwallet and nothing else, so it
prefixed 77 files while discriminating nothing; the longest path drops from
73 characters to 62. Flattening collided two modules both named errors.ts,
so each is now named for what it is: rpcErrors.ts for the JSON-RPC codes,
errors.ts for the blockchain hierarchy. security/ is gone too - it held two
unrelated files under a word that describes everything in a wallet, which is
how utils/ started.
components/ had the same drift one level down. ui/ is meant to be widgets
that do not know what an asset is, and domain/ is where the wallet-aware
ones go; domain/ held 4 files while ui/ held 27 referencing assets,
dispensers, balances and wallets 8 to 41 times each. Those move to domain/,
placed by subject because that is how domain/ is already organised. ui/ now
has none.
Deletes three modules reachable only from their own tests: qrCode.ts
sanitised scanned QR input and the wallet has no scanner, RequestManager is
superseded by approvalService, address-card has no reference at all. The
earlier dead-code sweep missed them because it counted any importer and a
test counts.
Filenames now agree with their area - core is camelCase, components is
kebab-case - and platform/wallet, a folder holding one file, is flattened.
Finally wires up the two validators the deletion pass left alone. The MPMA
form parsed CSV with a regex over split lines; parseCSV replaces it and adds
a row cap, real quoted-field parsing and rejection of spreadsheet formula
injection. The CSV upload had no size limit at all, so validateFile bounds
it. Neither is wired into the inscription upload: detectMaliciousContent
rejects any text file containing <script or <iframe, and an HTML inscription
carrying a script tag is exactly what that feature publishes.
Claude-Session: https://claude.ai/code/session_01CcjnCrgosSeshymXLBxdGj
This was referenced Aug 4, 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.
Squashes #244–#248 into one change. The tree is byte-identical to the stack tip — verified with
git diff --quietagainstsrc/.utils/was 139 files and 32,332 lines — 42% of the codebase, holding every security-critical module. The name said "small helpers"; the contents were the domain.The split is measured, not stylistic
The argument is a security property made enforceable: code that derives keys, builds transactions and verifies them should be unable to depend on browser or session state. That's what makes it auditable in isolation and testable without a browser.
Every file was checked for whether it — or anything it transitively imports — touches
chrome.*,browser.*,wxtorwebext-bridge:blockchain/validation/encryption/,qr-code/,hardware/storage/auth/100 of 139 files were already portable, and exactly one crossed the line —
walletManager, which reaches for session storage. Moving that single file took core's dependencies on platform from three to zero.core/— cannot reach the extension runtimeplatform/— is the extensioncore/__tests__/layering.test.tsasserts the direction rather than documenting it. An auto-import would otherwise undo it silently.Regrouping inside core
api/— client, status and cache existed as three loose files serving each otherblockchain/removed — it held bitcoin, counterparty and counterwallet and nothing else, prefixing 77 files while discriminating nothing. Longest path: 73 → 62 charserrors.ts, so each is now named for what it is:rpcErrors.ts(JSON-RPC codes) anderrors.ts(blockchain hierarchy)security/removed — two unrelated files under a word describing everything in a wallet. That's howutils/startedcomponents had the same drift
ui/is meant to be widgets that don't know what an asset is.domain/held 4 files;ui/held 27 referencing assets, dispensers, balances and wallets 8–41 times each (asset-select-input: 41). Those move todomain/, placed by subject since that's howdomain/is already organised.ui/now has none.Deletions — 2,312 lines
Three modules reachable only from their own tests:
qrCode.ts(sanitised scanned QR input; the wallet has no scanner),RequestManager.ts(superseded byapprovalService),address-card.tsx(no reference at all). The earlier sweep missed them because it counted any importer, and a test counts.Naming and wiring
Filenames now agree with their area — core is camelCase, components is kebab-case — and
platform/wallet/, a folder holding one file, is flattened.The two validators the deletion pass deliberately spared are now wired up.
parseCSVreplaces the MPMA form's regex-over-split-lines, adding a row cap, real quoted-field parsing and formula-injection rejection. The CSV upload had no size limit at all, sovalidateFilebounds it at 2 MB.Neither is wired into the inscription upload, deliberately.
detectMaliciousContentrejects any text file containing<scriptor<iframe— and an HTML inscription carrying a script tag is exactly what that feature publishes. It would have broken inscriptions, which only started working in 0.7.0.Verification
tsc --noEmitclean;biome check srcclean (672 files)vitest run src— 3965 passed, 49 skippedwxt buildsucceedsplaywright test e2e/pages/compose/send— 26 passed269+ of the changed files are pure renames. No logic changed except the CSV parsing swap, which has tests for both new protections.
Note
#232 (Trezor Connect 10, draft) touches
utils/hardware/, nowcore/hardware/. It needs rebasing whenever v10 stabilises.