diff --git a/.claude/rules/conventions.md b/.claude/rules/conventions.md index 995031b..88148f3 100644 --- a/.claude/rules/conventions.md +++ b/.claude/rules/conventions.md @@ -64,9 +64,8 @@ keep it lean (ceiling ~200 lines). The *why* behind a non-obvious choice belongs in `docs/adr/` — and the docstring that implements it cites the ADR, so an agent meets the rationale before it "simplifies" the choice away. -`docs/adr/` doesn't exist yet; it lands via a `grill-with-docs` pass that will -promote today's scattered rationale (HNSW-over-IVFFlat in the pgvector -migration, iTunes-over-`preview_url` in `services/audio_catalog.py`, the -two-layer tool-error convention, label binarization at the subject median) into -numbered records. Until then, cite the existing home and keep new rationale out -of `AGENTS.md`. +`docs/adr/` now holds four numbered records — HNSW-over-IVFFlat (0001), +iTunes-over-`preview_url` (0002), the two-layer tool-error convention (0003), +and subject-median label binarization (0004). A new non-obvious choice gets a +new ADR (next number, format per `.agents/skills/domain-modeling/ADR-FORMAT.md`), +and the docstring implementing it cites the file by path. diff --git a/.claude/rules/frontend/code-conventions.md b/.claude/rules/frontend/code-conventions.md index 1272d01..56a900d 100644 --- a/.claude/rules/frontend/code-conventions.md +++ b/.claude/rules/frontend/code-conventions.md @@ -1,6 +1,7 @@ --- paths: - "frontend/components/**/*.{ts,tsx}" + - "frontend/features/**/*.{ts,tsx}" - "frontend/app/**/*.{ts,tsx}" - "frontend/hooks/**/*.{ts,tsx}" - "frontend/api/hooks/**/*.{ts,tsx}" @@ -11,6 +12,23 @@ paths: TypeScript/Next.js conventions for the cortexdj frontend. +## Feature slices + +Domain UI lives in `frontend/features//` (kebab-case files, same as +everywhere else) — `sessions/` and `retrieval/` today. What goes where: + +- **A slice never imports another slice.** `frontend/features/tool-panel-registry.ts` + is the composition root and the only file that reaches across slices; it + spreads each slice's `tool-panels.tsx` map into `TOOL_PANELS`. +- **Shared chat chrome stays in `components/`** (message renderer, input, + sidebar, `ui/`). A component used by two slices belongs there, not in either + slice. +- **Cross-cutting data hooks stay in `api/hooks/`**, shared parsing/format + helpers in `lib/`. A slice may own a hook only it uses. + +Intra-slice imports are relative (`./tool-panels`); anything crossing a +top-level directory uses `@/`. + ## Imports - Use the `@/` path alias for imports that cross top-level directories diff --git a/.claude/rules/frontend/vercel-ai-sdk.md b/.claude/rules/frontend/vercel-ai-sdk.md index dcbbf93..cf52001 100644 --- a/.claude/rules/frontend/vercel-ai-sdk.md +++ b/.claude/rules/frontend/vercel-ai-sdk.md @@ -1,6 +1,7 @@ --- paths: - "frontend/components/**/*.tsx" + - "frontend/features/**/*.{ts,tsx}" - "frontend/app/(chat)/**/*.{ts,tsx}" - "frontend/hooks/**/*.{ts,tsx}" - "frontend/api/hooks/**/*.{ts,tsx}" @@ -44,6 +45,10 @@ authoritative for the underlying contract that adapted UI must wire into. `ChatMessage` type in `frontend/lib/types.ts`. Carry that generic through every `UseChatHelpers` site — falling back to plain `UIMessage` loses the project's custom data/tool-part typing. -- **Tool-call panels switch on `part.type === "tool-"`** in the message - renderer. A new backend tool that needs a custom UI panel adds a branch - there. +- **Tool-call panels come from a registry, not a branch.** Each feature slice + exports a tool-name → panel map from `frontend/features//tool-panels.tsx`; + `frontend/features/tool-panel-registry.ts` spreads those maps into + `TOOL_PANELS`, and `message.tsx` looks up `part.type === "tool-"` against + it. A new backend tool that needs a custom UI panel = a slice component plus + one map entry — add `hideRawOutput: true` when the panel replaces ``'s + raw JSON output block. Don't reintroduce per-tool branching in the renderer. diff --git a/AGENTS.md b/AGENTS.md index af478ac..00ed024 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,6 +68,7 @@ FastAPI Python backend using async patterns throughout. - **Contrastive EEG↔CLAP**: `contrastive.py` (EegCLAPEncoder with CBraMod backbone + SimCLR projection head, symmetric soft-target InfoNCE, encode_session), `contrastive_dataset.py` (DeapClapPairDataset + host-portable audio cache + `trial_to_eeg_windows` shared slicer), `contrastive_train.py` (SequentialLR warmup+cosine, TensorBoard scalars + val embedding projector, grad accumulation). - **`src/cortexdj/core/config.py`**: Settings via pydantic-settings - **`src/cortexdj/migrations/`**: Alembic migrations for PostgreSQL +- **`tests/`**: Three tiers — `unit/` (default run), `integration/` (real Postgres, `-m integration`), `evals/` (real LLM, `-m eval`). See `DEVELOPMENT.md` and `.claude/rules/backend/code-conventions.md`. ### Frontend (`frontend/`) @@ -77,9 +78,10 @@ Next.js 16 with App Router. - **`app/(chat)/api/chat/route.ts`**: Proxy route to backend agent - **`components/chat.tsx`**: Chat orchestrator using `@ai-sdk/react` useChat hook - **`components/brain-context-badge.tsx`**: Displays active brain context (mood/arousal/valence) -- **`components/session-visualization.tsx`**: Tabbed session viewer — wraps `components/emotion-trajectory.tsx` (default, animated SVG trajectory through Russell's affect space) and a recharts arousal/valence timeline in Radix Tabs, with the band-power chart shared below. Auto-rendered by `components/message.tsx` when an `analyze_session` tool call is detected -- **`components/emotion-trajectory.tsx`**: Custom SVG + `motion/react` chart that plots each 4-second segment as a point in the valence/arousal plane, draws a smoothed rolling-mean path via an animated `motion.path` (`style={{ pathLength: progress }}`), and exposes a play/pause + scrubber driven by a `requestAnimationFrame` loop -- **`components/retrieved-tracks-panel.tsx`**: Ranked tracks rendered beneath `retrieve_tracks_from_brain_state` tool calls — similarity bars, inline 30s preview playback via a shared `