Files
boocode/CLAUDE.md
indifferentketchup 051f3b96ae batch4.1-5.1: dedup audit, archive 400 fix, sidebar Delete, landing-page enrichment, auto-name tool-call fix
- Fastify global empty-JSON-body parser fixes archive/unarchive/stop 400s
- Removed redundant local sessionEvents.emit at all 5+2 sites with server-side WS publishers; added dedupe guards in useSidebar/Workspace/Project handlers
- Sidebar session right-click adds Delete (destructive) with confirm Dialog
- Session.tsx navigates away on session_deleted/session_archived for the active session
- SessionLandingPage chat rows show message_count, effective_context_tokens, last_message_preview via LATERAL joins on GET /api/sessions/:id/chats
- Workspace.tsx pane drag-to-reorder using native HTML5 events (no new deps)
- CompactCard: Copy toast, Send-to-chat with target chat name, empty-state in share popover, Re-run button
- auto_name.ts: filter count gate and assistant-fetch by content <> '' so tool-call assistant rows don't trip the once-and-only-once guard
- Adds CLAUDE.md and apps/web/src/lib/format.ts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 23:36:01 +00:00

6.4 KiB
Raw Blame History

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What is BooCode

Self-hosted single-user developer chat app. AI assistant with read-only file tools (view_file, list_dir, grep, find_files) running against a local llama-swap inference server. Sessions organized by project, with a multi-pane workspace (chat + file browser side by side).

Commands

# Development (run in separate terminals)
pnpm dev:server          # tsx watch, port 3000
pnpm dev:web             # Vite dev server, port 5173 (proxies /api to :3000)

# Build
pnpm build               # builds web then server
pnpm -C apps/server build  # server only (tsc + copy schema.sql)
pnpm -C apps/web build     # web only (vite)

# Type checking (no emit)
npx tsc --noEmit                              # project references (root)
npx tsc -p apps/web/tsconfig.app.json --noEmit  # web app specifically

# IMPORTANT: root tsc --noEmit uses project references and can miss errors
# that the per-app tsconfig catches. Always verify with the per-app command
# when editing web code. The server build (pnpm -C apps/server build) is
# authoritative for server code.

# Production
docker compose build --no-cache boocode && docker compose up -d

There are no tests or linters configured.

Architecture

Monorepo: pnpm workspaces with apps/server (Fastify + postgres) and apps/web (React + Vite).

Server (apps/server/src/)

  • Fastify with @fastify/websocket and @fastify/static (serves built frontend)
  • postgres (porsager/postgres) with tagged-template SQL — no ORM. Schema in schema.sql, applied on startup. LSP may false-positive on sql<Type[]>\...`generics; CLItsc/pnpm build` is authoritative.
  • Zod for request validation and config parsing.

Key services:

  • services/inference.ts — Streams LLM responses, executes tool loops (max 5 depth), flushes to DB every 500ms. Publishes InferenceFrame events through the broker.
  • services/broker.ts — In-memory pub/sub with two channel types: per-session (message streaming) and per-user (sidebar updates). No persistence; clients reconnect on restart.
  • services/tools.ts — Four read-only file tools exposed as OpenAI function-calling schemas. All file access goes through path_guard.ts which resolves against project root.
  • services/file_ops.ts — Shared file operation implementations used by both inference tools and HTTP routes.
  • services/auto_name.ts — Non-streaming LLM call to generate 4-word session titles after first assistant reply.

Route registration: all routes registered in index.ts via register*Routes(app, sql, ...) functions. Routes are in routes/*.ts.

Frontend (apps/web/src/)

  • React 18 + React Router v6 + Tailwind v4 + shadcn/radix-ui primitives.
  • Shiki for syntax highlighting (async codeToHtml in CodeBlock.tsx and FileViewer in FileBrowserPane.tsx).
  • Path alias: @/ maps to src/.

Key patterns:

  • hooks/sessionEvents.ts — Module-singleton event bus (Set of listeners). Used for cross-component communication: session renames, file-open events, attachment dispatch. 9 event types in the discriminated union. When adding a new event type to the SessionEvent union, you must also add a case to the applyEvent switch in useSidebar.ts (even if it's a no-op return prev).
  • hooks/useSessionStream.ts — WebSocket per session, applyFrame reducer builds message list from streaming frames.
  • hooks/useUserEvents.ts — Single app-level WS to /api/ws/user with exponential backoff reconnect. Forwards frames onto the sessionEvents bus.
  • hooks/usePanes.ts — Per-session pane CRUD with 300ms debounced state PATCH (Map-based coalescing for last-write-wins).
  • hooks/useSidebar.ts — Module-singleton with Set subscriber pattern. Handles all sessionEvent types to keep sidebar in sync.
  • api/client.ts — Centralized typed fetch wrapper. All endpoints under api.* namespace.

Data flow for chat

  1. User sends message → POST /api/sessions/:id/messages creates user + assistant (status=streaming) rows
  2. inference.enqueue() starts async streaming loop
  3. LLM deltas published via broker.publish(sessionId, frame)
  4. Client's useSessionStream WS receives frames, applyFrame reducer updates message list
  5. Tool calls: inference executes tools server-side, publishes tool_call/tool_result frames, loops back to LLM
  6. Terminal states (complete/error): DB updated with final content + token counts, session_updated frame published on user channel

Multi-pane workspace

Sessions hold 15 panes (chat or file_browser). Workspace.tsx renders tab strip + CSS grid layout. Pane state persisted in session_panes table (position + JSONB state). Tab reorder via native HTML5 drag events.

Database

PostgreSQL 16. Tables: projects, sessions, messages, settings, session_panes. Schema applied idempotently on startup via applySchema(). Use clock_timestamp() (not NOW()) inside transactions for accurate per-statement timestamps.

Position-shift pattern for panes: negate-and-restore to avoid UNIQUE(session_id, position) collisions during reorder/insert/delete. Sentinel value -100 for the moving pane.

Environment

Required: DATABASE_URL, LLAMA_SWAP_URL. Optional: PORT (3000), HOST (0.0.0.0), PROJECT_ROOT_WHITELIST (/opt), DEFAULT_MODEL, LOG_LEVEL.

Workflow

  • Sam reviews all diffs and commits manually. Do not commit unless explicitly asked.
  • Deploy: cd /opt/boocode && docker compose build --no-cache boocode && docker compose up -d
  • Don't accumulate .bak-* files. Clean them up in the same batch or immediately after merge.

Conventions

  • overflowWrap not wordWrap — TypeScript's CSSStyleDeclaration marks wordWrap as deprecated (error 6385).
  • No app-layer auth. Authelia handles auth at the reverse proxy. All broker.publishUser/subscribeUser calls use 'default' as the user key.
  • TypeScript strict mode. Both apps share tsconfig.base.json.
  • Server uses NodeNext module resolution (.js extensions in imports).
  • Discriminated unions for type narrowing: Pane (by kind), SessionEvent (by type), InferenceFrame (by type).
  • shadcn primitives live in components/ui/. Don't modify them unless adding a new primitive.
  • inferLanguage() from lib/attachments.ts is the canonical file-extension-to-language map. CodeBlock.tsx keeps its own LANG_MAP because it also resolves markdown fence names.