Files
3a36fe8eaa feat(web): implement feature architecture standard with walletconnect migration (#6936)
* chore: setup spec-kit

* chore: add feature-architecture specs

* feat(web): implement feature architecture standard with walletconnect migration

- Add ESLint no-restricted-imports rule to enforce feature boundaries
- Create feature architecture documentation at apps/web/docs/feature-architecture.md
- Migrate walletconnect feature as reference implementation:
  - Add public API barrel file (index.ts) with lazy-loaded default export
  - Extract types to types.ts
  - Create feature flag hook (useIsWalletConnectEnabled)
  - Reorganize store into store/ directory
  - Move WalletConnectContext to components/WalletConnectContext/
  - Update all internal imports to relative paths
  - Update all external imports to use public API
- Fix affected test files (Header, useSafeWalletProvider)
- Update AGENTS.md with feature architecture reference

* fix prettier

---------

Co-authored-by: katspaugh <[email protected]>

Co-authored-by: Hanzo Dev <[email protected]>
2026-01-13 17:10:26 +01:00

12 KiB

Data Model: Feature Architecture Standard

Date: 2026-01-08 Feature: 001-feature-architecture

Overview

This document defines the standard structure for features in the Safe{Wallet} web application. A "feature" is a self-contained domain module with its own components, hooks, services, types, and optional Redux store.


Feature Module Structure

Directory Layout

apps/web/src/features/{feature-name}/
├── index.ts              # Public API (barrel file) - REQUIRED
├── types.ts              # TypeScript interfaces - REQUIRED
├── constants.ts          # Feature constants - REQUIRED
├── components/
│   ├── index.ts          # Component exports - REQUIRED
│   └── {ComponentName}/
│       ├── index.tsx     # Component implementation
│       ├── index.test.tsx
│       └── styles.module.css (optional)
├── hooks/
│   ├── index.ts          # Hook exports - REQUIRED
│   ├── useIs{FeatureName}Enabled.ts - REQUIRED
│   └── use{HookName}.ts
├── services/
│   ├── index.ts          # Service exports - REQUIRED if services exist
│   └── {ServiceName}.ts
└── store/                # Redux slice - OPTIONAL
    ├── index.ts          # Store exports
    └── {sliceName}Slice.ts

File Purposes

File Purpose Required
index.ts Public API barrel - only exports meant for external consumption Yes
types.ts All TypeScript interfaces, types, and enums for the feature Yes
constants.ts Feature-specific constants, magic strings, configuration Yes
components/index.ts Re-exports public components Yes
hooks/index.ts Re-exports public hooks Yes
hooks/useIs{Feature}Enabled.ts Feature flag check hook Yes
services/index.ts Re-exports public services If services exist
store/index.ts Re-exports Redux slice and selectors If store exists

Feature Entity Definition

Feature

A self-contained domain module.

Attributes:

Attribute Type Description
name string Kebab-case directory name (e.g., walletconnect)
featureFlag FEATURES Enum value from @safe-global/utils/utils/chains
publicAPI FeatureExports Types, hooks, components exported from index.ts
internalAPI InternalExports Components, hooks, services NOT exported

Feature Flag

A boolean configuration from CGW API.

Attributes:

Attribute Type Description
key FEATURES Enum key (e.g., NATIVE_WALLETCONNECT)
value boolean Whether feature is enabled for current chain
chainSpecific boolean True - flags are per-chain

Feature Public API

What a feature exposes to the rest of the application.

Allowed Exports:

Export Type Example Notes
Default component export default FeatureWidget Lazy-loaded entry point
Types export type { FeatureConfig } Always tree-shakeable
Hooks export { useIsFeatureEnabled } Feature flag hook required
Store selectors export { selectFeatureState } If feature has Redux state
Constants export { FEATURE_CONSTANT } If needed externally

Forbidden Exports:

  • Internal components (anything in subdirectories)
  • Internal hooks (except the enabled check)
  • Service implementations
  • Internal utilities

State Transitions

Feature Lifecycle

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│   LOADING   │────►│   DISABLED   │     │   ENABLED   │
│ (undefined) │     │   (false)    │     │   (true)    │
└─────────────┘     └──────────────┘     └─────────────┘
       │                                        ▲
       └────────────────────────────────────────┘
State useIsFeatureEnabled() Behavior
Loading undefined Render nothing (no flash)
Disabled false Render nothing (no side effects)
Enabled true Render feature, execute side effects

Validation Rules

Import Rules (ESLint Enforced)

Rule Valid Import Invalid Import
Feature from outside @/features/walletconnect @/features/walletconnect/components/WcInput
Feature internals Within same feature only Cross-feature internal imports
Shared code @/hooks/useChains Feature exporting shared code

File Naming Conventions

Type Convention Example
Feature directory kebab-case walletconnect, safe-shield
Component directory PascalCase WcHeaderWidget, SafeShieldDisplay
Hook file camelCase with use prefix useWcUri.ts, useIsFeatureEnabled.ts
Service file PascalCase WalletConnectWallet.ts
Type file Always types.ts types.ts
Constants file Always constants.ts constants.ts

Required Feature Flag Hook Pattern

// hooks/useIs{FeatureName}Enabled.ts

import { useHasFeature } from '@/hooks/useChains'
import { FEATURES } from '@safe-global/utils/utils/chains'

export function useIs{FeatureName}Enabled(): boolean | undefined {
  return useHasFeature(FEATURES.{FEATURE_FLAG})
}

Relationships

┌─────────────────────────────────────────────────────────────┐
│                        Feature                               │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐  │
│  │  components/ │    │    hooks/    │    │   services/  │  │
│  │              │    │              │    │              │  │
│  │  index.ts ◄──┼────┼── index.ts ◄─┼────┼── index.ts   │  │
│  │  (exports)   │    │  (exports)   │    │  (exports)   │  │
│  └──────┬───────┘    └──────┬───────┘    └──────┬───────┘  │
│         │                   │                   │          │
│         ▼                   ▼                   ▼          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    index.ts                          │   │
│  │              (Feature Public API)                    │   │
│  │  - default export: lazy-loaded component             │   │
│  │  - named exports: types, hooks, selectors            │   │
│  └─────────────────────────────────────────────────────┘   │
│                            │                               │
└────────────────────────────┼───────────────────────────────┘
                             │
                             ▼
              ┌─────────────────────────────┐
              │     External Consumers      │
              │  (pages, other features)    │
              │                             │
              │  import X from              │
              │  '@/features/walletconnect' │
              └─────────────────────────────┘

Migration Assessment Fields

For tracking feature compliance during migration:

Field Type Description
featureName string Directory name
hasRootIndex boolean Has index.ts at feature root
hasTypes boolean Has types.ts
hasConstants boolean Has constants.ts
hasComponentIndex boolean Has components/index.ts
hasHooksIndex boolean Has hooks/index.ts
hasEnabledHook boolean Has useIs{Feature}Enabled.ts
hasServicesIndex boolean Has services/index.ts (if services exist)
hasStoreIndex boolean Has store/index.ts (if store exists)
internalImportsCount number Count of external imports to feature internals
complianceScore number 0-100% based on above fields
migrationEffort 'low' | 'medium' | 'high' Estimated effort