Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,7 @@ test-results.json

*storybook.log
storybook-static

# Per-developer agent overrides
AGENTS.local.md
CLAUDE.local.md
89 changes: 89 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# AGENTS.md — jumper-exchange

## What this repo is

`jumper.exchange` — the Next.js frontend for [jumper.xyz](https://jumper.xyz). Talks primarily to `jumper-backend` over REST; some legacy paths still hit `strapi-cms` directly. See [ARCHITECTURE.md](./ARCHITECTURE.md) for the app shape, route map, and dependency rules.

## Run, build, test

See @README.md
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use a standard Markdown link instead of @README.md.

@README.md is not a portable Markdown link format on GitHub. Use a regular relative link so it renders reliably.

Suggested fix
-See `@README.md`
+See [README.md](./README.md)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
See @README.md
See [README.md](./README.md)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` at line 9, Replace the non-portable reference token "@README.md"
with a standard relative Markdown link so it renders on GitHub (locate the "See
`@README.md`" line and change it to a regular link like "See
[README.md](README.md)" to ensure portability and correct rendering).

Comment thread
laurentsenta marked this conversation as resolved.

## Coding conventions

### Stack and idioms

- **Next.js 16 App Router** with locale segment `src/app/[lng]/…`. New pages go under `src/app/[lng]/<segment>/`; never under `src/app/` directly except for non-localized infrastructure (`api/`, `lib/`, root `layout.tsx`, etc.).
- **React 19** with the React Compiler enabled (see `babel-plugin-react-compiler`). Do not write manual `useMemo` / `useCallback` for cases the compiler handles; reach for them only when profiling shows it matters.
- **MUI v9 + Emotion** for styling. Prefer the `sx` prop or `styled()` over ad-hoc CSS modules. Theme tokens live under `src/theme/`.
- **State**: server state via `@tanstack/react-query`; client state via `zustand` stores under `src/stores/<feature>/`. Do not introduce a third state library.
- **Forms**: `@tanstack/react-form`.
- **i18n**: `next-i18n-router` + `i18next`. Translations under `src/i18n/translations/<lng>/`; regenerate the typed resources file with `pnpm i18next-resources-for-ts` after editing the `en` translation.
- **Wallet stack**: LI.FI SDK + widget, Wagmi/Viem for EVM, plus per-chain providers (Solana, Sui, Bitcoin, Tron). Wire new connectors through `src/providers/WalletProvider/` so the existing widget config picks them up.
- **API routes**: Next.js route handlers under `src/app/api/<name>/`. They proxy or wrap `jumper-backend`; do not put business logic here that belongs server-side.
- **Observability**: Sentry is wired via `instrumentation.ts`, `instrumentation-client.ts`, and `sentry.*.config.ts`. Use the existing helpers; do not call `Sentry.init` from feature code.

### Style rules

- **No barrel files** (`index.ts` re-exports). Import directly from the source file.
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The doc states “No barrel files (index.ts re-exports)”, but the current codebase already contains many index.ts re-export barrels (e.g. src/components/AccordionFAQ/index.ts, src/stores/*/index.ts). Either adjust this rule to “avoid introducing new barrels” / “prefer direct imports for new code”, or plan/enforce a migration and link to it here.

Suggested change
- **No barrel files** (`index.ts` re-exports). Import directly from the source file.
- **Avoid introducing new barrel files** (`index.ts` re-exports). Prefer direct imports from the source file in new code, and do not expand existing barrels unless there is a clear reason.

Copilot uses AI. Check for mistakes.
- **TypeScript path aliases**: `@/foo` and `src/foo` both resolve to `./src/foo` (see `tsconfig.json` and `vitest.config.ts`). Prefer `@/` in new code.
- **Comments explain _why_, not _what_.** Self-documenting names first; comments only for non-obvious logic or constraints.
- **Function names should be self-documenting** — see [`~/.claude/CLAUDE.md`](../../../.claude/CLAUDE.md) for the user-level style guide we follow.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix broken link target for the style guide reference.

../../../.claude/CLAUDE.md points outside the repo and won’t resolve in GitHub. Point to an in-repo doc (or remove the link if intentionally external).

Suggested fix
-- **Function names should be self-documenting** — see [`~/.claude/CLAUDE.md`](../../../.claude/CLAUDE.md) for the user-level style guide we follow.
+- **Function names should be self-documenting** — see [CLAUDE.md](./CLAUDE.md) for the user-level style guide we follow.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **Function names should be self-documenting** — see [`~/.claude/CLAUDE.md`](../../../.claude/CLAUDE.md) for the user-level style guide we follow.
- **Function names should be self-documenting** — see [CLAUDE.md](./CLAUDE.md) for the user-level style guide we follow.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@AGENTS.md` at line 30, The markdown reference to an external style guide in
the sentence "Function names should be self-documenting" uses a broken relative
link (`../../../.claude/CLAUDE.md`); update that link in AGENTS.md so it points
to a valid in-repo document (e.g., replace with the correct path to the repo's
style guide) or remove the hyperlink if the guide is intentionally external,
ensuring the visible text "Function names should be self-documenting" remains
unchanged.

Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This links to ../../../.claude/CLAUDE.md, but there’s no .claude/ directory in this repository, so the link will be broken on GitHub. If there’s a shared style guide, consider linking to a URL or adding a repo-local copy (or remove the link if it’s only intended for a specific local setup).

Suggested change
- **Function names should be self-documenting** — see [`~/.claude/CLAUDE.md`](../../../.claude/CLAUDE.md) for the user-level style guide we follow.
- **Function names should be self-documenting** and describe intent clearly at the call site.

Copilot uses AI. Check for mistakes.
- **Delete replaced code.** No backwards-compatibility shims inside this repo; this is a feature-branch codebase.

### Tests

- **Unit tests** colocate as `<file>.spec.ts(x)` under `src/`. Vitest picks them up via the `unit` project.
- **Snapshot tests** colocate as `<file>.snapshot.spec.tsx`. Regenerate with `pnpm test:snapshots:generate` after intentional UI changes; review the diff before committing.
- **Storybook tests** run the stories in `.storybook/` against a Playwright-driven Chromium browser via the `storybook` Vitest project.
- **E2E tests** live in `tests/` (Playwright). One spec per top-level feature; helpers in `tests/utils/`. See [tests/README.md](./tests/README.md).
- **Hot paths get benchmarks**, not just unit tests. Measure before claiming faster.

## Entry points

Read these first when picking up new work in this repo:

- `src/app/[lng]/layout.tsx` — top-level locale layout; wires providers.
- `src/providers/` — provider tree (wallet, theme, query, i18n, intercom). Cross-cutting wiring lives here.
- `src/components/` — feature components grouped by feature (e.g. `Earn*`, `Portfolio*`, `Quests*`). Reusable primitives in `core/`, `composite/`, `headless/`.
- `src/stores/<feature>/` — zustand stores. One folder per feature; each owns its store, selectors, and types.
- `src/hooks/<feature>/` — react-query hooks and feature-specific hooks. Mirror the `src/stores/` layout.
- `src/app/api/` — Next.js route handlers (proxy + wrap `jumper-backend`).
- `src/config/` — runtime config (`config.ts`, `env-config.ts`, wallet connector configs, widget config).
- `next.config.mjs`, `instrumentation*.ts`, `sentry.*.config.ts` — framework + observability wiring.
- `tests/` — Playwright E2E suite.

## Where new things go

| New thing | Goes in |
| -------------------------------- | -------------------------------------------------------------------------------- |
| New page | `src/app/[lng]/<segment>/page.tsx` (+ `layout.tsx` if it has children) |
| New API route handler | `src/app/api/<name>/route.ts` |
| New feature component | `src/components/<FeatureName>/` (subfolder; one component per file, no barrels) |
| New zustand store | `src/stores/<feature>/` |
| New react-query hook | `src/hooks/<feature>/use<Thing>.ts` |
| New wallet connector | `src/providers/WalletProvider/` + relevant `src/config/<connector>.ts` |
| New translation key | `src/i18n/translations/en/<namespace>.json` then `pnpm i18next-resources-for-ts` |
| New unit test | `<file>.spec.ts(x)` next to the source |
| New E2E test | `tests/<feature>.spec.ts` |
| New theme token / palette change | `src/theme/` |
| New env variable | `src/config/env-config.ts` + document in `.env.example` |

If a change does not fit any of the above, stop and ask — do not invent a new top-level folder.

## What NOT to do

- **Do not** add backwards-compatibility shims, deprecation wrappers, or dead-code "for safety". Delete and replace.
- **Do not** add barrel `index.ts` files.
- **Do not** put business logic in `src/app/api/` route handlers — they are thin proxies to `jumper-backend`.
- **Do not** call `Sentry.init` outside the existing instrumentation files.
- **Do not** import `strapi-cms` content directly when an equivalent endpoint exists on `jumper-backend`. New CMS access should go through the backend.

## Doc index

- [README.md](./README.md) — getting started, tools, lint, translations.
- [ARCHITECTURE.md](./ARCHITECTURE.md) — app shape, route map, dependency rules.
- [tests/README.md](./tests/README.md) — Playwright E2E setup and run commands.

<!-- local overrides (gitignored) -->

@AGENTS.local.md
138 changes: 138 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# ARCHITECTURE — jumper-exchange

Shape of this Next.js app. See [AGENTS.md](./AGENTS.md) for how to work in this repo (run, test, conventions, where new things go).

## What this app is

The B2C frontend for [jumper.xyz](https://jumper.xyz). Users land here to bridge and swap between any tokens across any chains (EVM, SVM, Sui, Bitcoin, Tron). Retention features (earn, portfolio, quests, campaigns, missions) are layered on top of the core swap flow.

The app embeds the `@lifi/widget` for the swap UX and wraps it with Jumper-specific surfaces (auth, profile, XP, content, partner themes, …).

## Stack

| Layer | Choice |
| --------------- | --------------------------------------------------------------------------------------------------- |
| Framework | Next.js 16 (App Router) on React 19 with the React Compiler |
| Styling | MUI v9 + Emotion |
| Server state | `@tanstack/react-query` |
| Client state | `zustand` (one store per feature, under `src/stores/<feature>/`) |
| Forms | `@tanstack/react-form` |
| i18n | `next-i18n-router` + `i18next`, locale segment in the URL (`/[lng]/…`) |
| Wallet | `@lifi/sdk` + `@lifi/widget` + per-chain providers (EVM via Wagmi/Viem, Solana, Sui, Bitcoin, Tron) |
| Observability | Sentry (browser + server + edge) |
| Tests | Playwright (E2E), Vitest (unit, snapshot, Storybook) |
| Package manager | pnpm (`packageManager` field pins the version) |

`node >=20` is required; `.nvmrc` pins the minor.
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node >=20 is accurate (package.json engines), but the statement “.nvmrc pins the minor” doesn’t match the current .nvmrc value (22, major only). Update the wording or pin a specific minor/patch if that’s required for tooling compatibility.

Suggested change
`node >=20` is required; `.nvmrc` pins the minor.
`node >=20` is required; `.nvmrc` pins the major version.

Copilot uses AI. Check for mistakes.
Comment thread
laurentsenta marked this conversation as resolved.
Outdated

## Directory layout

```
jumper-exchange/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── [lng]/ # all user-facing pages — locale-prefixed
│ │ │ ├── (main)/ # route group: landing + main shell
│ │ │ ├── (infos)/ # route group: legal / informational pages
│ │ │ ├── bridge/, swap/ # core swap surfaces
│ │ │ ├── earn/, portfolio/, quests/, missions/, campaign/, zap/, scan/, onboard/
│ │ │ ├── error-preview/, meta/
│ │ │ ├── error.tsx, layout.tsx
│ │ ├── api/ # Next.js route handlers (thin proxies to jumper-backend)
│ │ ├── lib/, ui/ # framework-level helpers
│ │ ├── layout.tsx, global.css, global-error.tsx, not-found.tsx, robots.ts, sitemap.xml/
│ ├── components/ # feature components (one folder per feature)
│ │ ├── core/, composite/, headless/ # primitives by composition level
│ │ ├── <FeatureName>/ # e.g. EarnDetails, ConnectButton, Cards, …
│ ├── providers/ # provider tree: WalletProvider, ThemeProvider,
│ │ # ReactQueryProvider, TranslationProvider, …
│ ├── stores/<feature>/ # zustand stores (one folder per feature)
│ ├── hooks/<feature>/ # react-query hooks + feature hooks
│ ├── config/ # runtime config (env, wallet connectors, widget)
│ ├── i18n/translations/<lng>/ # translation JSON; resources.d.ts is generated
│ ├── theme/ # MUI theme + design tokens
│ ├── const/, types/, utils/, fonts/, stories/
│ ├── Layout.tsx, proxy.ts
├── tests/ # Playwright E2E + page objects + test data
├── public/ # static assets
├── .storybook/ # Storybook config
├── instrumentation.ts, instrumentation-client.ts, sentry.*.config.ts
├── next.config.mjs, vitest.config.ts, playwright.config.ts, eslint.config.mjs
├── gen-api.sh # regenerates the typed API client from jumper-backend's Swagger
```
Comment on lines +28 to +60
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language identifiers to fenced code blocks (MD040).

These fenced blocks should specify a language to satisfy markdownlint and improve readability.

Suggested fix
-```
+```text
 jumper-exchange/
 ...
-```
+```

-```
+```text
        ┌─────────────────────────────────────────────────────────┐
 ...
-```
+```

Also applies to: 66-91

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 30-30: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ARCHITECTURE.md` around lines 30 - 62, The markdown fenced code blocks in
ARCHITECTURE.md (the ASCII project tree and other blocks shown around lines
30–62 and 66–91) lack language identifiers and trigger MD040; update each
triple-backtick fence (e.g., the block containing "jumper-exchange/" and the
other ASCII diagrams) to include a language token such as text (```text) so
markdownlint passes and readability improves—ensure every opening ``` has a
matching closing ``` and apply the same change to all similar fenced blocks in
the file.


## Request and data flow

```
┌─────────────────────────────────────────────────────────┐
│ Browser (Next.js client) │
│ ┌───────────────────────────────────────────────────┐ │
│ │ React tree (App Router) │ │
│ │ • providers/ wires query, wallet, theme, i18n │ │
│ │ • components/ render features │ │
│ │ • stores/ hold client state (zustand) │ │
│ │ • hooks/ wrap react-query / wallet calls │ │
│ └─────────────┬─────────────────────────────────────┘ │
└────────────────┼────────────────────────────────────────┘
┌───────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
Next.js API jumper-backend LI.FI SDK
routes (REST, (chains, quotes,
(src/app/api/) primary) executions)
jumper-backend (proxied)
┌─────────────────┐
│ strapi-cms │ ← legacy direct paths
│ (REST) │ (content + campaigns)
└─────────────────┘
```

- **Server state** (chains, tokens, quotes, profile, campaigns, …) is fetched through react-query hooks under `src/hooks/`. Most go through `jumper-backend` either directly or via a Next.js route handler under `src/app/api/`. A few legacy hooks still call `strapi-cms` directly.
- **Client state** (selected chain/token, route choice, settings, theme, modals, …) lives in zustand stores under `src/stores/<feature>/`. Stores never call APIs — they hold UI state and derived selectors.
- **The swap engine** is `@lifi/widget`. We embed it inside our pages and configure it via `src/config/widgetConfig.ts`. Wallet connectors are wired in `src/providers/WalletProvider/` so the widget sees them.
- **Sentry** is initialised once in `instrumentation*.ts` / `sentry.*.config.ts`. Feature code uses helpers — never `Sentry.init` directly.

## Internal dependency rules

| From → To | components | hooks | stores | providers | config | app/ | utils |
| -------------- | ---------- | ----- | ------ | ------------------- | ------ | ---- | ----- |
| **components** | ✓ | ✓ | ✓ | ✓ (consume context) | ✓ | ✗ | ✓ |
| **hooks** | ✗ | ✓ | ✓ | ✓ (consume context) | ✓ | ✗ | ✓ |
| **stores** | ✗ | ✗ | ✓ | ✗ | ✓ | ✗ | ✓ |
| **providers** | ✓ (render) | ✓ | ✓ | ✓ | ✓ | ✗ | ✓ |
| **config** | ✗ | ✗ | ✗ | ✗ | ✓ | ✗ | ✓ |
| **app/** | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| **utils** | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ | ✓ |

Read as: a row module _may_ import from a column module where ✓; _must not_ where ✗.

The load-bearing rules:
Comment thread
laurentsenta marked this conversation as resolved.
Outdated

- **`utils/` is a leaf.** It must not import from anything else inside `src/`. Pure helpers only.
- **`stores/` does not import from `components/`, `hooks/`, or `providers/`.** Stores must be renderable in isolation (and unit-testable) without pulling in the whole tree.
- **`hooks/` does not import from `components/`.** Hooks describe data; components consume them.
- **Nothing inside `src/` imports from `src/app/`.** App routes are leaves of the dependency graph — they compose everything else.
- **No barrel `index.ts` files.** Import from source files directly. This keeps the dependency graph honest and tree-shakeable.
Comment thread
laurentsenta marked this conversation as resolved.

If a change requires reversing one of these directions, **stop**. The right shape is usually to move the shared code down a level (often into `utils/` or `config/`) rather than to invert the arrow.

## Invariants

| # | Invariant | Enforcement |
| --- | ------------------------------------------------------------------------------ | ---------------------- |
| 1 | All user-facing pages live under `src/app/[lng]/` | doc; obvious on review |
| 2 | API route handlers in `src/app/api/` are thin proxies — no business logic | doc |
| 3 | Server state goes through react-query; no direct `fetch` in components | doc |
| 4 | One zustand store per feature folder under `src/stores/` | doc |
| 5 | No barrel files (`index.ts` re-exports) | doc; ESLint candidate |
| 6 | Sentry is initialised only in `instrumentation*.ts` / `sentry.*.config.ts` | doc |
| 7 | Secrets never committed; new env vars documented in `src/config/env-config.ts` | per-repo CI (existing) |
| 8 | Pre-commit hook (`tsc --noEmit` + ESLint + Prettier) must pass | Husky + lint-staged |

## Cross-repo position

- This app depends on `jumper-backend` (primary, via REST) and on `strapi-cms` (legacy direct paths, via REST).
- The TypeScript types of the backend API are vendored here, regenerated from `jumper-backend`'s Swagger via `pnpm api` (which calls `gen-api.sh`). Never hand-edit the generated file — change the upstream Swagger and regenerate.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See @AGENTS.md
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Switch to a standard Markdown link for reliability.

Use a normal relative link so the pointer renders consistently in GitHub and other Markdown viewers.

Suggested fix
-See `@AGENTS.md`
+See [AGENTS.md](./AGENTS.md)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
See @AGENTS.md
See [AGENTS.md](./AGENTS.md)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` at line 1, Replace the inline mention "See `@AGENTS.md`" with a
standard relative Markdown link so it renders reliably (e.g., change the text to
"See [AGENTS.md](AGENTS.md)"); locate the string "See `@AGENTS.md`" in CLAUDE.md
and update it to use the Markdown link syntax, making sure the link target
matches the repository filename exactly.

Comment thread
laurentsenta marked this conversation as resolved.
Loading
Loading