diff --git a/apps/cms/AGENTS.md b/apps/cms/AGENTS.md index 52ed90f..7021b72 100644 --- a/apps/cms/AGENTS.md +++ b/apps/cms/AGENTS.md @@ -1,108 +1,97 @@ -# apps/cms -- Payload CMS Admin Shell +# AGENTS.md — apps/cms + +**Thin shell** hosting the Payload CMS admin panel via Next.js. All CMS configuration (collections, globals, hooks, access control, `payload.config.ts`) lives in `@repo/core-cms`, which aggregates collections from feature packages. ## Purpose -**THIN SHELL ONLY** -- this app exists solely to serve the Payload CMS admin panel via Next.js. All CMS logic (collections, globals, hooks, access control, `payload.config.ts`) lives in `@repo/cms-core`. This app contains no custom CMS code beyond Next.js routing boilerplate that Payload generates automatically. +This app exists solely to serve the Payload Admin UI. It contains no custom CMS code beyond Next.js routing boilerplate. All business knowledge lives in feature packages (`@repo/auth`, `@repo/blog`, etc.), which export their collections/globals via subpath exports (`.../cms`). `@repo/core-cms` composes them into a single Payload config. ## Port: 3001 ```bash -pnpm dev --filter @repo/cms # http://localhost:3001/admin +docker compose up -d postgres # Start PostgreSQL on port 5432 +pnpm dev --filter @repo/cms # http://localhost:3001/admin ``` -Requires PostgreSQL running first: - -```bash -docker compose up -d postgres # Starts PostgreSQL on port 5432 -``` - -## Hard Rules - -- **NEVER** add collections, globals, hooks, or access control in this app -- put them in `@repo/cms-core` -- **NEVER** import from `@repo/core/infrastructure` -- **NEVER** modify auto-generated files (marked with "THIS FILE WAS GENERATED AUTOMATICALLY BY PAYLOAD") -- All CMS configuration changes go in `packages/cms-core/` - ## Key Files | File | Purpose | |---|---| -| `next.config.mjs` | Next.js config wrapped with `withPayload()` from `@payloadcms/next` | +| `next.config.mjs` | Minimal config wrapped with `withPayload()` from `@payloadcms/next` | | `tsconfig.json` | TypeScript config with `@payload-config` path alias | | `src/app/(payload)/layout.tsx` | Auto-generated Payload root layout (DO NOT MODIFY) | | `src/app/(payload)/admin/[[...segments]]/page.tsx` | Auto-generated catch-all admin page (DO NOT MODIFY) | -| `src/app/(payload)/admin/[[...segments]]/not-found.tsx` | Auto-generated 404 page (DO NOT MODIFY) | | `src/app/(payload)/importMap.js` | Auto-generated Payload import map (DO NOT MODIFY) | -| `src/app/(payload)/custom.scss` | Custom SCSS overrides for admin panel styling | + +## Hard Rules + +- **NEVER** add collections, globals, or hooks in this app — put them in feature packages +- **NEVER** create custom CMS logic here — use `@repo/core-cms` +- **NEVER** modify auto-generated files under `src/app/(payload)/` +- All Payload config changes go in `packages/core-cms/src/payload.config.ts` ## @payload-config Alias -The `tsconfig.json` defines a path alias that points to the config in `@repo/cms-core`: +The `tsconfig.json` points to `@repo/core-cms`: ```json { "compilerOptions": { "paths": { - "@payload-config": [ - "../../packages/cms-core/src/payload.config.ts" - ] + "@payload-config": ["../../packages/core-cms/src/payload.config.ts"] } } } ``` -When Payload or auto-generated files import `@payload-config`, it resolves to `packages/cms-core/src/payload.config.ts`. This is how the thin shell delegates all configuration to `@repo/cms-core`. +When Payload imports `@payload-config`, it resolves to the composed config from `@repo/core-cms`, which in turn imports feature collections. -## next.config.mjs +## Composition flow -The Next.js config is minimal -- just the `withPayload` wrapper: - -```javascript -import { withPayload } from "@payloadcms/next/withPayload"; - -/** @type {import('next').NextConfig} */ -const nextConfig = {}; - -export default withPayload(nextConfig); ``` +Feature 1 (@repo/blog) + └─ src/integrations/cms/collections/articles.ts + └─ exported as ./cms -`withPayload()` adds the necessary webpack aliases, module resolution, and middleware for Payload to work within Next.js. +Feature 2 (@repo/auth) + └─ src/integrations/cms/collections/users.ts + └─ exported as ./cms -## Auto-Generated Files +Feature 3 (@repo/navigation) + └─ src/integrations/cms/globals/header.ts + └─ exported as ./cms -The files under `src/app/(payload)/` are generated by Payload and should NOT be manually edited: +Core CMS (@repo/core-cms) + └─ src/payload.config.ts + imports all feature /cms exports + calls buildConfig({ collections, globals }) -- **`layout.tsx`** -- Wraps the admin panel with `RootLayout` from `@payloadcms/next/layouts`, injects config and importMap -- **`admin/[[...segments]]/page.tsx`** -- Catch-all route that renders `RootPage` from `@payloadcms/next/views` -- **`admin/[[...segments]]/not-found.tsx`** -- 404 handler using `NotFoundPage` from `@payloadcms/next/views` -- **`importMap.js`** -- Maps Payload component paths for the admin UI - -If you need to regenerate these files, Payload will do so automatically during dev/build. +This app (@repo/cms) + └─ src/app/(payload)/layout.tsx + loads config from @payload-config + Payload CLI auto-generates admin routes +``` ## Type Generation -To regenerate Payload TypeScript types after changing collections/globals: +After adding/modifying collections in any feature's `/cms` folder: ```bash cd apps/cms && pnpm generate:types -# Equivalent to: payload generate:types -# Output goes to: packages/cms-core/src/payload-types.ts +# Regenerates packages/core-cms/src/generated-types.ts ``` ## Dependencies | Dependency | Purpose | |---|---| -| `@repo/cms-core` | All Payload configuration (collections, globals, hooks, config) | -| `@payloadcms/next` | Next.js integration for Payload (withPayload, admin UI views) | -| `@payloadcms/ui` | Payload admin panel React components | +| `@repo/core-cms` | Payload config + buildConfig | +| `@payloadcms/next` | Next.js integration for Payload | | `payload` | Payload CMS core | | `next` | Next.js 15 framework | -| `react` / `react-dom` | React 19 runtime | -| `sharp` | Image processing for Payload uploads | +| `sharp` | Image processing | ## Cross-References -- **ALL CMS configuration:** `packages/cms-core/` -- see `packages/cms-core/AGENTS.md` -- **CMS client for querying data:** `packages/cms-client/` -- see `packages/cms-client/AGENTS.md` -- **Core business logic:** `packages/core/` -- see `packages/core/AGENTS.md` +- **Feature collections:** each feature's `src/integrations/cms/` folder +- **CMS composition:** `packages/core-cms/AGENTS.md` diff --git a/apps/storybook/AGENTS.md b/apps/storybook/AGENTS.md index 9e3ca7f..eb4b6c1 100644 --- a/apps/storybook/AGENTS.md +++ b/apps/storybook/AGENTS.md @@ -1,8 +1,10 @@ -# apps/storybook -- Centralized Storybook +# AGENTS.md — apps/storybook + +Centralized Storybook instance pulling stories from `@repo/core-ui`. Provides visual component development, documentation, and MCP integration for AI agents. ## Purpose -Centralized Storybook instance that pulls and renders all stories from `packages/core-ui`. Provides a visual development environment, component documentation, and MCP integration for AI agents. +Visual testing and documentation hub for the design system. All stories live colocated with their components in `@repo/core-ui`. Storybook serves as the single source of truth for component usage. ## Port: 6006 @@ -14,20 +16,17 @@ pnpm dev --filter @repo/storybook # http://localhost:6006 ### `.storybook/main.ts` -```typescript -import type { StorybookConfig } from "@storybook/react-vite"; +Stories are discovered from `@repo/core-ui`: +```typescript const config: StorybookConfig = { framework: "@storybook/react-vite", stories: ["../../../packages/core-ui/src/**/*.stories.@(ts|tsx)"], addons: ["@storybook/addon-essentials"], - docs: { - autodocs: "tag", - }, + docs: { autodocs: "tag" }, async viteFinal(config) { const { mergeConfig } = await import("vite"); const tailwindPlugin = await import("@tailwindcss/vite"); - return mergeConfig(config, { plugins: [tailwindPlugin.default()], }); @@ -35,14 +34,15 @@ const config: StorybookConfig = { }; ``` -Key configuration details: -- **`stories`** glob reaches into `packages/core-ui/src/` to find all `.stories.tsx` files -- **`viteFinal`** adds the `@tailwindcss/vite` plugin so Tailwind v4 classes render correctly in stories -- **`autodocs: "tag"`** generates documentation pages for stories tagged with `"autodocs"` -- **`@storybook/addon-essentials`** includes Controls, Actions, Backgrounds, Viewport, Docs +Key settings: +- **`stories` glob** — reaches into `packages/core-ui/src/` for all `*.stories.tsx` files +- **`viteFinal`** — adds Tailwind v4 plugin so classes render in Storybook +- **`autodocs: "tag"`** — auto-generates docs for tagged stories ### `.storybook/preview.ts` +Imports global styles: + ```typescript import type { Preview } from "@storybook/react"; import "../../../packages/core-ui/src/styles/globals.css"; @@ -59,77 +59,75 @@ const preview: Preview = { }; ``` -Key details: -- Imports `globals.css` from `@repo/ui` so all Tailwind v4 `@theme` tokens are available -- Control matchers auto-detect color and date props for appropriate editor widgets - ## Story Organization -Stories are organized by Atomic Design level via the `title` field in story metadata. The title determines the sidebar hierarchy in Storybook. +Stories are organized by Atomic Design level via the `title` field: -### Story Title Convention - -| Level | Title format | Example | Sidebar path | -|---|---|---|---| -| Atom | `"Atoms/{ComponentName}"` | `"Atoms/Button"` | Atoms > Button | -| Molecule | `"Molecules/{ComponentName}"` | `"Molecules/FormField"` | Molecules > FormField | -| Organism | `"Organisms/{ComponentName}"` | `"Organisms/DataTable"` | Organisms > DataTable | -| Template | `"Templates/{ComponentName}"` | `"Templates/DashboardLayout"` | Templates > DashboardLayout | - -### Existing Stories - -| Story title | Component | Location in `@repo/core-ui` | +| Level | Title format | Sidebar path | |---|---|---| -| `Atoms/Button` | Button (5 variants: Default, Secondary, Destructive, Outline, Ghost) | `src/atoms/button/button.stories.tsx` | -| `Atoms/Input` | Input (Default, Disabled) | `src/atoms/input/input.stories.tsx` | -| `Molecules/FormField` | FormField (Default, WithDescription, WithError) | `src/molecules/form-field/form-field.stories.tsx` | +| Atom | `"Atoms/{ComponentName}"` | Atoms > ComponentName | +| Molecule | `"Molecules/{ComponentName}"` | Molecules > ComponentName | +| Organism | `"Organisms/{ComponentName}"` | Organisms > ComponentName | +| Template | `"Templates/{ComponentName}"` | Templates > ComponentName | + +Example story file (`packages/core-ui/src/atoms/button/button.stories.tsx`): + +```typescript +import type { Meta, StoryObj } from "@storybook/react"; +import { Button } from "./button"; + +const meta = { + title: "Atoms/Button", + component: Button, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { children: "Click me" }, +}; + +export const Variant: Story = { + args: { children: "Secondary", variant: "secondary" }, +}; +``` ## MCP Integration -When Storybook is running, the MCP (Model Context Protocol) endpoint is available at: +When Storybook runs, the MCP endpoint is available at: ``` http://localhost:6006/mcp ``` -### Available MCP tools: +### Available tools: -- **`list-all-documentation`** -- Lists all documented components and their stories -- **`get-documentation`** -- Gets detailed documentation for a specific component (props, variants, usage) -- **`run-story-tests`** -- Runs visual tests on stories to validate rendering +- **`list-all-documentation`** — Lists all component stories and their properties +- **`get-documentation`** — Gets detailed component info (props, variants, usage examples) +- **`run-story-tests`** — Validates story rendering -### Installing addon-mcp +### Before building new components: -If `@storybook/addon-mcp` is not already installed: - -```bash -npx storybook add @storybook/addon-mcp -``` - -This adds the addon to `.storybook/main.ts` and enables the MCP endpoint. - -### Using MCP in workflows - -Always query MCP before creating new UI components: - -1. **Before creating:** `list-all-documentation` to check if a similar component exists -2. **Before extending:** `get-documentation` to understand existing props and variants -3. **After creating:** `run-story-tests` to validate the new story renders correctly +1. Query `list-all-documentation` to check if a similar component exists +2. Query `get-documentation` to understand existing props and variants +3. After creating: `run-story-tests` to validate ## Dependencies | Dependency | Purpose | |---|---| -| `@repo/core-ui` | Source of all component stories | -| `@storybook/react-vite` | Storybook framework using Vite bundler | -| `@storybook/addon-essentials` | Controls, Actions, Docs, Backgrounds, Viewport | -| `@tailwindcss/vite` | Vite plugin for Tailwind CSS v4 | -| `storybook` | Storybook core CLI and dev server | -| `tailwindcss` | Tailwind CSS v4 engine | -| `vite` | Build tool / dev server | -| `react` / `react-dom` | React 19 runtime | +| `@repo/core-ui` | Component source + stories | +| `@storybook/react-vite` | Storybook with Vite bundler | +| `@storybook/addon-essentials` | Controls, Actions, Docs, Backgrounds | +| `@tailwindcss/vite` | Vite plugin for Tailwind v4 | +| `storybook` | Storybook CLI + dev server | +| `tailwindcss` | Tailwind CSS v4 | +| `vite` | Build tool | +| `react` / `react-dom` | React 19 | ## Cross-References -- **Component source:** `packages/core-ui/` -- see `packages/core-ui/AGENTS.md` -- **Tailwind tokens:** `packages/core-ui/src/styles/globals.css` +- **Component source:** `packages/core-ui/AGENTS.md` +- **Storybook docs:** `.storybook/` folder diff --git a/apps/web-next/AGENTS.md b/apps/web-next/AGENTS.md index 40a334b..a57629b 100644 --- a/apps/web-next/AGENTS.md +++ b/apps/web-next/AGENTS.md @@ -1,8 +1,10 @@ -# apps/web-next -- Next.js 15 Reference App +# AGENTS.md — apps/web-next + +Next.js 15 reference application using App Router. Demonstrates consuming feature packages via tRPC, using `@repo/core-trpc/next` for client setup, and importing UI components from `@repo/core-ui`. ## Purpose -Next.js 15 reference application using App Router. Demonstrates how to consume `@repo/api-client` for tRPC data fetching, `@repo/ui` for components, and `@repo/api` for the tRPC HTTP endpoint. This is a thin app -- business logic lives in `@repo/core`, UI components live in `@repo/ui`. +Thin app showcasing how features work end-to-end. Business logic lives in feature packages (`@repo/auth`, `@repo/blog`, etc.); UI primitives live in `@repo/core-ui`; this app is mostly routes, layouts, and component composition. ## Port: 3000 @@ -10,22 +12,32 @@ Next.js 15 reference application using App Router. Demonstrates how to consume ` pnpm dev --filter @repo/web-next # http://localhost:3000 ``` +Requires `@repo/cms` and PostgreSQL running to fetch live data: + +```bash +docker compose up -d postgres # PostgreSQL on port 5432 +pnpm dev --filter @repo/cms # Payload admin on port 3001 +pnpm dev --filter @repo/web-next # Next.js on port 3000 +``` + ## Key Files | File | Purpose | |---|---| -| `src/app/layout.tsx` | Root layout -- wraps children with ``, sets HTML metadata | -| `src/app/providers.tsx` | Client component that wraps the app with `` | -| `src/app/page.tsx` | Home page (server component by default) | -| `src/app/api/trpc/[trpc]/route.ts` | tRPC HTTP endpoint using the Next.js fetch adapter | +| `src/app/layout.tsx` | Root layout — wraps app with `` from `@repo/core-trpc/next` | +| `src/app/providers.tsx` | Client component for tRPC + React Query setup | +| `src/app/page.tsx` | Home page — navigation + marketing content | +| `src/app/blog/[slug]/page.tsx` | Dynamic blog post route | +| `src/app/api/trpc/[trpc]/route.ts` | tRPC fetch adapter endpoint | +| `e2e/` | Playwright end-to-end tests | -## tRPC Endpoint Setup +## tRPC Setup -The file `src/app/api/trpc/[trpc]/route.ts` creates a catch-all API route that handles all tRPC requests: +The tRPC endpoint handler (in `src/app/api/trpc/[trpc]/route.ts`): ```typescript import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; -import { appRouter } from "@repo/api"; +import { appRouter } from "@repo/core-api"; const handler = (req: Request) => fetchRequestHandler({ @@ -38,149 +50,14 @@ const handler = (req: Request) => export { handler as GET, handler as POST }; ``` -How it works: -1. Next.js catch-all route `[trpc]` matches any path under `/api/trpc/` -2. `fetchRequestHandler` from tRPC's fetch adapter processes the request -3. `appRouter` from `@repo/api` contains all registered routers -4. `createContext` provides the context object to all procedures (currently empty `{}`) -5. Both GET (for queries) and POST (for mutations/batched queries) are exported - -## Provider Setup - -The `` from `@repo/api-client` must wrap the entire app. Since it uses React hooks, it lives in a `"use client"` component: - -```tsx -// src/app/providers.tsx -"use client"; - -import { ApiProvider } from "@repo/api-client"; - -export function Providers({ children }: { children: React.ReactNode }) { - return {children}; -} -``` - -```tsx -// src/app/layout.tsx -import { Providers } from "./providers"; - -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - - ); -} -``` - -## Recipe: Adding a New Page with Data Fetching - -This example adds an `/articles` page that lists published articles. - -### Step 1: Create the page route - -Create `src/app/articles/page.tsx`: - -```tsx -import { ArticleList } from "./article-list"; - -export default function ArticlesPage() { - return ( -
-

Articles

- -
- ); -} -``` - -### Step 2: Create the client component with data fetching - -Create `src/app/articles/article-list.tsx`: - -```tsx -"use client"; - -import { useTRPC } from "@repo/api-client"; -import { useQuery } from "@tanstack/react-query"; -import { Button } from "@repo/ui"; - -export function ArticleList() { - const trpc = useTRPC(); - const { data, isLoading, error } = useQuery( - trpc.content.listArticles.queryOptions({ status: "published", limit: 20 }) - ); - - if (isLoading) return

Loading articles...

; - if (error) return

Error loading articles: {error.message}

; - - return ( -
    - {data?.map((article) => ( -
  • -

    {article.title}

    - -
  • - ))} -
- ); -} -``` - -Key patterns: -- The page component (`page.tsx`) is a server component by default -- no `"use client"` needed -- Data-fetching components that use `useTRPC()` must be client components (`"use client"`) -- Import UI components from `@repo/ui`, never recreate them locally - -## Payload Initialization Pattern (Server-Side Local API) - -For server-side access to Payload CMS data (e.g., in server components, API routes, or server actions), create a Payload client initializer: +The provider (in `src/app/providers.tsx`): ```typescript -// src/lib/payload.ts -import { getPayload } from "payload"; -import config from "@repo/cms-core/src/payload.config"; -import { createPayloadClient, type PayloadClient } from "@repo/cms-client"; +"use client"; +import { TrpcProvider } from "@repo/core-trpc/next"; -let cachedClient: PayloadClient | null = null; - -export async function getPayloadClient(): Promise { - if (cachedClient) return cachedClient; - - const payload = await getPayload({ config }); - cachedClient = createPayloadClient({ mode: "local", payload }); - return cachedClient; -} -``` - -Usage in a server component: - -```tsx -// src/app/articles/page.tsx (server component) -import { getPayloadClient } from "@/lib/payload"; - -export default async function ArticlesPage() { - const client = await getPayloadClient(); - const result = await client.find("articles", { - where: { status: { equals: "published" } }, - sort: "-publishedAt", - limit: 20, - }); - - return ( -
-

Articles

-
    - {result.docs.map((article) => ( -
  • {article.title}
  • - ))} -
-
- ); +export function Providers({ children }: React.ReactNode) { + return {children}; } ``` @@ -188,16 +65,43 @@ export default async function ArticlesPage() { | Dependency | Purpose | |---|---| -| `@repo/api` | `appRouter` for the tRPC HTTP endpoint | -| `@repo/api-client` | `ApiProvider` + `useTRPC()` for client-side data fetching | -| `@repo/ui` | Shared UI components (Button, Input, Label, FormField, etc.) | -| `next` | Next.js 15 framework with App Router | -| `react` / `react-dom` | React 19 runtime | +| `@repo/core-api` | `appRouter` for tRPC endpoint | +| `@repo/core-trpc/next` | Next.js tRPC client + provider | +| `@repo/core-ui` | Design system components | +| `@repo/auth`, `@repo/blog`, etc. | Feature packages (indirectly via core-api) | +| `next` | Next.js 15 framework | +| `@trpc/server` | tRPC server (fetch adapter) | + +## Test conventions + +- Unit tests colocated: `src/app/blog/article-list.test.tsx` +- Vitest environment: `jsdom` +- e2e tests in `e2e/` folder: `*.spec.ts` +- Run: `pnpm test --filter @repo/web-next` (units) or `pnpm test:e2e` (Playwright) + +## E2E Test Setup + +Playwright config in `e2e/playwright.config.ts`: + +```typescript +import { defineConfig, devices } from "@playwright/test"; + +export default defineConfig({ + testDir: "./e2e", + webServer: { + command: "pnpm dev", + port: 3000, + reuseExistingServer: !process.env.CI, + }, + use: { ...devices["Desktop Chrome"].use }, +}); +``` + +Run: `pnpm test:e2e` starts the dev server and runs all `.spec.ts` files. ## Cross-References -- **tRPC routers:** `packages/api/` -- see `packages/api/AGENTS.md` -- **tRPC client/hooks:** `packages/api-client/` -- see `packages/api-client/AGENTS.md` -- **UI components:** `packages/ui/` -- see `packages/ui/AGENTS.md` -- **CMS client:** `packages/cms-client/` -- see `packages/cms-client/AGENTS.md` -- **CMS config:** `packages/cms-core/` -- see `packages/cms-core/AGENTS.md` +- **Feature packages:** `packages/{auth,blog,media,marketing-pages,navigation}/` +- **tRPC composition:** `packages/core-api/AGENTS.md` +- **tRPC client + provider:** `packages/core-trpc/AGENTS.md` +- **UI components:** `packages/core-ui/AGENTS.md` diff --git a/apps/web-tanstack/AGENTS.md b/apps/web-tanstack/AGENTS.md index d30f52e..65df905 100644 --- a/apps/web-tanstack/AGENTS.md +++ b/apps/web-tanstack/AGENTS.md @@ -1,8 +1,10 @@ -# apps/web-tanstack -- TanStack Start Reference App +# AGENTS.md — apps/web-tanstack + +TanStack Start reference application using TanStack Router with file-based routing. Demonstrates that feature packages are framework-agnostic by consuming the same features as Next.js (via `@repo/core-api`) using TanStack's architecture instead. ## Purpose -TanStack Start reference application using TanStack Router with file-based routing. Demonstrates how to consume `@repo/api-client` for tRPC data fetching and `@repo/ui` for components. Like `apps/web-next`, this is a thin app -- business logic lives in `@repo/core`, UI components live in `@repo/ui`. +Proof that features are framework-portable. This app consumes the exact same feature packages and tRPC routers as `apps/web-next`, but through TanStack Start's server/client architecture instead of Next.js App Router. ## Port: 3002 @@ -10,149 +12,107 @@ TanStack Start reference application using TanStack Router with file-based routi pnpm dev --filter @repo/web-tanstack # http://localhost:3002 ``` +Requires tRPC endpoint (from `apps/web-next` or another backend): + +```bash +pnpm dev --filter @repo/web-next # Serves tRPC at http://localhost:3000/api/trpc +pnpm dev --filter @repo/web-tanstack # http://localhost:3002 +``` + ## Key Files | File | Purpose | |---|---| -| `src/routes/__root.tsx` | Root layout -- creates the root route, wraps with `` and `` | -| `src/routes/index.tsx` | Home page route (`/`) | +| `src/routes/__root.tsx` | Root layout — wraps all routes with `` from `@repo/core-trpc/tanstack` | +| `src/routes/index.tsx` | Home page (`/`) | +| `src/routes/blog/index.tsx` | Blog listing (`/blog`) | +| `src/routes/blog/$slug.tsx` | Dynamic blog post (`/blog/:slug`) | +| `e2e/` | Playwright end-to-end tests | ## File-Based Routing -TanStack Router uses file-based routing where file paths in `src/routes/` map directly to URL paths: +TanStack Router uses file-based routing where file paths map directly to URL routes: -| File | URL | Description | -|---|---|---| -| `src/routes/__root.tsx` | (all routes) | Root layout, wraps all child routes | -| `src/routes/index.tsx` | `/` | Home page | -| `src/routes/about.tsx` | `/about` | Static page | -| `src/routes/articles/index.tsx` | `/articles` | Article listing | -| `src/routes/articles/$id.tsx` | `/articles/:id` | Single article (dynamic param) | +| File | URL | +|---|---| +| `src/routes/__root.tsx` | Root (all routes) | +| `src/routes/index.tsx` | `/` | +| `src/routes/blog/index.tsx` | `/blog` | +| `src/routes/blog/$slug.tsx` | `/blog/:slug` | -### Naming conventions: -- `__root.tsx` -- special root layout file, always wraps all routes -- `index.tsx` -- index route for its directory (e.g., `/articles/index.tsx` matches `/articles`) -- `$paramName.tsx` -- dynamic route segment (e.g., `$id.tsx` captures `:id`) -- Nested folders create nested URL segments +Naming conventions: +- `__root.tsx` — special root layout +- `index.tsx` — index route for its directory +- `$paramName.tsx` — dynamic segment -## Provider Setup +## tRPC Setup -The `` wraps the entire app in `__root.tsx`: +The root route wraps with ``: -```tsx +```typescript // src/routes/__root.tsx -import { Outlet, createRootRoute } from "@tanstack/react-router"; -import { ApiProvider } from "@repo/api-client"; +import { TrpcProvider } from "@repo/core-trpc/tanstack"; +import { Outlet } from "@tanstack/react-router"; export const Route = createRootRoute({ component: () => ( - + - + ), }); ``` -Note: The `trpcUrl` points to the Next.js app's tRPC endpoint at `http://localhost:3000/api/trpc`. In production, this should be configured via environment variables. +Note: `trpcUrl` must point to a running tRPC endpoint (e.g., from `apps/web-next`). -## Recipe: Adding a New Route with Data Fetching +## Fetching data in routes -This example adds an `/articles` route that lists published articles. - -### Step 1: Create the route file - -Create `src/routes/articles/index.tsx`: - -```tsx +```typescript +// src/routes/blog/$slug.tsx import { createFileRoute } from "@tanstack/react-router"; -import { useTRPC } from "@repo/api-client"; -import { useQuery } from "@tanstack/react-query"; -import { Button } from "@repo/ui"; - -export const Route = createFileRoute("/articles/")({ - component: ArticlesPage, -}); - -function ArticlesPage() { - const trpc = useTRPC(); - const { data, isLoading, error } = useQuery( - trpc.content.listArticles.queryOptions({ status: "published", limit: 20 }) - ); - - if (isLoading) return

Loading articles...

; - if (error) return

Error: {error.message}

; - - return ( -
-

Articles

-
    - {data?.map((article) => ( -
  • -

    {article.title}

    - -
  • - ))} -
-
- ); -} -``` - -### Step 2: Add a dynamic route for individual articles - -Create `src/routes/articles/$id.tsx`: - -```tsx -import { createFileRoute } from "@tanstack/react-router"; -import { useTRPC } from "@repo/api-client"; +import { useTRPC } from "@repo/core-trpc"; import { useQuery } from "@tanstack/react-query"; -export const Route = createFileRoute("/articles/$id")({ - component: ArticlePage, +export const Route = createFileRoute("/blog/$slug")({ + component: BlogPostPage, }); -function ArticlePage() { - const { id } = Route.useParams(); +function BlogPostPage() { + const { slug } = Route.useParams(); const trpc = useTRPC(); - - // Use the article ID from the URL parameter - // (Assuming a getArticle procedure exists on the content router) + const { data, isLoading } = useQuery( - trpc.content.listArticles.queryOptions({ limit: 1 }) + trpc.blog.getBySlug.queryOptions({ slug }) ); if (isLoading) return

Loading...

; - - return ( -
-

Article {id}

-
- ); + return
{data?.title}
; } ``` -Key patterns: -- Every route file exports a `Route` created via `createFileRoute(path)(...)` -- The `component` property defines the React component for that route -- Use `Route.useParams()` to access dynamic parameters (e.g., `$id`) -- Data fetching uses the same `useTRPC()` + `useQuery()` pattern as Next.js -- Import UI components from `@repo/ui`, never recreate them locally - ## Dependencies | Dependency | Purpose | |---|---| -| `@repo/api` | `AppRouter` type (transitive via `@repo/api-client`) | -| `@repo/api-client` | `ApiProvider` + `useTRPC()` for client-side data fetching | -| `@repo/ui` | Shared UI components | -| `@tanstack/react-router` | TanStack Router for file-based routing | +| `@repo/core-api` | AppRouter type (via core-trpc) | +| `@repo/core-trpc/tanstack` | TanStack tRPC client + provider | +| `@repo/core-ui` | Design system components | +| `@tanstack/react-router` | File-based routing | +| `@tanstack/react-query` | Data fetching + caching | | `react` / `react-dom` | React 19 runtime | +## Test conventions + +- e2e tests in `e2e/` folder: `*.spec.ts` +- Playwright config in `e2e/playwright.config.ts` +- Run: `pnpm test:e2e` (both Next.js and TanStack) + +Parallel to `apps/web-next` e2e: validates that features work across frameworks. + ## Cross-References -- **tRPC routers:** `packages/api/` -- see `packages/api/AGENTS.md` -- **tRPC client/hooks:** `packages/api-client/` -- see `packages/api-client/AGENTS.md` -- **UI components:** `packages/ui/` -- see `packages/ui/AGENTS.md` -- **Next.js app (serves the tRPC endpoint):** `apps/web-next/` -- see `apps/web-next/AGENTS.md` +- **Feature packages:** `packages/{auth,blog,media,marketing-pages,navigation}/` +- **tRPC composition:** `packages/core-api/AGENTS.md` +- **tRPC client + provider:** `packages/core-trpc/AGENTS.md` +- **UI components:** `packages/core-ui/AGENTS.md` +- **Next.js app (serves tRPC):** `apps/web-next/AGENTS.md`