# CLAUDE.md Guidance for Claude Code when working in this repository. Keep this file short and authoritative: rules here should be hard to infer from code or easy to get wrong. ## Conventions The source of truth for code naming, i18n glossary, and Chinese product voice is: - `apps/docs/content/docs/developers/conventions.mdx` - `apps/docs/content/docs/developers/conventions.zh.mdx` Read it before editing translations in `packages/views/locales/`, naming routes/packages/files/DB columns/types, or writing Chinese UI/docs copy. Do not rely on `packages/views/locales/glossary.md`; it is only a redirect stub. ## Project Shape Multica is an AI-native task management platform for small teams, with agents as first-class assignees that can own issues, comment, and change status. - `server/`: Go backend, Chi router, sqlc, gorilla/websocket. - `apps/web/`: Next.js App Router. - `apps/desktop/`: Electron desktop app. - `apps/mobile/`: Expo / React Native iOS app. Read `apps/mobile/CLAUDE.md` before touching it. - `packages/core/`: headless business logic, API client, React Query hooks, Zustand stores. - `packages/ui/`: atomic UI components only. - `packages/views/`: shared business pages/components for web and desktop. - `packages/tsconfig/`: shared TypeScript config. Shared packages export raw `.ts` / `.tsx` and are compiled by consuming apps. Dependency direction is `views -> core + ui`; `core` and `ui` must stay independent. ## State Rules Keep server state and client state separate. - TanStack Query owns server state: issues, users, workspaces, inbox, agents, members, and anything fetched from the API. - Zustand owns client/view state: filters, drafts, modals, tab layout, and navigation history. Current workspace identity is route-driven; platform stores/singletons may mirror slug/id only for headers, persistence namespaces, and reconnects. - Shared Zustand stores live in `packages/core/`, never in `packages/views/` or app directories. - React Context is for platform plumbing only, such as `WorkspaceIdProvider` and `NavigationProvider`. - Only auth/workspace stores may call `api.*` directly. Other server interaction belongs in queries/mutations. - Workspace-scoped query keys must include `wsId`. - Optimistic updates only when ALL hold: outcome locally predictable, user stays on the same screen (no navigation), failure is rare, rollback is trivial. Canonical: status/assignee/toggle field patches — patch determinate caches, roll back on failure, invalidate uncertain projections on settle. - Flows that navigate or confirm (create, delete, leave) must await the server before navigating or cleaning up; never optimistically remove an entity from cache. - Chat/message send uses the pending-message pattern: render immediately with a visible pending state and retry on failure, not silent optimism. - WebSocket events invalidate or patch Query cache for server data. They must never mirror server payload data into Zustand; clearing client-owned pointers (active session, selection, current workspace) is allowed only with a single responder and a self-initiated guard when this client can cause the event. - Persist durable preferences/drafts/layout. Do not persist server data or ephemeral UI state. - Zustand selectors must return stable references. Do not return freshly allocated objects/arrays from selectors without shallow comparison. - Hooks that need workspace context should accept `wsId`; do not call `useWorkspaceId()` internally unless the hook is guaranteed to run under the provider. ## Package Boundaries These are hard constraints: - `packages/core/`: no `react-dom`, `localStorage` (use `StorageAdapter`), `process.env`, or UI libraries. - `packages/ui/`: no `@multica/core` imports and no business logic. - `packages/views/`: no `next/*`, no `react-router-dom`, no stores. Use `NavigationAdapter`, `useNavigation()`, and ``. - `apps/web/platform/`: only place for Next.js navigation/platform APIs. - `apps/desktop/src/renderer/src/platform/`: only place for `react-router-dom` navigation wiring. - Every workspace under `apps/` and `packages/` must declare directly imported external packages in its own `package.json`. - Shared dependencies use `catalog:` from `pnpm-workspace.yaml`; `apps/mobile/` pins Expo/React Native related versions directly. ## Sharing Rules Web and desktop share business logic, hooks, stores, components, and views through `packages/core/`, `packages/ui/`, and `packages/views/`. If the same logic exists in both web and desktop, extract it unless it depends on platform APIs: 1. Next.js, Electron, or router APIs stay in the app/platform layer. 2. Headless logic belongs in `packages/core/`. 3. Shared UI or business views belong in `packages/views/`. 4. Shared primitives belong in `packages/ui/`. Mobile is independent. It may import types and pure functions from `@multica/core`, with `import type` for types, but owns its UI, state, hooks, providers, i18n, React version, build pipeline, and release cadence. ## Commands Use the repo scripts as the source of truth. Common commands: ```bash make dev # auto-setup and start the app make start # start backend + frontend make stop # stop app processes for this checkout make server # run Go server only make daemon # run local daemon make test # Go tests make sqlc # regenerate sqlc code after SQL changes pnpm install pnpm dev:web pnpm dev:desktop pnpm build pnpm typecheck pnpm lint pnpm test # TS/Vitest tests through Turborepo pnpm exec playwright test pnpm ui:add badge # shadcn/Base UI component into packages/ui ``` Worktrees share one PostgreSQL container and get isolated DB names/ports via `.env.worktree`. `make dev` auto-detects this. For manual setup use `make worktree-env`, `make setup-worktree`, and `make start-worktree`. `pnpm dev:desktop` additionally self-isolates per worktree (its own renderer port + app name) automatically, independent of `.env.worktree`. CI runs Node 22, Go 1.26.1, and a `pgvector/pgvector:pg17` PostgreSQL service. ## Coding Rules - TypeScript strict mode is enabled; keep types explicit. - Go follows standard conventions: `gofmt`, `go vet`, checked errors. - Code comments must be English. - Prefer existing patterns/components over new parallel abstractions. - Avoid broad refactors unless required by the task. - For internal, non-boundary code, do not add compatibility layers, fallback paths, dual writes, legacy adapters, or temporary shims unless explicitly requested. - API boundaries are different: installed desktop clients can talk to newer backends, so response parsing must follow the API compatibility rules below. - If a flow or API is being replaced and the product is not live, prefer removing the old path instead of preserving both. - New global pre-workspace routes must be a single word (`/login`, `/inbox`) or `/{noun}/{verb}` (`/workspaces/new`). Do not add hyphenated root routes like `/new-workspace`. - Reserved slugs live in `server/internal/handler/reserved_slugs.json`. Edit it, run `pnpm generate:reserved-slugs`, and commit the generated `packages/core/paths/reserved-slugs.ts`. - When changing CLI commands/flags, API fields, or product behavior documented by built-in skills under `server/internal/service/builtin_skills/*`, update the relevant `SKILL.md` and `references/*-source-map.md` in the same PR. ## API Compatibility Frontend code must survive backend response drift, especially in installed desktop builds. - Parse API JSON with `parseWithFallback` in `packages/core/api/schema.ts` and a zod schema. Do not cast network JSON to `T`. - Endpoint responses consumed by UI logic must pass through a schema before returning. - Downstream UI should optional-chain and default fields defensively. - Prefer explicit boolean checks (`=== true`) over truthy/falsy checks on server fields. - Do not pin critical affordances to one backend boolean; combine signals when possible. - Server-driven enum switches need a `default` branch. - When adding or changing an endpoint, add/update the schema and include a malformed-response test. ## Backend UUID Rules In `server/internal/handler/`, always know where a UUID came from before using it in write queries. - Resource path params that may be UUIDs or human-readable IDs must be resolved through loaders such as `loadIssueForUser`, `loadSkillForUser`, `loadAgentForUser`, or `requireDaemonRuntimeAccess`; subsequent writes use the resolved `entity.ID`. - Pure UUID inputs from request boundaries use `parseUUIDOrBadRequest(w, s, fieldName)` and return immediately on `ok=false`. - Trusted UUID round-trips from sqlc results or test fixtures use `parseUUID(s)`, which panics on invalid input. - Outside handlers, `util.ParseUUID(s) (pgtype.UUID, error)` is the safe variant; always check the error. ## Web/Desktop Features When adding a shared page or feature for web and desktop: 1. Put the page/component in `packages/views//`. 2. Add platform wiring in both `apps/web/app/` and the desktop router, unless the desktop flow is a transition overlay. 3. Use `useNavigation().push()` or `` in shared code. 4. Use shared guards/providers such as `DashboardGuard` from `packages/views/layout/`. 5. Keep platform-only UI in the app or inject it through props/slots. 6. Hooks that need workspace context should accept `wsId`. CSS for web/desktop is shared from `packages/ui/styles/`. Use semantic tokens such as `bg-background` and `text-muted-foreground`; avoid hardcoded Tailwind colors and duplicated base styles. ## Desktop Rules Desktop routing has three categories: - Session routes: workspace-scoped tab destinations such as `/:slug/issues`. - Transition flows: pre-workspace one-shot actions such as create workspace or accept invite. These are `WindowOverlay` state, not routes. - Error/stale states: stale workspace tabs should auto-heal by dropping stale tab groups, not render desktop error pages. More desktop constraints: - New pre-workspace desktop flows register a `WindowOverlay` type in `stores/window-overlay-store.ts`; do not add them to `routes.tsx`. - `setCurrentWorkspace(slug, uuid)` from `@multica/core/platform` mirrors the active route for headers, storage namespaces, and reconnects; workspace route layouts own setting it. - Code that leaves workspace context must call `setCurrentWorkspace(null, null)` explicitly. - Workspace delete must await the server before navigation/cleanup. Workspace leave currently clears/navigates before mutation only to avoid the `member:removed` realtime race; treat that as known debt, not a reusable pattern. - Cross-workspace navigation must go through the navigation adapter so it can call `switchWorkspace(slug, targetPath)`. - Full-window desktop views outside the dashboard shell must mount `` from `@multica/views/platform` as the first flex child. Interactive controls in the top 48px need `WebkitAppRegion: "no-drag"`. ## Mobile Rules Read `apps/mobile/CLAUDE.md` before touching `apps/mobile/`. It contains the mandatory pre-flight process, import limits, parity rules, tech stack, UI rules, data helpers, realtime strategy, and mobile release flow. Root-level reminders: - Mobile shares only `@multica/core` types and pure functions. - Mobile must match web/desktop product semantics: counts, permissions, enums/transitions, and data identity. - Mobile may differ in UI/interaction when the phone context requires it. ## UI Rules - Prefer shadcn/Base UI components over custom implementations. Add them with `pnpm ui:add ` from the repo root. - Use design tokens and semantic classes; avoid hardcoded colors. - Do not introduce extra local state unless the design requires it. - Handle overflow, long text, scrolling, alignment, and spacing deliberately. - If a component is identical between web and desktop, it belongs in a shared package. ## Testing Tests follow the code: | What is tested | Location | | --- | --- | | Shared business logic, stores, queries, hooks | `packages/core/*.test.ts` | | Shared UI components, pages, forms, modals | `packages/views/*.test.tsx` | | Platform wiring such as cookies, redirects, search params | `apps/web/*.test.tsx` or `apps/desktop/` | | End-to-end flows | `e2e/*.spec.ts` | | Backend | `server/` Go tests | Rules: - Never test shared component behavior in an app test file. - `packages/views/` tests must not mock `next/*` or `react-router-dom`. - Mock `@multica/core` stores with the Zustand callable-store shape (`selectorFn` plus `getState`). - Mock `@multica/core/api` for API calls. - E2E tests should use `TestApiClient` for setup/teardown. - Prefer writing the failing test in the correct package before implementation when the change is behavioral. ## Verification For code changes, run the narrowest useful checks while iterating, then run broader verification when risk justifies it or when asked. Useful checks: ```bash pnpm typecheck pnpm test make test pnpm exec playwright test make check ``` Do not claim verification passed unless you ran it. If you skip checks because the change is docs-only or the user asked not to run them, say so. ## Commits and Releases - Commits should be atomic and use conventional prefixes: `feat(scope)`, `fix(scope)`, `refactor(scope)`, `docs`, `test(scope)`, `chore(scope)`. - A production deployment requires a CLI release tag on `main`: create `v0.x.x`, push it, and let `release.yml` publish binaries and the Homebrew tap. - Bump patch by default unless the user specifies a version. ## Domain Reminders - All queries filter by `workspace_id`; membership gates access; `X-Workspace-ID` selects the workspace. - Issue assignees are polymorphic: `assignee_type` plus `assignee_id` can reference a member or an agent.