diff --git a/.agents/README.md b/.agents/README.md deleted file mode 100644 index 208dbff25..000000000 --- a/.agents/README.md +++ /dev/null @@ -1,74 +0,0 @@ -# Agent Rules Directory - -This directory contains conditional, service-specific rules that supplement the main [AGENTS.md](../AGENTS.md) file. - -## Structure - -``` -.agents/ -├── README.md # This file -└── rules/ - ├── cursor-specific.mdc # Tool-specific guidance for Cursor - └── dashboard/ # Dashboard (apps/dashboard) specific rules - └── design.mdc # Design system tokens, components, a11y -``` - -## How Rules Are Loaded - -### Cursor - -- Symlinked via `.cursor/rules/` → `.agents/rules/` -- Cursor loads all `.mdc` files based on glob patterns - -### Claude Code - -- Symlinked via `.claude/rules/` → `.agents/rules/` -- Claude Code loads rules contextually - -## Adding New Rules - -### When to add a rule here vs AGENTS.md - -| Belongs in AGENTS.md | Belongs in `.agents/rules/` | -| ------------------------ | ---------------------------- | -| ✅ Architecture overview | ✅ Detailed design tokens | -| ✅ Commands & workflows | ✅ Component import maps | -| ✅ File conventions | ✅ Service-specific patterns | -| ✅ Core code style | ✅ Tool-specific hints | -| ✅ Git workflow | ✅ Glob-conditional guidance | - -### Creating a new service-specific rule - -1. Create directory: `.agents/rules//` -2. Add `.mdc` files with frontmatter: - -```markdown ---- -description: Brief description -globs: - - "apps//**/*.{ts,tsx}" -alwaysApply: false ---- - -# Your rule content here -``` - -3. Symlink for both tools (if needed): - -```bash -ln -sf ../../../.agents/rules/ .cursor/rules/ -ln -sf ../../../.agents/rules/ .claude/rules/ -``` - -## Current Rules - -### cursor-specific.mdc - -- **Applies**: Always -- **Purpose**: Tool-specific hints for Cursor (e.g., when to use sequential thinking MCP) - -### dashboard/design.mdc - -- **Applies**: When editing `apps/dashboard/**/*.{ts,tsx,mdx}` -- **Purpose**: Design system tokens, component patterns, accessibility requirements -- **Why separate**: 200+ lines of detailed reference that would bloat AGENTS.md diff --git a/.agents/mcp.json b/.agents/mcp.json deleted file mode 100644 index cf1de173f..000000000 --- a/.agents/mcp.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "mcpServers": { - "context7": { - "command": "npx", - "args": ["-y", "@upstash/context7-mcp"] - }, - "playwright": { - "command": "npx", - "args": ["@playwright/mcp@latest"] - }, - "figma": { - "url": "https://mcp.figma.com/mcp", - "type": "http" - }, - "sequential-thinking": { - "command": "npx", - "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"] - } - } -} diff --git a/.agents/rules/clickup/clickup.mdc b/.agents/rules/clickup/clickup.mdc deleted file mode 100644 index ff8917de1..000000000 --- a/.agents/rules/clickup/clickup.mdc +++ /dev/null @@ -1,64 +0,0 @@ -# ClickUp Configuration - -## Default Settings - -- **Default List ID**: `90132341641` -- **Workspace**: Auto-detected from authenticated workspace - -## Usage Guidelines - -### Creating Tasks - -When creating tasks via ClickUp MCP, always use the default list ID unless explicitly asked otherwise: - -```typescript -// Use this list_id by default -list_id: "90132341641" -``` - -### Task Creation Best Practices - -1. **Always specify the list_id** - Use `90132341641` to avoid search overhead -2. **Ask for assignees** - Don't automatically assign tasks unless explicitly requested -3. **Use meaningful task names** - Keep them concise but descriptive -4. **Include markdown descriptions** - Use `markdown_description` for rich formatting -5. **Set priority appropriately** - Use 'urgent', 'high', 'normal', or 'low' based on context - -### Example Task Creation - -```typescript -clickup_create_task({ - name: "Implement user authentication flow", - list_id: "90132341641", - markdown_description: "## Requirements\n- Add login/signup forms\n- Implement JWT auth\n- Add protected routes", - priority: "high", - assignees: ["user@example.com"], // Only if specified - tags: ["backend", "auth"] -}) -``` - -### When to Ask First - -Before creating tasks, ask the user for: -- Task name and description -- Priority level -- Assignees (if any) -- Tags to apply -- Due date (if applicable) - -### When to Use Search - -Only search for list IDs when: -- User explicitly requests a different list -- User asks to create task in a specific project/folder -- Default list ID fails (e.g., permissions issue) - -## Quick Reference - -| Operation | List ID Required | Use Default | -|-----------|------------------|-------------| -| Create task | Yes | `90132341641` | -| Update task | No (uses task_id) | N/A | -| Get task | No (uses task_id) | N/A | -| List tasks | Optional | `90132341641` | -| Add comment | No (uses task_id) | N/A | diff --git a/.claude/mcp.json b/.claude/mcp.json deleted file mode 120000 index d5dbdcbe4..000000000 --- a/.claude/mcp.json +++ /dev/null @@ -1 +0,0 @@ -../.agents/mcp.json \ No newline at end of file diff --git a/.claude/mcp.json b/.claude/mcp.json new file mode 100644 index 000000000..cf1de173f --- /dev/null +++ b/.claude/mcp.json @@ -0,0 +1,20 @@ +{ + "mcpServers": { + "context7": { + "command": "npx", + "args": ["-y", "@upstash/context7-mcp"] + }, + "playwright": { + "command": "npx", + "args": ["@playwright/mcp@latest"] + }, + "figma": { + "url": "https://mcp.figma.com/mcp", + "type": "http" + }, + "sequential-thinking": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"] + } + } +} diff --git a/.claude/rules/clickup/clickup.mdc b/.claude/rules/clickup/clickup.mdc deleted file mode 120000 index 72bd809ce..000000000 --- a/.claude/rules/clickup/clickup.mdc +++ /dev/null @@ -1 +0,0 @@ -../../../.agents/rules/clickup/clickup.mdc \ No newline at end of file diff --git a/.claude/rules/clickup/clickup.mdc b/.claude/rules/clickup/clickup.mdc new file mode 100644 index 000000000..ff8917de1 --- /dev/null +++ b/.claude/rules/clickup/clickup.mdc @@ -0,0 +1,64 @@ +# ClickUp Configuration + +## Default Settings + +- **Default List ID**: `90132341641` +- **Workspace**: Auto-detected from authenticated workspace + +## Usage Guidelines + +### Creating Tasks + +When creating tasks via ClickUp MCP, always use the default list ID unless explicitly asked otherwise: + +```typescript +// Use this list_id by default +list_id: "90132341641" +``` + +### Task Creation Best Practices + +1. **Always specify the list_id** - Use `90132341641` to avoid search overhead +2. **Ask for assignees** - Don't automatically assign tasks unless explicitly requested +3. **Use meaningful task names** - Keep them concise but descriptive +4. **Include markdown descriptions** - Use `markdown_description` for rich formatting +5. **Set priority appropriately** - Use 'urgent', 'high', 'normal', or 'low' based on context + +### Example Task Creation + +```typescript +clickup_create_task({ + name: "Implement user authentication flow", + list_id: "90132341641", + markdown_description: "## Requirements\n- Add login/signup forms\n- Implement JWT auth\n- Add protected routes", + priority: "high", + assignees: ["user@example.com"], // Only if specified + tags: ["backend", "auth"] +}) +``` + +### When to Ask First + +Before creating tasks, ask the user for: +- Task name and description +- Priority level +- Assignees (if any) +- Tags to apply +- Due date (if applicable) + +### When to Use Search + +Only search for list IDs when: +- User explicitly requests a different list +- User asks to create task in a specific project/folder +- Default list ID fails (e.g., permissions issue) + +## Quick Reference + +| Operation | List ID Required | Use Default | +|-----------|------------------|-------------| +| Create task | Yes | `90132341641` | +| Update task | No (uses task_id) | N/A | +| Get task | No (uses task_id) | N/A | +| List tasks | Optional | `90132341641` | +| Add comment | No (uses task_id) | N/A | diff --git a/.agents/rules/cursor-specific.mdc b/.claude/rules/cursor-specific.mdc similarity index 100% rename from .agents/rules/cursor-specific.mdc rename to .claude/rules/cursor-specific.mdc diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md new file mode 100644 index 000000000..42ef062f2 --- /dev/null +++ b/.claude/rules/testing.md @@ -0,0 +1,289 @@ +# Testing Guidelines + +Follow these rules when writing tests for the Anticapture project. + +--- + +## Rule 1: Test Pyramid + +Follow the test pyramid strategy with three test types. +**Target ratio:** ~70% unit, ~20% integration, ~10% E2E + +1. **Unit Tests** (majority) + - Fast, cheap, isolated + - Test business logic in services and utilities + - Mock external dependencies (database, APIs) + +2. **Integration Tests** (moderate) + - Test component interactions + - Verify API endpoints with real HTTP calls + - May use test database + +3. **E2E Tests** (few) + - Test critical user flows end-to-end + - Slow and expensive to maintain + - Reserve for high-value scenarios only + +--- + +## Rule 2: Test Doubles Strategy + +Prefer **stubs** and **fakes** over mocks to avoid brittle tests. + +| Type | Use When | Example | +| -------- | ---------------------------------------------- | ----------------------------------- | +| **Stub** | Need fixed return values | `{ findAll: () => [mockData] }` | +| **Fake** | Need working simplified implementation | `InMemoryRepository` | +| **Mock** | Need to verify a call was made (use sparingly) | `jest.fn()` with `toHaveBeenCalled` | + +### Why? + +- **Mocks verify implementation** → tests break on refactor +- **Stubs/Fakes verify behavior** → tests survive refactor + +### Example + +```typescript +// ❌ Avoid: mock that verifies implementation +expect(repository.findById).toHaveBeenCalledWith("123"); + +// ✅ Prefer: stub that enables behavior testing +const stub = { findById: () => mockAccount }; +const result = await service.getAccount("123"); +expect(result.name).toBe("vitalik.eth"); +``` + +Exception: Use mocks when the call itself IS the behavior (e.g., verifying an event was emitted, an email was sent). + +--- + +## Rule 3: Arrange-Act-Assert (AAA) Pattern + +Structure every test using AAA for readability and consistency. **Do not write `// Arrange`, `// Act`, `// Assert` comments** — use blank lines to separate sections visually. + +### Structure + +```typescript +it("should calculate delegation percentage", async () => { + const mockData = [createMockRow({ delegated: 100n, total: 1000n })]; + stubRepository.getDelegationPercentage.mockResolvedValue(mockData); + + const result = await service.execute({ dao: "uni" }); + + expect(result[0].percentage).toBe(10); +}); +``` + +--- + +## Rule 4: Test Coverage Policy + +Write tests for all new business logic. There is no minimum coverage enforced in CI. + +### What must have tests: + +- Services and their business rules +- Utility functions (`lib/`) +- Calculations (voting power, percentages, etc.) +- Data transformations (mappers) + +### What may not have tests: + +- Infrastructure/boilerplate code +- UI components (nice-to-have, not required) +- Configuration and wiring + +> **Philosophy:** High coverage doesn't mean well-tested code. 100% coverage with bad tests is worse than 60% coverage with meaningful tests. + +--- + +## Rule 5: Test Happy Paths and Break Your Code + +Cover the **happy path** first, then actively try to break your code. + +### Mindset + +Don't just prove the code works — **try to break it**. Think like a malicious user or an unstable system. + +### Common edge cases to consider: + +| Category | Examples | +| ------------------------ | ---------------------------------------------------- | +| **Empty values** | `null`, `undefined`, `[]`, `""`, `{}` | +| **Zeros and boundaries** | `0`, `1`, `-1`, `MAX_INT`, overflow | +| **Divisions** | Division by zero, percentages > 100% | +| **Dates** | Future timestamps, distant past, midnight edge cases | +| **Strings** | Unicode, whitespace, special characters | +| **Concurrency** | Missing data, unexpected order | + +### Example + +```typescript +// ❌ Happy path only +it("should calculate percentage", () => { + expect(calcPercentage(50, 100)).toBe(50); +}); + +// ✅ Happy path + trying to break it +it("should calculate percentage", () => { + expect(calcPercentage(50, 100)).toBe(50); +}); + +it("should handle division by zero", () => { + expect(calcPercentage(50, 0)).toBe(0); // or throw? +}); + +it("should handle when value exceeds total", () => { + expect(calcPercentage(150, 100)).toBe(100); // cap? or 150? +}); +``` + +--- + +## Rule 6: Deterministic Tests + +Write tests that produce the **same result every time**, in any environment, in any order. + +### Never depend on: + +- Execution order between tests +- System date/time (`Date.now()`) +- Data generated by other tests +- Randomness without a fixed seed +- Shared global state + +### How to handle dates + +```typescript +// ❌ Non-deterministic +const result = service.getRecentItems(); // uses Date.now() internally + +// ✅ Deterministic - mock the time +beforeEach(() => { + jest.useFakeTimers(); + jest.setSystemTime(new Date("2025-01-15T00:00:00Z")); +}); + +afterEach(() => { + jest.useRealTimers(); +}); +``` + +### How to ensure isolation + +```typescript +// ✅ Each test creates its own data +it("should find account by id", () => { + const account = createAccount({ id: "test-123" }); + // ... +}); + +// ❌ Depends on data created in another test +it("should find the account", () => { + const account = repository.findById("test-123"); // created where? +}); +``` + +--- + +## Useful Tools + +### 1. Testing HTTP (Hono) + +Use Hono's native test client: + +```ts +import { testClient } from "hono/testing"; +import { app } from "../app"; +const client = testClient(app); + +it("should return proposals", async () => { + const res = await client.proposals.$get({ + query: { dao: "uni", limit: "10" }, + }); + expect(res.status).toBe(200); + const data = await res.json(); + expect(data.proposals).toHaveLength(10); +}); +``` + +### 2. Test Database + +Use Testcontainers for real PostgreSQL in Docker: + +```ts +import { PostgreSqlContainer } from "@testcontainers/postgresql"; + +let container: PostgreSqlContainer; + +beforeAll(async () => { + container = await new PostgreSqlContainer().start(); + process.env.DATABASE_URL = container.getConnectionUri(); + await runMigrations(); +}); + +afterAll(async () => { + await container.stop(); +}); +``` + +### 3. External APIs + +Use MSW (Mock Service Worker) to intercept HTTP requests: + +```ts +import { setupServer } from "msw/node"; +import { http, HttpResponse } from "msw"; + +const server = setupServer( + http.get("https://api.coingecko.com/api/v3/simple/price", () => { + return HttpResponse.json({ + uniswap: { usd: 7.5 }, + }); + }), +); + +beforeAll(() => server.listen()); +afterEach(() => server.resetHandlers()); +afterAll(() => server.close()); +``` + +--- + +## Important Patterns + +### 1. Test Data Builders / Factories + +```ts +// factories/proposal.ts +export function createProposal(overrides?: Partial): Proposal { + return { + id: randomId(), + dao: "uni", + title: "Test Proposal", + status: "active", + createdAt: new Date(), + ...overrides, + }; +} + +// In the test +const proposal = createProposal({ status: "executed" }); +await db.insert(proposals).values(proposal); +``` + +### 2. Database Seeding + +```ts +async function seedTestData() { + const account = createAccount({ address: "0x123" }); + const proposal = createProposal({ dao: "uni" }); + const vote = createVote({ proposalId: proposal.id, voter: account.address }); + + await db.insert(accounts).values(account); + await db.insert(proposals).values(proposal); + await db.insert(votes).values(vote); + + return { account, proposal, vote }; +} +``` diff --git a/.claude/skills/central-station b/.claude/skills/central-station deleted file mode 120000 index bfd02112d..000000000 --- a/.claude/skills/central-station +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/central-station \ No newline at end of file diff --git a/.agents/skills/central-station/SKILL.md b/.claude/skills/central-station/SKILL.md similarity index 100% rename from .agents/skills/central-station/SKILL.md rename to .claude/skills/central-station/SKILL.md diff --git a/.agents/skills/central-station/references/environment-config.md b/.claude/skills/central-station/references/environment-config.md similarity index 100% rename from .agents/skills/central-station/references/environment-config.md rename to .claude/skills/central-station/references/environment-config.md diff --git a/.agents/skills/central-station/references/monorepo.md b/.claude/skills/central-station/references/monorepo.md similarity index 100% rename from .agents/skills/central-station/references/monorepo.md rename to .claude/skills/central-station/references/monorepo.md diff --git a/.agents/skills/central-station/references/railpack.md b/.claude/skills/central-station/references/railpack.md similarity index 100% rename from .agents/skills/central-station/references/railpack.md rename to .claude/skills/central-station/references/railpack.md diff --git a/.agents/skills/central-station/references/variables.md b/.claude/skills/central-station/references/variables.md similarity index 100% rename from .agents/skills/central-station/references/variables.md rename to .claude/skills/central-station/references/variables.md diff --git a/.claude/skills/database b/.claude/skills/database deleted file mode 120000 index c50cf14f5..000000000 --- a/.claude/skills/database +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/database \ No newline at end of file diff --git a/.agents/skills/database/SKILL.md b/.claude/skills/database/SKILL.md similarity index 100% rename from .agents/skills/database/SKILL.md rename to .claude/skills/database/SKILL.md diff --git a/.agents/skills/database/references/environment-config.md b/.claude/skills/database/references/environment-config.md similarity index 100% rename from .agents/skills/database/references/environment-config.md rename to .claude/skills/database/references/environment-config.md diff --git a/.agents/skills/database/references/monorepo.md b/.claude/skills/database/references/monorepo.md similarity index 100% rename from .agents/skills/database/references/monorepo.md rename to .claude/skills/database/references/monorepo.md diff --git a/.agents/skills/database/references/railpack.md b/.claude/skills/database/references/railpack.md similarity index 100% rename from .agents/skills/database/references/railpack.md rename to .claude/skills/database/references/railpack.md diff --git a/.agents/skills/database/references/variables.md b/.claude/skills/database/references/variables.md similarity index 100% rename from .agents/skills/database/references/variables.md rename to .claude/skills/database/references/variables.md diff --git a/.agents/skills/database/scripts/railway-api.sh b/.claude/skills/database/scripts/railway-api.sh similarity index 100% rename from .agents/skills/database/scripts/railway-api.sh rename to .claude/skills/database/scripts/railway-api.sh diff --git a/.claude/skills/deploy b/.claude/skills/deploy deleted file mode 120000 index 9714c3220..000000000 --- a/.claude/skills/deploy +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/deploy \ No newline at end of file diff --git a/.agents/skills/deploy/SKILL.md b/.claude/skills/deploy/SKILL.md similarity index 100% rename from .agents/skills/deploy/SKILL.md rename to .claude/skills/deploy/SKILL.md diff --git a/.agents/skills/deploy/references/environment-config.md b/.claude/skills/deploy/references/environment-config.md similarity index 100% rename from .agents/skills/deploy/references/environment-config.md rename to .claude/skills/deploy/references/environment-config.md diff --git a/.agents/skills/deploy/references/monorepo.md b/.claude/skills/deploy/references/monorepo.md similarity index 100% rename from .agents/skills/deploy/references/monorepo.md rename to .claude/skills/deploy/references/monorepo.md diff --git a/.agents/skills/deploy/references/railpack.md b/.claude/skills/deploy/references/railpack.md similarity index 100% rename from .agents/skills/deploy/references/railpack.md rename to .claude/skills/deploy/references/railpack.md diff --git a/.agents/skills/deploy/references/variables.md b/.claude/skills/deploy/references/variables.md similarity index 100% rename from .agents/skills/deploy/references/variables.md rename to .claude/skills/deploy/references/variables.md diff --git a/.claude/skills/deployment b/.claude/skills/deployment deleted file mode 120000 index 7baf7cbf5..000000000 --- a/.claude/skills/deployment +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/deployment \ No newline at end of file diff --git a/.agents/skills/deployment/SKILL.md b/.claude/skills/deployment/SKILL.md similarity index 100% rename from .agents/skills/deployment/SKILL.md rename to .claude/skills/deployment/SKILL.md diff --git a/.agents/skills/deployment/references/environment-config.md b/.claude/skills/deployment/references/environment-config.md similarity index 100% rename from .agents/skills/deployment/references/environment-config.md rename to .claude/skills/deployment/references/environment-config.md diff --git a/.agents/skills/deployment/references/monorepo.md b/.claude/skills/deployment/references/monorepo.md similarity index 100% rename from .agents/skills/deployment/references/monorepo.md rename to .claude/skills/deployment/references/monorepo.md diff --git a/.agents/skills/deployment/references/railpack.md b/.claude/skills/deployment/references/railpack.md similarity index 100% rename from .agents/skills/deployment/references/railpack.md rename to .claude/skills/deployment/references/railpack.md diff --git a/.agents/skills/deployment/references/variables.md b/.claude/skills/deployment/references/variables.md similarity index 100% rename from .agents/skills/deployment/references/variables.md rename to .claude/skills/deployment/references/variables.md diff --git a/.claude/skills/domain b/.claude/skills/domain deleted file mode 120000 index 200d1eb58..000000000 --- a/.claude/skills/domain +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/domain \ No newline at end of file diff --git a/.agents/skills/domain/SKILL.md b/.claude/skills/domain/SKILL.md similarity index 100% rename from .agents/skills/domain/SKILL.md rename to .claude/skills/domain/SKILL.md diff --git a/.agents/skills/domain/references/environment-config.md b/.claude/skills/domain/references/environment-config.md similarity index 100% rename from .agents/skills/domain/references/environment-config.md rename to .claude/skills/domain/references/environment-config.md diff --git a/.agents/skills/domain/references/monorepo.md b/.claude/skills/domain/references/monorepo.md similarity index 100% rename from .agents/skills/domain/references/monorepo.md rename to .claude/skills/domain/references/monorepo.md diff --git a/.agents/skills/domain/references/railpack.md b/.claude/skills/domain/references/railpack.md similarity index 100% rename from .agents/skills/domain/references/railpack.md rename to .claude/skills/domain/references/railpack.md diff --git a/.agents/skills/domain/references/variables.md b/.claude/skills/domain/references/variables.md similarity index 100% rename from .agents/skills/domain/references/variables.md rename to .claude/skills/domain/references/variables.md diff --git a/.claude/skills/environment b/.claude/skills/environment deleted file mode 120000 index d57c0762e..000000000 --- a/.claude/skills/environment +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/environment \ No newline at end of file diff --git a/.agents/skills/environment/SKILL.md b/.claude/skills/environment/SKILL.md similarity index 100% rename from .agents/skills/environment/SKILL.md rename to .claude/skills/environment/SKILL.md diff --git a/.agents/skills/environment/references/environment-config.md b/.claude/skills/environment/references/environment-config.md similarity index 100% rename from .agents/skills/environment/references/environment-config.md rename to .claude/skills/environment/references/environment-config.md diff --git a/.agents/skills/environment/references/monorepo.md b/.claude/skills/environment/references/monorepo.md similarity index 100% rename from .agents/skills/environment/references/monorepo.md rename to .claude/skills/environment/references/monorepo.md diff --git a/.agents/skills/environment/references/railpack.md b/.claude/skills/environment/references/railpack.md similarity index 100% rename from .agents/skills/environment/references/railpack.md rename to .claude/skills/environment/references/railpack.md diff --git a/.agents/skills/environment/references/variables.md b/.claude/skills/environment/references/variables.md similarity index 100% rename from .agents/skills/environment/references/variables.md rename to .claude/skills/environment/references/variables.md diff --git a/.claude/skills/metrics b/.claude/skills/metrics deleted file mode 120000 index 40c171f4a..000000000 --- a/.claude/skills/metrics +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/metrics \ No newline at end of file diff --git a/.agents/skills/metrics/SKILL.md b/.claude/skills/metrics/SKILL.md similarity index 100% rename from .agents/skills/metrics/SKILL.md rename to .claude/skills/metrics/SKILL.md diff --git a/.agents/skills/metrics/references/environment-config.md b/.claude/skills/metrics/references/environment-config.md similarity index 100% rename from .agents/skills/metrics/references/environment-config.md rename to .claude/skills/metrics/references/environment-config.md diff --git a/.agents/skills/metrics/references/monorepo.md b/.claude/skills/metrics/references/monorepo.md similarity index 100% rename from .agents/skills/metrics/references/monorepo.md rename to .claude/skills/metrics/references/monorepo.md diff --git a/.agents/skills/metrics/references/railpack.md b/.claude/skills/metrics/references/railpack.md similarity index 100% rename from .agents/skills/metrics/references/railpack.md rename to .claude/skills/metrics/references/railpack.md diff --git a/.agents/skills/metrics/references/variables.md b/.claude/skills/metrics/references/variables.md similarity index 100% rename from .agents/skills/metrics/references/variables.md rename to .claude/skills/metrics/references/variables.md diff --git a/.agents/skills/metrics/scripts/railway-api.sh b/.claude/skills/metrics/scripts/railway-api.sh similarity index 100% rename from .agents/skills/metrics/scripts/railway-api.sh rename to .claude/skills/metrics/scripts/railway-api.sh diff --git a/.claude/skills/new b/.claude/skills/new deleted file mode 120000 index 20a10b8bf..000000000 --- a/.claude/skills/new +++ /dev/null @@ -1 +0,0 @@ -../../.agents/skills/new \ No newline at end of file diff --git a/.agents/skills/new/SKILL.md b/.claude/skills/new/SKILL.md similarity index 100% rename from .agents/skills/new/SKILL.md rename to .claude/skills/new/SKILL.md diff --git a/.agents/skills/new/references/environment-config.md b/.claude/skills/new/references/environment-config.md similarity index 100% rename from .agents/skills/new/references/environment-config.md rename to .claude/skills/new/references/environment-config.md diff --git a/.agents/skills/new/references/monorepo.md b/.claude/skills/new/references/monorepo.md similarity index 100% rename from .agents/skills/new/references/monorepo.md rename to .claude/skills/new/references/monorepo.md diff --git a/.agents/skills/new/references/railpack.md b/.claude/skills/new/references/railpack.md similarity index 100% rename from .agents/skills/new/references/railpack.md rename to .claude/skills/new/references/railpack.md diff --git a/.agents/skills/new/references/variables.md b/.claude/skills/new/references/variables.md similarity index 100% rename from .agents/skills/new/references/variables.md rename to .claude/skills/new/references/variables.md diff --git a/.claude/skills/next-best-practices/SKILL.md b/.claude/skills/next-best-practices/SKILL.md new file mode 100644 index 000000000..3d5e68693 --- /dev/null +++ b/.claude/skills/next-best-practices/SKILL.md @@ -0,0 +1,171 @@ +--- +name: next-best-practices +description: Next.js best practices - file conventions, RSC boundaries, data patterns, async APIs, metadata, error handling, route handlers, image/font optimization, bundling +user-invocable: false +--- + +# Next.js Best Practices + +Apply these rules when writing or reviewing Next.js code. + +## File Conventions + +See [file-conventions.md](./file-conventions.md) for: + +- Project structure and special files +- Route segments (dynamic, catch-all, groups) +- Parallel and intercepting routes +- Middleware rename in v16 (middleware → proxy) + +## RSC Boundaries + +Detect invalid React Server Component patterns. + +See [rsc-boundaries.md](./rsc-boundaries.md) for: + +- Async client component detection (invalid) +- Non-serializable props detection +- Server Action exceptions + +## Async Patterns + +Next.js 15+ async API changes. + +See [async-patterns.md](./async-patterns.md) for: + +- Async `params` and `searchParams` +- Async `cookies()` and `headers()` +- Migration codemod + +## Runtime Selection + +See [runtime-selection.md](./runtime-selection.md) for: + +- Default to Node.js runtime +- When Edge runtime is appropriate + +## Directives + +See [directives.md](./directives.md) for: + +- `'use client'`, `'use server'` (React) +- `'use cache'` (Next.js) + +## Functions + +See [functions.md](./functions.md) for: + +- Navigation hooks: `useRouter`, `usePathname`, `useSearchParams`, `useParams` +- Server functions: `cookies`, `headers`, `draftMode`, `after` +- Generate functions: `generateStaticParams`, `generateMetadata` + +## Error Handling + +See [error-handling.md](./error-handling.md) for: + +- `error.tsx`, `global-error.tsx`, `not-found.tsx` +- `redirect`, `permanentRedirect`, `notFound` +- `forbidden`, `unauthorized` (auth errors) +- `unstable_rethrow` for catch blocks + +## Data Patterns + +See [data-patterns.md](./data-patterns.md) for: + +- Server Components vs Server Actions vs Route Handlers +- Avoiding data waterfalls (`Promise.all`, Suspense, preload) +- Client component data fetching + +## Route Handlers + +See [route-handlers.md](./route-handlers.md) for: + +- `route.ts` basics +- GET handler conflicts with `page.tsx` +- Environment behavior (no React DOM) +- When to use vs Server Actions + +## Metadata & OG Images + +See [metadata.md](./metadata.md) for: + +- Static and dynamic metadata +- `generateMetadata` function +- OG image generation with `next/og` +- File-based metadata conventions + +## Image Optimization + +See [image.md](./image.md) for: + +- Always use `next/image` over `` +- Remote images configuration +- Responsive `sizes` attribute +- Blur placeholders +- Priority loading for LCP + +## Font Optimization + +See [font.md](./font.md) for: + +- `next/font` setup +- Google Fonts, local fonts +- Tailwind CSS integration +- Preloading subsets + +## Bundling + +See [bundling.md](./bundling.md) for: + +- Server-incompatible packages +- CSS imports (not link tags) +- Polyfills (already included) +- ESM/CommonJS issues +- Bundle analysis + +## Scripts + +See [scripts.md](./scripts.md) for: + +- `next/script` vs native script tags +- Inline scripts need `id` +- Loading strategies +- Google Analytics with `@next/third-parties` + +## Hydration Errors + +See [hydration-error.md](./hydration-error.md) for: + +- Common causes (browser APIs, dates, invalid HTML) +- Debugging with error overlay +- Fixes for each cause + +## Suspense Boundaries + +See [suspense-boundaries.md](./suspense-boundaries.md) for: + +- CSR bailout with `useSearchParams` and `usePathname` +- Which hooks require Suspense boundaries + +## Parallel & Intercepting Routes + +See [parallel-routes.md](./parallel-routes.md) for: + +- Modal patterns with `@slot` and `(.)` interceptors +- `default.tsx` for fallbacks +- Closing modals correctly with `router.back()` + +## Self-Hosting + +See [self-hosting.md](./self-hosting.md) for: + +- `output: 'standalone'` for Docker +- Cache handlers for multi-instance ISR +- What works vs needs extra setup + +## Debug Tricks + +See [debug-tricks.md](./debug-tricks.md) for: + +- MCP endpoint for AI-assisted debugging +- Rebuild specific routes with `--debug-build-paths` diff --git a/.claude/skills/next-best-practices/async-patterns.md b/.claude/skills/next-best-practices/async-patterns.md new file mode 100644 index 000000000..0d5f91494 --- /dev/null +++ b/.claude/skills/next-best-practices/async-patterns.md @@ -0,0 +1,87 @@ +# Async Patterns + +In Next.js 15+, `params`, `searchParams`, `cookies()`, and `headers()` are asynchronous. + +## Async Params and SearchParams + +Always type them as `Promise<...>` and await them. + +### Pages and Layouts + +```tsx +type Props = { params: Promise<{ slug: string }> }; + +export default async function Page({ params }: Props) { + const { slug } = await params; +} +``` + +### Route Handlers + +```tsx +export async function GET( + request: Request, + { params }: { params: Promise<{ id: string }> }, +) { + const { id } = await params; +} +``` + +### SearchParams + +```tsx +type Props = { + params: Promise<{ slug: string }>; + searchParams: Promise<{ query?: string }>; +}; + +export default async function Page({ params, searchParams }: Props) { + const { slug } = await params; + const { query } = await searchParams; +} +``` + +### Synchronous Components + +Use `React.use()` for non-async components: + +```tsx +import { use } from "react"; + +type Props = { params: Promise<{ slug: string }> }; + +export default function Page({ params }: Props) { + const { slug } = use(params); +} +``` + +### generateMetadata + +```tsx +type Props = { params: Promise<{ slug: string }> }; + +export async function generateMetadata({ params }: Props): Promise { + const { slug } = await params; + return { title: slug }; +} +``` + +## Async Cookies and Headers + +```tsx +import { cookies, headers } from "next/headers"; + +export default async function Page() { + const cookieStore = await cookies(); + const headersList = await headers(); + + const theme = cookieStore.get("theme"); + const userAgent = headersList.get("user-agent"); +} +``` + +## Migration Codemod + +```bash +npx @next/codemod@latest next-async-request-api . +``` diff --git a/.claude/skills/next-best-practices/bundling.md b/.claude/skills/next-best-practices/bundling.md new file mode 100644 index 000000000..4d09f56be --- /dev/null +++ b/.claude/skills/next-best-practices/bundling.md @@ -0,0 +1,182 @@ +# Bundling + +Fix common bundling issues with third-party packages. + +## Server-Incompatible Packages + +Some packages use browser APIs (`window`, `document`, `localStorage`) and fail in Server Components. + +### Error Signs + +``` +ReferenceError: window is not defined +ReferenceError: document is not defined +ReferenceError: localStorage is not defined +Module not found: Can't resolve 'fs' +``` + +### Solution 1: Mark as Client-Only + +If the package is only needed on client: + +```tsx +// Bad: Fails - package uses window +import SomeChart from "some-chart-library"; + +export default function Page() { + return ; +} + +// Good: Use dynamic import with ssr: false +import dynamic from "next/dynamic"; + +const SomeChart = dynamic(() => import("some-chart-library"), { + ssr: false, +}); + +export default function Page() { + return ; +} +``` + +### Solution 2: Externalize from Server Bundle + +For packages that should run on server but have bundling issues: + +```js +// next.config.js +module.exports = { + serverExternalPackages: ["problematic-package"], +}; +``` + +Use this for: + +- Packages with native bindings (sharp, bcrypt) +- Packages that don't bundle well (some ORMs) +- Packages with circular dependencies + +### Solution 3: Client Component Wrapper + +Wrap the entire usage in a client component: + +```tsx +// components/ChartWrapper.tsx +"use client"; + +import { Chart } from "chart-library"; + +export function ChartWrapper(props) { + return ; +} + +// app/page.tsx (server component) +import { ChartWrapper } from "@/components/ChartWrapper"; + +export default function Page() { + return ; +} +``` + +## CSS Imports + +Import CSS files instead of using `` tags. Next.js handles bundling and optimization. + +```tsx +// Bad: Manual link tag +; + +// Good: Import CSS +import "./styles.css"; + +// Good: CSS Modules +import styles from "./Button.module.css"; +``` + +## Polyfills + +Next.js includes common polyfills automatically. Don't load redundant ones from polyfill.io or similar CDNs. + +Already included: `Array.from`, `Object.assign`, `Promise`, `fetch`, `Map`, `Set`, `Symbol`, `URLSearchParams`, and 50+ others. + +```tsx +// Bad: Redundant polyfills +; + +// Good: Next.js Script component +import Script from "next/script"; + + +``` + +## Don't Put Script in Head + +`next/script` should not be placed inside `next/head`. It handles its own positioning. + +```tsx +// Bad: Script inside Head +import Head from 'next/head' +import Script from 'next/script' + + + + +// Good: Next.js component +import { GoogleAnalytics } from '@next/third-parties/google' + +export default function Layout({ children }) { + return ( + + {children} + + + ) +} +``` + +## Google Tag Manager + +```tsx +import { GoogleTagManager } from "@next/third-parties/google"; + +export default function Layout({ children }) { + return ( + + + {children} + + ); +} +``` + +## Other Third-Party Scripts + +```tsx +// YouTube embed +import { YouTubeEmbed } from "@next/third-parties/google"; + +; + +// Google Maps +import { GoogleMapsEmbed } from "@next/third-parties/google"; + +; +``` + +## Quick Reference + +| Pattern | Issue | Fix | +| --------------------------------------------- | -------------------------- | ------------------------- | +| `; + +// Good: Next.js Script component +import Script from "next/script"; + + +``` + +## Don't Put Script in Head + +`next/script` should not be placed inside `next/head`. It handles its own positioning. + +```tsx +// Bad: Script inside Head +import Head from 'next/head' +import Script from 'next/script' + + + + +// Good: Next.js component +import { GoogleAnalytics } from '@next/third-parties/google' + +export default function Layout({ children }) { + return ( + + {children} + + + ) +} +``` + +## Google Tag Manager + +```tsx +import { GoogleTagManager } from "@next/third-parties/google"; + +export default function Layout({ children }) { + return ( + + + {children} + + ); +} +``` + +## Other Third-Party Scripts + +```tsx +// YouTube embed +import { YouTubeEmbed } from "@next/third-parties/google"; + +; + +// Google Maps +import { GoogleMapsEmbed } from "@next/third-parties/google"; + +; +``` + +## Quick Reference + +| Pattern | Issue | Fix | +| --------------------------------------------- | -------------------------- | ------------------------- | +| `