> ## Documentation Index
> Fetch the complete documentation index at: https://docs.equa.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Frontend Architecture

> React/Redux frontend modules, routing, state management, and component library

# Frontend Architecture

> **Repository:** `equa-web` | **Stack:** React 18.2, TypeScript 5.3, Redux 4, Webpack 4
> **Last updated:** 2026-04-11 (Phase 1 React 18 upgrade, ecosystem libraries, bundle optimization, API client network-error handling, dashboard empty state, gateway token sync)

## Entry Point

Source: `equa-web/src/index.tsx` and `equa-web/lab/main-prod.ts`

The app initializes polyfills (`core-js/stable` + `regenerator-runtime/runtime`), creates a Redux store via `newStore()`, creates browser history with `createBrowserHistory()`, wraps in a Redux `Provider`, and renders to `#root` via `createRoot()` from `react-dom/client` (upgraded from `ReactDOM.render()` in PR #506).

## Module Inventory

The frontend is organized into feature modules under `equa-web/src/modules/`:

| Module                     | Directory                         | Purpose                                                               |
| -------------------------- | --------------------------------- | --------------------------------------------------------------------- |
| **admin**                  | `modules/admin/`                  | Admin dashboard, user management, site stats                          |
| **agreements**             | `modules/agreements/`             | Governing documents management                                        |
| **auth**                   | `modules/auth/`                   | Login, registration, password reset, email verification               |
| **captable**               | `modules/captable/`               | Cap table management, shareholdings, certificates                     |
| **convertibles**           | `modules/convertibles/`           | Convertible instruments and notes                                     |
| **documents**              | `modules/documents/`              | Data room and document management                                     |
| **equanaut**               | `modules/equanaut/`               | AI agent integration, action executor, onboarding                     |
| **esop**                   | `modules/esop/`                   | Employee stock option plans, pools, vesting schedules                 |
| **google-drive**           | `modules/google-drive/`           | Google Drive sync integration                                         |
| **guest**                  | `modules/guest/`                  | Guest user pages (portfolio, dashboard, referrals)                    |
| **hh-finance**             | `modules/hh-finance/`             | Finance dashboard, transactions, metrics                              |
| **landing**                | `modules/landing/`                | Landing page, auth modal                                              |
| **organization**           | `modules/organization/`           | Organization management, securities, legends                          |
| **organization-dashboard** | `modules/organization-dashboard/` | Organization dashboard, capital summary, empty-state onboarding guide |
| **actions**                | `modules/actions/`                | Organization actions, feature requests                                |
| **payments**               | `modules/payments/`               | Billing, payment profiles, checkout, subscriptions                    |
| **profile**                | `modules/profile/`                | User profile, portfolio, wallet, account settings                     |
| **referrals**              | `modules/referrals/`              | Referral system, EquaCash transfers                                   |
| **reports**                | `modules/reports/`                | Reports (holder, pools, holdings)                                     |
| **roles**                  | `modules/roles/`                  | Role management, permissions                                          |
| **subscriptions**          | `modules/subscriptions/`          | Subscription management                                               |
| **team-members**           | `modules/team-members/`           | Team member management, invitations                                   |
| **user-dashboard**         | `modules/user-dashboard/`         | User dashboard                                                        |
| **welcome**                | `modules/welcome/`                | Onboarding flow, profile building, PIN setup                          |

## Routing and Code Splitting

Source: `equa-web/src/app/routes/routes.ts`, `equa-web/src/app/routes/lazy.tsx`, `equa-web/src/app/components/routes/routes.tsx`

* **Router:** React Router v5 (`react-router-dom` 5.1.2) with `redux-first-history` (replaced `connected-react-router` in PR #465)
* **Route groups:** `profileRoutes`, `guestRoutes`, `guestOrganizationsRoutes`, `organizationListRoutes`, `organizationRoutes`, `routes`
* **Protection:** Routes flagged as `protected` redirect unauthenticated users
* **HOCs:** `withNavigation`, `withTracker`, `withService`, `withHeader`, `withOrganizationHeader`, `withProfileHeader`
* **Path constants:** `equa-web/src/logic/paths.ts`

### Lazy Loading (Spec 050)

All feature module page components are lazy-loaded via `React.lazy()` + `<Suspense>`. The `lazyPage()` utility in `src/app/routes/lazy.tsx` wraps each dynamic import with a `<Suspense>` fallback using the existing `<Loading />` spinner component.

```typescript theme={null}
const CaptablePage = lazyPage(() =>
  import('@modules/captable').then(m => ({ default: m.CaptablePage }))
)
```

**What stays in the main bundle (static imports):**

* Auth module (`@modules/auth/pages`) — needed for login page to render immediately
* Shell components: headers, navigation, loading, error boundary, permissions HOCs
* Redux reducers (all 10 registered at store initialization per clarification C5)

**What is lazy-loaded (137 dynamic imports across 25+ module groups):**

* All other page components: captable, ESOP, organization, convertibles, payments, profile, guest, admin, referrals, reports, roles, documents, welcome, team-members, hh-finance, google-drive, equabot-settings, agreements, landing, marketing, messaging, user-dashboard, organization-dashboard

The production build produces **48+ JS chunks**: 4 named entrypoint chunks (`react-vendor`, `styled`, `vendor`, `main`) plus 4 async vendor chunks (`crypto-vendor`, `pixi-vendor`, `form-vendor`, `pdf-vendor`) plus \~40 lazy module chunks.

### Chunk Error Boundary

Source: `equa-web/src/app/components/routes/chunk-error-boundary.tsx`

A `ChunkErrorBoundary` wraps the route `<Switch>` in `routes.tsx`. If a lazy chunk fails to load (network error, deployment mismatch), it catches the error via `getDerivedStateFromError` and renders a "Failed to load this page" message with a Retry button instead of a white screen.

### Adding New Routes

When adding a new route or module page:

1. **Always use `lazyPage()`** for the component import — never add a static `import` from a module barrel
2. Follow the pattern: `const NewPage = lazyPage(() => import('@modules/new-module').then(m => ({ default: m.NewPage })))`
3. The only exception is `@modules/auth/pages` which must stay static for the login critical path

## State Management

Source: `equa-web/src/logic/store.ts`, `equa-web/src/logic/reducers/root-reducer.ts`

| Component      | Library                  | Version |
| -------------- | ------------------------ | ------- |
| Store          | Redux                    | 4.0.1   |
| React bindings | react-redux              | ^8.1.0  |
| Async actions  | redux-thunk              | 2.3.0   |
| Side effects   | redux-loop               | 4.5.4   |
| Router sync    | redux-first-history      | ^5.x    |
| DevTools       | Redux DevTools Extension | —       |

### Reducers

| Reducer               | Purpose                         |
| --------------------- | ------------------------------- |
| `userReducer`         | Current user state, auth status |
| `organizationReducer` | Active organization data        |
| `accessReducer`       | Permissions and access control  |
| `toastsReducer`       | Toast notification queue        |
| `myReferralReducer`   | Referral program state          |
| `capTableReducer`     | Cap table data cache            |
| `checklistReducer`    | Onboarding checklist progress   |
| `serviceReducer`      | API service configuration       |

## API Communication

Source: `equa-web/src/service/lib/http-client.ts`, `equa-web/src/service/services/web-client.ts`

* **HTTP client:** Native `fetch()` for standard requests, `axios` 0.21.1 for multipart/form-data
* **Base URL:** Configured via `API_URL` (default: `/api/v1`)
* **Credentials:** `credentials: 'include'` (session cookies)
* **Error handling:** `HttpError` class with auto-logout on 401
* **Methods:** `get()`, `post()`, `patch()`, `put()`, `delete()`, `postMultipart()`, `postFiles()`

### Network Error Handling (PR #514)

`baseRequest()` in `http-client.ts` wraps every `fetch()` call in a try-catch. When the backend is unreachable — DNS failure, CORS block, connection refused, offline — `fetch()` throws a `TypeError`. Rather than letting that throw propagate as an unhandled rejection (which was the root cause of the infinite splash screen bug, issue #482), the client converts it into a structured error:

```typescript theme={null}
return new HttpError(ResponseCode.other, 'networkError', /* ... */)
```

This lets the normal redux-loop failure path dispatch, the `getCurrentUser` callback fires, `initialLoad` flips to `true`, and the app renders the login page instead of hanging on the Equa logo spinner forever.

**Callers do not need special handling** — the network-error response follows the same `HttpError` contract as 4xx/5xx responses. Anywhere the code already checks `isHttpError(result)`, the network-error case is handled automatically.

**Source:** `equa-web/src/service/lib/http-client.ts` (PR #514, 2026-04-02).

### Service modules

Located in `equa-web/src/service/services/`:

* `actions`, `billing`, `captable`, `google-drive`, `organizations`, `payments`, `profile`, `roles`, `wallet`

## Component Library

Source: `equa-web/package.json` (dependency: `equa-patternlib` from GitHub)

The pattern library provides 26+ shared UI components:

Avatar, Badge, Button, Card, Checkbox, Chip, DatePicker, Dropdown, FileUpload, Input, Menu, Modal, Pagination, Progress, Radio, Rating, Sidebar, Skeleton, Slider, Stepper, Switch, Table, Tabs, Toast, Toggle, Tooltip

Components are re-exported via `equa-web/src/shared/components/`.

## Styling

| Approach      | Library                  | Source                          |
| ------------- | ------------------------ | ------------------------------- |
| Primary       | styled-components ^6.1.0 | `equa-web/src/styles/styled.ts` |
| Global styles | `createGlobalStyle`      | `equa-web/src/styles/global.ts` |
| Theme         | ThemeProvider            | `equa-web/src/styles/theme.ts`  |
| Utilities     | polished 3.4.1           | Color manipulation              |
| SCSS          | sass-loader (webpack)    | Legacy components               |

Theme switching is supported and persisted via cookies.

### Font Loading

Six NunitoSans variants are declared via `@font-face` in `src/styles/global.ts`. All declarations include `font-display: swap` to prevent Flash of Invisible Text (FOIT). When adding new `@font-face` rules, always include `font-display: swap`.

### styled-components Build Plugin

The `babel-plugin-styled-components` (^2.1.4) is active in the webpack babel-loader config. In production builds it strips `displayName` and enables `pure` annotation for dead code elimination. In development it preserves `displayName` for debugging.

## Webpack Configuration

Source: `equa-web/webpack.config.js`

| Setting             | Value                                                                           |
| ------------------- | ------------------------------------------------------------------------------- |
| Entry               | `lab/main-prod.ts`                                                              |
| Output              | `dist/` with content hashes (`[name]-[hash].js`)                                |
| Dev server port     | 8080                                                                            |
| API proxy           | `/api` -> `http://localhost:3000`                                               |
| Equanaut proxy      | `/equanaut-api` -> `http://localhost:19792`                                     |
| Production minifier | TerserPlugin                                                                    |
| Bundle analyzer     | `ANALYZE_BUNDLE=1` env var triggers `webpack-bundle-analyzer`                   |
| Babel plugins       | `babel-plugin-styled-components` (production: `displayName: false, pure: true`) |

### splitChunks (Production Only)

The production build uses `cacheGroups` to separate vendor code into named chunks for optimal caching:

**Initial chunks (loaded on every page):**

| Cache Group | Name           | Priority | Content                                            |
| ----------- | -------------- | -------- | -------------------------------------------------- |
| `react`     | `react-vendor` | 20       | react, react-dom, react-router, react-redux, redux |
| `styled`    | `styled`       | 15       | styled-components                                  |
| `vendor`    | `vendor`       | 10       | All other initial `node_modules`                   |

**Async chunks (loaded on-demand when navigating to specific routes):**

| Cache Group | Name            | Priority | Content                           | Loaded When                |
| ----------- | --------------- | -------- | --------------------------------- | -------------------------- |
| `crypto`    | `crypto-vendor` | 25       | ethers, web3, crypto libs (18 KB) | Wallet/MetaMask pages      |
| `pixi`      | `pixi-vendor`   | 25       | pixi.js-legacy (629 KB)           | Chart/visualization pages  |
| `form`      | `form-vendor`   | 15       | react-select, react-datepicker    | Pages with form components |
| `pdf`       | `pdf-vendor`    | 15       | react-pdf, pdfjs-dist             | Document viewer pages      |

Additional settings: `maxInitialRequests: 10`, `minSize: 20000`. The async vendor chunks were added in PR #510 to reduce the initial page load by \~2.5 MB — pixi.js, crypto libs, and the login background image (compressed 95% from 1.93 MB to 93 KB) are no longer loaded on first visit.

### Performance Budgets

| Budget              | Value  | Mode      |
| ------------------- | ------ | --------- |
| Max entrypoint size | 500 KB | `warning` |
| Max asset size      | 300 KB | `warning` |

These budgets emit build warnings for oversized chunks. The entrypoint size was significantly reduced in PR #510 by moving heavy vendor libraries to async chunks (\~2.5 MB reduction). Further reduction requires upgrading to Webpack 5 (better tree shaking) or migrating to Vite (Phase 2).

### Polyfill Strategy

The entry point imports `core-js/stable` and `regenerator-runtime/runtime` (replacing the deprecated `@babel/polyfill`). The `@babel/preset-env` handles transpilation targeting browsers specified in the webpack config.

### Path Aliases

| Alias         | Maps To                 |
| ------------- | ----------------------- |
| `@src`        | `src/`                  |
| `@logic`      | `src/logic`             |
| `@config`     | `local.json`            |
| `@styles`     | `src/shared/styles`     |
| `@modules`    | `src/modules`           |
| `@components` | `src/shared/components` |
| `@shared`     | `src/shared`            |
| `@helpers`    | `src/shared/helpers`    |
| `@image`      | `src/assets/image`      |

## Key Utilities

| Utility       | File                                     | Purpose                       |
| ------------- | ---------------------------------------- | ----------------------------- |
| Type helpers  | `src/shared/helpers/util.ts`             | Uuid, Hash, Money, formatting |
| Validators    | `src/shared/helpers/field-validators.ts` | Form field validation         |
| Data cache    | `src/shared/helpers/data-cache.ts`       | Client-side caching           |
| Shareholdings | `src/shared/helpers/shareholdings.ts`    | Shareholding calculations     |
| MS Graph      | `src/shared/helpers/ms/msGraph.ts`       | Microsoft integration         |
| Constants     | `src/shared/helpers/constants.tsx`       | App-wide constants            |

## Coding Conventions

### Lodash Imports

Use per-function imports, never the full library:

```typescript theme={null}
import range from 'lodash/range'
import isEqual from 'lodash/isEqual'
```

Never use `import _ from 'lodash'` — this pulls the entire 72 KB library into the bundle even if only one function is used. If a file doesn't actually call any lodash function, remove the import entirely.

### Route Imports

All page component imports in `routes.ts` must use `lazyPage()` dynamic imports. The only exception is `@modules/auth/pages` (static for the login critical path). See [Routing and Code Splitting](#routing-and-code-splitting) above.

### Dependency Hygiene

Before adding a new dependency, check if it will end up in the initial entrypoint bundle or a lazy chunk. Heavy libraries (>100 KB) should only be imported from lazy-loaded modules. The following unused packages were removed during Spec 050 and must not be re-added: `moment` (use `date-fns` instead), `recharts`, `react-hot-loader`, `@hot-loader/react-dom`.

## Testing

| Framework                          | Purpose                                                |
| ---------------------------------- | ------------------------------------------------------ |
| Jest 29 + React Testing Library 14 | Unit tests (migrated from Jest 26 + Enzyme in PR #465) |
| Playwright                         | E2E tests (`e2e/` directory)                           |
| Visual regression                  | Screenshot comparison in CI (PR #509)                  |

### Performance Testing

Source: `Comet-Bridge/scripts/perf-audit.mjs`

An automated performance audit script runs via Comet-Bridge Playwright against the equa-web dev server or production build. It navigates all major route groups, captures `performance.timing` metrics (TTI, DCL, load time), checks for chunk load errors, and produces a JSON report with screenshots.

```bash theme={null}
cd Comet-Bridge
node scripts/perf-audit.mjs --url http://localhost:8080
node scripts/perf-audit.mjs --url http://localhost:8090 --throttle  # fast 3G
```

Output: `~/.claude/comet-browser/output/audit/s050-perf/perf-report.json`
