Clarify that desktop dev worktree isolation (renderer port + app name) is automatic and independent of .env.worktree, which only covers backend/ frontend DB names/ports. Follow-up to MUL-3724 (#4598). Co-authored-by: Lambda <agent@multica.ai> Co-authored-by: multica-agent <github@multica.ai>
12 KiB
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.mdxapps/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. Readapps/mobile/CLAUDE.mdbefore 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 state: selected workspace, filters, drafts, modals, tab layout, and navigation history.
- Shared Zustand stores live in
packages/core/, never inpackages/views/or app directories. - React Context is for platform plumbing only, such as
WorkspaceIdProviderandNavigationProvider. - Only auth/workspace stores may call
api.*directly. Other server interaction belongs in queries/mutations. - Workspace-scoped query keys must include
wsId. - Mutations should be optimistic by default: patch locally, send request, roll back on failure, invalidate on settle.
- WebSocket events invalidate or patch Query cache; they never write directly to Zustand stores.
- 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 calluseWorkspaceId()internally unless the hook is guaranteed to run under the provider.
Package Boundaries
These are hard constraints:
packages/core/: noreact-dom,localStorage(useStorageAdapter),process.env, or UI libraries.packages/ui/: no@multica/coreimports and no business logic.packages/views/: nonext/*, noreact-router-dom, no stores. UseNavigationAdapter,useNavigation(), and<AppLink>.apps/web/platform/: only place for Next.js navigation/platform APIs.apps/desktop/src/renderer/src/platform/: only place forreact-router-domnavigation wiring.- Every workspace under
apps/andpackages/must declare directly imported external packages in its ownpackage.json. - Shared dependencies use
catalog:frompnpm-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:
- Next.js, Electron, or router APIs stay in the app/platform layer.
- Headless logic belongs in
packages/core/. - Shared UI or business views belong in
packages/views/. - 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:
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, runpnpm generate:reserved-slugs, and commit the generatedpackages/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 relevantSKILL.mdandreferences/*-source-map.mdin the same PR.
API Compatibility
Frontend code must survive backend response drift, especially in installed desktop builds.
- Parse API JSON with
parseWithFallbackinpackages/core/api/schema.tsand a zod schema. Do not cast network JSON toT. - 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
defaultbranch. - 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, orrequireDaemonRuntimeAccess; subsequent writes use the resolvedentity.ID. - Pure UUID inputs from request boundaries use
parseUUIDOrBadRequest(w, s, fieldName)and return immediately onok=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:
- Put the page/component in
packages/views/<domain>/. - Add platform wiring in both
apps/web/app/and the desktop router, unless the desktop flow is a transition overlay. - Use
useNavigation().push()or<AppLink>in shared code. - Use shared guards/providers such as
DashboardGuardfrompackages/views/layout/. - Keep platform-only UI in the app or inject it through props/slots.
- 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
WindowOverlaystate, 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
WindowOverlaytype instores/window-overlay-store.ts; do not add them toroutes.tsx. setCurrentWorkspace(slug, uuid)from@multica/core/platformis the active workspace source of truth.- Code that leaves workspace context must call
setCurrentWorkspace(null, null)explicitly. - Leave/delete workspace flow order: read cached destination, clear current workspace, navigate, then run the mutation.
- 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
<DragStrip />from@multica/views/platformas the first flex child. Interactive controls in the top 48px needWebkitAppRegion: "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/coretypes 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 <component>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 mocknext/*orreact-router-dom.- Mock
@multica/corestores with the Zustand callable-store shape (selectorFnplusgetState). - Mock
@multica/core/apifor API calls. - E2E tests should use
TestApiClientfor 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:
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: createv0.x.x, push it, and letrelease.ymlpublish 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-IDselects the workspace. - Issue assignees are polymorphic:
assignee_typeplusassignee_idcan reference a member or an agent.