# Investibook — Claude Code Instructions

> Architecture and project layout: see [STRUCTURE.md](./STRUCTURE.md)
> Changelog: see [CHANGELOG.md](./CHANGELOG.md)
> Troubleshooting (deployment, cluster access, log queries): see [docs/troubleshooting.md](./docs/troubleshooting.md)

## Development Commands

```bash
make install          # Install all deps (backend: uv sync, frontend: pnpm install, agentkit plugins: bun install)
make web              # Start frontend on :3000 (via frontend/Makefile dev target)
make api              # Start backend on :8000 with OpenCode agent (uvicorn investibook.main:app --reload)
make kill             # pkill uvicorn + next
make clean            # Remove frontend node_modules/.next, backend __pycache__/.venv
```

`NEXT_PUBLIC_API_URL` is set in `frontend/.env.local` for local dev (defaults to `http://localhost:8000`; CORS is allowed for localhost:3000/3001). In production (K8s), this env var is unset and the frontend uses relative paths routed through the ALB ingress. Backend env vars: `ACP_AGENT_TYPE` (`opencode`/`claude`), `ACP_AGENT_BIN` (optional absolute path to the selected ACP binary; otherwise `shutil.which()` detects `opencode` or `claude-code-acp`), `DATA_DIR` (defaults to `backend/data/`), `TEMPLATES_DIR` (defaults to `backend/templates/`), `AGENTKIT_SKILLS_DIR` (defaults to `agentkit/skills/`). The backend Docker image installs `opencode`; local development does not install it via `make install`, so ensure `opencode` is on `PATH` or set `ACP_AGENT_BIN`.

## Conventions

- **Workspace compatibility**: 除数据库层需要兼容历史数据迁移外，单个 workspace 内的文件布局、SKILL 指引、backend/frontend 读取逻辑不做旧 workspace 兼容。每个 workspace 都是独立新建并写入自己的文件，改动时只保留当前标准布局。
- **All pages are client components** — files use `'use client'` directive. No Server Components currently.
- **UI imports**: Prefer `@tidbcloud/uikit` (`Stack`, `Text`, `Title`, `Group`, `Box`, `Badge`, `ActionIcon`, `Tooltip`, `Button`, `Divider`) over raw HTML or other libraries. Ant Design is used selectively (e.g., `Button` with `@ant-design/icons`).
- **Path aliases**: Use `@/` for imports from the `frontend/` directory root (e.g., `@/components/ChatPanel`, `@/lib/workspace`). The alias is configured in `frontend/tsconfig.json`.
- **Component structure**: Each component lives in its own folder under `frontend/components/` with an `index.tsx` entry point and co-located helpers/types.
- **Inline styles**: Components use inline `style={{}}` objects rather than CSS modules or Tailwind classes.
- **Source types**: Defined in `frontend/lib/workspace.tsx` as `SourceType = 'jira_issue' | 'git_repo' | 'cluster_meta' | 'user_meta' | 'skill'` with metadata in `SOURCE_TYPE_META`.
- **Default sources**: New workspaces get display-only skill capability entries. Jira issue sources are user-added.
- **Authentication**: Email-based login (`/login`). `fetchWithAuth()` in `frontend/lib/auth.ts` injects `Authorization: Bearer <token>` on every API call and redirects to `/login` on 401. Always use `fetchWithAuth` instead of raw `fetch` for API calls.
- **SSE event types**: `session_created`, `text`, `thought`, `tool`, `complete`, `error` — keep in sync between `backend/investibook/core/acp.py` (`WebACPClient.session_update`) and `frontend/components/ChatPanel/types.ts` (`SSEEvent`).
- **Agent skills**: Configure built-in skills under `agentkit/skills/builtin/`; workspaces receive skills via `.opencode/skills/`.
- **Backend package**: All backend logic lives in the `backend/investibook/` Python package. Route handlers are in `backend/investibook/api/`, business logic in `backend/investibook/core/`, DB access in `backend/investibook/db/`. The legacy `backend/main.py` monolith still exists as a backward-compat shim but should not be modified.
- **i18n (frontend)**: Three locales are supported (`zh` / `en` / `ja`) via `frontend/lib/i18n/`. **Every user-visible string** — labels, tooltips, aria-labels, placeholders, slash-command descriptions, badge text, error/empty/loading copy, etc. — must render through `t()` from `useTranslation()` (e.g. `const { t } = useTranslation(); <Text>{t('chat.copyMarkdown')}</Text>`). Add the key to **all three** message files (`frontend/lib/i18n/messages/{en,zh,ja}.ts`); `Record<TranslationKey, string>` makes missing keys a TypeScript error. `en.ts` is the source of truth — the `TranslationKey` union is derived from it. Strings that legitimately stay locale-neutral (technical identifiers like `Mon`–`Sun` axis labels, code blocks, persisted chat content) are exempt and should be called out in the PR description. See `## Adding User-Visible UI Strings` below for the full workflow.

## Adding User-Visible UI Strings

Follow this whenever you add or change any text, tooltip, aria-label, placeholder, badge label, slash-command description, or empty/loading/error copy that the user can see.

1. **Pick a stable, semantic key** using the screen / component as the namespace: e.g. `chat.copyMarkdown`, `sources.addJiraTicket`, `workspaceHeader.copyLink`, `studio.evidenceTitle`, `admin.clinic.title`. Reuse existing keys when the same string already appears elsewhere.
2. **Add the key + English value to `frontend/lib/i18n/messages/en.ts`** — this file is the source of truth and the `TranslationKey` union is derived from it.
3. **Add the same key with translations to `frontend/lib/i18n/messages/zh.ts` and `frontend/lib/i18n/messages/ja.ts`**. `Record<TranslationKey, string>` makes missing keys a TypeScript error, so all three locales must contain every key.
4. **Render via `t()`**: `const { t } = useTranslation();` then `t('your.key')`. For interpolation pass a vars object: `t('chat.toolRunning', { count })` (templates use `{name}` placeholders).
5. **Validate**: `cd frontend && pnpm tsc --noEmit` must pass before committing.

Caveats:
- The `useTranslation()` hook only works inside React components / hooks. Helper functions that produce user-visible text should accept `t` as a parameter (see `buildWelcomeMessage(t)` in `frontend/components/ChatPanel/index.tsx`) — do **not** import the dictionaries directly.
- Persisted content (e.g. real chat user/assistant messages saved to the backend) intentionally stays in the language it was written in. Do **not** wrap such content in `t()`.
- Locale-neutral technical identifiers (axis day abbreviations, code in code blocks, raw cluster / job IDs) are exempt — surface this exemption explicitly in the PR description.

## Adding a New Source Type

1. Add the type string to `SourceType` union in `frontend/lib/workspace.tsx`
2. Add metadata entry to `SOURCE_TYPE_META` (label, icon, color, description) — `label` and `description` are user-visible, so add i18n keys for them and render via `t()` at the call sites (see [Adding User-Visible UI Strings](#adding-user-visible-ui-strings))
3. Update `AddSourceModal` in `frontend/components/SourcesPanel/AddSourceModal.tsx` if user-selectable — any new copy in the modal also needs i18n keys
4. If it has a display-only default, add to `createDefaultSources()` in `frontend/lib/workspace.tsx`

## Adding a New Artifact Type

1. Add the type string to `ArtifactType` union in `frontend/lib/workspace.tsx`
2. Create a renderer component under `frontend/components/` — every user-visible string in the renderer (titles, section headers, empty states, tooltips, button labels) must go through `t()`; add the corresponding keys to all three locale files (see [Adding User-Visible UI Strings](#adding-user-visible-ui-strings))
3. Wire it into `frontend/components/StudioPanel/index.tsx` detail view logic

## Adding a New Backend API Route

1. Create or update the appropriate router in `backend/investibook/api/` (e.g. `workspaces.py` for workspace endpoints)
2. Add Pydantic request/response models to `backend/investibook/models/requests.py`
3. Add DB helpers to `backend/investibook/db/queries.py` if persistence is needed
4. Register the router in `backend/investibook/main.py` if it's a new router
