{"owner":"electric-sql","repo":"electric","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md","CLAUDE.md"],"skills":{"AGENTS.md":"# AGENTS.md – ElectricSQL + TanStack DB\n\n> **Audience:** coding agents/codegen tools\n> **Goal:** ship fast, reliable, local-first apps by pairing **Electric** (Postgres sync engine over HTTP) with **TanStack DB** (embedded client DB with live queries & optimistic mutations).\n> **Status:** current as of **2025-09-18**.\n\n## TL;DR\n\n- **Electric:** read-path sync Postgres→clients via HTTP (shapes→changelog→client) ([Electric][1])\n- **TanStack DB:** client collections+live queries+transactional optimistic mutations. Swap `queryCollectionOptions`→`electricCollectionOptions` without touching components ([TanStack][2])\n- **Electric Collection:** subscribes to Electric Shapes (single-table, optional `where`/`columns`) ([TanStack][3])\n- **Writes:** mutations→API→Postgres txid→await in Electric collection→drop optimistic state when change arrives ([TanStack][3])\n- **Live queries:** differential dataflow→sub-ms updates+cross-collection joins ([TanStack][2])\n- **Security/scale:** proxy auth, shape-scoped authorization, CDN caching. Use Electric Cloud to skip ops ([Electric][4])\n\n## 🔒 Security Rules (ALWAYS)\n\n1. **Never expose `SOURCE_SECRET` to browser** – inject server-side via proxy\n2. **Electric HTTP API public by default** – enforce auth at proxy\n3. **Put Electric behind server/proxy** – never call directly from production ([Electric][10])\n4. **Define shapes in server/proxy** – no client-defined tables/WHERE clauses\n\n## Golden Path\n\n### 0) Create project\n\n```sh\nnpx gitpick electric-sql/electric/tree/main/examples/tanstack-db-web-starter my-tanstack-db-project\ncd my-tanstack-db-project\ncp .env.example .env\npnpm install\npnpm dev\n# in new terminal\npnpm migrate\n```\n\n### 1) Electric proxy (server)\n\n```ts\n// TanStack Start server function\nimport { createServerFileRoute } from '@tanstack/react-start/server'\nimport { ELECTRIC_PROTOCOL_QUERY_PARAMS } from '@electric-sql/client'\n\nconst ELECTRIC_URL = 'https://api.electric-sql.cloud/v1/shape'\n\nconst serve = async ({ request }: { request: Request }) => {\n  const url = new URL(request.url)\n  const origin = new URL(ELECTRIC_URL)\n\n  // Pass Electric protocol params\n  url.searchParams.forEach((v, k) => {\n    if (ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(k))\n      origin.searchParams.set(k, v)\n  })\n\n  // Server decides shape\n  origin.searchParams.set('table', 'todos')\n  // Tenant isolation: origin.searchParams.set('where', `user_id=$1`)\n  // origin.searchParams.set('params', JSON.stringify([user.id]))\n  origin.searchParams.set('source_id', process.env.SOURCE_ID!)\n  origin.searchParams.set('secret', process.env.SOURCE_SECRET!)\n\n  const res = await fetch(origin)\n  const headers = new Headers(res.headers)\n  headers.delete('content-encoding')\n  headers.delete('content-length')\n  return new Response(res.body, {\n    status: res.status,\n    statusText: res.statusText,\n    headers,\n  })\n}\n\nexport const ServerRoute = createServerFileRoute('/api/todos').methods({\n  GET: serve,\n})\n```\n\n### 2) Electric Collection (client)\n\n```ts\nimport { createCollection } from '@tanstack/react-db'\nimport { electricCollectionOptions } from '@tanstack/electric-db-collection'\nimport { todoSchema } from './schema'\n\nexport const todoCollection = createCollection(\n  electricCollectionOptions({\n    id: 'todos',\n    schema: todoSchema,\n    getKey: (row) => row.id,\n    shapeOptions: { url: '/api/todos' },\n    onInsert: async ({ transaction }) => {\n      const newTodo = transaction.mutations[0].modified\n      const { txid } = await api.todos.create(newTodo)\n      return { txid }\n    },\n    // onUpdate/onDelete same pattern\n  })\n)\n```\n\n**Shape config:**\n\n- Single-table only + optional `where`/`columns`\n- Include PK if using `columns`\n- Shapes immutable per subscription ([Electric][6]) use collection factory function to make dynamic\n\n### 3) Write-path contract\n\n1. UI mutates collection (instant optimistic)\n2. Collection calls API in `onInsert`/`onUpdate`/`onDelete`\n3. API writes Postgres, returns txid\n4. Client awaits tx on Electric stream→drops optimistic state\n\n**Backend: get Postgres txid and return as an integer**\n\n```sql\nSELECT pg_current_xact_id()::xid::text as txid\n```\n\n### 4) Live queries\n\nTanStack DB SQL-like queries **sub-ms performance** differential dataflow ([TanStack][7]):\n\n```tsx\nimport { useLiveQuery, eq } from '@tanstack/react-db'\n\nexport function TodoList() {\n  const { data: todos } = useLiveQuery((q) =>\n    q\n      .from({ todo: todoCollection })\n      .where(({ todo }) => eq(todo.completed, false))\n      .orderBy(({ todo }) => todo.created_at, 'desc')\n      .limit(50)\n  )\n  return (\n    <ul>\n      {todos.map((todo) => (\n        <li key={todo.id}>{todo.text}</li>\n      ))}\n    </ul>\n  )\n}\n```\n\nDependencies:\n\n```tsx\nconst [direction, setDirection] = useState('desc')\nconst { data } = useLiveQuery(\n  (q) =>\n    q\n      .from({ todo: todoCollection })\n      .orderBy(({ todo }) => todo.createdAt, direction)\n      .limit(50),\n  [direction]\n)\n```\n\nCross-collection joins:\n\n```tsx\n.join({ user: userCollection }, ({ todo, user }) => eq(todo.user_id, user.id))\n.where(({ user }) => eq(u.active, true))\n.select(({ todo, user }) => ({ id: todo.id, text: todo.text, userName: user.name }))\n```\n\nAggregations:\n\n```tsx\n.groupBy(({ todo }) => todo.listId)\n.select(({ todo }) => ({ listId: todo.listId, totalTodos: count(todo.id) }))\n```\n\n## Optimistic Mutations\n\n### Direct mutations\n\n```tsx\nfunction TodoActions() {\n  const handleAdd = () => {\n    todoCollection.insert({\n      id: crypto.randomUUID(),\n      text: 'New todo',\n      completed: false,\n      createdAt: Date.now(),\n    })\n  }\n  const handleToggle = (todo) => {\n    todoCollection.update(todo.id, (draft) => {\n      draft.completed = !draft.completed\n    })\n  }\n  const handleDelete = (todoId) => todoCollection.delete(todoId)\n}\n```\n\n### Custom optimistic actions\n\n```tsx\nimport { createOptimisticAction } from '@tanstack/react-db'\n\nconst bootstrapTodoListAction = createOptimisticAction<string>({\n  onMutate: (listId, itemText) => {\n    listCollection.insert({ id: listId })\n    todoCollection.insert({ id: crypto.randomUUID(), text: itemText, listId })\n  },\n  mutationFn: async (listId, itemText) => {\n    const { txid } = await api.todos.bootstrapTodoList({ listId, itemText })\n    await Promise.all([\n      listCollection.utils.awaitTxId(txid),\n      todoCollection.utils.awaitTxId(txid),\n    ])\n  },\n})\n```\n\n## Developing Electric Agents\n\nThe agents subsystem spans seven packages: `agents-runtime`, `agents-mcp` (MCP bridge library used by built-ins), `agents-server`, `agents` (built-in Horton & Worker), `agents-server-ui`, `agents-desktop` (Electron wrapper for the UI), and `agents-server-conformance-tests`.\n\n**Quick start** (from project root, ensure `.env` has `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`):\n\n```sh\n./scripts/dev.sh build       # install + build typescript-client, agents-runtime,\n                             # agents-mcp, agents-server, agents\n./scripts/dev.sh start       # docker + 5 dev processes; Ctrl-C stops everything\n./scripts/dev.sh start --with-agents   # also spawn built-in agents (Horton + Worker)\n./scripts/dev.sh desktop     # run the Electron desktop app (in a separate terminal,\n                             # against an already-running stack)\n./scripts/dev.sh stop        # stop processes + docker compose down\n./scripts/dev.sh teardown    # also remove Postgres volume + .streams-data/\n```\n\nBuilt-in agents (Horton + Worker) register against `agents-server` at startup and will fail with `Stream not found` if they race ahead of it. Pass `--with-agents` to `start` to spawn them after `agents-server` binds `:4437`, or run them manually in a separate terminal:\n\n```sh\nELECTRIC_AGENTS_SERVER_URL=http://localhost:4437 \\\n  node packages/agents/dist/entrypoint.js\n```\n\nLogs land in `.dev-logs/`. Use `--detach` with `start` to background the stack. See **[docs/agents-development.md](docs/agents-development.md)** for the full manual flow, env vars, testing, and iteration workflows.\n\n## Working on the TypeScript client\n\nBefore making changes to `packages/typescript-client`, **always read `packages/typescript-client/SPEC.md` first**. It is the single source of truth for the ShapeStream state machine — invariants, constraints, state transitions, and how they're enforced. Design fixes and features around the spec's invariants rather than patching symptoms ad-hoc.\n\n## Testing\n\nBefore running `pnpm typecheck`, `pnpm test`, or package-level variants such as\n`pnpm run typecheck`, run `pnpm install` from the repository/worktree root first.\nFresh worktrees do not have package-local dependency links until install has\nmaterialized the workspace, which can otherwise produce misleading TypeScript or\nmodule-resolution errors.\n\n### Unit testing (mocked)\n\n```ts\nshapeOptions: {\n  url: '/api/todos',\n  fetchClient: vi.fn(), // mock fetch\n  onError: (error) => // ... handle fetch errors\n}\n```\n\n### Running integration tests locally\n\nIntegration tests in `packages/typescript-client` (and other TS packages) run against\na real Electric server. Three services are required: **PostgreSQL**, **Electric**, and **Nginx**.\n\n**Step 1 — Start Docker Desktop** (if not already running):\n\n```sh\nopen -a Docker   # macOS\n```\n\n**Step 2 — Build the Electric image from the current branch:**\n\nThe `electricsql/electric:canary` image tracks `main` and may lack features from\nfeature branches. Build locally when working on branches that change the sync service:\n\n```sh\ncd /path/to/electric-3\ndocker build -t electric-local \\\n  -f packages/sync-service/Dockerfile \\\n  --build-context electric-telemetry=packages/electric-telemetry \\\n  packages/sync-service\n```\n\nIf your branch only changes TS client code and doesn't need new server features,\nyou can skip the build and use the canary image instead:\n\n```sh\nexport ELECTRIC_IMAGE=electricsql/electric:canary\n```\n\n**Step 3 — Start services:**\n\n```sh\ncd packages/sync-service/dev\nELECTRIC_IMAGE=electric-local \\\n  docker compose -f docker-compose.yml -f docker-compose-electric.yml \\\n  up --wait postgres electric nginx\n```\n\nServices will be available at:\n\n- PostgreSQL: `localhost:54321` (postgres/password)\n- Electric API: `http://localhost:3000`\n- Nginx proxy: `http://localhost:3002`\n\n**Step 4 — Run tests:**\n\n```sh\ncd packages/typescript-client\npnpm test              # watch mode\npnpm test --run        # single run\npnpm test -t \"pattern\" # filter by test name\n```\n\n**Teardown:**\n\n```sh\ncd packages/sync-service/dev\ndocker compose -f docker-compose.yml -f docker-compose-electric.yml down\n```\n\n## ⚠️ Critical Gotchas\n\n1. **Use latest packages** - Check npm for `@electric-sql/*` & `@tanstack/*-db`\n2. **txid handshake required** - Prevents UI flicker when optimistic→synced state\n3. **Local dev slow shapes** - HTTP/1.1 6-connection limit. Fix: HTTP/2 proxy (Caddy/nginx) or Electric Cloud ([Electric][18])\n4. **Proxy must forward headers/params** - Preserve Electric query params\n5. **Parse custom types:**\n\n```ts\nshapeOptions: {\n  parser: {\n    timestamptz: (date: string) => new Date(date)\n  }\n}\n```\n\n## Framework integrations\n\n```sh\nnpm install @tanstack/{angular,react,solid,svelte,vue}-db\n```\n\n```ts\nimport { useLiveQuery } from '...'\nconst { data, isLoading } = useLiveQuery((q) =>\n  q.from({ todos: todosCollection })\n)\n```\n\n**React Native:** Requires `react-native-random-uuid` + import in entry point\n\n## Migration from TanStack Query\n\n1. Wrap `useQuery` in Query Collection (`queryCollectionOptions`)\n2. Replace selectors with live queries\n3. Port mutations to collection handlers\n4. Switch to Electric Collection (no component changes)\n\n## Deployment\n\n### Electric Cloud\n\n```sh\nnpx @electric-sql/start my-app\npnpm claim && pnpm deploy\n```\n\n### Self-hosted\n\n```sh\ndocker run -e DATABASE_URL=postgres://... electricsql/electric\n```\n\nDocker compose:\n\n```yaml\nname: 'electric-backend'\nservices:\n  postgres:\n    image: postgres:16-alpine\n    environment:\n      POSTGRES_DB: electric\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: password\n    ports: ['54321:5432']\n    volumes: ['./postgres.conf:/etc/postgresql/postgresql.conf:ro']\n    tmpfs: ['/var/lib/postgresql/data', '/tmp']\n    command: ['postgres', '-c', 'config_file=/etc/postgresql/postgresql.conf']\n\n  backend:\n    image: electricsql/electric:canary\n    environment:\n      DATABASE_URL: postgresql://postgres:password@postgres:5432/electric?sslmode=disable\n      ELECTRIC_INSECURE: true\n    ports: ['3000:3000']\n    depends_on: ['postgres']\n```\n\n**Postgres requirements:** v14+, logical replication, user with REPLICATION role, `wal_level=logical`\n\n## Stack (web/mobile)\n\n- **DB:** Postgres (Neon/Supabase/Crunchy with logical replication)\n- **Backend:** TanStack Start+Drizzle+tRPC/REST\n- **Proxy:** Edge function/server route\n- **Client:** TanStack DB (React/Expo)\n\n## Evolution from Old Electric\n\n**Old:** Bidirectional SQLite sync, handled reads+writes\n**New:** Electric (Read-only HTTP streaming from Postgres) + TanStack DB (optimistic writes via API)\n\nAvoid old patterns:\n\n```ts\n// ❌ OLD (doesn't exist)\nconst { db } = await electrify(conn, schema)\nawait db.todos.create({ text: 'New todo' })\n```\n\nWrite path: `todos.insert()`→optimistic→`onInsert`→API→Postgres txid→Electric streams→reconcile→drop optimistic\nPrefer TanStack DB collections over lower-level Shape/ShapeStream/useShape APIs.\n\n## References\n\n[1]: https://electric-sql.com/docs/api/http.md\n[2]: https://tanstack.com/db/latest/docs/overview.md\n[3]: https://tanstack.com/db/latest/docs/collections/electric-collection.md\n[4]: https://electric-sql.com/docs/guides/auth.md\n[5]: https://electric-sql.com/docs/quickstart.md\n[6]: https://electric-sql.com/docs/guides/shapes.md\n[7]: https://tanstack.com/db/latest/docs/guides/live-queries.md\n[8]: https://electric-sql.com/blog/2024/11/21/local-first-with-your-existing-api.md\n[9]: https://electric-sql.com/docs/api/clients/typescript.md\n[10]: https://electric-sql.com/docs/guides/security.md\n[11]: https://electric-sql.com/product/cloud.md\n[12]: https://tanstack.com/db/latest/docs/collections/query-collection.md\n[13]: https://tanstack.com/db/latest/docs/guides/error-handling.md\n[14]: https://electric-sql.com/docs/stacks.md\n[15]: https://electric-sql.com/blog/2025/07/29/local-first-sync-with-tanstack-db.md\n[16]: https://tanstack.com/blog/tanstack-db-0.1-the-embedded-client-database-for-tanstack-query.md\n[17]: https://frontendatscale.com/blog/tanstack-db/\n[18]: https://electric-sql.com/docs/guides/troubleshooting.md#slow-shapes\n","CLAUDE.md":"# Claude Code Guidelines\n\nSee [AGENTS.md](./AGENTS.md) for project conventions, architecture, and coding guidelines.\n"},"files":{"AGENTS.md":"# AGENTS.md – ElectricSQL + TanStack DB\n\n> **Audience:** coding agents/codegen tools\n> **Goal:** ship fast, reliable, local-first apps by pairing **Electric** (Postgres sync engine over HTTP) with **TanStack DB** (embedded client DB with live queries & optimistic mutations).\n> **Status:** current as of **2025-09-18**.\n\n## TL;DR\n\n- **Electric:** read-path sync Postgres→clients via HTTP (shapes→changelog→client) ([Electric][1])\n- **TanStack DB:** client collections+live queries+transactional optimistic mutations. Swap `queryCollectionOptions`→`electricCollectionOptions` without touching components ([TanStack][2])\n- **Electric Collection:** subscribes to Electric Shapes (single-table, optional `where`/`columns`) ([TanStack][3])\n- **Writes:** mutations→API→Postgres txid→await in Electric collection→drop optimistic state when change arrives ([TanStack][3])\n- **Live queries:** differential dataflow→sub-ms updates+cross-collection joins ([TanStack][2])\n- **Security/scale:** proxy auth, shape-scoped authorization, CDN caching. Use Electric Cloud to skip ops ([Electric][4])\n\n## 🔒 Security Rules (ALWAYS)\n\n1. **Never expose `SOURCE_SECRET` to browser** – inject server-side via proxy\n2. **Electric HTTP API public by default** – enforce auth at proxy\n3. **Put Electric behind server/proxy** – never call directly from production ([Electric][10])\n4. **Define shapes in server/proxy** – no client-defined tables/WHERE clauses\n\n## Golden Path\n\n### 0) Create project\n\n```sh\nnpx gitpick electric-sql/electric/tree/main/examples/tanstack-db-web-starter my-tanstack-db-project\ncd my-tanstack-db-project\ncp .env.example .env\npnpm install\npnpm dev\n# in new terminal\npnpm migrate\n```\n\n### 1) Electric proxy (server)\n\n```ts\n// TanStack Start server function\nimport { createServerFileRoute } from '@tanstack/react-start/server'\nimport { ELECTRIC_PROTOCOL_QUERY_PARAMS } from '@electric-sql/client'\n\nconst ELECTRIC_URL = 'https://api.electric-sql.cloud/v1/shape'\n\nconst serve = async ({ request }: { request: Request }) => {\n  const url = new URL(request.url)\n  const origin = new URL(ELECTRIC_URL)\n\n  // Pass Electric protocol params\n  url.searchParams.forEach((v, k) => {\n    if (ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(k))\n      origin.searchParams.set(k, v)\n  })\n\n  // Server decides shape\n  origin.searchParams.set('table', 'todos')\n  // Tenant isolation: origin.searchParams.set('where', `user_id=$1`)\n  // origin.searchParams.set('params', JSON.stringify([user.id]))\n  origin.searchParams.set('source_id', process.env.SOURCE_ID!)\n  origin.searchParams.set('secret', process.env.SOURCE_SECRET!)\n\n  const res = await fetch(origin)\n  const headers = new Headers(res.headers)\n  headers.delete('content-encoding')\n  headers.delete('content-length')\n  return new Response(res.body, {\n    status: res.status,\n    statusText: res.statusText,\n    headers,\n  })\n}\n\nexport const ServerRoute = createServerFileRoute('/api/todos').methods({\n  GET: serve,\n})\n```\n\n### 2) Electric Collection (client)\n\n```ts\nimport { createCollection } from '@tanstack/react-db'\nimport { electricCollectionOptions } from '@tanstack/electric-db-collection'\nimport { todoSchema } from './schema'\n\nexport const todoCollection = createCollection(\n  electricCollectionOptions({\n    id: 'todos',\n    schema: todoSchema,\n    getKey: (row) => row.id,\n    shapeOptions: { url: '/api/todos' },\n    onInsert: async ({ transaction }) => {\n      const newTodo = transaction.mutations[0].modified\n      const { txid } = await api.todos.create(newTodo)\n      return { txid }\n    },\n    // onUpdate/onDelete same pattern\n  })\n)\n```\n\n**Shape config:**\n\n- Single-table only + optional `where`/`columns`\n- Include PK if using `columns`\n- Shapes immutable per subscription ([Electric][6]) use collection factory function to make dynamic\n\n### 3) Write-path contract\n\n1. UI mutates collection (instant optimistic)\n2. Collection calls API in `onInsert`/`onUpdate`/`onDelete`\n3. API writes Postgres, returns txid\n4. Client awaits tx on Electric stream→drops optimistic state\n\n**Backend: get Postgres txid and return as an integer**\n\n```sql\nSELECT pg_current_xact_id()::xid::text as txid\n```\n\n### 4) Live queries\n\nTanStack DB SQL-like queries **sub-ms performance** differential dataflow ([TanStack][7]):\n\n```tsx\nimport { useLiveQuery, eq } from '@tanstack/react-db'\n\nexport function TodoList() {\n  const { data: todos } = useLiveQuery((q) =>\n    q\n      .from({ todo: todoCollection })\n      .where(({ todo }) => eq(todo.completed, false))\n      .orderBy(({ todo }) => todo.created_at, 'desc')\n      .limit(50)\n  )\n  return (\n    <ul>\n      {todos.map((todo) => (\n        <li key={todo.id}>{todo.text}</li>\n      ))}\n    </ul>\n  )\n}\n```\n\nDependencies:\n\n```tsx\nconst [direction, setDirection] = useState('desc')\nconst { data } = useLiveQuery(\n  (q) =>\n    q\n      .from({ todo: todoCollection })\n      .orderBy(({ todo }) => todo.createdAt, direction)\n      .limit(50),\n  [direction]\n)\n```\n\nCross-collection joins:\n\n```tsx\n.join({ user: userCollection }, ({ todo, user }) => eq(todo.user_id, user.id))\n.where(({ user }) => eq(u.active, true))\n.select(({ todo, user }) => ({ id: todo.id, text: todo.text, userName: user.name }))\n```\n\nAggregations:\n\n```tsx\n.groupBy(({ todo }) => todo.listId)\n.select(({ todo }) => ({ listId: todo.listId, totalTodos: count(todo.id) }))\n```\n\n## Optimistic Mutations\n\n### Direct mutations\n\n```tsx\nfunction TodoActions() {\n  const handleAdd = () => {\n    todoCollection.insert({\n      id: crypto.randomUUID(),\n      text: 'New todo',\n      completed: false,\n      createdAt: Date.now(),\n    })\n  }\n  const handleToggle = (todo) => {\n    todoCollection.update(todo.id, (draft) => {\n      draft.completed = !draft.completed\n    })\n  }\n  const handleDelete = (todoId) => todoCollection.delete(todoId)\n}\n```\n\n### Custom optimistic actions\n\n```tsx\nimport { createOptimisticAction } from '@tanstack/react-db'\n\nconst bootstrapTodoListAction = createOptimisticAction<string>({\n  onMutate: (listId, itemText) => {\n    listCollection.insert({ id: listId })\n    todoCollection.insert({ id: crypto.randomUUID(), text: itemText, listId })\n  },\n  mutationFn: async (listId, itemText) => {\n    const { txid } = await api.todos.bootstrapTodoList({ listId, itemText })\n    await Promise.all([\n      listCollection.utils.awaitTxId(txid),\n      todoCollection.utils.awaitTxId(txid),\n    ])\n  },\n})\n```\n\n## Developing Electric Agents\n\nThe agents subsystem spans seven packages: `agents-runtime`, `agents-mcp` (MCP bridge library used by built-ins), `agents-server`, `agents` (built-in Horton & Worker), `agents-server-ui`, `agents-desktop` (Electron wrapper for the UI), and `agents-server-conformance-tests`.\n\n**Quick start** (from project root, ensure `.env` has `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`):\n\n```sh\n./scripts/dev.sh build       # install + build typescript-client, agents-runtime,\n                             # agents-mcp, agents-server, agents\n./scripts/dev.sh start       # docker + 5 dev processes; Ctrl-C stops everything\n./scripts/dev.sh start --with-agents   # also spawn built-in agents (Horton + Worker)\n./scripts/dev.sh desktop     # run the Electron desktop app (in a separate terminal,\n                             # against an already-running stack)\n./scripts/dev.sh stop        # stop processes + docker compose down\n./scripts/dev.sh teardown    # also remove Postgres volume + .streams-data/\n```\n\nBuilt-in agents (Horton + Worker) register against `agents-server` at startup and will fail with `Stream not found` if they race ahead of it. Pass `--with-agents` to `start` to spawn them after `agents-server` binds `:4437`, or run them manually in a separate terminal:\n\n```sh\nELECTRIC_AGENTS_SERVER_URL=http://localhost:4437 \\\n  node packages/agents/dist/entrypoint.js\n```\n\nLogs land in `.dev-logs/`. Use `--detach` with `start` to background the stack. See **[docs/agents-development.md](docs/agents-development.md)** for the full manual flow, env vars, testing, and iteration workflows.\n\n## Working on the TypeScript client\n\nBefore making changes to `packages/typescript-client`, **always read `packages/typescript-client/SPEC.md` first**. It is the single source of truth for the ShapeStream state machine — invariants, constraints, state transitions, and how they're enforced. Design fixes and features around the spec's invariants rather than patching symptoms ad-hoc.\n\n## Testing\n\nBefore running `pnpm typecheck`, `pnpm test`, or package-level variants such as\n`pnpm run typecheck`, run `pnpm install` from the repository/worktree root first.\nFresh worktrees do not have package-local dependency links until install has\nmaterialized the workspace, which can otherwise produce misleading TypeScript or\nmodule-resolution errors.\n\n### Unit testing (mocked)\n\n```ts\nshapeOptions: {\n  url: '/api/todos',\n  fetchClient: vi.fn(), // mock fetch\n  onError: (error) => // ... handle fetch errors\n}\n```\n\n### Running integration tests locally\n\nIntegration tests in `packages/typescript-client` (and other TS packages) run against\na real Electric server. Three services are required: **PostgreSQL**, **Electric**, and **Nginx**.\n\n**Step 1 — Start Docker Desktop** (if not already running):\n\n```sh\nopen -a Docker   # macOS\n```\n\n**Step 2 — Build the Electric image from the current branch:**\n\nThe `electricsql/electric:canary` image tracks `main` and may lack features from\nfeature branches. Build locally when working on branches that change the sync service:\n\n```sh\ncd /path/to/electric-3\ndocker build -t electric-local \\\n  -f packages/sync-service/Dockerfile \\\n  --build-context electric-telemetry=packages/electric-telemetry \\\n  packages/sync-service\n```\n\nIf your branch only changes TS client code and doesn't need new server features,\nyou can skip the build and use the canary image instead:\n\n```sh\nexport ELECTRIC_IMAGE=electricsql/electric:canary\n```\n\n**Step 3 — Start services:**\n\n```sh\ncd packages/sync-service/dev\nELECTRIC_IMAGE=electric-local \\\n  docker compose -f docker-compose.yml -f docker-compose-electric.yml \\\n  up --wait postgres electric nginx\n```\n\nServices will be available at:\n\n- PostgreSQL: `localhost:54321` (postgres/password)\n- Electric API: `http://localhost:3000`\n- Nginx proxy: `http://localhost:3002`\n\n**Step 4 — Run tests:**\n\n```sh\ncd packages/typescript-client\npnpm test              # watch mode\npnpm test --run        # single run\npnpm test -t \"pattern\" # filter by test name\n```\n\n**Teardown:**\n\n```sh\ncd packages/sync-service/dev\ndocker compose -f docker-compose.yml -f docker-compose-electric.yml down\n```\n\n## ⚠️ Critical Gotchas\n\n1. **Use latest packages** - Check npm for `@electric-sql/*` & `@tanstack/*-db`\n2. **txid handshake required** - Prevents UI flicker when optimistic→synced state\n3. **Local dev slow shapes** - HTTP/1.1 6-connection limit. Fix: HTTP/2 proxy (Caddy/nginx) or Electric Cloud ([Electric][18])\n4. **Proxy must forward headers/params** - Preserve Electric query params\n5. **Parse custom types:**\n\n```ts\nshapeOptions: {\n  parser: {\n    timestamptz: (date: string) => new Date(date)\n  }\n}\n```\n\n## Framework integrations\n\n```sh\nnpm install @tanstack/{angular,react,solid,svelte,vue}-db\n```\n\n```ts\nimport { useLiveQuery } from '...'\nconst { data, isLoading } = useLiveQuery((q) =>\n  q.from({ todos: todosCollection })\n)\n```\n\n**React Native:** Requires `react-native-random-uuid` + import in entry point\n\n## Migration from TanStack Query\n\n1. Wrap `useQuery` in Query Collection (`queryCollectionOptions`)\n2. Replace selectors with live queries\n3. Port mutations to collection handlers\n4. Switch to Electric Collection (no component changes)\n\n## Deployment\n\n### Electric Cloud\n\n```sh\nnpx @electric-sql/start my-app\npnpm claim && pnpm deploy\n```\n\n### Self-hosted\n\n```sh\ndocker run -e DATABASE_URL=postgres://... electricsql/electric\n```\n\nDocker compose:\n\n```yaml\nname: 'electric-backend'\nservices:\n  postgres:\n    image: postgres:16-alpine\n    environment:\n      POSTGRES_DB: electric\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: password\n    ports: ['54321:5432']\n    volumes: ['./postgres.conf:/etc/postgresql/postgresql.conf:ro']\n    tmpfs: ['/var/lib/postgresql/data', '/tmp']\n    command: ['postgres', '-c', 'config_file=/etc/postgresql/postgresql.conf']\n\n  backend:\n    image: electricsql/electric:canary\n    environment:\n      DATABASE_URL: postgresql://postgres:password@postgres:5432/electric?sslmode=disable\n      ELECTRIC_INSECURE: true\n    ports: ['3000:3000']\n    depends_on: ['postgres']\n```\n\n**Postgres requirements:** v14+, logical replication, user with REPLICATION role, `wal_level=logical`\n\n## Stack (web/mobile)\n\n- **DB:** Postgres (Neon/Supabase/Crunchy with logical replication)\n- **Backend:** TanStack Start+Drizzle+tRPC/REST\n- **Proxy:** Edge function/server route\n- **Client:** TanStack DB (React/Expo)\n\n## Evolution from Old Electric\n\n**Old:** Bidirectional SQLite sync, handled reads+writes\n**New:** Electric (Read-only HTTP streaming from Postgres) + TanStack DB (optimistic writes via API)\n\nAvoid old patterns:\n\n```ts\n// ❌ OLD (doesn't exist)\nconst { db } = await electrify(conn, schema)\nawait db.todos.create({ text: 'New todo' })\n```\n\nWrite path: `todos.insert()`→optimistic→`onInsert`→API→Postgres txid→Electric streams→reconcile→drop optimistic\nPrefer TanStack DB collections over lower-level Shape/ShapeStream/useShape APIs.\n\n## References\n\n[1]: https://electric-sql.com/docs/api/http.md\n[2]: https://tanstack.com/db/latest/docs/overview.md\n[3]: https://tanstack.com/db/latest/docs/collections/electric-collection.md\n[4]: https://electric-sql.com/docs/guides/auth.md\n[5]: https://electric-sql.com/docs/quickstart.md\n[6]: https://electric-sql.com/docs/guides/shapes.md\n[7]: https://tanstack.com/db/latest/docs/guides/live-queries.md\n[8]: https://electric-sql.com/blog/2024/11/21/local-first-with-your-existing-api.md\n[9]: https://electric-sql.com/docs/api/clients/typescript.md\n[10]: https://electric-sql.com/docs/guides/security.md\n[11]: https://electric-sql.com/product/cloud.md\n[12]: https://tanstack.com/db/latest/docs/collections/query-collection.md\n[13]: https://tanstack.com/db/latest/docs/guides/error-handling.md\n[14]: https://electric-sql.com/docs/stacks.md\n[15]: https://electric-sql.com/blog/2025/07/29/local-first-sync-with-tanstack-db.md\n[16]: https://tanstack.com/blog/tanstack-db-0.1-the-embedded-client-database-for-tanstack-query.md\n[17]: https://frontendatscale.com/blog/tanstack-db/\n[18]: https://electric-sql.com/docs/guides/troubleshooting.md#slow-shapes\n","CLAUDE.md":"# Claude Code Guidelines\n\nSee [AGENTS.md](./AGENTS.md) for project conventions, architecture, and coding guidelines.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md – ElectricSQL + TanStack DB\n\n> **Audience:** coding agents/codegen tools\n> **Goal:** ship fast, reliable, local-first apps by pairing **Electric** (Postgres sync engine over HTTP) with **TanStack DB** (embedded client DB with live queries & optimistic mutations).\n> **Status:** current as of **2025-09-18**.\n\n## TL;DR\n\n- **Electric:** read-path sync Postgres→clients via HTTP (shapes→changelog→client) ([Electric][1])\n- **TanStack DB:** client collections+live queries+transactional optimistic mutations. Swap `queryCollectionOptions`→`electricCollectionOptions` without touching components ([TanStack][2])\n- **Electric Collection:** subscribes to Electric Shapes (single-table, optional `where`/`columns`) ([TanStack][3])\n- **Writes:** mutations→API→Postgres txid→await in Electric collection→drop optimistic state when change arrives ([TanStack][3])\n- **Live queries:** differential dataflow→sub-ms updates+cross-collection joins ([TanStack][2])\n- **Security/scale:** proxy auth, shape-scoped authorization, CDN caching. Use Electric Cloud to skip ops ([Electric][4])\n\n## 🔒 Security Rules (ALWAYS)\n\n1. **Never expose `SOURCE_SECRET` to browser** – inject server-side via proxy\n2. **Electric HTTP API public by default** – enforce auth at proxy\n3. **Put Electric behind server/proxy** – never call directly from production ([Electric][10])\n4. **Define shapes in server/proxy** – no client-defined tables/WHERE clauses\n\n## Golden Path\n\n### 0) Create project\n\n```sh\nnpx gitpick electric-sql/electric/tree/main/examples/tanstack-db-web-starter my-tanstack-db-project\ncd my-tanstack-db-project\ncp .env.example .env\npnpm install\npnpm dev\n# in new terminal\npnpm migrate\n```\n\n### 1) Electric proxy (server)\n\n```ts\n// TanStack Start server function\nimport { createServerFileRoute } from '@tanstack/react-start/server'\nimport { ELECTRIC_PROTOCOL_QUERY_PARAMS } from '@electric-sql/client'\n\nconst ELECTRIC_URL = 'https://api.electric-sql.cloud/v1/shape'\n\nconst serve = async ({ request }: { request: Request }) => {\n  const url = new URL(request.url)\n  const origin = new URL(ELECTRIC_URL)\n\n  // Pass Electric protocol params\n  url.searchParams.forEach((v, k) => {\n    if (ELECTRIC_PROTOCOL_QUERY_PARAMS.includes(k))\n      origin.searchParams.set(k, v)\n  })\n\n  // Server decides shape\n  origin.searchParams.set('table', 'todos')\n  // Tenant isolation: origin.searchParams.set('where', `user_id=$1`)\n  // origin.searchParams.set('params', JSON.stringify([user.id]))\n  origin.searchParams.set('source_id', process.env.SOURCE_ID!)\n  origin.searchParams.set('secret', process.env.SOURCE_SECRET!)\n\n  const res = await fetch(origin)\n  const headers = new Headers(res.headers)\n  headers.delete('content-encoding')\n  headers.delete('content-length')\n  return new Response(res.body, {\n    status: res.status,\n    statusText: res.statusText,\n    headers,\n  })\n}\n\nexport const ServerRoute = createServerFileRoute('/api/todos').methods({\n  GET: serve,\n})\n```\n\n### 2) Electric Collection (client)\n\n```ts\nimport { createCollection } from '@tanstack/react-db'\nimport { electricCollectionOptions } from '@tanstack/electric-db-collection'\nimport { todoSchema } from './schema'\n\nexport const todoCollection = createCollection(\n  electricCollectionOptions({\n    id: 'todos',\n    schema: todoSchema,\n    getKey: (row) => row.id,\n    shapeOptions: { url: '/api/todos' },\n    onInsert: async ({ transaction }) => {\n      const newTodo = transaction.mutations[0].modified\n      const { txid } = await api.todos.create(newTodo)\n      return { txid }\n    },\n    // onUpdate/onDelete same pattern\n  })\n)\n```\n\n**Shape config:**\n\n- Single-table only + optional `where`/`columns`\n- Include PK if using `columns`\n- Shapes immutable per subscription ([Electric][6]) use collection factory function to make dynamic\n\n### 3) Write-path contract\n\n1. UI mutates collection (instant optimistic)\n2. Collection calls API in `onInsert`/`onUpdate`/`onDelete`\n3. API writes Postgres, returns txid\n4. Client awaits tx on Electric stream→drops optimistic state\n\n**Backend: get Postgres txid and return as an integer**\n\n```sql\nSELECT pg_current_xact_id()::xid::text as txid\n```\n\n### 4) Live queries\n\nTanStack DB SQL-like queries **sub-ms performance** differential dataflow ([TanStack][7]):\n\n```tsx\nimport { useLiveQuery, eq } from '@tanstack/react-db'\n\nexport function TodoList() {\n  const { data: todos } = useLiveQuery((q) =>\n    q\n      .from({ todo: todoCollection })\n      .where(({ todo }) => eq(todo.completed, false))\n      .orderBy(({ todo }) => todo.created_at, 'desc')\n      .limit(50)\n  )\n  return (\n    <ul>\n      {todos.map((todo) => (\n        <li key={todo.id}>{todo.text}</li>\n      ))}\n    </ul>\n  )\n}\n```\n\nDependencies:\n\n```tsx\nconst [direction, setDirection] = useState('desc')\nconst { data } = useLiveQuery(\n  (q) =>\n    q\n      .from({ todo: todoCollection })\n      .orderBy(({ todo }) => todo.createdAt, direction)\n      .limit(50),\n  [direction]\n)\n```\n\nCross-collection joins:\n\n```tsx\n.join({ user: userCollection }, ({ todo, user }) => eq(todo.user_id, user.id))\n.where(({ user }) => eq(u.active, true))\n.select(({ todo, user }) => ({ id: todo.id, text: todo.text, userName: user.name }))\n```\n\nAggregations:\n\n```tsx\n.groupBy(({ todo }) => todo.listId)\n.select(({ todo }) => ({ listId: todo.listId, totalTodos: count(todo.id) }))\n```\n\n## Optimistic Mutations\n\n### Direct mutations\n\n```tsx\nfunction TodoActions() {\n  const handleAdd = () => {\n    todoCollection.insert({\n      id: crypto.randomUUID(),\n      text: 'New todo',\n      completed: false,\n      createdAt: Date.now(),\n    })\n  }\n  const handleToggle = (todo) => {\n    todoCollection.update(todo.id, (draft) => {\n      draft.completed = !draft.completed\n    })\n  }\n  const handleDelete = (todoId) => todoCollection.delete(todoId)\n}\n```\n\n### Custom optimistic actions\n\n```tsx\nimport { createOptimisticAction } from '@tanstack/react-db'\n\nconst bootstrapTodoListAction = createOptimisticAction<string>({\n  onMutate: (listId, itemText) => {\n    listCollection.insert({ id: listId })\n    todoCollection.insert({ id: crypto.randomUUID(), text: itemText, listId })\n  },\n  mutationFn: async (listId, itemText) => {\n    const { txid } = await api.todos.bootstrapTodoList({ listId, itemText })\n    await Promise.all([\n      listCollection.utils.awaitTxId(txid),\n      todoCollection.utils.awaitTxId(txid),\n    ])\n  },\n})\n```\n\n## Developing Electric Agents\n\nThe agents subsystem spans seven packages: `agents-runtime`, `agents-mcp` (MCP bridge library used by built-ins), `agents-server`, `agents` (built-in Horton & Worker), `agents-server-ui`, `agents-desktop` (Electron wrapper for the UI), and `agents-server-conformance-tests`.\n\n**Quick start** (from project root, ensure `.env` has `ANTHROPIC_API_KEY` or `OPENAI_API_KEY`):\n\n```sh\n./scripts/dev.sh build       # install + build typescript-client, agents-runtime,\n                             # agents-mcp, agents-server, agents\n./scripts/dev.sh start       # docker + 5 dev processes; Ctrl-C stops everything\n./scripts/dev.sh start --with-agents   # also spawn built-in agents (Horton + Worker)\n./scripts/dev.sh desktop     # run the Electron desktop app (in a separate terminal,\n                             # against an already-running stack)\n./scripts/dev.sh stop        # stop processes + docker compose down\n./scripts/dev.sh teardown    # also remove Postgres volume + .streams-data/\n```\n\nBuilt-in agents (Horton + Worker) register against `agents-server` at startup and will fail with `Stream not found` if they race ahead of it. Pass `--with-agents` to `start` to spawn them after `agents-server` binds `:4437`, or run them manually in a separate terminal:\n\n```sh\nELECTRIC_AGENTS_SERVER_URL=http://localhost:4437 \\\n  node packages/agents/dist/entrypoint.js\n```\n\nLogs land in `.dev-logs/`. Use `--detach` with `start` to background the stack. See **[docs/agents-development.md](docs/agents-development.md)** for the full manual flow, env vars, testing, and iteration workflows.\n\n## Working on the TypeScript client\n\nBefore making changes to `packages/typescript-client`, **always read `packages/typescript-client/SPEC.md` first**. It is the single source of truth for the ShapeStream state machine — invariants, constraints, state transitions, and how they're enforced. Design fixes and features around the spec's invariants rather than patching symptoms ad-hoc.\n\n## Testing\n\nBefore running `pnpm typecheck`, `pnpm test`, or package-level variants such as\n`pnpm run typecheck`, run `pnpm install` from the repository/worktree root first.\nFresh worktrees do not have package-local dependency links until install has\nmaterialized the workspace, which can otherwise produce misleading TypeScript or\nmodule-resolution errors.\n\n### Unit testing (mocked)\n\n```ts\nshapeOptions: {\n  url: '/api/todos',\n  fetchClient: vi.fn(), // mock fetch\n  onError: (error) => // ... handle fetch errors\n}\n```\n\n### Running integration tests locally\n\nIntegration tests in `packages/typescript-client` (and other TS packages) run against\na real Electric server. Three services are required: **PostgreSQL**, **Electric**, and **Nginx**.\n\n**Step 1 — Start Docker Desktop** (if not already running):\n\n```sh\nopen -a Docker   # macOS\n```\n\n**Step 2 — Build the Electric image from the current branch:**\n\nThe `electricsql/electric:canary` image tracks `main` and may lack features from\nfeature branches. Build locally when working on branches that change the sync service:\n\n```sh\ncd /path/to/electric-3\ndocker build -t electric-local \\\n  -f packages/sync-service/Dockerfile \\\n  --build-context electric-telemetry=packages/electric-telemetry \\\n  packages/sync-service\n```\n\nIf your branch only changes TS client code and doesn't need new server features,\nyou can skip the build and use the canary image instead:\n\n```sh\nexport ELECTRIC_IMAGE=electricsql/electric:canary\n```\n\n**Step 3 — Start services:**\n\n```sh\ncd packages/sync-service/dev\nELECTRIC_IMAGE=electric-local \\\n  docker compose -f docker-compose.yml -f docker-compose-electric.yml \\\n  up --wait postgres electric nginx\n```\n\nServices will be available at:\n\n- PostgreSQL: `localhost:54321` (postgres/password)\n- Electric API: `http://localhost:3000`\n- Nginx proxy: `http://localhost:3002`\n\n**Step 4 — Run tests:**\n\n```sh\ncd packages/typescript-client\npnpm test              # watch mode\npnpm test --run        # single run\npnpm test -t \"pattern\" # filter by test name\n```\n\n**Teardown:**\n\n```sh\ncd packages/sync-service/dev\ndocker compose -f docker-compose.yml -f docker-compose-electric.yml down\n```\n\n## ⚠️ Critical Gotchas\n\n1. **Use latest packages** - Check npm for `@electric-sql/*` & `@tanstack/*-db`\n2. **txid handshake required** - Prevents UI flicker when optimistic→synced state\n3. **Local dev slow shapes** - HTTP/1.1 6-connection limit. Fix: HTTP/2 proxy (Caddy/nginx) or Electric Cloud ([Electric][18])\n4. **Proxy must forward headers/params** - Preserve Electric query params\n5. **Parse custom types:**\n\n```ts\nshapeOptions: {\n  parser: {\n    timestamptz: (date: string) => new Date(date)\n  }\n}\n```\n\n## Framework integrations\n\n```sh\nnpm install @tanstack/{angular,react,solid,svelte,vue}-db\n```\n\n```ts\nimport { useLiveQuery } from '...'\nconst { data, isLoading } = useLiveQuery((q) =>\n  q.from({ todos: todosCollection })\n)\n```\n\n**React Native:** Requires `react-native-random-uuid` + import in entry point\n\n## Migration from TanStack Query\n\n1. Wrap `useQuery` in Query Collection (`queryCollectionOptions`)\n2. Replace selectors with live queries\n3. Port mutations to collection handlers\n4. Switch to Electric Collection (no component changes)\n\n## Deployment\n\n### Electric Cloud\n\n```sh\nnpx @electric-sql/start my-app\npnpm claim && pnpm deploy\n```\n\n### Self-hosted\n\n```sh\ndocker run -e DATABASE_URL=postgres://... electricsql/electric\n```\n\nDocker compose:\n\n```yaml\nname: 'electric-backend'\nservices:\n  postgres:\n    image: postgres:16-alpine\n    environment:\n      POSTGRES_DB: electric\n      POSTGRES_USER: postgres\n      POSTGRES_PASSWORD: password\n    ports: ['54321:5432']\n    volumes: ['./postgres.conf:/etc/postgresql/postgresql.conf:ro']\n    tmpfs: ['/var/lib/postgresql/data', '/tmp']\n    command: ['postgres', '-c', 'config_file=/etc/postgresql/postgresql.conf']\n\n  backend:\n    image: electricsql/electric:canary\n    environment:\n      DATABASE_URL: postgresql://postgres:password@postgres:5432/electric?sslmode=disable\n      ELECTRIC_INSECURE: true\n    ports: ['3000:3000']\n    depends_on: ['postgres']\n```\n\n**Postgres requirements:** v14+, logical replication, user with REPLICATION role, `wal_level=logical`\n\n## Stack (web/mobile)\n\n- **DB:** Postgres (Neon/Supabase/Crunchy with logical replication)\n- **Backend:** TanStack Start+Drizzle+tRPC/REST\n- **Proxy:** Edge function/server route\n- **Client:** TanStack DB (React/Expo)\n\n## Evolution from Old Electric\n\n**Old:** Bidirectional SQLite sync, handled reads+writes\n**New:** Electric (Read-only HTTP streaming from Postgres) + TanStack DB (optimistic writes via API)\n\nAvoid old patterns:\n\n```ts\n// ❌ OLD (doesn't exist)\nconst { db } = await electrify(conn, schema)\nawait db.todos.create({ text: 'New todo' })\n```\n\nWrite path: `todos.insert()`→optimistic→`onInsert`→API→Postgres txid→Electric streams→reconcile→drop optimistic\nPrefer TanStack DB collections over lower-level Shape/ShapeStream/useShape APIs.\n\n## References\n\n[1]: https://electric-sql.com/docs/api/http.md\n[2]: https://tanstack.com/db/latest/docs/overview.md\n[3]: https://tanstack.com/db/latest/docs/collections/electric-collection.md\n[4]: https://electric-sql.com/docs/guides/auth.md\n[5]: https://electric-sql.com/docs/quickstart.md\n[6]: https://electric-sql.com/docs/guides/shapes.md\n[7]: https://tanstack.com/db/latest/docs/guides/live-queries.md\n[8]: https://electric-sql.com/blog/2024/11/21/local-first-with-your-existing-api.md\n[9]: https://electric-sql.com/docs/api/clients/typescript.md\n[10]: https://electric-sql.com/docs/guides/security.md\n[11]: https://electric-sql.com/product/cloud.md\n[12]: https://tanstack.com/db/latest/docs/collections/query-collection.md\n[13]: https://tanstack.com/db/latest/docs/guides/error-handling.md\n[14]: https://electric-sql.com/docs/stacks.md\n[15]: https://electric-sql.com/blog/2025/07/29/local-first-sync-with-tanstack-db.md\n[16]: https://tanstack.com/blog/tanstack-db-0.1-the-embedded-client-database-for-tanstack-query.md\n[17]: https://frontendatscale.com/blog/tanstack-db/\n[18]: https://electric-sql.com/docs/guides/troubleshooting.md#slow-shapes\n","category":"root","tokens":3612},{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# Claude Code Guidelines\n\nSee [AGENTS.md](./AGENTS.md) for project conventions, architecture, and coding guidelines.\n","category":"root","tokens":30}]}