.agents/skills/frontend-large-feature-architecture/references/controller-migration.md
Use this when a frontend surface has grown into one component or hook that owns data fetching, route glue, filters, table/list state, selection, drawers, actions, and expensive rendering.
The target is clear ownership, not "everything in a store." Start from the
ownership baseline in big-feature-rules.md.
Most existing features are not there yet. Migrate in narrow slices and keep behavior stable.
Do not start by designing the perfect final feature. Start by making the next change safer than the previous one.
For each migration PR, write down:
This is how large features become managed features without review-hostile rewrites. The feature README is the living owner map; update it in place as the feature moves forward.
Map the controller. List every state group, query, derived value, effect, callback, action, and expensive child render owned by the component.
Classify state. Separate server/query, route, persisted browser, high-frequency local UI, derived view data, imperative integration, and one-off modal/form state.
Instrument one symptom. Pick a concrete interaction such as row selection, scroll, filter change, drawer open, form step change, or saved-view change. Measure what rerenders, remounts, refetches, or recalculates.
Choose one boundary. Start with the smallest high-value boundary: selection, lazy-row state, batch action workflow, filter-target mapping, column/view state, wizard step state, or pure data preparation. Do not migrate everything at once.
Choose the lightest tool. A pure helper or action extraction may be the right first PR. Add a local store only when selective subscriptions or per-mount persistence are needed.
Create a local store instance when needed. Use lazy useState in the
page/view:
const [store] = useState(() => createFeatureStore(initialState));
Provide only this stable store instance through context.
Move mutations into named actions. Put state-changing logic in store actions or external action functions. Keep components responsible for user events, not workflows.
Split containers from views. Containers may subscribe, call hooks, or fetch data. Views should render props or tiny selected values.
Move data preparation out of render. Put expensive or complicated transformations in pure functions. Backend data should flow into compiled UI data, then into rendering.
Bridge query state explicitly. If local store decisions depend on React Query data, use a named feature hook or action to express that relationship.
Isolate imperative integration. Virtualizers, observers, keyboard listeners, third-party DOM mutation handling, and timers belong in narrow integration hooks.
Update the feature README. Record what this PR improved, desired boundaries, known spread state, and the next extraction target.
Remove debug instrumentation. Temporary logs are useful during migration, but should not survive the slice.
Repeat. Each slice should make one semantic interaction narrower and easier to reason about.
Use the feature's current shape to pick the first slice:
If a feature already has hooks for part of this work, treat them as partial migration, not proof that the whole surface is healthy.
Avoid:
src/components/* exports.