# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. compass === a construction project management system built with Next.js 15 + React 19, designed to replace BuilderTrend with a lean, self-hosted, open-source alternative. deployed to Cloudflare Workers via OpenNext. quick start --- ```bash bun dev # turbopack dev server on :3000 bun run build # production build bun preview # test build on cloudflare runtime bun deploy # build and deploy to cloudflare workers bun lint # run eslint ``` database commands: ```bash bun run db:generate # generate drizzle migrations from schema bun run db:migrate:local # apply migrations locally bun run db:migrate:prod # apply migrations to production D1 ``` mobile (capacitor): ```bash bun cap:sync # sync web assets + plugins to native projects bun cap:ios # open xcode project bun cap:android # open android studio project ``` tech stack --- | layer | tech | |-------|------| | framework | Next.js 15 App Router, React 19 | | language | TypeScript 5.x | | ui | shadcn/ui (new-york style), Tailwind CSS v4 | | charts | Recharts | | database | Cloudflare D1 (SQLite) via Drizzle ORM | | auth | WorkOS (SSO, directory sync) | | ai agent | AI SDK v6 + OpenRouter (kimi-k2.5 model) | | integrations | NetSuite REST API, Google Drive API | | mobile | Capacitor (iOS + Android webview) | | themes | 10 presets + AI-generated custom themes (oklch) | | state | React Context, server actions | critical architecture patterns --- ### server actions & data flow - all data mutations go through server actions in `src/app/actions/` - pattern: `return { success: true } | { success: false; error: string }` - server actions revalidate paths with `revalidatePath()` to update client - no fetch() in components - use actions instead - environment variables accessed via `getCloudflareContext()` → `env.DB` for D1 ### database - drizzle ORM with D1 (SQLite dialect) - text IDs (UUIDs), text dates (ISO 8601 format) - migrations in `drizzle/` directory - **add new migrations, never modify old ones** - schema files: - `src/db/schema.ts` - core tables (users, projects, customers, vendors, etc.) - `src/db/schema-netsuite.ts` - netsuite sync tables - `src/db/schema-plugins.ts` - plugins, plugin_config, plugin_events - `src/db/schema-theme.ts` - custom_themes, user_theme_preference ### authentication & middleware - WorkOS handles SSO, email/password, directory sync - middleware in `src/middleware.ts` checks session and redirects unauthenticated users to `/login` - public paths: `/`, `/login`, `/signup`, `/reset-password`, `/verify-email`, `/invite`, `/callback`, `/api/auth/*`, `/api/netsuite/*` - `getCurrentUser()` from `lib/auth.ts` returns user info with database lookup fallback ### ai agent harness - located in `src/lib/agent/` - a complete AI-assisted system - `provider.ts`: OpenRouter setup for kimi-k2.5 model - `tools.ts`: queryData, navigateTo, showNotification, renderComponent, plus theme tools (listThemes, setTheme, generateTheme, editTheme) and plugin tools (installSkill, uninstallSkill, toggleInstalledSkill, listInstalledSkills) - `system-prompt.ts`: dynamic prompt builder with page/user context - `catalog.ts`: component specs for DynamicUI rendering - `chat-adapter.ts`: getTextFromParts, action registry, tool dispatch - `src/app/api/agent/route.ts`: streamText endpoint with 10-step multi-tool loop - `src/app/actions/agent.ts`: D1 persistence (save/load/delete conversations) - unified chat architecture: one component, two presentations - `ChatProvider` (layout level) owns chat state + panel open/close + persistence - `ChatView variant="page"` on /dashboard (hero idle, typewriter, stats) - `ChatView variant="panel"` in `ChatPanelShell` on all other pages - `src/hooks/use-compass-chat.ts`: shared hook (useChat + action handlers + tool dispatch) - chat state persists across navigation - usage tracking: `agent_config`, `agent_usage`, `user_model_preference` tables track tokens/costs per conversation, per-user model override if admin allows - ai sdk v6 gotchas: - `inputSchema` not `parameters` for tool() definitions - UIMessage uses `parts` array, no `.content` field - useChat: `sendMessage({ text })` not `append({ role, content })` - useChat: `status` is "streaming"|"submitted"|"ready"|"error", not `isGenerating` - useChat: needs `transport: new DefaultChatTransport({ api })` not `api` prop - zod schemas must use `import { z } from "zod/v4"` to match AI SDK internals - ToolUIPart: properties may be flat or nested under toolInvocation ### netsuite integration - full bidirectional sync via REST API - key files in `src/lib/netsuite/`: - `config.ts`: account setup, URL builders - `auth/`: oauth 2.0 flow, token manager, AES-GCM encryption - `client/`: base HTTP client (retry, circuit breaker), record client, suiteql client - `rate-limiter/`: semaphore-based concurrency limiter (15 concurrent default) - `sync/`: sync engine, delta sync, conflict resolver, push logic, idempotency - `mappers/`: customer, vendor, project, invoice, vendor-bill mappers - env vars: NETSUITE_CLIENT_ID, NETSUITE_CLIENT_SECRET, NETSUITE_ACCOUNT_ID, NETSUITE_REDIRECT_URI, NETSUITE_TOKEN_ENCRYPTION_KEY, NETSUITE_CONCURRENCY_LIMIT - gotchas: - 401 errors can mean timeout, not auth failure - "field doesn't exist" often means permission denied - 15 concurrent request limit shared across ALL integrations - no batch create/update via REST (single record per request) - sandbox URLs use different separators (123456-sb1 vs 123456_SB1) - omitting "line" param on line items adds new line (doesn't update) ### capacitor mobile app - webview-based native wrapper loading the live cloudflare deployment (not a static export) - the web app must never break because of native code: all capacitor imports are dynamic (`await import()` inside effects), gated behind `isNative()` checks, components return `null` on web - platform detection: `src/lib/native/platform.ts` exports `isNative()`, `isIOS()`, `isAndroid()` - native hooks in `src/hooks/`: `use-native.ts`, `use-native-push.ts`, `use-native-camera.ts`, `use-biometric-auth.ts`, `use-photo-queue.ts` - native components in `src/components/native/`: biometric lock screen, offline banner, status bar sync, upload queue indicator, push registration - offline photo queue (`src/lib/native/photo-queue.ts`): survives app kill, uses filesystem + preferences + background uploader - push notifications via FCM HTTP v1 (`src/lib/push/send.ts`), routes to both iOS APNS and Android FCM - `src/app/api/push/register/route.ts`: POST/DELETE for device token management - env: FCM_SERVER_KEY - see `docs/native-mobile.md` for full iOS/Android setup and app store submission guide ### plugin/skills system - agent can install external "skills" (github-hosted SKILL.md files in skills.sh format) or full plugin modules - skills inject system prompt sections at priority 80, full plugins can also provide tools, components, query types, and action handlers - source types: `builtin`, `local`, `npm`, `skills` - key files in `src/lib/agent/plugins/`: - `types.ts`: PluginManifest, PluginModule, SkillFrontmatter - `skills-client.ts`: fetchSkillFromGitHub, parseSkillMd - `loader.ts`: loadPluginModule (local/npm/builtin) - `registry.ts`: buildRegistry, getRegistry (30s TTL cache per worker isolate) - `src/app/actions/plugins.ts`: installSkill, uninstallSkill, toggleSkill, getInstalledSkills - database: `plugins`, `plugin_config`, `plugin_events` tables in `src/db/schema-plugins.ts` ### visual theme system - per-user themeable UI with 10 built-in presets + AI-generated custom themes - themes are full oklch color maps (32 keys for light + dark), fonts (sans/serif/mono with google fonts), design tokens (radius, spacing, shadows) - presets: native-compass (default), corpo, notebook, doom-64, bubblegum, developers-choice, anslopics-clood, violet-bloom, soy, mocha - key files in `src/lib/theme/`: - `types.ts`: ThemeDefinition, ColorMap, ThemeFonts, ThemeTokens - `presets.ts`: all 10 preset definitions - `apply.ts`: `applyTheme()` injects css vars into `