Files
d12b2b2617 refactor: optimize useLoadFeature with shared registry and reduced re-renders (#7154)
* 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]>
2026-02-12 16:05:20 +01:00

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.tsx to 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.ts uses 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.md with 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

  1. Review/migrate source files first
  2. Update test mocks second
  3. Run tests to verify third
  4. 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)

  1. Complete Phase 1: Setup (create 3 infrastructure files)
  2. Complete Phase 2: Foundational (verify compiles)
  3. Complete Phase 3: User Story 1 (verify feature handle works)
  4. STOP and VALIDATE: v3 architecture is functional
  5. Can demo feature handle pattern

Critical Path (Phase 4-5)

  1. Add Phase 4: User Story 2 (safe-shield) → CRITICAL for security
  2. Add Phase 5: User Story 3 (OAuth) → CRITICAL for user flow
  3. STOP and VALIDATE: Critical integrations work
  4. Can safely deploy

Full Migration (Phase 6-8)

  1. Add Phase 6: User Story 4 (banners) → Better UX
  2. Add Phase 7: User Story 6 (bundle) → Performance gains
  3. Add Phase 8: Polish → Complete migration
  4. 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