From 9de2fd29c3dc2f0b5edffd471ffdb1fc8bf98e4b Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 24 Mar 2026 12:42:49 +0100 Subject: [PATCH 1/2] first grill round --- .cursor/skills/grill-me/SKILL.md | 8 ++ architecture_design.md | 220 +++++++++++++++++++++++++++++++ 2 files changed, 228 insertions(+) create mode 100644 .cursor/skills/grill-me/SKILL.md create mode 100644 architecture_design.md diff --git a/.cursor/skills/grill-me/SKILL.md b/.cursor/skills/grill-me/SKILL.md new file mode 100644 index 0000000000..75e8484f3c --- /dev/null +++ b/.cursor/skills/grill-me/SKILL.md @@ -0,0 +1,8 @@ +--- +name: grill-me +description: Interview the user relentlessly about a plan or design until reaching shared understanding, resolving each branch of the decision tree. Use when user wants to stress-test a plan, get grilled on their design, or mentions "grill me". +--- + +Interview me relentlessly about every aspect of this plan until we reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one. For each question, provide your recommended answer. + +If a question can be answered by exploring the codebase, explore the codebase instead. \ No newline at end of file diff --git a/architecture_design.md b/architecture_design.md new file mode 100644 index 0000000000..5fa838bfa9 --- /dev/null +++ b/architecture_design.md @@ -0,0 +1,220 @@ +# Client architecture blueprint (draft — round 1) + +Living document: extend after further design rounds. Intended for **human engineers and agents** implementing the migration. + +--- + +## 1. Purpose + +- **Co-locate** everything that belongs to a **slice** (core explorer domain) or **feature** (optional / config-driven) in one place: UI, hooks, utils, **API response types** for that area, mocks, stubs, etc. +- **Retire** top-level `lib/`, `mocks/`, `stubs/`, `types/` (and `ui/`) as *unscoped* buckets — their contents move under **`/client`** or other agreed homes (see §6). +- Keep **clear boundaries** so the graph stays maintainable (see §8). + +--- + +## 2. Naming conventions (agreed) + +Aligned with **Next.js-style** paths: **kebab-case for directories** and for **non-component TS modules** (helpers, parsers, constants files). + +| Artifact | Convention | Examples | +|----------|------------|----------| +| **Directories** | `kebab-case` | `slices/tx/`, `features/user-ops/`, `pages/tx-details/` | +| **React components** | `PascalCase.tsx` | `TxDetails.tsx`, `TxIndexTable.tsx` | +| **Hooks** | `use` + `camelCase` in the filename (ecosystem norm) | `useTxQuery.ts`, `useIsMobile.ts` | +| **Helper / util modules** (non-hook) | `kebab-case.ts` | `format-tx-hash.ts`, `get-tab-list.ts` | +| **Role / barrel files** | lowercase | `types.ts`, `mocks.ts`, `stubs.ts`, `index.ts` | + +Same folder may contain `TxDetails.tsx`, `use-tx-query.ts` — hooks stay **`useCamelCase.ts`**; everything else non-component prefers **kebab-case**. + +--- + +## 3. Top-level repo layout (conceptual) + +| Path | Role | +|------|------| +| **`/client`** | Product explorer: shell, API client layer, slices, features, shared. Replaces **`/ui`** and absorbs most of **`lib`**, **`mocks`**, **`stubs`**, **`types`** (see §6). | +| **`/configs`** | Env-derived config and feature flags. **Must not import from `/client`.** Consts + types derived from those consts (e.g. supported ad providers) live here. | +| **`/nextjs`** | Next integration, SSR helpers, server utilities. | +| **`/pages`** | Next.js **file-based routes** only (thin wrappers). **404 vs “empty page”** is defined here — invalid routes do not render the page component. | +| **`/toolkit`** | Design system and shared code **published to npm** / reused across company projects — **stays outside** `client`. | +| **Root `lib/`** | **Removed** after migration except pieces explicitly moved elsewhere (e.g. monitoring → `nextjs`). | + +--- + +## 4. Inside `/client` + +### 4.1 `client/shell` + +Application **chrome**: layout, error UI, header, footer, page title, and other frame-level concerns. + +Former **`ui/snippets`** that belong to the frame are **split** and moved here or into the right slice/feature: + +- **Search bar** → **`slices/search`** (not shell-only). +- **Auth / account entry** → **`features/account`** (or equivalent). +- **Header / footer / layout** composition → **`shell`**. + +### 4.2 `client/api` + +- **Full migration** of today’s **`lib/api`** (transport: fetch, resources, URL building, query client wiring, etc.). +- **`client/api` must not import runtime logic** from `client/slices/*` or `client/features/*` (avoids cycles). **`import type`** from slices is permitted when an API service file maps a resource to its response type — the type lives with the slice that owns it, and `client/api` references it for the payload mapping only. +- **Per-resource response types** and view-specific types are **not** centralized under `client/api`; they live in the **slice or feature** that owns the domain (co-location with hooks/components). + +### 4.3 `client/slices` + +**Core explorer entities** — always part of the product surface (not optional “features” in the `configs/app/features` sense). + +**Examples (non-exhaustive):** `tx`, `block`, `search`, `token`, `address`, `contract`, `internal-tx`, `home`, … + +- **`contract` (slice):** **Contract verification** is a **slice** concern (available on any chain), **not** a `features/*` entry. +- **`internal-tx` (slice):** Own slice; **`tx`** composes **internal-tx** components (e.g. tab content). Prefer **no imports** from `internal-tx` back into `tx` to avoid cycles; share via **`client/shared`** or a **thin types-only surface** if needed. + +**Typical slice shape:** + +```text +client/slices/tx/ + pages/ + tx-index/ # kebab-case folder = one “screen” / route target + TxIndex.tsx + TxIndexTable.tsx + ... + tx-details/ + TxDetails.tsx + ... + components/ + hooks/ # e.g. useTxQuery.ts + utils/ # kebab-case .ts files + types/ + api.ts # response / DTO types owned by this slice + mocks.ts + stubs.ts +``` + +**Routing rule:** **One Next route ≈ one folder under** `pages/` inside the slice (or feature). The **file** under root `pages/` stays a thin dynamic import into `client/...`. + +### 4.4 `client/features` + +**Optional** product areas — **roughly mirrors `configs/app/features`**, but: + +- Folders may exist for **readability and maintenance** (e.g. per rollup type) even when not everything is enabled for a given chain; **runtime behavior** still comes from **config/env**, not “folder exists = on.” + +**Examples:** `user-ops`, `blobs`, `multichain`, `name-domains`, `account`, … + +**Variant / chain-specific transaction lists** (e.g. kettle, zeta-specific list UIs) live under **`features//pages/...`**, **not** as variants inside `slices/tx`. + +**Rollups:** `features/rollup//` (e.g. `optimism`, `arbitrum`, …) — **organized by rollup type**, because **views and helpers differ by rollup**. Sub-areas (deposits, batches, withdrawals) are **subfolders** under that rollup, not separate top-level rollup taxonomies. + +**Typical feature shape:** same idea as slices — `pages/`, `components/`, `hooks/`, `utils/`, `types/`, `mocks.ts`, `stubs.ts`. + +### 4.5 `client/shared` + +**Cross-cutting UI and helpers** with **no single domain owner**. + +- **No files directly in `client/shared/`** — only **subfolders** grouped by purpose (`pagination`, `charts`, `entities`, `hooks`, `router`, …). +- Examples to migrate from current `lib` / `ui/shared`: `useQueryWithPages` → e.g. `shared/pagination/`; very generic hooks → `shared/hooks/` or a dedicated subfolder. + +--- + +## 5. Config vs client + +- **`/configs` never imports `/client`.** +- Types that describe **config shape** (including types **derived from consts** in config, e.g. supported ad banner providers) live **in config** next to the relevant feature/config module. +- **`/client`** imports **`/configs`** for feature flags and app configuration. + +--- + +## 6. Migration map (sources → destinations) + +| Current | Destination | +|---------|-------------| +| `ui/**` | `client/**` (restructured into shell / slices / features / shared) | +| `ui/snippets/**` | `client/shell`, `client/slices/search`, `client/features/account`, etc. | +| `lib/api/**` | `client/api/**` | +| Other `lib/**` (hooks, router, errors, web3, …) | `client/shared/**` or the **slice/feature** that owns the usage — case by case | +| `lib/monitoring/**` | **`nextjs/`** (used from Next server / `pages/api`, not browser) | +| `mocks/**`, `stubs/**` | Co-located **`mocks.ts` / `stubs.ts`** (or folders) under the relevant **slice/feature**; shared test data → `client/shared/...` if truly cross-domain | +| `types/api/*`, `types/client/*`, etc. | **Slice/feature `types/`** (and config-owned types → `configs`) — **not** a single global `types/` tree at repo root after migration | + +**Monitoring:** no client-side monitoring in scope for now; server metrics stay in **nextjs**. + +**Migration process:** **incremental PRs/tasks**; update imports in each task. **No** long-lived re-export shim unless the team later decides otherwise. + +--- + +## 7. Testing + +- **Playwright / visual tests:** stay **next to** the implementation (e.g. `*.pw.tsx` alongside components), same as today. + +--- + +## 8. Dependency rules (high level) + +- Prefer **eslint** enforcement over a long written matrix: e.g. **`import/no-cycle`**, and optionally **boundaries** rules. +- **Guidelines:** + - **`client/api`** allows **`import type`** from slices/features for response-shape typing; **no runtime imports** (no logic, hooks, or components — types only). + - **Slice → feature type imports** are intentional: the backend includes feature-specific fields (e.g. `arbitrum?`, `scroll?`) in API responses unconditionally when the rollup is enabled — the frontend type must mirror the full API contract. UI component imports follow the same direction (e.g. `TxDetails` imports `TxDetailsArbitrum`). + - **Avoid circular graphs** between slices/features; use **composition at a shallow level** (page or a single container component) when optional UI plugs into a core screen. + - **`internal-tx` → `tx`:** avoid **`tx` → `internal-tx` → `tx`** cycles; lift shared bits to **`shared`** or **types-only** exports. + +--- + +## 9. Config ↔ feature folder naming + +- **Feature directories** under `client/features/`: **`kebab-case`** (`user-ops`, `name-domains`). +- **Config files** under `configs/app/features/`: keep **consistent** with existing repo style (`userOps.ts` vs `user-ops.ts`) — align in a **single** convention pass to avoid mixed mental models. + +--- + +## 10. Next rounds (open topics) + +Use this list for follow-up design passes: + +- [ ] **Full inventory of slices** — classify ambiguous areas (`stats`, `gas-tracker`, `epochs`, `validators`, marketplace, rewards, …) as slice vs feature vs `shared`. +- [ ] **Exact `lib/*` split** — each remaining module: `client/api` vs `shared/*` vs specific slice/feature. +- [ ] **ESLint config** — enable `import/no-cycle`; add explicit boundary rules for `client/api`, `configs`, `toolkit`. +- [ ] **Path aliases** — `client/*` imports; document in TS config / CONTRIBUTING. +- [ ] **Public type surfaces** — if a feature needs types from a slice, document **thin** exports (e.g. `slices/tx/types/index.ts`) to avoid deep imports and cycles. +- [ ] **Storybook / toolkit** — if any stories live under `client`, how they resolve paths to `toolkit`. + +--- + +## 11. Example: rollup-specific field on tx page + +- **Core** tx UI stays in **`slices/tx`**. +- **Arbitrum-only** (or other rollup) presentation lives in **`features/rollup/arbitrum/`**. +- **Compose** at a **high level** (`tx-details` page or `TxDetails` section) so rollup-specific imports are localized and **cycles** remain unlikely. +- **Types:** the `Transaction` type in `slices/tx/types/api.ts` imports feature-specific sub-types (e.g. `ArbitrumTransactionData` from `features/rollup/arbitrum/types/api.ts`) because the backend includes these fields in the response unconditionally when the rollup is enabled — the frontend type must reflect the full API contract. `client/api` then references `Transaction` via `import type` from the slice. UI component imports follow the same direction: `TxDetails` imports `TxDetailsArbitrum`. + +### File layout for this example + +```text +client/ + api/ + services/ + general/ + tx.ts + └─ import type { Transaction } from 'client/slices/tx/types/api' + slices/ + tx/ + pages/ + tx-details/ + TxDetails.tsx + └─ import TxDetailsArbitrum from 'client/features/rollup/arbitrum/components/TxDetailsArbitrum' + types/ + api.ts # owns the Transaction interface + └─ import type { ArbitrumTransactionData } from 'client/features/rollup/arbitrum/types/api' + export interface Transaction { + // ... base tx fields ... + arbitrum?: ArbitrumTransactionData + } + features/ + rollup/ + arbitrum/ + types/ + api.ts # owns ArbitrumTransactionData + components/ + TxDetailsArbitrum.tsx +``` + +--- + +*End of round 1 blueprint.* \ No newline at end of file From 477caf7a503c9ff54bcbcdd80be639a070391bf6 Mon Sep 17 00:00:00 2001 From: tom Date: Tue, 24 Mar 2026 17:43:11 +0100 Subject: [PATCH 2/2] second grill round --- architecture_design.md | 228 ++++++++++++++++++++++++++++++++++------- 1 file changed, 191 insertions(+), 37 deletions(-) diff --git a/architecture_design.md b/architecture_design.md index 5fa838bfa9..c0e617821e 100644 --- a/architecture_design.md +++ b/architecture_design.md @@ -1,4 +1,4 @@ -# Client architecture blueprint (draft — round 1) +# Client architecture blueprint (draft — round 2) Living document: extend after further design rounds. Intended for **human engineers and agents** implementing the migration. @@ -22,10 +22,12 @@ Aligned with **Next.js-style** paths: **kebab-case for directories** and for **n | **React components** | `PascalCase.tsx` | `TxDetails.tsx`, `TxIndexTable.tsx` | | **Hooks** | `use` + `camelCase` in the filename (ecosystem norm) | `useTxQuery.ts`, `useIsMobile.ts` | | **Helper / util modules** (non-hook) | `kebab-case.ts` | `format-tx-hash.ts`, `get-tab-list.ts` | -| **Role / barrel files** | lowercase | `types.ts`, `mocks.ts`, `stubs.ts`, `index.ts` | +| **Role files** | lowercase | `types.ts`, `mocks.ts`, `stubs.ts` | Same folder may contain `TxDetails.tsx`, `use-tx-query.ts` — hooks stay **`useCamelCase.ts`**; everything else non-component prefers **kebab-case**. +**No `index.ts` barrel files inside `/client`.** Deep imports are preferred; barrels slow TypeScript server performance and break tree-shaking at scale. + --- ## 3. Top-level repo layout (conceptual) @@ -35,8 +37,8 @@ Same folder may contain `TxDetails.tsx`, `use-tx-query.ts` — hooks stay **`use | **`/client`** | Product explorer: shell, API client layer, slices, features, shared. Replaces **`/ui`** and absorbs most of **`lib`**, **`mocks`**, **`stubs`**, **`types`** (see §6). | | **`/configs`** | Env-derived config and feature flags. **Must not import from `/client`.** Consts + types derived from those consts (e.g. supported ad providers) live here. | | **`/nextjs`** | Next integration, SSR helpers, server utilities. | -| **`/pages`** | Next.js **file-based routes** only (thin wrappers). **404 vs “empty page”** is defined here — invalid routes do not render the page component. | -| **`/toolkit`** | Design system and shared code **published to npm** / reused across company projects — **stays outside** `client`. | +| **`/pages`** | Next.js **file-based routes** only (thin wrappers — a dynamic import and `getServerSideProps` call; nothing else). **404 vs "empty page"** is defined here — invalid routes do not render the page component. | +| **`/toolkit`** | Design system and shared code **published to npm** / reused across company projects — **stays outside** `client`. Referenced as a local path alias during this migration; publishing scope is out of scope for this task. | | **Root `lib/`** | **Removed** after migration except pieces explicitly moved elsewhere (e.g. monitoring → `nextjs`). | --- @@ -45,35 +47,81 @@ Same folder may contain `TxDetails.tsx`, `use-tx-query.ts` — hooks stay **`use ### 4.1 `client/shell` -Application **chrome**: layout, error UI, header, footer, page title, and other frame-level concerns. +Application **chrome**: layout, error UI, header, footer, page title, top-bar, and other frame-level concerns. + +**`ui/snippets/` split (complete):** + +| Source | Destination | +|--------|-------------| +| `header/`, `footer/`, `navigation/`, `topBar/` | `client/shell/` | +| `searchBar/` | `client/slices/search/` | +| `networkLogo/` | `client/shared/` | +| `networkMenu/` | `client/features/multichain/` | +| `auth/`, `user/` | `client/features/account/` | + +**Context split from `lib/contexts/`:** -Former **`ui/snippets`** that belong to the frame are **split** and moved here or into the right slice/feature: +| Source | Destination | +|--------|-------------| +| `app.tsx`, `fallback.tsx` | `client/shell/` | +| `settings.tsx` | `client/shell/top-bar/` (alongside `TopBar` component) | +| `multichain.tsx` | `client/features/multichain/` | +| `rewards.tsx` | `client/features/rewards/` | +| `marketplace.tsx` | `client/features/marketplace/` | +| `addressHighlight.tsx` | `client/slices/address/` | -- **Search bar** → **`slices/search`** (not shell-only). -- **Auth / account entry** → **`features/account`** (or equivalent). -- **Header / footer / layout** composition → **`shell`**. +**Hooks from `lib/hooks/`:** + +- `useNavItems` → `client/shell/` +- Everything else generic → `client/shared/hooks/` (see §4.5) ### 4.2 `client/api` -- **Full migration** of today’s **`lib/api`** (transport: fetch, resources, URL building, query client wiring, etc.). +- **Full migration** of today's **`lib/api`** (transport: fetch, resources, URL building, query client wiring, etc.). - **`client/api` must not import runtime logic** from `client/slices/*` or `client/features/*` (avoids cycles). **`import type`** from slices is permitted when an API service file maps a resource to its response type — the type lives with the slice that owns it, and `client/api` references it for the payload mapping only. - **Per-resource response types** and view-specific types are **not** centralized under `client/api`; they live in the **slice or feature** that owns the domain (co-location with hooks/components). +- **WebSocket transport** (`lib/socket/`) → `client/api/socket/` — same reasoning as fetch: it is a transport mechanism, not a UI concern. + +**`client/api/services/` structure:** + +Keep the current organization largely intact. Do not flatten everything into a 1:1 mapping with slices — feature services often have only one or two resources and do not warrant separate files. + +```text +client/api/ + services/ + general/ ← core API resources; only split files inside where genuinely needed + tx.ts + block.ts + misc.ts ← resources with no clear single-domain home stay here + ... + rewards.ts ← feature-specific APIs stay flat alongside general/ + stats.ts + user-ops.ts + bens.ts + ... + types.ts ← shared API types incl. IsPaginated (absorbs services/utils.ts) + socket/ ← migrated from lib/socket/ +``` ### 4.3 `client/slices` -**Core explorer entities** — always part of the product surface (not optional “features” in the `configs/app/features` sense). +**Core explorer entities** — always part of the product surface (not optional "features" in the `configs/app/features` sense). + +**Classification criterion:** *"Can this area exist on a vanilla EVM chain with no feature flag?"* Yes → slice. No (requires a `configs/app/features` flag) → feature. -**Examples (non-exhaustive):** `tx`, `block`, `search`, `token`, `address`, `contract`, `internal-tx`, `home`, … +**Confirmed slices (non-exhaustive):** `tx`, `block`, `search`, `token`, `address`, `contract`, `internal-tx`, `home`, `tokens`, `accounts`, `token-instance`, … -- **`contract` (slice):** **Contract verification** is a **slice** concern (available on any chain), **not** a `features/*` entry. +- **`contract` (slice):** **Contract verification** and **SolidityScan** (`lib/solidityScan/`) are **slice** concerns (available on any chain). - **`internal-tx` (slice):** Own slice; **`tx`** composes **internal-tx** components (e.g. tab content). Prefer **no imports** from `internal-tx` back into `tx` to avoid cycles; share via **`client/shared`** or a **thin types-only surface** if needed. +**Rollup sub-types on slice types:** `slices/tx/types/api.ts` imports feature-specific sub-types (e.g. `ArbitrumTransactionData`) from their respective feature via `import type`. Optional fields fan out at the slice type level — this mirrors the real API contract and is intentional (see §11). + **Typical slice shape:** ```text client/slices/tx/ pages/ - tx-index/ # kebab-case folder = one “screen” / route target + tx-index/ # kebab-case folder = one "screen" / route target TxIndex.tsx TxIndexTable.tsx ... @@ -95,9 +143,12 @@ client/slices/tx/ **Optional** product areas — **roughly mirrors `configs/app/features`**, but: -- Folders may exist for **readability and maintenance** (e.g. per rollup type) even when not everything is enabled for a given chain; **runtime behavior** still comes from **config/env**, not “folder exists = on.” +- Folders may exist for **readability and maintenance** (e.g. per rollup type) even when not everything is enabled for a given chain; **runtime behavior** still comes from **config/env**, not "folder exists = on." + +**Confirmed features (non-exhaustive):** `user-ops`, `blobs`, `multichain`, `name-domains`, `account`, `stats`, `gas-tracker`, `epochs`, `validators`, `marketplace`, `rewards`, `rollup/*`, … -**Examples:** `user-ops`, `blobs`, `multichain`, `name-domains`, `account`, … +- **`stats` and `gas-tracker`** are **features** (not slices) — they are config-gated per chain. +- **`epochs` and `validators`** are **features** — chain-specific, not present on a vanilla EVM chain. **Variant / chain-specific transaction lists** (e.g. kettle, zeta-specific list UIs) live under **`features//pages/...`**, **not** as variants inside `slices/tx`. @@ -109,8 +160,34 @@ client/slices/tx/ **Cross-cutting UI and helpers** with **no single domain owner**. -- **No files directly in `client/shared/`** — only **subfolders** grouped by purpose (`pagination`, `charts`, `entities`, `hooks`, `router`, …). -- Examples to migrate from current `lib` / `ui/shared`: `useQueryWithPages` → e.g. `shared/pagination/`; very generic hooks → `shared/hooks/` or a dedicated subfolder. +- **No files directly in `client/shared/`** — only **subfolders** grouped by purpose. +- **No `index.ts` barrel files** (same rule as the rest of `/client`). + +**Confirmed subfolders (round 2 decisions):** + +| Subfolder | Contents / origin | +|-----------|-------------------| +| `analytics/` | `lib/mixpanel/` | +| `auth/` | `lib/decodeJWT.ts` | +| `chain/` | `lib/networks/` (all `network` → `chain` renamed) + `lib/units.ts` | +| `date-and-time/` | `lib/hooks/useTimeAgoIncrement` and similar date utils | +| `errors/` | `lib/errors/` | +| `feature-flags/` | `lib/growthbook/` (runtime A/B flags — distinct from build-time `configs/`) | +| `hooks/` | Generic hooks from `lib/hooks/` with no single domain owner | +| `i18n/` | `lib/setLocale.ts` | +| `links/utils/` | `lib/utils/stripUtmParams.ts` and URL-related helpers | +| `lists/` | `lib/hooks/useLazyRenderedList`, `lib/getItemIndex.ts` | +| `metadata/` | `lib/metadata/` (centralized — see note below) | +| `monitoring/rollbar/` | `lib/rollbar/` (client-side observability infrastructure) | +| `router/` | `lib/router/` + query-param filter helpers (`getFilterValueFromQuery`, etc.) | +| `storage/` | `lib/cookies.ts` | +| `text/` | `lib/capitalizeFirstLetter.ts`, `shortenString.ts`, `escapeRegExp.ts`, `highlightText.ts` | +| `transformers/` | Hex / bytes / base64 conversion utils (`hexToBytes`, `base64ToHex`, etc.) | +| `utils/` | Tiny misc utilities with no better home (`delay.ts`, `isMetaKey.tsx`) | +| `web3/` | `lib/web3/` | +| *(existing)* | `pagination/`, `charts/`, `entities/`, etc. — keep as-is | + +**`client/shared/metadata/` note:** The title/description template map is a routing manifest (`Record`) with TypeScript exhaustiveness enforced across all routes. Splitting it into slices/features loses that guarantee. It stays **centralized** in `client/shared/metadata/`. The `ApiData` type within it uses `import type` from the relevant slices (e.g. `TokenInfo` from `slices/token/types/api.ts`) — a shared → slice `import type` is acceptable here (no cycle, no runtime import). --- @@ -119,24 +196,79 @@ client/slices/tx/ - **`/configs` never imports `/client`.** - Types that describe **config shape** (including types **derived from consts** in config, e.g. supported ad banner providers) live **in config** next to the relevant feature/config module. - **`/client`** imports **`/configs`** for feature flags and app configuration. +- **Runtime feature flags** (Growthbook A/B) live in **`client/shared/feature-flags/`** — they require a React context and cannot live in `configs/`. --- ## 6. Migration map (sources → destinations) +### `ui/` → `client/` + | Current | Destination | |---------|-------------| | `ui/**` | `client/**` (restructured into shell / slices / features / shared) | -| `ui/snippets/**` | `client/shell`, `client/slices/search`, `client/features/account`, etc. | -| `lib/api/**` | `client/api/**` | -| Other `lib/**` (hooks, router, errors, web3, …) | `client/shared/**` or the **slice/feature** that owns the usage — case by case | -| `lib/monitoring/**` | **`nextjs/`** (used from Next server / `pages/api`, not browser) | -| `mocks/**`, `stubs/**` | Co-located **`mocks.ts` / `stubs.ts`** (or folders) under the relevant **slice/feature**; shared test data → `client/shared/...` if truly cross-domain | -| `types/api/*`, `types/client/*`, etc. | **Slice/feature `types/`** (and config-owned types → `configs`) — **not** a single global `types/` tree at repo root after migration | +| `ui/snippets/header/`, `footer/`, `navigation/`, `topBar/` | `client/shell/` | +| `ui/snippets/searchBar/` | `client/slices/search/` | +| `ui/snippets/networkLogo/` | `client/shared/` | +| `ui/snippets/networkMenu/` | `client/features/multichain/` | +| `ui/snippets/auth/`, `user/` | `client/features/account/` | + +### `lib/` → destinations -**Monitoring:** no client-side monitoring in scope for now; server metrics stay in **nextjs**. +| Current | Destination | +|---------|-------------| +| `lib/api/**` | `client/api/**` (services structure preserved; `services/utils.ts` → `client/api/types.ts`) | +| `lib/socket/` | `client/api/socket/` | +| `lib/monitoring/` | `nextjs/` | +| `lib/rollbar/` | `client/shared/monitoring/rollbar/` | +| `lib/metadata/` | `client/shared/metadata/` | +| `lib/router/` | `client/shared/router/` | +| `lib/web3/` | `client/shared/web3/` | +| `lib/errors/` | `client/shared/errors/` | +| `lib/mixpanel/` | `client/shared/analytics/` | +| `lib/growthbook/` | `client/shared/feature-flags/` | +| `lib/networks/` + `lib/units.ts` | `client/shared/chain/` (rename `network` → `chain` throughout) | +| `lib/hooks/useNavItems` | `client/shell/` | +| `lib/hooks/useTimeAgoIncrement` | `client/shared/date-and-time/` | +| `lib/hooks/useLazyRenderedList` | `client/shared/lists/` | +| `lib/hooks/useRewardsActivity` | `client/features/rewards/` | +| `lib/hooks/useAddressProfileApiQuery` | `client/slices/address/` | +| `lib/hooks/` (remaining) | `client/shared/hooks/` | +| `lib/contexts/app.tsx`, `fallback.tsx` | `client/shell/` | +| `lib/contexts/settings.tsx` | `client/shell/top-bar/` | +| `lib/contexts/multichain.tsx` | `client/features/multichain/` | +| `lib/contexts/rewards.tsx` | `client/features/rewards/` | +| `lib/contexts/marketplace.tsx` | `client/features/marketplace/` | +| `lib/contexts/addressHighlight.tsx` | `client/slices/address/` | +| `lib/tx/` | `client/slices/tx/utils/` | +| `lib/token/` | `client/slices/token/` | +| `lib/address/` | `client/slices/address/` | +| `lib/rollups/` | `client/features/rollup/` | +| `lib/multichain/` | `client/features/multichain/` | +| `lib/search/` | `client/slices/search/` | +| `lib/contracts/` | `client/slices/contract/` | +| `lib/solidityScan/` | `client/slices/contract/` | +| `lib/stats/` | `client/features/stats/` | +| `lib/utils/stripUtmParams.ts` | `client/shared/links/utils/` | +| `lib/capitalizeFirstLetter.ts`, `shortenString.ts`, `escapeRegExp.ts`, `highlightText.ts` | `client/shared/text/` | +| `lib/base64ToHex.ts`, `bytesToBase64.ts`, `bytesToHex.ts`, `hexToBase64.ts`, `hexToBytes.ts`, `hexToAddress.ts`, `hexToDecimal.ts`, `hexToUtf8.ts` | `client/shared/transformers/` | +| `lib/cookies.ts` | `client/shared/storage/` | +| `lib/decodeJWT.ts` | `client/shared/auth/` | +| `lib/setLocale.ts` | `client/shared/i18n/` | +| `lib/delay.ts`, `lib/isMetaKey.tsx` | `client/shared/utils/` | +| `lib/recentSearchKeywords.ts` | `client/slices/search/` | +| `lib/getFilterValueFromQuery.ts`, `getFilterValuesFromQuery.ts`, `getValuesArrayFromQuery.ts` | `client/shared/router/` | +| `lib/getItemIndex.ts` | `client/shared/lists/` | +| `lib/getErrorMessage.ts` | `client/shared/errors/` | + +### Other sources + +| Current | Destination | +|---------|-------------| +| `mocks/**`, `stubs/**` | Co-located **`mocks.ts` / `stubs.ts`** under the relevant **slice/feature**; shared test data → `client/shared/...` if truly cross-domain | +| `types/api/*`, `types/client/*`, etc. | **Slice/feature `types/`** (and config-owned types → `configs`) — **not** a single global `types/` tree at repo root after migration | -**Migration process:** **incremental PRs/tasks**; update imports in each task. **No** long-lived re-export shim unless the team later decides otherwise. +**Migration process:** **incremental PRs/tasks** — update all imports in the **same PR** as the move. **No long-lived re-export shims.** Import path updates for test files can be scripted with a codemod. --- @@ -148,10 +280,11 @@ client/slices/tx/ ## 8. Dependency rules (high level) -- Prefer **eslint** enforcement over a long written matrix: e.g. **`import/no-cycle`**, and optionally **boundaries** rules. +- **ESLint rules (`import/no-cycle` + explicit `boundaries` rules for `client/api`, `configs`, `toolkit`) must be enabled _before_ the migration starts.** Running them against the existing code surfaces real problem areas cheaply, before they become migration blockers. - **Guidelines:** - **`client/api`** allows **`import type`** from slices/features for response-shape typing; **no runtime imports** (no logic, hooks, or components — types only). - **Slice → feature type imports** are intentional: the backend includes feature-specific fields (e.g. `arbitrum?`, `scroll?`) in API responses unconditionally when the rollup is enabled — the frontend type must mirror the full API contract. UI component imports follow the same direction (e.g. `TxDetails` imports `TxDetailsArbitrum`). + - **`client/shared` → slice `import type`** is acceptable where shared infrastructure genuinely needs a slice-owned type (e.g. `client/shared/metadata/` referencing `TokenInfo` from `slices/token/types/api.ts`). No runtime imports from shared → slice. - **Avoid circular graphs** between slices/features; use **composition at a shallow level** (page or a single container component) when optional UI plugs into a core screen. - **`internal-tx` → `tx`:** avoid **`tx` → `internal-tx` → `tx`** cycles; lift shared bits to **`shared`** or **types-only** exports. @@ -160,24 +293,42 @@ client/slices/tx/ ## 9. Config ↔ feature folder naming - **Feature directories** under `client/features/`: **`kebab-case`** (`user-ops`, `name-domains`). -- **Config files** under `configs/app/features/`: keep **consistent** with existing repo style (`userOps.ts` vs `user-ops.ts`) — align in a **single** convention pass to avoid mixed mental models. +- **Config files** under `configs/app/features/`: **migrate to `kebab-case`** in a **single dedicated pre-migration PR** (before feature folder migration starts), so config file names and feature folder names share one mental model. --- -## 10. Next rounds (open topics) +## 10. Migration execution plan + +### Order of operations + +1. **Pre-migration PR:** rename `configs/app/features/` files to kebab-case. +2. **Pre-migration PR:** enable ESLint `import/no-cycle` + `boundaries` rules; fix existing violations. +3. **Bottom-up migration:** start with `client/api` and `client/shared` (lowest in the dependency graph, most imported). Establishes the foundation without touching any UI. +4. **Pilot slice — `tx`:** migrate `lib/tx/`, the `tx` API service, `ui/tx/**`, hooks, types, mocks end-to-end. Use the result as the canonical template for all subsequent slices. +5. **Remaining slices** in parallel (one PR per slice). +6. **Features** in parallel (one PR per feature or logical group). +7. **`client/shell`** — migrate after slices/features it depends on are in place. +8. **Remove root `lib/`** — confirm empty, delete. + +### Rules during migration + +- Update **all imports in the same PR** as the file move — no shims. +- Each PR leaves ESLint green; do not merge with new cycle violations. +- `pages/` thin wrappers: **dynamic import only** — `getServerSideProps` helpers stay in `nextjs/`; SEO metadata lives in the slice/feature page folder. + +--- -Use this list for follow-up design passes: +## 11. Open topics (round 3) -- [ ] **Full inventory of slices** — classify ambiguous areas (`stats`, `gas-tracker`, `epochs`, `validators`, marketplace, rewards, …) as slice vs feature vs `shared`. -- [ ] **Exact `lib/*` split** — each remaining module: `client/api` vs `shared/*` vs specific slice/feature. -- [ ] **ESLint config** — enable `import/no-cycle`; add explicit boundary rules for `client/api`, `configs`, `toolkit`. -- [ ] **Path aliases** — `client/*` imports; document in TS config / CONTRIBUTING. -- [ ] **Public type surfaces** — if a feature needs types from a slice, document **thin** exports (e.g. `slices/tx/types/index.ts`) to avoid deep imports and cycles. +- [ ] **Path aliases** — `client/*` imports; document in `tsconfig.json` / CONTRIBUTING. +- [ ] **Public type surfaces** — document **thin** export files (e.g. `slices/tx/types/api.ts`) to avoid deep cross-slice imports. - [ ] **Storybook / toolkit** — if any stories live under `client`, how they resolve paths to `toolkit`. +- [ ] **`lib/hooks/` full audit** — confirm every remaining hook in `lib/hooks/` that is not explicitly listed above goes to `client/shared/hooks/`. +- [ ] **`lib/api/services/general/` audit** — identify any files inside `general/` that should be split out (beyond `tx.ts`, `block.ts`, etc.) now that slices own their types. --- -## 11. Example: rollup-specific field on tx page +## 12. Example: rollup-specific field on tx page - **Core** tx UI stays in **`slices/tx`**. - **Arbitrum-only** (or other rollup) presentation lives in **`features/rollup/arbitrum/`**. @@ -205,6 +356,9 @@ client/ export interface Transaction { // ... base tx fields ... arbitrum?: ArbitrumTransactionData + optimism?: OptimismTransactionData + scroll?: ScrollTransactionData + // ... other rollup optional fields ... } features/ rollup/ @@ -217,4 +371,4 @@ client/ --- -*End of round 1 blueprint.* \ No newline at end of file +*End of round 2 blueprint.*