MetaMask / MetaMask/metamask-extension
[State Sync Redesign] Epic 3: UI-controller subscriptions
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 13.2k
- Forks
- 5.6k
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 451
Description
Remove the monolithic `metamask` Redux slice via controller-scoped `useSyncExternalStore` subscriptions. Builds on A1's background restructuring (Epics 1 + 2) by adding per-controller UI stores, migrating selectors and components, and removing the slice.
## Key Benefits
- **Scoped concurrent rendering**: Per-controller subscriptions scope React 18 concurrent features (`useDeferredValue`, `useTransition`) to per-controller granularity — deferred or transitioned re-renders evaluate only the affected controller's selectors, not all ~635. (Redux already prevents tearing via react-redux v8, but the monolithic dispatch still evaluates all selectors.) See [#6657](https://github.com/MetaMask/MetaMask-planning/issues/6657).
- **Remove `metamask` Redux slice** and `forceUpdateMetamaskState` (~109 RPC round-trips)
- **8 of 10 circular dependency cycles eliminated**
- **Fine-grained reactivity**: selectors only evaluate when their subscribed controllers change, eliminating fan-out evaluation cost
- **Mechanical migration**: ~670 `useSelector` + ~37 `connect()` via compatibility hook, ~85% automatable
- **Enables `selectors.js` decomposition**: `controllerDeps` annotations provide a natural splitting criterion; slice removal eliminates `selectors.js`'s role as the sole import hub for controller state
## ADR Option
A2 (UI migration)
## Phase
A2 (UI-controller subscriptions) — follows A1
## Dependencies
Epic 1 + Epic 2 (A1 phase must be complete: controller keys preserved in transport, keyed initial state, background cleanup done)
## React 18 sequencing
MVP lands on React 17 (using `use-sync-external-store/shim`). Horizontal expansion benefits from React 18's automatic batching and native `useSyncExternalStore`. No blocking dependency in either direction. On React 18, per-controller subscriptions additionally scope concurrent rendering features (`useDeferredValue`, `useTransition`) to per-controller granularity — deferred or transitioned re-renders evaluate only the affected controller's selectors instead of all ~635. (Tearing is already handled by react-redux v8's internal use of `useSyncExternalStore`; the benefit here is scope reduction.)
## Issues
### MVP (vertical slice)
- [ ] 3.1: Implement `ControllerStateProxy`, `StateSubscriptionService`, and `useControllerState` — #40038
- [ ] 3.2: Create `renderWithControllerState` test utility — #40039
- [ ] 3.3: Migrate one PreferencesController component (MVP validation) — #40040
### Horizontal expansion
- [ ] 3.4: Implement `useControllerSelector` compatibility hook — #40041
- [ ] 3.5: Annotate high-frequency selectors with `controllerDeps` — #40042
- [ ] 3.6: Codemod for `useSelector` → `useControllerSelector` — #40043
- [ ] 3.7: Component migration (per domain, repeating) — #40044
### Slice removal
- [ ] 3.8: Remove `forceUpdateMetamaskState` (~109 call sites) — #40045
- [ ] 3.9: Remove dual-write and `updateMetamaskState` thunk — #40046
- [ ] 3.10: Update Sentry state capture chain — #40047
- [ ] 3.11: Update state logs and debug tools — #40048
- [ ] 3.12: Update Storybook state mocks — #40049
- [ ] 3.13: Remove `metamask` Redux slice — #40050
- [ ] 3.14: Debug-only controller state inspector (optional) — #40051
## Critical path
3.1 → 3.4 → 3.5 → 3.6 → 3.7 → 3.8 → 3.9 → 3.10 → 3.13
## Validation Criteria (Epic complete)
- [ ] `ui/ducks/metamask/metamask.js` deleted
- [ ] `FlattenedBackgroundStateProxy` type removed or repurposed
- [ ] Zero `useSelector` calls reading `state.metamask` remain
- [ ] Zero `forceUpdateMetamaskState` calls remain
- [ ] Zero `connect()` HOCs reading `state.metamask` remain
- [ ] Sentry state capture validated in dev
- [ ] State logs export works
- [ ] 8 of 10 circular dependency cycles eliminated (`yarn circular-deps:check`)
- [ ] `selectors.js` decomposition unblocked: `controllerDeps` annotations provide splitting criterion, `selectors.js` no longer required as sole import hub
- [ ] E2E test suite passes
- [ ] No new circular dependencies introduced
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the prerequisite MVP issues, especially #40038 for ControllerStateProxy, StateSubscriptionService, and useControllerState, then follow the critical path through the listed UI migration issues. Use the validation criteria as the definition of done, including removing ui/ducks/metamask/metamask.js, eliminating state.metamask useSelector calls, and passing the circular-dependency and E2E checks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, redux, typescript
- Domain
- frontend
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 18/100