This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A Flutter desktop/iOS client that wraps Claude Code CLI in a persistent, multi-project chat UI with streaming, thinking visualization, tool-call tracking, and image support. It does not call Anthropic API directly — it talks to a local Go relay (clawrelay-api submodule, port 50009) that forks a claude CLI subprocess per request and streams output as OpenAI-compatible SSE.
flutter pub get # Install dependencies
flutter pub run build_runner build # Regenerate Drift database code (database.g.dart)
flutter run -d macos # Run on macOS (also: -d linux, -d windows, -d ios)
flutter build macos --release # Build release for macOS
flutter analyze # Run static analysis (flutter_lints)
flutter test # Run testsAfter modifying lib/database/database.dart, always regenerate with build_runner.
Flutter Client ──HTTP/SSE──▶ clawrelay-api (Go :50009) ──subprocess──▶ claude CLI ──▶ Anthropic API
All state flows through Riverpod providers in lib/providers/:
databaseProvider— DriftAppDatabasesingleton, overridden inmain()viaProviderScopeprojectsStreamProvider— Watches all projects from DB (ordered byupdatedAt DESC)selectedProjectIdProvider— Currently selected project ID (StateProvider<int?>)unreadProjectIdsProvider— In-memory set of project IDs with unread messageschatProvider(projectId)—AsyncNotifierProvider.familykeyed by project ID; managesChatState(messages, streaming text, thinking, tool calls, error)serverUrlProvider/defaultModelProvider/themeModeProvider— Settings persisted via SharedPreferences
ChatNotifier.sendMessage() in chat_provider.dart:
- Saves user message to DB, appends to display list
- Builds
ChatCompletionRequestwith full message history + system prompt + working directory - Calls
ClaudeApi.streamChat()which POSTs to/v1/chat/completionsand parses SSE - Listens for
StreamEventvariants:TextDelta(accumulate text),ToolUseStart(collect tool names),ThinkingDelta(accumulate reasoning) - On completion: saves assistant message to DB, marks other projects as unread
Schema in lib/database/database.dart, generated code in database.g.dart:
- Projects —
id,name,workingDirectory,systemPrompt,model(default:vllm/claude-sonnet-4-6), timestamps - Messages —
id,projectId(FK with cascade delete),role(user/assistant/system),content(plain text or JSON array for multipart with images), timestamp
Key queries: watchProjects(), recentMessagesForProject(id, limit=50) for display, messagesForProject(id) for full API context.
- Text only: stored as plain string in
contentcolumn - With images: stored as JSON array
[{"type":"text","text":"..."},{"type":"image_url","image_url":{"url":"data:image/...;base64,..."}}] - Display code in
MessageBubbledetects format via JSON parse attempt
Parses line-by-line SSE from the Go relay. Emits sealed StreamEvent types:
- Lines starting with
:→ keepalive/comments (skipped) data: [DONE]→ stream enddata: {...}→ JSON parsed intoTextDelta,ToolUseStart, orThinkingDelta
- Widget types:
ConsumerStatefulWidgetwhen needing local state + providers;ConsumerWidgetfor stateless provider access; plainStatelessWidgetfor pure UI - Debug logging:
debugPrint()with[SSE]/[PROVIDER]prefixes - Theming: Material 3 with color seed
0xFF6750A4, light + dark themes defined inapp.dart - Responsive layout:
AdaptiveLayoutsplits sidebar (200px) + detail; mobile shows sidebar only with navigation - Message pagination: Display last 50 messages; full history sent to API for context
Desktop: Linux, macOS, Windows. Mobile: iOS. No Android support.