Skip to content

Latest commit

 

History

History
87 lines (66 loc) · 5.47 KB

CLAUDE.md

File metadata and controls

87 lines (66 loc) · 5.47 KB

CLAUDE.md

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

Build & Run

pnpm install          # install dependencies
pnpm dev              # development mode (tsx hot-reload)
pnpm dev:debug        # DEV=true — enables React DevTools integration
pnpm build            # TypeScript compile to dist/
pnpm start            # run compiled output
pnpm tsc --noEmit     # type-check only

Architecture

This is a terminal AI coding assistant built on sema-core (event-driven engine) with Ink (React for terminal) as the TUI layer.

Three-layer data flow:

sema-core engine (events) → React Hooks (event→state) → Ink Components (render)
  1. Engine (sema-core npm package) — provides SemaCore (process-level: models, sessions) and SemaSession (session-level: processUserInput(), event bus). All AI logic, tool execution, and permission handling is inside sema-core.

  2. Hooks (src/hooks/) — bridge layer. Each hook subscribes to sema-core events and converts them to React state:

    • useBootstrap — initialization state machine: create SemaCore → check models → create session
    • useSession — state:update, session:interrupted → idling/processing
    • useMessages — input:processing, message:complete, tool:execution:*, task:agent:* → message array
    • useStreaming — message:text:chunk, message:thinking:chunk (32ms throttled, ref+buffer) → streamingText + thinkingText
    • useUsage — conversation:usage, session:ready → token usage
    • usePermissions — tool:permission:request → pending state + respond callback
    • useForms — pick:option:request, plan:exit:request → inline form state
    • useModels — calls SemaCore API directly for model CRUD
    • useTodos — todos:update → todo list
    • useEvents — compact:exec, topic:update → compact notification + session topic
  3. Components (src/components/) — Ink React components consuming hook state. No component directly calls sema-core APIs. Key patterns:

    • MessageList uses a top <Box flexGrow={1} /> spacer to push messages to the bottom — oldest content scrolls off the top naturally
    • PermissionConfirm, FormInline, PlanExitInline are conditionally rendered inline (not modal overlays)

Screen routing: App.tsx manages chat vs models screen via a React state switch. /model command triggers the switch.

UI Design Rules

  • Claude Code style REPL — no panel borders, no chat bubbles. Text flows naturally in terminal.
  • Permission is inline — not a modal overlay. (y) agree (a) always allow (n) refuse rendered inside the conversation flow.
  • Forms are inline — pick:option:request renders radio/checkbox/text directly in flow.
  • Streaming throttled — useMessages uses a 32ms ref+buffer pattern to avoid rendering 1000s of times per response.
  • Status bar — single line: model · mode · tokens · state. No horizontal separator line.
  • Color — logo uses #CD4B1A (Claude Code orange).

Key Integration Points with sema-core

  • Model config persists to ~/.sema/model.conf automatically after addModel()
  • First launch: if no models exist, auto-routes to ModelScreen for configuration
  • session:error with context_length_exceeded triggers auto-compact (handled by sema-core internally)
  • The session is created once on app start; ChatScreen receives the SemaSession instance as a prop
  • SemaSession events are session-scoped; use session.on() / session.off() for lifecycle management

Important Implementation Notes

  • Package has "type": "module" in package.json — all files are ESM, imports must include .js extension
  • Ink 7 + React 19 — render() uses incrementalRendering: true (only redraws changed lines) and alternateScreen: true (alt screen buffer, like vim) for flicker-free terminal output
  • MessageList uses a top <Box flexGrow={1} /> spacer to push messages to bottom — overflow scrolls off the top naturally
  • Streaming throttle split — useStreaming manages text/thinking chunk buffering (32ms ref+buffer), useMessages manages only message array
  • ModelScreen views use separate useInput({ isActive }) — each sub-view has its own input handler, avoiding giant switch blocks
  • useBootstrap hook — encapsulates the init state machine, keeping App.tsx lean (~70 lines)
  • Custom useInput-based text input with slash command autocomplete in InputArea.tsx
  • marked v15 for markdown rendering (custom renderer in src/render/markdown.tsx)
  • sema-core source is at ../sema-code-core/ — reference when debugging engine behavior
  • React DevTools: run pnpm dev:debug + npx react-devtools to inspect component tree/props/state in real-time
  • No API keys or env vars needed at startup — user configures models inside the app on first launch
  • pnpm is the required package manager (packageManager field in package.json)

Git Commit Convention

  • Format: <emoji> <description> — always prefix commit messages with an emoji, followed by a short description.
  • Examples: 🐛 fix: backspace key on macOS, ✨ feat: add systemPrompt identity, 📝 docs: update README.

Project-Scoped Memory

The memory/ directory in this project contains design decisions:

  • bruce-code-ui-reference.md — UI must reference Claude Code style (REPL, inline permissions, > prompt)
  • See ARCHITECTURE.md for architecture design, data flow, and component tree