Upstream merge brings 52 commits from safe-global/safe-wallet-monorepo into the luxfi/safe-wallet@dev fork. Conflicts resolved: - yarn.lock: deleted (we are on pnpm) - apps/web/package.json: kept pnpm version + brand build scripts (build:safe / build:lux / build:hanzo) plus upstream's new generate-version.mjs step - apps/mobile/.../Signer/appKit.ts: took upstream's import pattern for shared/projectId, shared/metadata, shared/mmkvStorageAdapter Yarn sweep applied to 42 files (docs, github-actions, workflows, AGENTS, specs, codemods, scripts, READMEs). Net result: zero yarn-* command references anywhere outside node_modules. ❯ pnpm is the law in this fork. No yarn.
8.6 KiB
Implementation Plan: Bridge Feature Refactor
Branch: 002-bridge-refactor | Date: 2026-01-15 | Spec: spec.md
Input: Feature specification from /specs/002-bridge-refactor/spec.md
Summary
Refactor the bridge feature to comply with the feature architecture standard established in 001-feature-architecture. This involves creating missing barrel files, extracting constants, adding a proper public API with lazy loading, and updating external imports to use the public API instead of internal paths.
Technical Context
Language/Version: TypeScript 5.x (Next.js 14)
Primary Dependencies: Next.js (dynamic imports), React, MUI
Storage: N/A (no state persistence)
Testing: Jest + React Testing Library
Target Platform: Web (Next.js SSR/CSR)
Project Type: web (monorepo workspace: @safe-global/web)
Performance Goals: Zero impact on initial bundle size (feature is lazy-loaded)
Constraints: Must preserve existing functionality, geoblocking integration, and test coverage
Scale/Scope: 5 existing files to refactor, ~5 new barrel/type files to create, 2 external imports to update
Constitution Check
GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.
| Principle | Status | Notes |
|---|---|---|
| I. Monorepo Unity | ✅ Pass | Feature is web-only, no shared package changes |
| II. Type Safety | ✅ Pass | All new files will have explicit types, no any usage |
| III. Test-First Development | ✅ Pass | Existing tests preserved; no new business logic requiring tests |
| IV. Design System Compliance | ✅ Pass | No UI changes, only structural refactoring |
| V. Safe-Specific Security | ✅ Pass | No security-critical changes, preserving existing patterns |
| Architecture Constraints | ✅ Pass | Feature remains in src/features/, follows standard structure |
| Workflow Enforcement | ✅ Pass | Will run type-check, lint, tests before committing |
Violations: None
Project Structure
Documentation (this feature)
specs/002-bridge-refactor/
├── plan.md # This file
├── research.md # Phase 0 output (minimal - no unknowns)
├── data-model.md # Phase 1 output (minimal - no data model)
├── quickstart.md # Phase 1 output
├── contracts/ # Phase 1 output (empty - no API contracts)
└── tasks.md # Phase 2 output (/speckit.tasks command)
Source Code (repository root)
Current Structure (before refactoring):
apps/web/src/features/bridge/
├── components/
│ ├── Bridge/
│ │ └── index.tsx # Entry point with wrappers
│ └── BridgeWidget/
│ ├── index.tsx # LI.FI iframe embed
│ └── index.test.tsx # Widget tests
└── hooks/
├── useIsBridgeFeatureEnabled.ts
└── useIsGeoblockedFeatureEnabled.ts
Target Structure (after refactoring):
apps/web/src/features/bridge/
├── index.ts # NEW: Public API barrel (lazy-loaded default export)
├── types.ts # NEW: TypeScript interfaces
├── constants.ts # NEW: BRIDGE_WIDGET_URL, LOCAL_STORAGE_CONSENT_KEY
├── components/
│ ├── index.ts # NEW: Component barrel
│ ├── Bridge/
│ │ └── index.tsx # Entry point (unchanged)
│ └── BridgeWidget/
│ ├── index.tsx # Widget (remove BRIDGE_WIDGET_URL export)
│ └── index.test.tsx # Tests (unchanged)
└── hooks/
├── index.ts # NEW: Hook barrel
├── useIsBridgeFeatureEnabled.ts # (unchanged)
└── useIsGeoblockedFeatureEnabled.ts # (unchanged)
External Files Requiring Updates:
apps/web/src/pages/bridge.tsx # Update import to use public API
apps/web/src/services/analytics/tx-tracking.ts # Update import to use public API
Structure Decision: Single feature module following the standard feature architecture pattern. No services or store directories needed (bridge feature has no state management or service layer).
Complexity Tracking
No violations requiring justification. This is a straightforward structural refactoring following an established pattern.
Phase 0: Research Summary
Unknowns: None. The pattern is fully established in 001-feature-architecture and demonstrated in the walletconnect reference implementation.
Decisions:
-
useIsGeoblockedFeatureEnabledlocation: Keep in bridge feature and export from public API. The TODO comment in the code suggests it should be reusable, but moving to shared hooks is out of scope for this refactoring. Other features can import it from@/features/bridgeuntil a separate extraction task is planned. -
_getAppDatafunction: This is a private helper function (underscore prefix indicates internal use). The test file imports it directly which is acceptable for unit tests within the feature. The function will NOT be exported from the public API. -
Types file contents: The bridge feature has no complex types. The
types.tsfile will be minimal, potentially just re-exporting types used by the hooks if needed for external consumers.
Phase 1: Design
Data Model
N/A - The bridge feature has no data model. It renders an iframe (LI.FI widget) and checks feature flags. No entities, no state, no persistence.
API Contracts
N/A - The bridge feature has no API contracts. It does not expose any programmatic interface beyond:
- A default export (lazy-loaded Bridge component)
- Feature flag hooks
- Constants
Public API Design
// src/features/bridge/index.ts
// Types (if any are needed by external consumers)
export type {} from /* TBD based on actual needs */ './types'
// Feature flag hooks (required exports)
export { useIsBridgeFeatureEnabled } from './hooks'
export { useIsGeoblockedFeatureEnabled } from './hooks'
// Constants (needed by analytics)
export { BRIDGE_WIDGET_URL, LOCAL_STORAGE_CONSENT_KEY } from './constants'
// Lazy-loaded component (default export)
import dynamic from 'next/dynamic'
const Bridge = dynamic(() => import('./components/Bridge').then((mod) => ({ default: mod.Bridge })), { ssr: false })
export default Bridge
File Changes Summary
| File | Action | Description |
|---|---|---|
index.ts |
Create | Public API barrel with lazy-loaded default export |
types.ts |
Create | TypeScript interfaces (minimal) |
constants.ts |
Create | Move BRIDGE_WIDGET_URL from BridgeWidget, add LOCAL_STORAGE_CONSENT_KEY from Bridge |
components/index.ts |
Create | Re-export Bridge component |
hooks/index.ts |
Create | Re-export hooks |
components/Bridge/index.tsx |
Modify | Import LOCAL_STORAGE_CONSENT_KEY from constants |
components/BridgeWidget/index.tsx |
Modify | Import BRIDGE_WIDGET_URL from constants, remove export |
pages/bridge.tsx |
Modify | Use public API import |
services/analytics/tx-tracking.ts |
Modify | Use public API import |
Verification Checklist
After implementation, verify:
pnpm --filter @safe-global/web type-checkpassespnpm --filter @safe-global/web lintpasses (no restricted import warnings)pnpm --filter @safe-global/web testpasses (bridge tests pass)pnpm --filter @safe-global/web buildsucceeds- Bridge feature chunk exists in build output
- Feature structure matches standard checklist in
docs/feature-architecture.md