frontend sweep, round three: one reload per change, token facts on the wire, dead transport, identity readiness, dead exports - #1020
Merged
Conversation
…hing, and the raw-event reloads beside it go The one reload path, `wallet.refresh` → `useWalletRefreshListener`, had a "cooldown" that counted dropped events rather than frames: after any reload the next 119 events from a low-priority source (`inbox.sync`, `storage.sync`, `sofi`, an adoption, a dialog's completion) were discarded outright, and only nine named sources bypassed it. Around that gate the provider and the wallet screen subscribed to the raw `inbox.updated` and `bilateral.transferComplete` events as well, and the provider reloaded again on `wallet.bilateralCommitted`, so one inbox sync with new items reloaded the projection and the screen three to four times when the gate was open, and the raw subscriptions were the only reload when it was shut. The event bridge also emitted a `wallet.refresh` claiming `bilateral.transfer_complete` on one BLE prepare response in eight — a prepare response is not a wallet change. The listener now coalesces onto an animation frame, runs one reload at a time and owes exactly one more for events that arrive mid-reload: nothing is dropped, and there are no priority classes. The provider and the screen hook reload on `wallet.refresh` alone; the raw subscriptions and the provider's reload on `wallet.bilateralCommitted` are deleted (its toast stays). The BLE prepare-response emit is deleted. The accept path's staggered re-reads stay beside Rust's TRANSFER_COMPLETE announcement and name themselves `bilateral.accept_followup`; whether the announcement alone suffices on a device is undecided and recorded Open (§6.29). The provider's store and the wallet screen's hook still each hold a copy of balances and history — a store duplication, recorded, not a reload duplication. Tests: useWalletRefreshListener (nothing dropped after a completed refresh; events mid-refresh owe exactly one more; no owed refresh after unmount), WalletCreditSound (the event bridge's own announcements, decoded from bytes, reload the projection once each; the coin sound follows the credit), EnhancedWalletScreen.events (one inbox sync reloads the wallet data once), EventBridge.bilateral (a BLE prepare response announces no wallet change); the five suites that pinned the old behaviour now assert the production pair of events and the accept follow-up's own name. Mutation controls, each red on its named test: the drop gate reintroduced; the provider's raw inbox.updated reload re-added; the screen hook's raw inbox.updated reload re-added; the BLE prepare-response emit restored. Verified: tsc, lint, jest 843/843, npm run build, and the purity, flow, scan, bridge, codegen, proto and bridge-RPC-name gates.
…icy fixes and permits; the screen's CPTA copy goes AccountsScreen carried its own table of what ERA and dBTC are — a "DJTE emission token" type, an invented anchor formula, a "PROTOCOL-DEFINED" anchor id and a supply figure — beside the anchor Rust reports, and decided whether a token is protocol-defined, which withholds BURN and the adoption code, from its ticker text: a created token whose ticker read ERA would have been treated as the protocol's. BURN was offered on every created token whether or not its policy permits burning, and the coin artwork keyed on the ticker containing "btc". A created token's row carried its policy's icon and nothing else the policy fixes. Rust now reports the facts on each `BalanceGetResponse`: `protocol_defined`, decided from the builtin policy commit the ticker resolves to; `genesis_supply_display`, the supply the committed policy fixes at creation, rendered with the token's decimals — ERA's is the native reserve's genesis supply, and it is empty where Rust holds none; and `permissions` (`burn_enabled`, `transferable`), read from bytes verified against the anchor and absent, never defaulted, where Rust holds no committed policy — ERA's blob does not exist yet (§6.32), and "not stated" is not "not permitted". A registered token's row is checked against its own policy (ticker, alias, decimals, supply) and a disagreeing row is refused with nothing reported from either side. The frontend refuses a created token's row that lacks its policy facts, takes a token for a protocol asset on Rust's word only, draws in the panel exactly the lines Rust reports, offers BURN only where the policy permits it, and keys the protocol artwork on Rust's word plus the ticker. `CPTA_INFO` is deleted. The Rust → TypeScript balance wire fixture carries the new fields and its contract test decodes them, the permissions present. The Bitcoin chain-balance row lists the new fields as unstated; nothing else there changes. Tests: dsm_sdk wallet_routes tests (ERA protocol-defined with the reserve's supply and no stated permissions; a registered token's supply, permissions and icon from a policy packed by the one packer and stored under its anchor; a row disagreeing with its policy refused), AccountsScreen.tokens tests (a created token's supply and permissions as Rust reports them; a protocol asset's supply and nothing Rust does not state; no BURN where the policy forbids it; protocol-ness on Rust's word, never the ticker), wallet.test (a created token's row without its policy facts refused), balanceWireContract (the fixture's new fields decode, permissions present). Mutation controls, each red on its named test: ERA's permissions defaulted; a registered token reported as protocol-defined; a disagreeing row reported anyway; protocol-ness keyed on the ticker; BURN offered regardless of the policy; permissions defaulted where none are stated; a factless created row accepted. Verified: make lint, the production-safety lints, the dsm_sdk release wallet_routes tests, tsc, lint, jest 840/840, npm run build, and the purity, flow, scan, bridge, codegen, proto and bridge-RPC-name gates.
…op nothing sends, and the stubs of methods that no longer exist go `isNativeHostUnavailableError` matched a Kotlin error string nothing called it for. `processEnvelopeV3Bin` sent an `envelope` ingress op no production code builds — the frontend has no envelope to hand Rust; BLE envelopes reach Rust from Kotlin — and lived on through two tests that used it as their way into the boundary. Three test bridges carried `getDeviceIdBin`, `getGenesisHashBin` and `getTransportHeadersV3Bin` members index.html does not install (one with a comment saying call sites read them; none do), and two answered `getSigningPublicKeyBin`, an RPC deleted with #1012 — stubs of methods that do not exist. All deleted, with `buildEnvelopeIngressRequest`. The boundary tests enter through `routerQueryBin`, the op production sends. `ci/bridge_rpc_names.py` now also holds every `method === '…'` arm in a test bridge to a name Kotlin handles, so a stub cannot outlive its method again. Tests: WebViewBridge.framing (a router query goes to nativeBoundaryIngress as a routerQuery op; a non-bytes native answer is refused), bridgeDecoding.integration (the boundary's answers, entered through routerQueryBin). Gate control, failing naming the offender: a test stub arm answering getSigningPublicKeyBin. Verified: tsc, lint, the boundary and E2E suites this touches, and the bridge-RPC-name gate; the full suite, build and remaining gates ran on the tip, with the identity change that follows.
…issing, runtime not ready, or not read — never a null `getIdentity` answered `null` after its cold-start window for three different reasons: the native session reporting no identity, the session not ready in time, and headers that could not be read — and it waited the whole window (~5.75 s) on a device that has no identity at all. `isReady` reduced a device-id read to a boolean, `false` for an empty answer and for a failed one alike, and `useTransactions` read that as "no identity yet". The wallet store treated the null as "not initialized, no error"; the wallet screen threw "Identity not initialized" for every case; diagnostics printed "getIdentity answered null". `getIdentity` now answers the identity or throws `IdentityUnavailableError` with its state: `missing` — Rust's word, the native session's `identity_status`, answered at once with nothing to wait for; `runtime_not_ready` — the session not ready within the window, carrying the last read's failure; or `read_failed` — a session that reports ready whose headers still could not be read. The store treats `missing` as a state (uninitialized, no error, no reads) and reports the other two as errors; the screen shows the reason; diagnostics print the state and message; `useTransactions` asks the native session (`checkIdentityState`) and logs which of the three it saw. `getDeviceIdentity` and `isReady` are deleted with the dead mocks of them. Tests: identity.test (the three answers: not-ready after the window with no session state; missing at once, without the window and without a headers read; not-read with the reason on a ready session), walletStore.test (a missing identity leaves the store uninitialized with no error and no reads; a runtime not ready in time is an error as reported), useDiagnostics.test (a missing identity reported as missing), useTransactions.test (no history read while the session reports NO_IDENTITY or RUNTIME_NOT_READY). Mutation controls, each red on its named test: the missing fast-exit removed; a failed window answering null again; the store treating a missing identity as an error. Verified: tsc, lint, jest 839/839, npm run build, and the purity, flow, scan, bridge, codegen, proto and bridge-RPC-name gates.
…w's startup-boundary path Kotlin crosses on its own
A survey of production exports nothing in production references found five
whole modules and fourteen functions dead: `dsm/crypto.ts`, `dsm/nfc.ts`,
`vectors/`, `services/policy/policyScanService.ts`, `utils/binary.ts`;
`getNativeHostCapabilities`, `stopNativeQrScan`, `requestHostPermissions`,
`authorizeBiometricHost`, `readNfcTagPayloadHost`, `invokeRouterEnvelope`,
`queryRouterEnvelope`, `loadPersistedBleMappings` (a documented no-op),
`subscribeBleEvents`, `useBle`, `useUXTerms` ("terms" that answered the key
they were asked for), `useBridgeSessionStore`. The WebView's
startup-boundary path — `startupBoundary` / `startupBoundaryOk`, the
`startup` wrapper index.html installed, the `startup` member of
`AndroidBridgeV3`, a test-setup stub arm and Kotlin's `nativeBoundaryStartup`
arm — had no caller: Kotlin crosses the startup boundary itself at app start
(`BridgeIdentityHandler.dispatchStartupOrThrow`), and the bridge-RPC-name
gate had counted the wrapper's own `callBridgeMethod('nativeBoundaryStartup')`
as "sent".
All deleted. `AndroidBridgeV3` and index.html's object are six members;
`callBoundaryMethod` crosses the ingress boundary only; the Kotlin fuzz
lists lose the name. Exports referenced only by tests are production code
kept alive by tests and are recorded Open in CONFORMANCE_GAPS §6.29, to be
swept export by export; Bitcoin and recovery exports are not touched.
Tests: none added — deletions. Gate controls: the `nativeBoundaryStartup`
stub arm in setupTests.ts, refused by name while the Kotlin arm was already
gone; a `startup` member left on `AndroidBridgeV3` after index.html dropped
it, refused as typed but not installed. Verified: tsc, lint, jest 836/836,
npm run build, the Kotlin main, androidTest and unit-test compiles, and the
purity, flow, scan, bridge, codegen, proto and bridge-RPC-name gates.
…esses move to the tests, and the committed signal fires once Exports referenced by nothing in production and by tests only: a second balances decoder (`decodeBalancesListResponseStrict`) beside the one `getAllBalances` uses, `encodeEnvelope`, an event encoder production never encodes with (`encodeBilateralEventNotification`), `parseBinary32`/`64`, `clearBleIdentityCache`, `pruneBleIdentityMappings`, a second history reader (`getTransactions`), `getBluetoothStatus`, `mapPoliciesToDisplayEntries` and its module, six wallet-store selector hooks no screen calls, `decodeBase32Crockford32`, `FX_ANIMS`, and the native session store's `…ForTest` setters. The tests of them tested nothing real; one "reproduction" test mimicked the balance mapper with its own fallback and `console.log`. The accept path also dispatched the committed signal twice: as a window event the adapter re-emitted on the bus, and on the bus again. Dead exports deleted with the tests of them. The event encoder moves to `tests/helpers/bilateralEventFixture.ts`, a harness the tests own. The session store is driven in its tests by the production input — `session.state` on the bus — and made fresh per test by module isolation; the BLE cache tests likewise. The accept path emits the committed signal once, through `emitBilateralCommitted`, and the window hop for it is deleted with the adapter's listener. Tests: bilateralAcceptEvent (the committed signal exactly once per accept), utils/identity (the session published on the bus, a fresh store per test), resolution (a fresh module per test). Mutation control, red on its named test: the committed signal emitted twice. Verified: tsc, lint, jest 788/788 across 117 suites (48 tests of dead code gone with it), npm run build, and the purity, flow, scan, bridge, codegen, proto and bridge-RPC-name gates.
…e copy of balances and history goes The wallet screen held its own copies of balances (a private `Balance` shape mapped from `TokenBalanceView`) and history beside the provider's store, and reloaded them — with the identity and the contacts — on every `wallet.refresh`, so one wallet change was two reads each of `balance.list` and `wallet.history` and one of `contacts.list` and the headers. The screen now reads balances and history from the wallet store, the one copy, reloaded once per change in the provider; the tabs take `TokenBalanceView` and the private `Balance` type is deleted. The screen owns the identity (read once, or the reason it was not) and the send tab's contacts with their send-readiness, re-read when the contacts store reports a change and on a manual refresh; a manual refresh reloads the store and the contacts. The overview says "Loading balances…" while the store has not answered, rather than "No balances yet". What the stores report failed is the screen's warning, in their words. Tests: EnhancedWalletScreen.events — every test mounts the screen inside the wallet provider, as the app does, and the store is reset per test; `one inbox sync with new items reloads the wallet data once` now counts the store's one reload with the screen adding none. Mutation control, red on that test: a `wallet.refresh` reload re-added to the screen hook beside the store's. Verified: tsc, lint, jest 788/788, npm run build, and the purity, flow, scan, bridge, codegen, proto and bridge-RPC-name gates.
…contacts store, recorded Open
… store; the wallet screen's copy of the contacts goes Two mappers read Rust's contact list into two shapes: the contacts store's `Contact` (`id`, `publicKey`, `isVerified`, the raw BLE address) for the contacts screen, and `mapContactList`'s `DomainContact` (the signing key, `genesisVerifiedOnline`, the normalised BLE address or the one the native side resolved this session, and the send-readiness Rust reports) for the send tab — so the wallet screen kept its own copy of the contacts, re-read from Rust beside the store's. The contacts store maps with `mapContactList` into `DomainContact`, the one shape every screen reads; `Contact` and `mapContacts` are deleted; the contacts screen reads `deviceId`, `signingPublicKey` and `genesisVerifiedOnline`; the wallet screen reads the store's contacts and re-reads nothing of its own. A manual refresh reloads both stores. Tests: contactsStore (the DomainContact shape in Base32 Crockford from the one mapper; genesisVerifiedOnline as Rust reports it), EnhancedWalletScreen (the refresh button re-reads balances, history and contacts; the screen's contacts come from the store the harness loads), the contacts screen tests on the renamed fields. Mutation control, red on its named test: the manual refresh no longer reloading the contacts store. Verified: tsc, lint, jest 789/789, npm run build, and the purity, flow, scan, bridge, codegen, proto and bridge-RPC-name gates.
…DOM hops nothing dispatched go, and the identity wake-up finally hears its event The adapter re-emitted seven DOM events on the bus that nothing ever dispatched — `dsm-history-updated`, `dsm-balances-updated`, `dsm-wallet-send-committed`, `dsm-contact-added`, `DSM_PORT_TX`, `DSM_PORT_RX`, `DSM_UI_TICK` — and `useWalletSync`, the contacts provider and the loading spinner subscribed to the bus events they would have produced: reloads and an activity indicator that could never fire. The native lifecycle topics reached the bus through DOM hops: `dsm-identity-ready` was dispatched on `document`, re-emitted by the adapter, and listened for by `getIdentity`'s early wake-up on `window`, where it never arrived, so the cold-start wait always ran its full delays; `dsm-wallet-refresh` and `dsm-env-config-error` likewise went DOM → adapter → bus. `session.state` and `bilateral.event` were also fanned out as DOM events with no listener. The seven hops, their bus event types, `useWalletSync` (its one live subscription, `identity.ready`, is now the wallet provider's own), the contacts provider's `contact.added` subscription and the spinner's activity effect are deleted. The event bridge emits `identity.ready`, `wallet.refresh` (`native`) and `env.config.error` on the bus directly; the adapter keeps only `visibilitychange`; `getIdentity` wakes on the bus event, which now reaches it; `DSM_WALLET_REFRESH_EVENT` and the two listener-less fan-outs are deleted. `dsm-biometric-result`, `bluetooth-permissions` and `ble-dev-automation` stay, recorded Open: whether device automation reads them from outside the bundle is a device question. Tests: E2E.uiCoordination (the identity-ready, wallet-refresh and env-config-error topics each reach the bus with what they carry; the adapter-hop tests of events nothing dispatched are gone), identity.test (the wait wakes early on identity.ready). Mutation controls, each red on its named test: the identity-ready topic no longer emitted on the bus; the wake-up listener removed. Verified: tsc, lint, jest 777/777 across 116 suites, npm run build, and the purity, flow, scan, bridge, codegen, proto and bridge-RPC-name gates.
…nes that did not — with the BLE gate and provider they drove — go
`useBridgeEvent` took any string, so subscriptions to events nothing can
emit compiled: two toasts (`ble.permission.recovery.needed`,
`ble.features.disabled`) and a `ble.features.enabled` handler — the last
not even a bus event — which together drove a `bleFeaturesDisabled` flag
that gated every BLE call and could never be set. Two BLE advertising events
were emitted with no consumer left, and `nfc.writeStarted` had neither.
`BleContext` provided a context nothing consumed since `useBle` went: scan
state and four no-op calls, mounted in `App` for nothing.
`useBridgeEvent`'s name is now `keyof BridgeEventMap`, so a subscription
to a name the bus does not carry does not compile. The dead subscriptions,
the flag and its gate, the two emits, the three event types and
`BleContext` are deleted. `wallet.exitCompleted`, subscribed to in three
places and emitted by nothing, is Bitcoin's exit flow and is left alone.
Tests: none added — a type and deletions. Compile control: a
`useBridgeEvent('nothing.emits', …)` added to a provider fails tsc, naming
the file. Verified: tsc, lint, jest 777/777 across 116 suites, npm run
build, and the purity, flow, scan, bridge, codegen, proto and
bridge-RPC-name gates.
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.
Frontend sweep, round three. Five verified chunks, each its own commit with a
CONFORMANCE_GAPS.md§6.29 row, tests and mutation or gate controls. All frontend; one proto change; one Kotlin arm deleted.1. One reload per announced change (
7987158c9)useWalletRefreshListener's "cooldown" counted dropped events, not frames: after any reload the next 119 low-prioritywallet.refreshevents were discarded, so the provider and the wallet screen had grown rawinbox.updated/bilateral.transferCompletesubscriptions beside it and reloaded three to four times per event when the gate was open. Now the listener coalesces onto a frame, runs one reload at a time and owes exactly one more for events mid-reload; nothing is dropped, no priority classes; the raw subscriptions and the provider's second reload onwallet.bilateralCommittedare gone; the event bridge'swallet.refreshclaimingbilateral.transfer_completeon one BLE prepare response in eight is gone. The accept path's staggered re-reads stay, namedbilateral.accept_followup(Open: a device run decides whether Rust's TRANSFER_COMPLETE announcement alone suffices).2. A balance row carries what its token is and what its policy fixes and permits (
e1c0708bd)AccountsScreencarried its own table of what ERA and dBTC are (a "DJTE emission token" type, an invented anchor formula, a supply figure) and decided protocol-ness from the ticker text. Rust now reports on eachBalanceGetResponse:protocol_defined(12),genesis_supply_display(13; ERA's is the native reserve's genesis supply), andpermissions(14, aTokenPolicyPermissionssub-message, absent — never defaulted — where Rust holds no committed policy; ERA's blob is the §6.32 hole). A registered token's row is checked against its own verified policy bytes and refused if it disagrees.CPTA_INFOis deleted; BURN is offered only where the policy permits it; the Rust→TS wire fixture carries the new fields.3. Dead transport paths (
8aca54105)isNativeHostUnavailableError(no caller),processEnvelopeV3Bin/buildEnvelopeIngressRequest(anenvelopeingress op no production code sends), phantom test-bridge getters and stub arms forgetSigningPublicKeyBin(deleted with #1012).ci/bridge_rpc_names.pynow refuses a test stub arm for any name Kotlin does not handle.4. Identity readiness (
5994ccbb0)getIdentityanswerednullfor three different reasons and waited its whole ~5.75 s window on a device with no identity. It now answers the identity or throwsIdentityUnavailableErrorwith its state:missing(Rust's word, the native session, answered at once),runtime_not_ready, orread_failed(with the reason). The wallet store treatsmissingas a state (uninitialized, no error) and the other two as errors; diagnostics print the state;useTransactionsasks the native session.isReady/getDeviceIdentityare deleted.5. Production exports nothing calls (
3fa3e830a)Five whole modules (
dsm/crypto.ts,dsm/nfc.ts,vectors/,policyScanService.ts,utils/binary.ts) and fourteen functions with no production reference — among themloadPersistedBleMappings, a documented no-op, anduseUXTerms, which answered the key it was asked for. Also the WebView's startup-boundary path end to end (TS functions, the index.htmlstartupwrapper, theAndroidBridgeV3member, a test-setup stub arm, Kotlin'snativeBoundaryStartuparm): Kotlin crosses the startup boundary itself at app start; the frontend never sent it. Exports referenced only by tests are recorded Open for an export-by-export sweep.6. Production exports kept alive only by tests (
fb4d9a511)A second balances decoder beside the one
getAllBalancesuses,encodeEnvelope, an event encoder production never encodes with,parseBinary32/64, the BLE cache's clear/prune, a second history reader,getBluetoothStatus, the policy display mapper and its module, six wallet-store selector hooks,decodeBase32Crockford32,FX_ANIMS, and the session store's…ForTestsetters — deleted with the tests of them (48 tests of dead code; one "reproduction" test mimicked the balance mapper with its own fallback andconsole.log). The event encoder moves totests/helpers/, a harness the tests own; the session store and BLE cache tests drive the production input and get a fresh module per test. The accept path dispatched the committed signal twice (a window event the adapter re-emitted, and the bus again): once now, throughemitBilateralCommitted.7. The wallet screen reads the one wallet store (
728bbcd68)The wallet screen held its own copies of balances and history beside the provider's store and reloaded them, with the identity and the contacts, on every
wallet.refresh— two reads each ofbalance.listandwallet.historyper change. It now reads balances and history from the store (the one copy, reloaded once per change in the provider); the tabs takeTokenBalanceViewand the privateBalancetype is gone. The screen owns the identity (read once, or the reason it was not) and the send tab's contacts with their send-readiness, re-read when the contacts store reports a change and on a manual refresh. The overview says "Loading balances…" while the store has not answered. The wallet screen tests mount the screen inside the wallet provider, as the app does; the "one inbox sync reloads once" test now counts the store's one reload with the screen adding none.8. One contact shape, in the contacts store (
89cd72996)Two mappers read Rust's contact list into two shapes — the contacts store's
Contactfor the contacts screen andmapContactList'sDomainContact(signing key,genesisVerifiedOnline, the normalised or session-resolved BLE address, the send-readiness Rust reports) for the send tab — so the wallet screen kept its own copy of the contacts. The store now maps withmapContactListintoDomainContact, the one shape every screen reads;ContactandmapContactsare gone; the wallet screen reads the store's contacts and re-reads nothing of its own; a manual refresh reloads both stores.9. The native lifecycle topics reach the bus directly; the DOM hops nothing dispatched go (
4811e405b)The adapter re-emitted seven DOM events on the bus that nothing ever dispatched (
dsm-history-updated,dsm-balances-updated,dsm-wallet-send-committed,dsm-contact-added,DSM_PORT_TX,DSM_PORT_RX,DSM_UI_TICK), anduseWalletSync, the contacts provider, the contacts screen and the loading spinner subscribed to what they would have produced — reloads and an activity indicator that could never fire. The lifecycle topics went DOM → adapter → bus, andgetIdentity's early wake-up listened onwindowfordsm-identity-ready, dispatched ondocument, where it never arrived — so the cold-start wait always ran its full delays. Now the event bridge emitsidentity.ready,wallet.refresh(native) andenv.config.erroron the bus directly; the adapter keeps onlyvisibilitychange;getIdentitywakes on the bus event; the seven hops, their bus types,useWalletSync,DSM_WALLET_REFRESH_EVENTand two listener-less fan-outs are gone. Three BLE/biometric window events with no frontend listener stay, recorded Open (device automation may read them).10. A subscription names an event the bus carries (
a02d1b0fd)useBridgeEventtook any string, so subscriptions to events nothing can emit compiled: two toasts and able.features.enabledhandler (not even a bus event) that together drove ableFeaturesDisabledflag gating every BLE call, never settable; two BLE advertising events emitted with no consumer left;nfc.writeStartedwith neither.BleContextprovided a context nothing consumed sinceuseBlewent. The name is nowkeyof BridgeEventMap— a subscription to a name the bus does not carry does not compile (control: one added to a provider fails tsc naming the file) — and the dead subscriptions, flag, gate, emits, types andBleContextare gone.wallet.exitCompleted(Bitcoin's exit flow, subscribed to in three places, emitted by nothing) is left alone.Verification
Each chunk: tsc, lint, the touched suites, mutation or gate controls (named in §6.29 and the commit messages). On the tip:
make lintand the production-safety lints (chunk 2), thedsm_sdkreleasewallet_routestests on Postgres (chunk 2), jest 777/777 across 116 suites,npm run build, the Kotlin main / androidTest / unit-test compiles (chunk 5), and the purity, flow, scan, bridge, codegen, proto and bridge-RPC-name gates.Still Open (§6.29)
BLE priming and the accept follow-up cadence (device run); SofiScreen §28 (the SoFi track);
recovery.statuskey=value (dependency boundary); index.html's 30-minute timer and response/event discrimination (device); the androidTestclaimFaucetzeroschema_hash;dsm-biometric-result/bluetooth-permissions/ble-dev-automationwindow events with no frontend listener (device automation may read them).