MetaMask / MetaMask/metamask-extension

[State Sync Redesign] Epic 3: UI-controller subscriptions

Open
#40,060 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

INVALID-ISSUE-TEMPLATE team-extension-platform
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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.