Skip to content

refactor(redux): replace injected selectors with getters - #30837

Open
peter-sanderson wants to merge 16 commits into
developfrom
refactor/extra-selector-getters
Open

refactor(redux): replace injected selectors with getters#30837
peter-sanderson wants to merge 16 commits into
developfrom
refactor/extra-selector-getters

Conversation

@peter-sanderson

@peter-sanderson peter-sanderson commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Description

Redux selectors injected through ExtraDependenciesStatic make suite-common thunks depend on an unknown state shape supplied by the upper application store. This removes the selectors group, creates zero-argument services with toGetter in the desktop and native composition roots, and updates all consumers, mocks, affected tests, and documentation. The remaining any-based SuiteCompatibleSelector alias is also removed; firmware selector factories and authenticity hooks now preserve the platform selector state generically.

  • Injected selectors: 12 -> 0
  • SuiteCompatibleSelector references: 5 -> 0
  • Affected type-check: 179 projects passed
  • Focused tests: 15 passed
  • Lint: all modified projects passed
  • Other checks: format, depcheck (179 projects), and project references passed

🌐 Preview deployments

🌐 Suite Web preview: https://dev.suite.sldev.cz/suite-web/refactor/extra-selector-getters/web/

🔍 Currents Test Results

🔍 Suite web test results: View in Currents

🔍 Suite desktop test results: View in Currents

🔍 Suite native android test results: View in Currents

🔒 Quarantined E2E Tests

Trezor Suite (desktop) — 2 test(s)
Test Type
Quarantine test: "Recovery - dry run,Recovery after partial recovery" 🙋 manual
Quarantine test: "Recovery - dry run,Recovery with device reconnection" 🙋 manual

Updated: 2026-08-05T09:48:37.695Z • 2 test(s) total

Trezor Suite (web) — 3 test(s)
Test Type
Quarantine test: "Recovery - dry run,Recovery with device reconnection" 🙋 manual
Quarantine test: "TrezorConnect webextension -> Suite Web,second call after popup was closed by user should work" 🙋 manual
Quarantine test: "Recovery T2T1 - dry run,Recovery after partial recovery" 🙋 manual

Updated: 2026-08-05T09:47:33.065Z • 3 test(s) total

🤖 LLM Test Recommendations

Summary: The change set is a cross-package refactor of dependency-injection/extra-dependencies wiring with concentrated changes in firmware, TrezorConnect init, trading initialization, and app startup. Risk is broad but shallow: the highest-impact regressions would appear in firmware flows, Connect popup lifecycle, trading initial data loading, and onboarding/app startup. The recommended set focuses on these areas with a small number of representative end-to-end tests rather than the full static-mapping list.

Changed files (30)
  • packages/suite/src/actions/suite/initAction.test.ts
  • packages/suite/src/components/suite/Preloader/Preloader.tsx
  • packages/suite/src/support/extraDependencies.ts
  • packages/suite/src/views/settings/SettingsDebug/FirmwareUpdateEnvironmentSelect.tsx
  • suite-common/connect-init/src/connectInitThunks.ts
  • suite-common/dependency-injection/src/index.ts
  • suite-common/dependency-injection/src/toGetter.test.ts
  • suite-common/dependency-injection/src/toGetter.ts
  • suite-common/firmware-authenticity/package.json
  • suite-common/firmware-authenticity/src/useReportDeviceCompromised.ts
  • suite-common/firmware-authenticity/tsconfig.json
  • suite-common/firmware/src/firmwareReducer.ts
  • suite-common/firmware/src/firmwareThunks.ts
  • suite-common/firmware/src/getBinFilesBaseUrlThunk.ts
  • suite-common/redux-utils/src/extraDependenciesType.ts
  • suite-common/redux-utils/src/notImplemented.ts
  • suite-common/redux-utils/src/types.ts
  • suite-common/suite-types/package.json
  • suite-common/suite-types/src/firmware.ts
  • suite-common/suite-types/src/index.ts
  • suite-common/suite-types/tsconfig.json
  • suite-common/test-utils/src/extraDependenciesCommonMock.ts
  • suite-common/token-definitions/src/tokenDefinitionsThunks.ts
  • suite-common/trading/src/thunks/common/loadInitialDataThunk.test.ts
  • suite-common/trading/src/thunks/common/loadInitialDataThunk.ts
  • suite-common/wallet-core/src/accounts/accountsThunks.ts
  • suite-common/wallet-core/src/blockchain/blockchainThunks.ts
  • suite-common/wallet-core/src/fiat-rates/fiatRatesThunks.ts
  • suite-native/app/src/hooks/useGlobalHooks.tsx
  • suite-native/state/src/extraDependencies.ts

Recommended tests (10)

🔴 High priority (6)
  • suite/e2e/tests/firmware/custom-firmware.test.ts — Directly exercises custom firmware installation, which is served by the changed firmware thunks/reducer and the debug firmware-update-environment selector. A regression in firmware binary URL resolution or thunk wiring would surface here.
  • suite/e2e/tests/onboarding/authenticity-check.test.ts — Runs the device authenticity path during onboarding, which is affected by firmware-authenticity reporting and firmware thunk changes. It asserts discovery finishes after the authenticity flow, validating end-to-end wiring.
  • suite/e2e/tests/onboarding/t2t1/t2t1-entropy-check-failure.test.ts — Specifically asserts the 'device compromised' modal behavior governed by useReportDeviceCompromised and entropy-check handling. This is the most direct user-facing coverage for the firmware-authenticity change.
  • suite/e2e/tests/suite/initial-run.test.ts — Verifies onboarding/initial-run state persistence and app loading across reloads, exercising the Preloader component and the suite init/extra-dependencies wiring that underlies all other flows.
  • suite/e2e/tests/trading/buy-bitcoin.test.ts — Exercises the full trading buy flow, which depends on loadInitialDataThunk and the wallet-core account/blockchain/fiat-rates thunks. A regression in trading initial-data loading or thunk dependencies would be visible in the quotes/confirmation payloads.
  • suite/e2e/tests/trezor-connect/connectPopupWeb.test.ts — Directly covers TrezorConnect popup initialization and permission flows, which rely on connectInitThunks. Changes to connect init wiring or extra dependencies would break the popup lifecycle assertions in this test.
🟡 Medium priority (4)
  • suite/e2e/tests/browser/safari.test.ts — Validates the unsupported-browser gate and subsequent Suite load, which passes through the Preloader/initialization path. Useful as a lightweight smoke test that the app still loads correctly after Preloader changes.
  • suite/e2e/tests/onboarding/t3t1/t3t1-create-wallet-offline.test.ts — Covers offline onboarding including firmware revision checks and the no-connection banner. This validates firmware thunk behavior when firmware metadata cannot be fetched and the Preloader/offline state is active.
  • suite/e2e/tests/trading/swap-coin-to-token.test.ts — Exercises a cross-chain token swap, which depends on trading loadInitialDataThunk, token definitions, and wallet-core thunks. Provides broader trading coverage beyond the buy flow.
  • suite/e2e/tests/wallet/discovery.test.ts — Activates many coins and verifies discovery completes after reload, exercising the wallet-core accounts/blockchain/fiat-rates thunks and persistence logic. Good representative coverage for the broad wallet-core changes.

⚠️ Changes with no test coverage (7)

  • suite-common/redux-utils/src/extraDependenciesType.ts
  • suite-common/redux-utils/src/notImplemented.ts
  • suite-common/redux-utils/src/types.ts
  • suite-common/suite-types/src/firmware.ts
  • suite-common/suite-types/src/index.ts
  • suite-native/app/src/hooks/useGlobalHooks.tsx
  • suite-native/state/src/extraDependencies.ts

Updated: 2026-08-05T09:48:59.227Z

@peter-sanderson peter-sanderson added the no-project This label is used to specify that PR doesn't need to be added to a project label Aug 4, 2026
@peter-sanderson
peter-sanderson marked this pull request as ready for review August 4, 2026 20:15
@peter-sanderson
peter-sanderson force-pushed the refactor/extra-selector-getters branch from 3cba561 to df7a373 Compare August 5, 2026 07:20
peter-sanderson and others added 4 commits August 5, 2026 09:28
Getter services created by `toGetter` now carry the wrapped selector under a
`selector` property, so the same injected dependency can be consumed in React
components via `useSelector(getSomething.selector)`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Getter services created by `toGetter` now carry the wrapped selector under a
`selector` property, so the same injected dependency can be consumed in React
components via `useSelector(getSomething.selector)`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@trezor-bot

trezor-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

✅ Previously successful run of [Test] suite-native Android E2E workflow has been found.
⏭️ Skipping tests for this run.
💡 If you are unsure about your latest changes, please rerun the workflow manually. (Use the Re-run all jobs option)

vojtatranta and others added 3 commits August 5, 2026 11:27
`Getter.selector` wrapped the original selector, so a component using it stayed
coupled to the state shape — its tests had to build store state instead of just
mocking the getter. The selector now ignores the state it is handed and reads
through the getter's own `getState`; the state argument exists only so
`useSelector` re-evaluates on every store change.

`Getter` therefore no longer needs its state type parameter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A getter called during render is read once, so the component never re-renders when
the value changes — an easy mistake, since getters look like ordinary services.

- `Getter` carries a type-only brand, `asGetter` puts it on getters not built from a
  selector (test mocks, constants).
- `useGetter(selectGetterDep)` takes the same dependency selector as `useServices`,
  holding a single getter, and returns its current value, subscribed through
  `useSelector`. The store state never reaches the getter, it reads through its own
  `getState`, so a component and its test stay independent of the state shape and
  only have to mock the getter.
- `useServices` refuses to hand out branded getters: its result type resolves to an
  explanation, so the call site fails on the first property access.

`ExperimentalFeatureConfig.onToggle` declared a `SuiteServices` field no feature ever
read, so the whole bag is no longer threaded into it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`useServices` refuses to hand out branded getters, but a dependency bag can legitimately
be taken as a whole and only used imperatively — `ExperimentalFeatureConfig.onToggle`
receives all `SuiteServices` and is called from an event handler, nothing is read during
render. `useImperativeServices` selects such dependencies without the getter check, which
keeps the experimental features API unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread suite-common/dependency-injection/src/useServices.test.tsx Outdated
Comment thread suite-common/dependency-injection/src/useServices.test.tsx Outdated
Comment thread suite-common/dependency-injection/src/useGetter.ts Outdated
Comment thread suite-common/dependency-injection/src/useGetter.ts
vojtatranta and others added 3 commits August 5, 2026 12:40
Reading one getter never needed params: dropping them collapses the hook's types to
one conditional and makes a second argument a plain arity error.

Review feedback: use typedObjectValues over a cast Object.values, and keep the
type-only rejections in the type-test rather than dressing them up as runtime tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Getters can take params, so useGetter takes them after the dependency selector and
passes them on: `useGetter(selectGetIsSelectedWalletDep, descriptor)`. The subscription
closes over them, so re-renders stay as precise as with a plain selector.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The params are only picked up because the subscription selector is rebuilt every
render. Prove it: the value follows a changed param, and the new param is what gets
watched afterwards.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread suite-common/dependency-injection/src/useServices.tsx
export const selectEffectiveFirmwareChannel =
(allowPrerelease: boolean) =>
(state: RootState): FirmwareChannel =>
// When a user is in the Early Access Program, the firmware channel is forced to `production-early-access`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preserve a original comment

vojtatranta and others added 2 commits August 5, 2026 13:07
Getters and plain services are consumed differently — a getter holds a value that
changes over time and must be subscribed to, a service is just called — so a
dependency lumping both together cannot be consumed correctly as a whole. Point at
selecting the individual services instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment still described the removed `.selector` property.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Comment thread suite-common/suite-types/src/firmware.ts
Comment thread suite-common/dependency-injection/src/useServices.tsx Outdated
Comment thread suite-common/dependency-injection/src/useServices.tsx Outdated
Comment thread suite-common/dependency-injection/src/useServices.tsx Outdated
Review feedback: keep general type helpers in one place. `UnionToIntersection` already
lives there, so useServices takes both from the package instead of declaring its own —
the local copy would conflict on the next rebase anyway.

Also restores the original comment on the firmware channel selectors, which the value
parameter change had reworded and moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-project This label is used to specify that PR doesn't need to be added to a project

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants