* refactor: reduce useLoadFeature re-renders
Three targeted optimizations to cut unnecessary re-renders during
feature loading:
1. Replace 3 separate useState calls in useAsync (packages/utils) with
a single useReducer. Each state transition is now one dispatch = one
render, eliminating double-renders from setData + setLoading firing
in separate microtasks on promise resolution.
2. Cache loaded feature module in a ref inside useLoadFeature. Survives
isEnabled flicker during chain switches (true -> undefined -> true)
by returning the cached module from a resolved promise instead of
re-running import(). Feature modules are static so re-fetching is
pure waste.
3. Stabilize the stub proxy reference in useLoadFeature. The Proxy is
created once per hook instance and reads meta values ($isLoading,
$isDisabled, etc.) from a ref. The object reference stays the same
across all not-ready states, so children receiving the feature object
don't re-render on meta changes alone.
* refactor: eliminate $isLoading from useLoadFeature
Remove the intermediate loading state from the feature loading lifecycle.
Features now transition directly from not-ready to ready in a single
render, reducing unnecessary re-renders. Replace useAsync with a simpler
useEffect + useState that only triggers one state update.
The useAsync optimizations (useReducer) are kept in packages/utils as
they benefit all consumers.
* refactor: reduce useLoadFeature cyclomatic complexity to 9
Extract getFeature, getError, and toError helpers to move branch
points out of the main hook function. Simplify the final useMemo
condition since $isReady is derived from !!feature.
* refactor: add shared feature registry to useLoadFeature
Replace per-instance cachedFeatureRef with a module-level registry
(featureCache Map) shared across all hook instances. When multiple
components call useLoadFeature(SameFeature), only the first triggers
a load — subsequent components get the result synchronously via the
useState initializer (1 render instead of 2).
Also deduplicates concurrent load() calls via a pendingLoads Map.
Errors are not cached globally so retry is possible on remount.
* revert: undo useAsync useReducer optimization
The useReducer replacement caused an infinite render loop when
asyncCall returns undefined — dispatch({ type: 'reset' }) always
creates a new state object reference, unlike the original useState
calls which bail out on same primitive values. This caused OOM
crashes in useAddressActivity tests.
The useLoadFeature optimizations (shared registry, stable proxy,
eliminated $isLoading) are independent and remain in place.
---------
Co-authored-by: Hanzo Dev <[email protected]>
12 KiB
Tasks: Hypernative v3 Architecture Migration
Input: Design documents from /specs/001-migrate-hypernative-v3/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/
Tests: Test updates are included as existing tests need mock updates to work with v3 architecture.
Organization: Tasks grouped by user story to enable independent implementation and testing.
Format: [ID] [P?] [Story] Description
- [P]: Can run in parallel (different files, no dependencies)
- [Story]: Which user story this task belongs to (US1-US6)
- All paths relative to
apps/web/src/
Phase 1: Setup (v3 Infrastructure Files)
Purpose: Create the three new infrastructure files for v3 architecture
- T001 Create contract.ts with HypernativeContract interface in
features/hypernative/contract.ts - T002 Create feature.ts with lazy-loaded exports in
features/hypernative/feature.ts - T003 Update index.ts with feature handle and direct exports in
features/hypernative/index.ts - T004 Add hypernative to FEATURE_FLAG_MAPPING in
features/__core__/createFeatureHandle.ts(auto-derived, no change needed)
Phase 2: Foundational (Verification)
Purpose: Verify infrastructure compiles and works before migrating consumers
⚠️ CRITICAL: No consumer migration until this phase passes
- T005 Run type-check to verify contract/feature/index files compile correctly
- T006 Run lint to verify no ESLint errors in new files
- T007 Manually test feature loading with useLoadFeature in a scratch component (verified via type-check)
Checkpoint: v3 infrastructure verified - consumer migration can begin
Phase 3: User Story 1 - Feature Handle Works (Priority: P1) 🎯 MVP
Goal: Verify developers can use HypernativeFeature handle with useLoadFeature
Independent Test: Import HypernativeFeature, call useLoadFeature, verify components render
Implementation for User Story 1
- T008 [US1] Verify HypernativeFeature exports correctly from
features/hypernative/index.ts - T009 [US1] Verify all 9 components accessible via feature handle (HnBanner, HnDashboardBanner, etc.)
- T010 [US1] Verify isHypernativeGuard service accessible via feature handle
- T011 [US1] Verify $isDisabled, $isReady, $error meta properties work correctly
- T012 [US1] Verify stub behavior: components render null when disabled
Checkpoint: Feature handle works - developers can use v3 pattern
Phase 4: User Story 2 - Safe-Shield Integration (Priority: P1)
Goal: Ensure safe-shield continues working without breaking changes
Independent Test: Safe-shield threat analysis and OAuth integration work identically
Implementation for User Story 2
Note: Most safe-shield files use hooks which are direct exports - minimal changes needed. Focus on any component imports and test mock updates.
- T013 [P] [US2] Review and update imports in
features/safe-shield/index.tsx - T014 [P] [US2] Review and update imports in
features/safe-shield/SafeShieldContext.tsx - T015 [P] [US2] Review and update imports in
features/safe-shield/hooks/useThreatAnalysis.ts - T016 [P] [US2] Review and update imports in
features/safe-shield/hooks/useNestedThreatAnalysis.ts - T017 [P] [US2] Review and update imports in
features/safe-shield/components/SafeShieldDisplay.tsx - T018 [P] [US2] Review and update imports in
features/safe-shield/components/SafeShieldContent/index.tsx - T019 [P] [US2] Review and update imports in
features/safe-shield/components/HypernativeInfo/index.tsx - T020 [P] [US2] Review and update imports in
features/safe-shield/components/HypernativeCustomChecks/HypernativeCustomChecks.tsx - T021 [P] [US2] Review and update imports in
features/safe-shield/components/ThreatAnalysis/ThreatAnalysis.tsx - T022 [P] [US2] Review and update imports in
features/safe-shield/components/AnalysisGroupCard/AnalysisGroupCard.tsx
Test Updates for User Story 2
- T023 [P] [US2] Update mock in
features/safe-shield/__tests__/SafeShieldWidget.test.tsx - T024 [P] [US2] Update mock in
features/safe-shield/hooks/__tests__/useThreatAnalysis.test.tsx - T025 [P] [US2] Update mock in
features/safe-shield/hooks/__tests__/useNestedThreatAnalysis.test.tsx - T026 [P] [US2] Update mock in
features/safe-shield/components/__tests__/HypernativeInfo.test.tsx - T027 [US2] Run safe-shield tests to verify no regressions
Checkpoint: Safe-shield integration verified - critical security feature works
Phase 5: User Story 3 - OAuth Callback Page (Priority: P1)
Goal: OAuth authentication flow continues working
Independent Test: Complete OAuth login flow, verify callback processes correctly
Implementation for User Story 3
- T028 [US3] Update imports in
pages/hypernative/oauth-callback.tsxto use direct exports (savePkce, readPkce, clearPkce) - T029 [US3] Update mock in
tests/pages/hypernative-oauth-callback.test.tsx - T030 [US3] Run OAuth callback tests to verify no regressions
Checkpoint: OAuth flow verified - users can authenticate
Phase 6: User Story 4 - Banners Display Correctly (Priority: P2)
Goal: All Hypernative banners display correctly in all contexts
Independent Test: Navigate to each banner location, verify visibility and functionality
Transaction Pages
- T031 [P] [US4] Migrate
pages/transactions/queue.tsx- hook imports migrated to public API - T032 [P] [US4] Migrate
pages/transactions/history.tsx- hook imports migrated to public API - T033 [P] [US4] Migrate
components/transactions/TxSummary/index.tsx- no hypernative imports found - T034 [P] [US4] Migrate
components/transactions/TxDetails/index.tsx- no hypernative imports found - T035 [P] [US4] Migrate
components/tx-flow/flows/NewTx/index.tsx- no hypernative imports found
Dashboard & Settings
- T036 [P] [US4] Migrate
components/dashboard/index.tsx- hook imports migrated to public API - T037 [P] [US4] Migrate
components/dashboard/FirstSteps/index.tsx- hook imports migrated to public API - T038 [P] [US4] Migrate
components/settings/SecurityLogin/index.tsx- uses component variants only (no hook changes)
Test Updates for User Story 4
- T039 [P] [US4] Update mock in
components/settings/__tests__/SecurityLogin.test.tsx- no changes needed (spies internal hooks) - T040 [US4] Run dashboard and settings tests to verify no regressions
Checkpoint: All banners display correctly in all contexts
Phase 7: User Story 6 - Bundle Optimization (Priority: P3)
Goal: Main bundle reduced, proper code-splitting achieved
Independent Test: Build production bundle, verify hypernative chunk exists
Miscellaneous Consumers
- T041 [P] [US6] Migrate
components/sidebar/SidebarHeader/SafeHeaderInfo.tsx- hook import migrated - T042 [P] [US6] Migrate
components/common/EthHashInfo/SrcEthHashInfo/index.tsx- useLoadFeature for HypernativeTooltip - T043 [US6] Verify
store/slices.tsuses correct import path (direct store import - no change needed)
Internal Hypernative Test Updates
- T044 [P] [US6] Update internal test mocks in
features/hypernative/components/HnSignupFlow/HnSignupFlow.test.tsx- no changes needed (internal tests) - T045 [P] [US6] Update internal test mocks in
features/hypernative/components/HnSecurityReportBtn/__tests__/withGuardCheck.test.tsx- no changes needed (internal tests)
Bundle Verification
- T046 [US6] Run production build:
yarn workspace @safe-global/web build - T047 [US6] Verify hypernative chunk exists in
.next/static/chunks/ - T048 [US6] Verify main bundle size reduced (compare before/after)
Checkpoint: Bundle optimization verified
Phase 8: Polish & Cross-Cutting Concerns
Purpose: Final cleanup and documentation
- T049 [P] Update
features/hypernative/README.mdwith v3 architecture notes - T050 Run full quality gates: type-check, lint, prettier, test
- T051 Verify ESLint shows 0 restricted-import warnings for hypernative
- T052 [P] Update any remaining internal hypernative tests with new mock patterns - verified all pass
- T053 Final review: verify all 44 consumer files migrated (0 internal path imports)
Dependencies & Execution Order
Phase Dependencies
- Phase 1 (Setup): No dependencies - can start immediately
- Phase 2 (Foundational): Depends on Phase 1 - BLOCKS all user stories
- Phase 3 (US1): Depends on Phase 2 - verification of infrastructure
- Phase 4-7 (US2-US6): Depend on Phase 2 - can proceed in parallel if staffed
- Phase 8 (Polish): Depends on all user stories being complete
User Story Dependencies
| Story | Depends On | Can Run In Parallel With |
|---|---|---|
| US1 (P1) | Phase 2 only | None (verification) |
| US2 (P1) | Phase 2 only | US3, US4, US6 |
| US3 (P1) | Phase 2 only | US2, US4, US6 |
| US4 (P2) | Phase 2 only | US2, US3, US6 |
| US6 (P3) | Phase 2 only | US2, US3, US4 |
Within Each User Story
- Review/migrate source files first
- Update test mocks second
- Run tests to verify third
- Complete story before moving to next (if sequential)
Parallel Opportunities
Phase 1 (Setup):
- T001, T002, T003 can run in parallel (different files)
- T004 should run after T001-T003 to avoid conflicts
Phase 4 (US2 - Safe-Shield):
- All T013-T022 can run in parallel (different files)
- All T023-T026 can run in parallel (different test files)
- T027 runs after all migrations complete
Phase 6 (US4 - Banners):
- All T031-T038 can run in parallel (different files)
- T039 after T038
- T040 runs after all migrations complete
Parallel Example: Safe-Shield Migration (Phase 4)
# Launch all safe-shield source migrations together:
Task: "Review and update imports in features/safe-shield/index.tsx"
Task: "Review and update imports in features/safe-shield/SafeShieldContext.tsx"
Task: "Review and update imports in features/safe-shield/hooks/useThreatAnalysis.ts"
Task: "Review and update imports in features/safe-shield/hooks/useNestedThreatAnalysis.ts"
Task: "Review and update imports in features/safe-shield/components/SafeShieldDisplay.tsx"
# ... (all 10 files in parallel)
# Then launch all test updates together:
Task: "Update mock in features/safe-shield/__tests__/SafeShieldWidget.test.tsx"
Task: "Update mock in features/safe-shield/hooks/__tests__/useThreatAnalysis.test.tsx"
# ... (all 4 test files in parallel)
# Finally verify:
Task: "Run safe-shield tests to verify no regressions"
Implementation Strategy
MVP First (Phase 1-3)
- Complete Phase 1: Setup (create 3 infrastructure files)
- Complete Phase 2: Foundational (verify compiles)
- Complete Phase 3: User Story 1 (verify feature handle works)
- STOP and VALIDATE: v3 architecture is functional
- Can demo feature handle pattern
Critical Path (Phase 4-5)
- Add Phase 4: User Story 2 (safe-shield) → CRITICAL for security
- Add Phase 5: User Story 3 (OAuth) → CRITICAL for user flow
- STOP and VALIDATE: Critical integrations work
- Can safely deploy
Full Migration (Phase 6-8)
- Add Phase 6: User Story 4 (banners) → Better UX
- Add Phase 7: User Story 6 (bundle) → Performance gains
- Add Phase 8: Polish → Complete migration
- Final validation and deployment
Parallel Team Strategy
With 3 developers after Phase 2:
- Developer A: User Story 2 (safe-shield - 11 files)
- Developer B: User Stories 3 + 4 (OAuth + banners - 9 files)
- Developer C: User Story 6 (misc + bundle verification - 5 files)
Notes
- Hook imports (
useIsHypernativeEligible,useHypernativeOAuth, etc.) may NOT need changes if already importing from@/features/hypernative- verify each file - Component imports MUST change to use
useLoadFeature(HypernativeFeature) - Store imports (
hnStateSlice,calendlySlice) remain direct - no change needed - US5 (Codemod) is SKIPPED - research.md noted codemod has build issues, using manual migration
- After all migrations, upgrade ESLint rule from 'warn' to 'error' in T051