Frontend Architecture
Repository: equa-web | Stack: React 16.11, TypeScript 3.7, Redux 4, Webpack 4
Last updated: 2026-02-28 (Spec 050 — SPA hydration performance optimization)
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 ReactDOM.render().
Module Inventory
The frontend is organized into 24 feature modules underequa-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 |
| 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-dom5.1.2) withconnected-react-router6.3.2 - Route groups:
profileRoutes,guestRoutes,guestOrganizationsRoutes,organizationListRoutes,organizationRoutes,routes - Protection: Routes flagged as
protectedredirect 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 viaReact.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.
- 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)
- 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
react-vendor, styled, vendor, main) 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:- Always use
lazyPage()for the component import — never add a staticimportfrom a module barrel - Follow the pattern:
const NewPage = lazyPage(() => import('@modules/new-module').then(m => ({ default: m.NewPage }))) - The only exception is
@modules/auth/pageswhich 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 | 7.2.6 |
| Async actions | redux-thunk | 2.3.0 |
| Side effects | redux-loop | 4.5.4 |
| Router sync | connected-react-router | 6.3.2 |
| 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,axios0.21.1 for multipart/form-data - Base URL: Configured via
API_URL(default:/api/v1) - Credentials:
credentials: 'include'(session cookies) - Error handling:
HttpErrorclass with auto-logout on 401 - Methods:
get(),post(),patch(),put(),delete(),postMultipart(),postFiles()
Service modules
Located inequa-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 4.2 | 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 |
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
Thebabel-plugin-styled-components 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 usescacheGroups to separate vendor code into named chunks for optimal caching:
| 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 |
maxInitialRequests: 10, minSize: 20000. Lazy-loaded module chunks are NOT merged into the vendor group (chunks: 'initial'), ensuring heavy libraries like pixi.js-legacy, ethers, and react-pdf stay in their own async chunks.
Performance Budgets
| Budget | Value | Mode |
|---|---|---|
| Max entrypoint size | 500 KB | warning |
| Max asset size | 300 KB | warning |
Polyfill Strategy
The entry point importscore-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: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 inroutes.ts must use lazyPage() dynamic imports. The only exception is @modules/auth/pages (static for the login critical path). See 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 | Unit tests |
| Playwright | E2E tests (e2e/ directory) |
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.
~/.claude/comet-browser/output/audit/s050-perf/perf-report.json