Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
6c7027c
readme
bulkcade Feb 12, 2026
ce77e8a
change menu to use memo more often and not have inline options
bulkcade Feb 18, 2026
379b86d
change factsheet to use memo more and avoid sub compponent remounting
bulkcade Feb 18, 2026
a5cf391
add more conditional rerender instead of hidden, use memo more often …
bulkcade Feb 18, 2026
c76514b
larger refactor to make product detail events lazy load the ag grid t…
bulkcade Feb 18, 2026
d6ec14c
avoid () => definition given potential remounting, refactor constants
bulkcade Feb 18, 2026
e033691
same for mobile
bulkcade Feb 18, 2026
1776e28
bugfix selector array generator for memo use, implement use createsel…
bulkcade Feb 18, 2026
a164425
redo graphs to memo options to avoid remounting, change graphs to be …
bulkcade Feb 18, 2026
901b20c
additional component and subcomponent definition changes to avoid pro…
bulkcade Feb 18, 2026
6970d28
hook changes needed for memo changes and lazy loading
bulkcade Feb 18, 2026
b5149c6
avoid price load remount
bulkcade Feb 20, 2026
965f1d1
missed memo
bulkcade Feb 20, 2026
0056370
ellipsis better hiding and inlining some components
bulkcade Feb 20, 2026
6ace35e
more load flag granularity during list loading
bulkcade Feb 20, 2026
2f230f9
toggle product explorer profiler fixes
bulkcade Feb 23, 2026
b92a435
bring product detail collapsable section stylings inline and make som…
bulkcade Feb 23, 2026
3aaab61
revamp stale fixed menu given new collapsable sections
bulkcade Feb 23, 2026
223f0dd
active key scrolling bug fix
bulkcade Feb 23, 2026
96b25e2
add vite prefix to the env
bulkcade Feb 27, 2026
b2552f0
fix linting
bulkcade Feb 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
331 changes: 310 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,319 @@
# React + TypeScript + Vite
# QuantAMM Web App

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Frontend for QuantAMM product discovery, documentation/fact sheets, and simulation workflows.

Currently, two official plugins are available:
The app is built with React + TypeScript + Vite and uses both:
- GraphQL (Apollo) for Balancer data
- REST (RTK Query) for app/backend operations such as simulations, docs, prices, and audit logging

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Prerequisites

## Expanding the ESLint configuration
- Node.js `20.x` (matches CI)
- npm `10+` (or compatible with your Node install)

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
## Quick Start

- Configure the top-level `parserOptions` property like this:
1. Install dependencies

```js
export default {
// other rules...
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
};
```bash
npm ci
```

- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
2. Create local env file

```bash
cp env.local.template .env.local
```

3. Fill in required values in `.env.local` (see variables below)
4. Run the app

```bash
npm run dev
```

Default local URL: `http://localhost:5173`

## Environment Variables

Create `.env.local` from `env.local.template`.

| Variable | Required | Purpose |
| --- | --- | --- |
| `VITE_BASE_URL` | Yes | Base URL for REST endpoints used by RTK Query services |
| `VITE_GRAPH_TARGET` | Yes | GraphQL endpoint used by Apollo + GraphQL codegen |
| `VITE_USE_STUBS_DATA` | Optional | Enables/disables stub product list flow in hooks |
| `VITE_AG_GRID_LICENSE_KEY` | Required for enterprise features | AG Grid/AG Charts enterprise license key |

Notes:
- Never commit `.env.local`.
- `npm run codegen` reads `.env.local` via `dotenv_config_path=.env.local`.
- `VITE_AG_GRID_LICENSE_KEY` must be set at build time for AG Grid/AG Charts enterprise features.

## Scripts

- `npm run dev`: start Vite dev server
- `npm run start`: same as `dev`
- `npm run build`: type-check (`tsc`) + production Vite build
- `npm run preview`: preview production build locally
- `npm run lint`: ESLint with strict warning policy (`--max-warnings 0`)
- `npm run format`: Prettier write on `src/`
- `npm run test`: run Vitest once
- `npm run test:watch`: run Vitest in watch mode
- `npm run test:coverage`: run tests with V8 coverage output
- `npm run codegen`: regenerate GraphQL types/hooks from `src/queries/*.graphql`

## Project Structure

Top-level frontend source is in `src/`, and the repo follows a mostly feature-first structure.

### High-level layout

| Path | Responsibility |
| --- | --- |
| `src/index.tsx` | Runtime entry point; wires Apollo provider, Redux provider, and router |
| `src/App.tsx` | Main app shell; layout/menu, global bootstrapping, route outlet host |
| `src/routes.tsx` | Route graph (`createBrowserRouter`) and page-level composition |
| `src/routeComponents.ts` | Lazy imports for route-level code splitting |
| `src/app/` | Store, typed hooks, app-level Redux plumbing |
| `src/features/` | Domain features (UI, slice logic, feature-local utils/tests) |
| `src/services/` | Shared RTK Query services and service helpers |
| `src/queries/` | Apollo client + GraphQL operation documents |
| `src/__generated__/` | GraphQL codegen output (generated types/hooks) |
| `src/hooks/` | Cross-feature hooks for data fetch/transform orchestration |
| `src/utils/` | Generic utilities not owned by a single feature |
| `src/test/setup.ts` | Global test setup for Vitest |
| `public/prerun_sims/` | Static MessagePack files used for pre-run simulation data |

### Product/domain structure under `src/features`

| Path | What it owns |
| --- | --- |
| `src/features/simulationRunner/` | Simulation wizard orchestration, run controls, import/export, run-status flow |
| `src/features/simulationRunConfiguration/` | Pool/token selection, update-rule parameters, pre-run configuration state |
| `src/features/simulationResults/` | Result summaries, visualisations, breakdown tables, compare/save flows |
| `src/features/productExplorer/` | Product listing, filtering, sorting, pagination, explorer state |
| `src/features/productDetail/` | Product detail page, sidebar/content panels, product modal actions |
| `src/features/coinData/` | Coin data page, current price polling, coin-level state |
| `src/features/documentation/` | Documentation pages, fact sheets, landing/TOS/ineligible flows |
| `src/features/shared/` | Shared graphs, tables, and explanatory content components |
| `src/features/themes/` | Theme slice and AG Grid theme integration |

### Typical module pattern inside a feature

Common pattern in this repo:
- `FeaturePage.tsx` style containers for major screens
- `featureSlice.ts` for feature state and reducers
- `*Service.ts` for feature-specific RTK Query APIs
- `*.ts` utilities for transforms/view-model logic
- `*.test.ts` / `*.test.tsx` colocated near logic being tested

This keeps UI, state, and logic near their domain ownership while still allowing shared hooks/services across features.

## App Architecture Overview

### Runtime composition

Runtime boot sequence:
1. `src/index.tsx` mounts React root
2. Wraps app with `ApolloProvider`
3. Wraps app with Redux `Provider`
4. Mounts `RouterProvider` with route config from `src/routes.tsx`

`src/App.tsx` then provides:
- App-level layout (`antd` `Layout`, header/menu/content)
- App bootstrap side effects (for example, price history load and simulation initialization when entering simulation routes)
- Route outlet rendering via `<Outlet />`

### Routing architecture

- Routes are centralized in `src/routes.tsx`.
- Route components are lazy-loaded through `src/routeComponents.ts` to keep initial bundle size smaller.
- Route groups include:
- landing/company/research/contact/docs pages
- product explorer and product detail routes
- simulation runner and simulation result comparison routes
- fact-sheet and simulator-example routes
- `src/routeErrorBoundary.tsx` handles router-level errors.

### State architecture and ownership

Redux Toolkit store is in `src/app/store.ts`.

Core state slices:
- `simConfig`: simulation pool/time/rule configuration state
- `simRunner`: simulation wizard step/run status and active run breakdowns
- `simResults`: result-focused state (comparison, chart/breakdown selections)
- `docs`: documentation-related state
- `productExplorer`: explorer filters/search/sort/tab/page-level state
- `currentPrices`: current token pricing state
- `theme`: UI theme state

In the same store, RTK Query API slices are registered for:
- simulation execution
- coin price retrieval
- documentation retrieval
- product and filter retrieval
- financial analysis
- audit logging

### Data access and integration boundaries

There are three primary data paths:

1. GraphQL via Apollo
- Client: `src/queries/apolloClient.ts`
- Operations: `src/queries/*.graphql`
- Generated types/hooks: `src/__generated__/graphql-types.ts`

2. REST via RTK Query
- Feature-local example: `src/features/simulationRunner/simulationRunnerService.ts`
- Shared service examples: `src/services/productRetrievalService.ts`, `src/services/financialAnalysisService.ts`, `src/services/auditLogService.ts`
- Most requests include CSRF token propagation from cookie helpers

3. Local static runtime assets
- Precomputed simulation data loaded from `public/prerun_sims/*.msgpack`

### Simulation workflow architecture

The simulation product flow is split across:
- `simulationRunConfiguration` for building pool config
- `simulationRunner` for run orchestration and step transitions
- `simulationResults` for post-run analysis and visual output

Runner step model (managed by `simRunner.simulationRunnerCurrentStepIndex`):
- `0`: options
- `1`: pool constituent selection
- `2`: time range
- `3`: hooks
- `4`: final review
- `5`: run progress/state view
- `6`: results summary
- `7`: save-to-compare view

Execution path:
1. UI triggers `createRunSimulationsThunk` (`simulationRunButtonLogic.ts`)
2. Thunk initializes ranges and pools in runner state
3. Calls `runSimulation` RTK Query mutation per pool/time range
4. Converts API response into snapshots + analysis payload
5. Dispatches success/failure reducers (`addSimRunResults`, `completeRun`, `failRun`)
6. Completes run and advances to results step

### UI and styling architecture

- UI components primarily use Ant Design primitives
- Data-heavy tables/charts use AG Grid and AG Charts enterprise modules
- Styling uses a mix of CSS modules (`*.module.css`) and Sass modules (`*.module.scss`)
- Feature folders generally keep style modules next to owning components

## Testing

Current test stack:
- Vitest
- Testing Library (`@testing-library/*`)
- Coverage via `@vitest/coverage-v8`

Configuration: `vite.config.ts`

Important current convention:
- Vitest environment is `node` by default.
- Most current tests focus on pure logic (reducers, utilities, view-model logic).
- If you add DOM-heavy component tests, prefer file-level jsdom override:

```ts
// @vitest-environment jsdom
```

Run specific test file:

```bash
npx vitest run src/features/simulationRunner/simulationRunButton.test.ts
```

## Linting, Formatting, and Type Safety

- TypeScript is strict (`strict: true` and additional unused/fallthrough checks).
- ESLint uses type-aware rules (`@typescript-eslint/*-type-checked`).
- Prettier config is in `.prettierrc`.

Recommended pre-PR local check:

```bash
npm run lint
npm run test
npm run build
```

## GraphQL Codegen Workflow

1. Update queries in `src/queries/*.graphql`
2. Ensure `.env.local` has correct `VITE_GRAPH_TARGET`
3. Run:

```bash
npm run codegen
```

Generated output:
- `src/__generated__/graphql-types.ts`

## CI and Branch Quality Gates

GitHub Actions workflow: `.github/workflows/tests.yml`

Current CI jobs on PRs to `main` and pushes to `main`:
- Lint
- Test
- Build

To enforce merge blocking on CI:
1. Go to GitHub repo settings
2. `Branches` -> branch protection rule for `main`
3. Enable `Require status checks to pass before merging`
4. Select required checks from this workflow (e.g. `Lint`, `Test`, `Build`)

## Common Developer Tasks

### Add a new route/page

1. Create feature/page component under `src/features/...`
2. Add lazy export in `src/routeComponents.ts`
3. Register route in `src/routes.tsx`

### Add a new REST endpoint

1. Add/extend an RTK Query service in `src/services` or feature-local service
2. Register reducer + middleware in `src/app/store.ts` (if new API slice)
3. Use generated hook in feature component/hook

### Add a new Redux slice

1. Create slice in relevant `src/features/...`
2. Add reducer to `src/app/store.ts`
3. Add selectors + tests near the slice

## Troubleshooting

### Build fails with missing module path

If files were moved during refactors, check relative imports first (especially CSS module imports).

### Tests pass locally but fail in CI

- Re-run `npm ci` to align dependency tree with lockfile
- Run `npm run lint && npm run test && npm run build` locally
- Confirm no generated or local-only files are accidentally required

### GraphQL codegen issues

- Validate `VITE_GRAPH_TARGET` in `.env.local`
- Ensure query files are valid under `src/queries/*.graphql`

---

If you are new to this codebase, start with:
1. `src/routes.tsx`
2. `src/app/store.ts`
3. `src/features/simulationRunner/` and `src/features/simulationRunConfiguration/`

Those files give the fastest understanding of routing, global state, and the main workflow engine in this app.
5 changes: 4 additions & 1 deletion env.local.template
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ VITE_USE_STUBS_DATA=false
VITE_GRAPH_TARGET=https://api-v3.balancer.fi/

# if backend is running locally
VITE_BASE_URL=http://localhost:5001/api
VITE_BASE_URL=http://localhost:5001/api

# if backend is running on production
# VITE_BASE_URL=https://your-api-host/api

# AG Grid / AG Charts enterprise license key
VITE_AG_GRID_LICENSE_KEY=
9 changes: 3 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const { Content, Header } = Layout;
const isRoute = (value: string): value is ROUTES =>
(Object.values(ROUTES) as string[]).includes(value);

const AG_GRID_LICENSE_KEY = import.meta.env.AG_GRID_LICENCE_KEY ?? '';
const AG_GRID_LICENSE_KEY = import.meta.env.VITE_AG_GRID_LICENSE_KEY ?? '';

const initialiseSimsToRun = (): AppThunk => (dispatch, getState) => {
if (getState().simRunner.simulationsToRun.length === 0) {
Expand All @@ -46,17 +46,14 @@ function App() {
return;
}

if (page === ROUTES.SIMULATION_RUNNER) {
if (page !== ROUTES.SIMULATION_RUNNER) {
dispatch(loadPriceHistoryAsync());
dispatch(initialiseSimsToRun());
}
},
[dispatch]
);

useEffect(() => {
dispatch(loadPriceHistoryAsync());
}, [dispatch]);

useEffect(() => {
const segment = location.pathname.split('/')[1] ?? '';
const page = isRoute(segment) ? segment : ROUTES.HOME;
Expand Down
Loading