From ffbfe333049f44c082abdc91ec016701900681e4 Mon Sep 17 00:00:00 2001
From: RAIT-09 <51452399+RAIT-09@users.noreply.github.com>
Date: Sat, 21 Mar 2026 22:04:45 +0900
Subject: [PATCH] docs: update CLAUDE.md and ARCHITECTURE.md to reflect
simplified architecture
---
ARCHITECTURE.md | 477 +++++++++++++++++++++++++++---------------------
CLAUDE.md | 254 ++++++++++++++++----------
2 files changed, 417 insertions(+), 314 deletions(-)
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 041b269..b80b603 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -2,164 +2,202 @@
## Overview
-This plugin uses **React Hooks Architecture** with clear separation of concerns. State and logic are managed by custom hooks, UI by components, and external integrations by adapters.
+This plugin uses **React Hooks + ChatPanel Architecture**. A central `ChatPanel` component composes hooks and renders children directly. Services are injected via React Context. ACP protocol changes are isolated in the `acp/` layer.
## Directory Structure
```
src/
-├── domain/ # Domain Layer (innermost)
-│ ├── models/ # Pure domain models (no dependencies)
-│ │ ├── agent-config.ts
-│ │ ├── agent-error.ts
-│ │ ├── chat-message.ts
-│ │ └── chat-session.ts
-│ └── ports/ # Interfaces (Dependency Inversion)
-│ ├── agent-client.port.ts
-│ ├── settings-access.port.ts
-│ └── vault-access.port.ts
+├── types/ # Type Definitions (no logic)
+│ ├── chat.ts # ChatMessage, MessageContent, PromptContent, AttachedFile
+│ ├── session.ts # ChatSession, SessionUpdate, SessionInfo, Capabilities
+│ ├── agent.ts # AgentConfig, agent settings (Claude/Gemini/Codex/Custom)
+│ └── errors.ts # AcpError, ProcessError, ErrorInfo
+│
+├── acp/ # ACP Protocol Layer (SDK dependency confined here)
+│ ├── acp-client.ts # ACP communication, process lifecycle, IAgentClient
+│ ├── type-converter.ts # ACP SDK types ↔ internal types
+│ ├── permission-handler.ts # Permission queue, auto-approve, Promise resolution
+│ └── terminal-handler.ts # Terminal process create/output/kill
+│
+├── services/ # Business Logic (non-React)
+│ ├── vault-service.ts # Vault access + fuzzy search + CM6 selection tracking
+│ ├── settings-service.ts # Settings persistence, session storage, normalization
+│ ├── session-helpers.ts # Agent config building, API key injection (pure functions)
+│ ├── message-sender.ts # Prompt preparation + sending (pure functions)
+│ ├── chat-exporter.ts # Markdown export with frontmatter
+│ ├── view-registry.ts # Multi-view management, focus, broadcast
+│ └── update-checker.ts # Agent/plugin version checking
│
├── hooks/ # React Custom Hooks (state + logic)
-│ ├── useAgentSession.ts # Session lifecycle, agent switching
-│ ├── useChat.ts # Message sending, callbacks
-│ ├── usePermission.ts # Permission handling
-│ ├── useMentions.ts # @[[note]] suggestions
+│ ├── useSession.ts # Session lifecycle, create/close/restart, config options
+│ ├── useMessages.ts # Message state, send/receive, streaming updates
+│ ├── usePermission.ts # Permission request handling
+│ ├── useMentions.ts # @[[note]] suggestions + auto-mention active note
│ ├── useSlashCommands.ts # /command suggestions
-│ ├── useAutoMention.ts # Auto-mention active note
-│ ├── useAutoExport.ts # Auto-export on new/close
-│ └── useSettings.ts # Settings subscription
+│ ├── useSessionHistory.ts # Session list/load/resume/fork
+│ └── useSettings.ts # Settings subscription (useSyncExternalStore)
│
-├── adapters/ # Interface Adapters
-│ ├── acp/ # Agent Client Protocol
-│ │ ├── acp.adapter.ts # Implements IAgentClient port
-│ │ └── acp-type-converter.ts # Converts ACP types to domain
-│ └── obsidian/ # Obsidian platform
-│ ├── vault.adapter.ts # Implements IVaultAccess port
-│ ├── settings-store.adapter.ts # Implements ISettingsAccess port
-│ └── mention-service.ts # File indexing, fuzzy search
+├── ui/ # React Components
+│ ├── ChatContext.ts # React Context (plugin, acpClient, vaultService)
+│ ├── ChatPanel.tsx # Hook aggregation + rendering (replaces useChatController)
+│ ├── ChatView.tsx # Sidebar view (ItemView + Context Provider)
+│ ├── FloatingChatView.tsx # Floating window (position/drag/resize + Context Provider)
+│ ├── FloatingButton.tsx # Draggable launch button
+│ ├── ChatHeader.tsx # Header (sidebar + floating variants)
+│ ├── MessageList.tsx # Message list with auto-scroll
+│ ├── MessageBubble.tsx # Single message (content type dispatch)
+│ ├── ToolCallBlock.tsx # Tool call display + diff
+│ ├── TerminalBlock.tsx # Terminal output polling
+│ ├── InputArea.tsx # Textarea, attachments, mentions, history
+│ ├── InputToolbar.tsx # Mode/model selectors, usage, send button
+│ ├── SuggestionPopup.tsx # Mention/command dropdown
+│ ├── PermissionBanner.tsx # Permission request buttons
+│ ├── ErrorBanner.tsx # Error/notification overlay
+│ ├── SessionHistoryModal.tsx # Session history modal (list + confirm delete)
+│ ├── SettingsTab.ts # Plugin settings UI
+│ ├── types.ts # IChatViewHost interface
+│ └── shared/
+│ ├── IconButton.tsx # Icon button + Lucide icon wrapper
+│ ├── MarkdownRenderer.tsx # Obsidian markdown rendering
+│ └── AttachmentStrip.tsx # Attachment preview strip
│
-├── components/ # UI Components
-│ ├── chat/ # Chat UI (14 components)
-│ │ ├── ChatView.tsx # Main view, hook composition
-│ │ ├── ChatHeader.tsx
-│ │ ├── ChatMessages.tsx
-│ │ ├── ChatInput.tsx
-│ │ ├── MessageRenderer.tsx
-│ │ ├── MessageContentRenderer.tsx
-│ │ ├── ToolCallRenderer.tsx
-│ │ ├── TerminalRenderer.tsx
-│ │ ├── PermissionRequestSection.tsx
-│ │ ├── SuggestionDropdown.tsx
-│ │ ├── CollapsibleThought.tsx
-│ │ ├── MarkdownTextRenderer.tsx
-│ │ ├── TextWithMentions.tsx
-│ │ └── HeaderButton.tsx
-│ └── settings/
-│ └── AgentClientSettingTab.ts
+├── utils/ # Shared Utilities (pure functions)
+│ ├── platform.ts # Shell, WSL, Windows env, command building
+│ ├── paths.ts # Path resolution, file:// URI
+│ ├── error-utils.ts # ACP error conversion
+│ ├── mention-parser.ts # @[[note]] detection/extraction
+│ └── logger.ts # Debug-mode logger
│
-├── shared/ # Utilities
-│ ├── message-service.ts # prepareMessage, sendPreparedMessage
-│ ├── terminal-manager.ts # Process spawn, stdout/stderr
-│ ├── chat-exporter.ts # Markdown export
-│ ├── mention-utils.ts # Mention parsing
-│ ├── settings-utils.ts # Settings validation
-│ ├── logger.ts # Debug logging
-│ ├── path-utils.ts # Path resolution
-│ └── wsl-utils.ts # WSL support
-│
-├── plugin.ts # Obsidian plugin entry point
-└── main.ts # Re-exports plugin
+├── plugin.ts # Obsidian plugin lifecycle, commands, view management
+└── main.ts # Entry point (re-exports plugin)
```
## Architectural Layers
-### 1. Domain Layer (`src/domain/`)
+### 1. Types Layer (`src/types/`)
-**Purpose**: Pure types and interfaces, zero external dependencies.
+**Purpose**: Pure type definitions. No logic, no dependencies.
-#### Models (`src/domain/models/`)
-- `ChatMessage`: Message structure with content types
-- `ChatSession`: Session state, available commands
-- `AgentError`: Error types
-- `AgentConfig`: Agent configuration
-
-#### Ports (`src/domain/ports/`)
-- `IAgentClient`: Agent communication interface
-- `IVaultAccess`: File system access interface
-- `ISettingsAccess`: Settings management interface
-
-**Dependency Rule**: Zero dependencies on other layers.
+| File | Contents |
+|------|----------|
+| `chat.ts` | ChatMessage, MessageContent (9-type union), Role, ToolCallStatus, ToolKind, AttachedFile, PromptContent |
+| `session.ts` | ChatSession, SessionState, SessionUpdate (11-type union), SessionConfigOption, Capabilities, SessionInfo |
+| `agent.ts` | AgentEnvVar, BaseAgentSettings, ClaudeAgentSettings, GeminiAgentSettings, CodexAgentSettings |
+| `errors.ts` | AcpErrorCode, AcpError, ProcessError, ErrorInfo |
---
-### 2. Hooks Layer (`src/hooks/`)
+### 2. ACP Layer (`src/acp/`)
-**Purpose**: State management and business logic using React hooks.
-
-| Hook | Responsibility |
-|------|---------------|
-| `useAgentSession` | Session lifecycle, create/close/restart, agent switching |
-| `useChat` | Messages state, send/receive, callbacks for AcpAdapter |
-| `usePermission` | Permission request handling, auto-approve logic |
-| `useMentions` | @[[note]] dropdown state, selection |
-| `useSlashCommands` | /command dropdown state, filtering |
-| `useAutoMention` | Auto-prepend active note to messages |
-| `useAutoExport` | Export chat on new/close |
-| `useSettings` | Settings subscription with useSyncExternalStore |
-
-**Dependency Rule**: Hooks depend on Domain (ports/models) and Shared utilities.
-
----
-
-### 3. Adapters Layer (`src/adapters/`)
-
-**Purpose**: Implement ports, bridge external systems to domain.
-
-#### ACP Adapters (`src/adapters/acp/`)
-- `acp.adapter.ts`: Implements `IAgentClient`, manages agent process
-- `acp-type-converter.ts`: Converts ACP protocol types to domain types
-
-#### Obsidian Adapters (`src/adapters/obsidian/`)
-- `vault.adapter.ts`: Implements `IVaultAccess`
-- `settings-store.adapter.ts`: Implements `ISettingsAccess` with observer pattern
-- `mention-service.ts`: File indexing, fuzzy search
-
-**Dependency Rule**: Adapters depend on Domain only.
-
----
-
-### 4. Components Layer (`src/components/`)
-
-**Purpose**: UI rendering, receives state from hooks.
-
-#### ChatView.tsx
-- **Hook Composition**: Combines all hooks
-- **Adapter Instantiation**: Creates AcpAdapter, VaultAdapter via useMemo
-- **Rendering**: Delegates to child components
-
-#### Child Components
-- Receive data via props
-- Call callbacks from hooks
-- No direct business logic
-
-**Dependency Rule**: Components depend on Hooks and Adapters.
-
----
-
-### 5. Shared Layer (`src/shared/`)
-
-**Purpose**: Cross-cutting utilities.
+**Purpose**: Isolate ACP protocol dependency. All `@agentclientprotocol/sdk` imports are confined here.
| File | Purpose |
|------|---------|
-| `message-service.ts` | Pure functions: prepareMessage, sendPreparedMessage |
-| `terminal-manager.ts` | Process spawn, stdout/stderr capture |
-| `chat-exporter.ts` | Export chat to markdown |
-| `mention-utils.ts` | Parse @[[note]] syntax |
-| `settings-utils.ts` | Validate and normalize settings |
-| `logger.ts` | Debug logging (respects debugMode) |
+| `acp-client.ts` | Process spawn/kill, JSON-RPC communication, session management. Defines `IAgentClient` and `ITerminalClient` interfaces. |
+| `type-converter.ts` | Converts ACP SDK types to internal types (change buffer for protocol updates) |
+| `permission-handler.ts` | Permission request queue, auto-approve, Promise-based resolution |
+| `terminal-handler.ts` | Terminal process create/output/kill, stdout/stderr buffering |
-**Dependency Rule**: Can be used by any layer.
+**Key design**: When the ACP protocol changes, only files in this directory need updating.
+
+---
+
+### 3. Services Layer (`src/services/`)
+
+**Purpose**: Non-React business logic. Classes and pure functions.
+
+| File | Purpose |
+|------|---------|
+| `vault-service.ts` | `VaultService` class — vault note access, fuzzy search, CM6 selection tracking. Exports `IVaultAccess`, `NoteMetadata`, `EditorPosition`. |
+| `settings-service.ts` | `SettingsService` class — settings persistence (data.json), session storage (sessions/*.json), observer pattern. Exports `ISettingsAccess`. |
+| `session-helpers.ts` | Pure functions — agent config building, API key injection, agent settings resolution |
+| `message-sender.ts` | Pure functions — prompt preparation (mention expansion, context building), sending with auth retry |
+| `chat-exporter.ts` | `ChatExporter` class — markdown export with frontmatter, image handling |
+| `view-registry.ts` | `ChatViewRegistry` class — multi-view focus tracking, broadcast commands. Exports `IChatViewContainer`. |
+| `update-checker.ts` | Agent version checking via npm registry |
+
+---
+
+### 4. Hooks Layer (`src/hooks/`)
+
+**Purpose**: React state management. Each hook owns a specific domain of state.
+
+| Hook | Responsibility |
+|------|---------------|
+| `useSession` | Session lifecycle (create/close/restart), mode/model/configOption, optimistic updates |
+| `useMessages` | Message state, streaming updates (agent_message_chunk, tool_call, etc.), send/receive |
+| `usePermission` | Permission request detection, approve/reject |
+| `useMentions` | @[[note]] dropdown + auto-mention active note tracking (merged) |
+| `useSlashCommands` | /command dropdown filtering and selection |
+| `useSessionHistory` | Session list/load/resume/fork, local session storage, 5-min cache |
+| `useSettings` | Settings subscription via useSyncExternalStore |
+
+**Dependency Rule**: Hooks import from `types/`, `acp/`, `services/`, `utils/`. Never from `ui/`.
+
+---
+
+### 5. UI Layer (`src/ui/`)
+
+**Purpose**: React components. Rendering and user interaction.
+
+#### Core Architecture
+
+**ChatContext** provides shared services to the component tree:
+```typescript
+interface ChatContextValue {
+ plugin: AgentClientPlugin;
+ acpClient: AcpAdapter;
+ vaultService: VaultService;
+ settingsService: SettingsService;
+}
+```
+
+**ChatPanel** is the central orchestrator:
+- Calls all hooks (useSession, useMessages, usePermission, useMentions, useSlashCommands, useSessionHistory, useSettings)
+- Manages session update routing (ACP → hooks)
+- Handles workspace events, message persistence, cleanup
+- Renders ChatHeader, MessageList, InputArea directly
+
+**ChatView** (sidebar) and **FloatingChatView** (floating window) are thin wrappers:
+- Create services (AcpClient, VaultService) in lifecycle methods
+- Provide ChatContext
+- Render ChatPanel with `variant` prop
+- Implement IChatViewContainer for broadcast commands
+
+#### Component Tree
+
+```
+ChatView / FloatingChatView
+ └── ChatContextProvider
+ └── ChatPanel (variant="sidebar" | "floating")
+ ├── ChatHeader (variant-based rendering)
+ ├── MessageList
+ │ └── MessageBubble (per message)
+ │ ├── ToolCallBlock → PermissionBanner
+ │ ├── TerminalBlock
+ │ └── MarkdownRenderer
+ ├── InputArea
+ │ ├── SuggestionPopup (mentions / commands)
+ │ ├── ErrorBanner
+ │ ├── AttachmentStrip
+ │ └── InputToolbar (mode/model/usage/send)
+ └── SessionHistoryModal (imperative, via ref)
+```
+
+---
+
+### 6. Utils Layer (`src/utils/`)
+
+**Purpose**: Pure utility functions. No React, no Obsidian dependencies (except `platform.ts`).
+
+| File | Purpose |
+|------|---------|
+| `platform.ts` | Shell detection, WSL path conversion, Windows PATH from registry, platform-specific command preparation |
+| `paths.ts` | Path resolution (which/where), file:// URI building, relative path conversion |
+| `error-utils.ts` | ACP error code → user-friendly title/suggestion conversion |
+| `mention-parser.ts` | @[[note]] detection, replacement, extraction from text |
+| `logger.ts` | Singleton logger respecting debugMode setting |
---
@@ -167,92 +205,96 @@ src/
```
┌─────────────────────────────────────────────────────────────┐
-│ Components Layer │
-│ ┌────────────────────────────────────────────────────────┐ │
-│ │ ChatView.tsx (hook composition + rendering) │ │
-│ │ ├─ ChatHeader, ChatMessages, ChatInput │ │
-│ │ └─ MessageRenderer, ToolCallRenderer, etc. │ │
-│ └────────────────────────────────────────────────────────┘ │
+│ UI Layer │
+│ │
+│ ChatView / FloatingChatView (Context Providers) │
+│ └── ChatPanel (hook composition + rendering) │
+│ ├── ChatHeader, MessageList, InputArea │
+│ └── MessageBubble, ToolCallBlock, etc. │
└─────────────────────────────┬───────────────────────────────┘
- ↓ uses
+ ↓ calls hooks
┌─────────────────────────────┴───────────────────────────────┐
│ Hooks Layer │
-│ ┌──────────────────┐ ┌──────────────────┐ │
-│ │ useAgentSession │ │ useChat │ │
-│ │ usePermission │ │ useMentions │ │
-│ │ useSlashCommands │ │ useAutoExport │ │
-│ └──────────────────┘ └──────────────────┘ │
-└─────────────────────────────┬───────────────────────────────┘
- ↓ uses ports
-┌─────────────────────────────┴───────────────────────────────┐
-│ Ports (Interfaces) │
-│ ┌─────────────────┐ ┌──────────────────┐ │
-│ │ IAgentClient │ │ IVaultAccess │ │
-│ │ ISettingsAccess │ └──────────────────┘ │
-│ └─────────────────┘ │
-└─────────────────────────────┬───────────────────────────────┘
- ↑ implements
-┌─────────────────────────────┴───────────────────────────────┐
-│ Adapters (Implementations) │
-│ ┌─────────────────┐ ┌──────────────────┐ │
-│ │ AcpAdapter │ │ VaultAdapter │ │
-│ │ SettingsStore │ │ MentionService │ │
-│ └─────────────────┘ └──────────────────┘ │
-└─────────────────────────────────────────────────────────────┘
- ↑
-┌─────────────────────────────┴───────────────────────────────┐
-│ Domain Models │
-│ ┌─────────────────┐ ┌──────────────────┐ │
-│ │ ChatMessage │ │ ChatSession │ │
-│ │ AgentError │ │ AgentConfig │ │
-│ └─────────────────┘ └──────────────────┘ │
-└─────────────────────────────────────────────────────────────┘
+│ useSession, useMessages, usePermission, useMentions, │
+│ useSlashCommands, useSessionHistory, useSettings │
+└───────────┬─────────────────────────────┬───────────────────┘
+ ↓ calls ↓ reads types
+┌───────────┴───────────┐ ┌─────────────┴───────────────────┐
+│ Services Layer │ │ Types Layer │
+│ VaultService │ │ chat.ts, session.ts, │
+│ SettingsService │ │ agent.ts, errors.ts │
+│ session-helpers │ └─────────────────────────────────┘
+│ message-sender │
+│ chat-exporter │
+│ view-registry │
+└───────────┬───────────┘
+ ↓ communicates
+┌───────────┴───────────┐
+│ ACP Layer │
+│ acp-client.ts │
+│ type-converter.ts │
+│ permission-handler │
+│ terminal-handler │
+└───────────────────────┘
+ ↑
+ @agentclientprotocol/sdk
```
---
## Design Patterns
-### 1. Custom Hooks Pattern
-- State and logic encapsulated in hooks
-- Composable and reusable
-- Testable in isolation
+### 1. ChatPanel Pattern (Component-as-Orchestrator)
+- Central component calls all hooks and renders children directly
+- Replaces the "god hook" pattern (useChatController)
+- No massive return object — props flow directly to JSX
-### 2. Dependency Inversion (Ports & Adapters)
-- Domain defines interfaces (ports)
-- Adapters implement those interfaces
-- ACP protocol changes isolated to adapters
+### 2. React Context for Services
+- `ChatContext` provides plugin, acpClient, vaultService, settingsService
+- Value is stable (service instances don't change)
+- Eliminates prop drilling for shared dependencies
-### 3. Observer Pattern
-- `SettingsStore` notifies subscribers on change
+### 3. Custom Hooks for State
+- Each hook owns a specific state domain
+- Composable and independently testable
+- No inter-hook dependencies (communication via ChatPanel)
+
+### 4. ACP Isolation
+- All `@agentclientprotocol/sdk` imports confined to `acp/`
+- `type-converter.ts` is the change buffer for protocol updates
+- Interface types (`IAgentClient`) defined alongside implementation
+
+### 5. Observer Pattern
+- `SettingsService` notifies subscribers on change
- React components use `useSyncExternalStore`
-### 4. Pure Functions in Shared
-- `message-service.ts`: prepareMessage, sendPreparedMessage
-- No React dependencies, easy to test
+### 6. Ref Pattern for Callbacks
+- IChatViewContainer callbacks use refs for latest values
+- Prevents stale closures in broadcast command handlers
+- Unmount cleanup uses refs to access latest state
---
## Key Benefits
-### 1. React-idiomatic
-- Hooks for state management
-- No ViewModel classes
-- Standard React patterns
+### 1. Flat and Readable
+- 4 layers (types → acp/services → hooks → ui) instead of 5
+- No port/adapter indirection
+- File names reflect functionality (MessageBubble, ToolCallBlock, ErrorBanner)
### 2. ACP Change Resistance
-- `IAgentClient` interface isolates protocol
-- Only `adapters/acp/` needs changes for protocol updates
+- Only `acp/` directory needs changes for protocol updates
+- `type-converter.ts` localizes type mapping changes
-### 3. Testability
-- Hooks can be tested with React Testing Library
-- Pure functions in shared/ easily unit tested
-- Adapters mockable via ports
+### 3. Easy Feature Addition
+- New hook: create in `hooks/`, call in `ChatPanel`, pass to child
+- New message type: add to `types/session.ts`, handle in `useMessages`, render in `MessageBubble`
+- New agent: add settings in `plugin.ts`, configure in `SettingsTab`
### 4. Maintainability
-- Clear file locations by responsibility
-- Single Responsibility Principle
-- ~9,100 lines across 45 files
+- ~20,400 lines across 50 files
+- Services testable without React
+- Clear dependency direction (no circular dependencies)
---
@@ -260,11 +302,12 @@ src/
| Pattern | Example |
|---------|---------|
-| Ports | `*.port.ts` |
-| Adapters | `*.adapter.ts` |
-| Hooks | `use*.ts` |
-| Components | `PascalCase.tsx` |
-| Utilities | `kebab-case.ts` |
+| Types | `kebab-case.ts` in `types/` |
+| ACP | `kebab-case.ts` in `acp/` |
+| Services | `kebab-case.ts` in `services/` |
+| Hooks | `use*.ts` in `hooks/` |
+| Components | `PascalCase.tsx` in `ui/` |
+| Utilities | `kebab-case.ts` in `utils/` |
---
@@ -273,23 +316,31 @@ src/
### Adding a New Hook
1. Create `hooks/use[Feature].ts`
2. Define state with useState/useReducer
-3. Export state and functions
-4. Compose in ChatView.tsx
+3. Call the hook in `ui/ChatPanel.tsx`
+4. Pass state/callbacks to child components as props
-### Adding a New Agent
-1. Implement `IAgentClient` in `adapters/[agent]/`
-2. Add settings to `plugin.ts`
-3. Update AgentClientSettingTab
+### Adding a New Session Update Type
+1. Add interface to `types/session.ts`, add to `SessionUpdate` union
+2. Handle in `hooks/useMessages.ts` `handleSessionUpdate()` (for message-level) or `hooks/useSession.ts` (for session-level)
+3. Convert from ACP type in `acp/type-converter.ts`
+4. Render in `ui/MessageBubble.tsx` if needed
-**No changes needed** in hooks or components!
+### Adding a New Agent Type
+1. Add settings type to `types/agent.ts`
+2. Add config in `plugin.ts` settings
+3. Add API key injection in `services/session-helpers.ts`
+4. Update `ui/SettingsTab.ts` for configuration UI
---
## Migration Notes
-Refactored from Clean Architecture (MVVM) to React Hooks Architecture in November 2025:
+### March 2026: Simplified Architecture Refactoring
-- **Removed**: `core/use-cases/`, `adapters/view-models/`, `infrastructure/`
-- **Added**: `hooks/` layer with 8 custom hooks
-- **Moved**: `plugin.ts` to src root
-- **Result**: Simpler, more React-idiomatic codebase
+Refactored from Port/Adapter Architecture to simplified layered architecture:
+
+- **Removed**: `domain/models/` (9 files → `types/` 4 files), `domain/ports/` (5 files → interfaces moved to implementation files), `adapters/` directory, `components/` directory, `shared/` directory
+- **Added**: `types/`, `acp/`, `services/`, `ui/`, `utils/` flat directories, `ChatPanel` + `ChatContext`
+- **Merged**: VaultAdapter + MentionService → VaultService, useMentions + useAutoMention → useMentions
+- **Removed**: useChatController (god hook → ChatPanel component), Port files (no implementation swapping planned)
+- **Result**: 76 → 50 files, 5 → 4 layers, flat directory structure
diff --git a/CLAUDE.md b/CLAUDE.md
index dce5163..9b88bd5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,7 +1,7 @@
# Agent Client Plugin - LLM Developer Guide
## Overview
-Obsidian plugin for AI agent interaction (Claude Code, Gemini CLI, custom agents). **React Hooks Architecture**.
+Obsidian plugin for AI agent interaction (Claude Code, Codex, Gemini CLI, custom agents). **ChatPanel + React Hooks Architecture**.
**Tech**: React 19, TypeScript, Obsidian API, Agent Client Protocol (ACP)
@@ -9,127 +9,164 @@ Obsidian plugin for AI agent interaction (Claude Code, Gemini CLI, custom agents
```
src/
-├── domain/ # Pure domain models + ports (interfaces)
-│ ├── models/ # agent-config, agent-error, chat-message, chat-session, session-update
-│ └── ports/ # IAgentClient, ISettingsAccess, IVaultAccess
-├── adapters/ # Interface implementations
-│ ├── acp/ # ACP protocol (acp.adapter.ts, acp-type-converter.ts)
-│ └── obsidian/ # Platform adapters (vault, settings, mention-service)
+├── types/ # Type definitions (no logic, no dependencies)
+│ ├── chat.ts # ChatMessage, MessageContent, PromptContent, AttachedFile
+│ ├── session.ts # ChatSession, SessionUpdate, SessionInfo, Capabilities
+│ ├── agent.ts # AgentConfig, agent settings (Claude/Codex/Gemini/Custom)
+│ └── errors.ts # AcpError, ProcessError, ErrorInfo
+├── acp/ # ACP protocol (SDK dependency confined here)
+│ ├── acp-client.ts # Process lifecycle, JSON-RPC, IAgentClient + ITerminalClient
+│ ├── type-converter.ts # ACP SDK ↔ internal type conversion
+│ ├── permission-handler.ts # Permission queue, auto-approve, Promise resolution
+│ └── terminal-handler.ts # Terminal process create/output/kill
+├── services/ # Business logic (non-React)
+│ ├── vault-service.ts # Vault access + fuzzy search + CM6 selection tracking
+│ ├── settings-service.ts # Settings persistence, session storage, normalization
+│ ├── session-helpers.ts # Agent config building, API key injection (pure functions)
+│ ├── message-sender.ts # Prompt preparation + sending (pure functions)
+│ ├── chat-exporter.ts # Markdown export with frontmatter
+│ ├── view-registry.ts # Multi-view management, focus, broadcast
+│ └── update-checker.ts # Agent/plugin version checking
├── hooks/ # React custom hooks (state + logic)
-│ ├── useAgentSession.ts # Session lifecycle, agent switching
-│ ├── useChat.ts # Message sending, session update handling
+│ ├── useSession.ts # Session lifecycle, config options, optimistic updates
+│ ├── useMessages.ts # Message state, streaming updates, send/receive
│ ├── usePermission.ts # Permission handling
-│ ├── useMentions.ts # @[[note]] suggestions
+│ ├── useMentions.ts # @[[note]] suggestions + auto-mention active note
│ ├── useSlashCommands.ts # /command suggestions
-│ ├── useAutoMention.ts # Auto-mention active note
-│ ├── useAutoExport.ts # Auto-export on new/close
+│ ├── useSessionHistory.ts # Session list/load/resume/fork
│ └── useSettings.ts # Settings subscription
-├── components/ # UI components
-│ ├── chat/ # ChatView, ChatHeader, ChatMessages, ChatInput, etc.
-│ └── settings/ # AgentClientSettingTab
-├── shared/ # Utilities
-│ ├── message-service.ts # prepareMessage, sendPreparedMessage (pure functions)
-│ ├── terminal-manager.ts # Process spawn, stdout/stderr capture
-│ ├── logger.ts, chat-exporter.ts, mention-utils.ts, etc.
+├── ui/ # React components
+│ ├── ChatContext.ts # React Context (plugin, acpClient, vaultService)
+│ ├── ChatPanel.tsx # Hook aggregation + rendering (core orchestrator)
+│ ├── ChatView.tsx # Sidebar view (ItemView wrapper)
+│ ├── FloatingChatView.tsx # Floating window (position/drag/resize)
+│ ├── ChatHeader.tsx # Header (sidebar + floating variants)
+│ ├── MessageList.tsx # Message list with auto-scroll
+│ ├── MessageBubble.tsx # Single message rendering (content dispatch)
+│ ├── ToolCallBlock.tsx # Tool call + diff display
+│ ├── TerminalBlock.tsx # Terminal output polling
+│ ├── InputArea.tsx # Textarea, attachments, mentions, history
+│ ├── InputToolbar.tsx # Mode/model selectors, usage, send button
+│ └── ... # SuggestionPopup, PermissionBanner, ErrorBanner, etc.
+├── utils/ # Shared utilities
+│ ├── platform.ts # Shell, WSL, Windows env, command building
+│ ├── paths.ts # Path resolution, file:// URI
+│ ├── error-utils.ts # ACP error conversion
+│ ├── mention-parser.ts # @[[note]] detection/extraction
+│ └── logger.ts # Debug-mode logger
├── plugin.ts # Obsidian plugin lifecycle, settings persistence
└── main.ts # Entry point
```
## Key Components
-### ChatView (`components/chat/ChatView.tsx`)
-- **Hook Composition**: Combines all hooks (useAgentSession, useChat, usePermission, etc.)
-- **Adapter Instantiation**: Creates AcpAdapter, VaultAdapter, MentionService via useMemo
-- **Callback Registration**: Registers `onSessionUpdate` for unified event handling
-- **Rendering**: Delegates to ChatHeader, ChatMessages, ChatInput
+### ChatPanel (`ui/ChatPanel.tsx`)
+Central orchestrator component. Replaces the former `useChatController` hook.
+- **Hook Composition**: Calls all hooks (useSession, useMessages, usePermission, etc.)
+- **Session Update Routing**: Routes ACP updates to useMessages (message-level) or useSession (session-level)
+- **Callback Registration**: Provides IChatViewContainer callbacks via ref pattern
+- **Workspace Events**: Handles toggle-auto-mention, new-chat-requested, approve/reject-permission, cancel, export
+- **Rendering**: Renders ChatHeader, MessageList, InputArea directly (no prop bag)
+
+### ChatView / FloatingChatView (`ui/ChatView.tsx`, `ui/FloatingChatView.tsx`)
+Thin wrappers that:
+- Create services (AcpClient, VaultService) in lifecycle methods
+- Provide ChatContext (plugin, acpClient, vaultService, settingsService)
+- Render ``
+- Implement IChatViewContainer for broadcast commands
### Hooks (`hooks/`)
-**useAgentSession**: Session lifecycle
-- `createSession()`: Load config, inject API keys, initialize + newSession
-- `switchAgent()`: Change active agent, restart session
+**useSession**: Session lifecycle
+- `createSession()`: Build config, inject API keys, initialize + newSession
+- `restartSession()`: Change active agent, restart session
- `closeSession()`: Cancel session, disconnect
-- `updateAvailableCommands()`: Handle slash command updates
-- `updateCurrentMode()`: Handle mode change updates
+- `setConfigOption()`: Optimistic update + rollback on error
+- `handleSessionUpdate()`: Handle session-level updates (commands, config, usage)
-**useChat**: Messaging and session update handling
-- `sendMessage()`: Prepare (auto-mention, path conversion) → send via IAgentClient
-- `handleNewChat()`: Export if enabled, restart session
-- `handleSessionUpdate()`: Unified handler for all session updates (agent_message_chunk, tool_call, etc.)
+**useMessages**: Messaging and streaming updates
+- `sendMessage()`: Prepare (auto-mention, path conversion) → send via AcpClient
+- `handleSessionUpdate()`: Handle message-level updates (agent_message_chunk, tool_call, etc.)
- `upsertToolCall()`: Create or update tool call in single `setMessages` callback (avoids race conditions)
- `updateLastMessage()`: Append text/thought chunks to last assistant message
- `updateMessage()`: Update specific message by tool call ID
**usePermission**: Permission handling
-- `handlePermissionResponse()`: Respond with selected option
+- `approvePermission()` / `rejectPermission()`: Respond with selected option
- Auto-approve logic based on settings
-**useMentions / useSlashCommands**: Input suggestions
-- Dropdown state management
-- Selection handlers
+**useMentions**: @mention + auto-mention (merged)
+- Dropdown state management + selection
+- Active note tracking + toggle for slash commands
-### AcpAdapter (`adapters/acp/acp.adapter.ts`)
-Implements IAgentClient + IAcpClient (terminal ops)
+**useSessionHistory**: Session persistence
+- `restoreSession()`: Load/resume with local message fallback
+- `forkSession()`: Create new branch from existing session
+
+### ACP Client (`acp/acp-client.ts`)
+Implements IAgentClient + ITerminalClient
- **Process**: spawn() with login shell (macOS/Linux -l, Windows shell:true)
- **Protocol**: JSON-RPC over stdin/stdout via ndJsonStream
-- **Flow**: initialize() → newSession() → sendMessage() → sessionUpdate via `onSessionUpdate`
-- **Updates**: agent_message_chunk, agent_thought_chunk, tool_call, tool_call_update, plan, available_commands_update, current_mode_update
-- **Unified Callback**: Single `onSessionUpdate(callback)` replaces legacy `onMessage`, `onError`, `onPermissionRequest`
-- **Permissions**: Promise-based Map
-- **Terminal**: createTerminal, terminalOutput, killTerminal, releaseTerminal
+- **Flow**: initialize() → newSession() → sendPrompt() → sessionUpdate via `onSessionUpdate`
+- **Updates**: agent_message_chunk, agent_thought_chunk, user_message_chunk, tool_call, tool_call_update, plan, available_commands_update, current_mode_update, session_info_update, usage_update, config_option_update
+- **Unified Callback**: Single `onSessionUpdate(callback)` for all agent events
+- **Permissions**: Promise-based via PermissionHandler
+- **Terminal**: TerminalHandler manages create/output/kill/release
-### Obsidian Adapters (`adapters/obsidian/`)
+### Services (`services/`)
-**VaultAdapter**: IVaultAccess - searchNotes (fuzzy), getActiveNote, readNote
-**SettingsStore**: ISettingsAccess - Observer pattern, getSnapshot(), subscribe()
-**MentionService**: File index, fuzzy search (basename, path, aliases)
+**VaultService**: Vault access + file index + fuzzy search + CM6 selection tracking
+**SettingsService**: Observer pattern settings store, session storage (data.json + sessions/*.json)
+**session-helpers**: Pure functions — buildAgentConfigWithApiKey, findAgentSettings, getAvailableAgents
+**message-sender**: Pure functions — preparePrompt (auto-mention, context), sendPreparedPrompt (auth retry)
-### Message Service (`shared/message-service.ts`)
-Pure functions (non-React):
-- `prepareMessage()`: Auto-mention, convert @[[note]] → paths
-- `sendPreparedMessage()`: Send via IAgentClient, auth retry
+## Types
-## Domain Models
-
-### SessionUpdate (`domain/models/session-update.ts`)
+### SessionUpdate (`types/session.ts`)
Union type for all session update events from the agent:
```typescript
type SessionUpdate =
- | AgentMessageChunkUpdate // Text chunk from agent's response
- | AgentThoughtChunkUpdate // Text chunk from agent's reasoning
- | ToolCallUpdate // New tool call event
- | ToolCallUpdateUpdate // Update to existing tool call
- | PlanUpdate // Agent's task plan
- | AvailableCommandsUpdate // Slash commands changed
- | CurrentModeUpdate // Mode changed
- | ErrorUpdate; // Error from agent operations
+ | AgentMessageChunk // Text chunk from agent's response
+ | AgentThoughtChunk // Text chunk from agent's reasoning
+ | UserMessageChunk // Text chunk from user message (session/load)
+ | ToolCall // New tool call event
+ | ToolCallUpdate // Update to existing tool call
+ | Plan // Agent's task plan
+ | AvailableCommandsUpdate // Slash commands changed
+ | CurrentModeUpdate // Mode changed
+ | SessionInfoUpdate // Session metadata changed
+ | UsageUpdate // Context window usage
+ | ConfigOptionUpdate; // Config options changed
```
-This domain type abstracts ACP's `SessionNotification.update.sessionUpdate` values, allowing the application layer to handle events without depending on ACP protocol specifics.
+### Key Interfaces
-## Ports (Interfaces)
+Interfaces are defined in their implementation files (no separate port files):
```typescript
+// acp/acp-client.ts
interface IAgentClient {
initialize(config: AgentConfig): Promise;
- newSession(workingDirectory: string): Promise;
+ newSession(workingDirectory: string): Promise;
authenticate(methodId: string): Promise;
- sendMessage(sessionId: string, message: string): Promise;
+ sendPrompt(sessionId: string, content: PromptContent[]): Promise;
cancel(sessionId: string): Promise;
disconnect(): Promise;
-
- // Unified callback for all session updates
onSessionUpdate(callback: (update: SessionUpdate) => void): void;
-
+ onError(callback: (error: ProcessError) => void): void;
respondToPermission(requestId: string, optionId: string): Promise;
isInitialized(): boolean;
getCurrentAgentId(): string | null;
- setSessionMode(sessionId: string, modeId: string): Promise;
- setSessionModel(sessionId: string, modelId: string): Promise;
+ setSessionConfigOption(sessionId: string, configId: string, value: string): Promise;
+ listSessions(cwd?: string, cursor?: string): Promise;
+ loadSession(sessionId: string, cwd: string): Promise;
+ resumeSession(sessionId: string, cwd: string): Promise;
+ forkSession(sessionId: string, cwd: string): Promise;
}
+// services/vault-service.ts
interface IVaultAccess {
readNote(path: string): Promise;
searchNotes(query: string): Promise;
@@ -137,21 +174,29 @@ interface IVaultAccess {
listNotes(): Promise;
}
+// services/settings-service.ts
interface ISettingsAccess {
getSnapshot(): AgentClientPluginSettings;
updateSettings(updates: Partial): Promise;
subscribe(listener: () => void): () => void;
+ saveSession(info: SavedSessionInfo): Promise;
+ getSavedSessions(agentId?: string, cwd?: string): SavedSessionInfo[];
+ deleteSession(sessionId: string): Promise;
+ saveSessionMessages(sessionId: string, agentId: string, messages: ChatMessage[]): Promise;
+ loadSessionMessages(sessionId: string): Promise;
+ deleteSessionMessages(sessionId: string): Promise;
}
```
## Development Rules
### Architecture
-1. **Hooks for state + logic**: No ViewModel, no Use Cases classes
-2. **Pure functions in shared/**: Non-React business logic
-3. **Ports for ACP resistance**: IAgentClient interface isolates protocol changes
-4. **Domain has zero deps**: No `obsidian`, `@agentclientprotocol/sdk`
-5. **Unified callbacks**: Use `onSessionUpdate` for all agent events (not multiple callbacks)
+1. **ChatPanel as orchestrator**: All hooks called in ChatPanel, renders children directly
+2. **Services for non-React logic**: Pure functions and classes in `services/`
+3. **ACP isolation**: All `@agentclientprotocol/sdk` imports confined to `acp/`
+4. **Types have zero deps**: No `obsidian`, no SDK, no React in `types/`
+5. **Unified callbacks**: Use `onSessionUpdate` for all agent events
+6. **Context for services**: plugin, acpClient, vaultService via ChatContext
### Obsidian Plugin Review (CRITICAL)
1. No innerHTML/outerHTML - use createEl/createDiv/createSpan
@@ -161,19 +206,22 @@ interface ISettingsAccess {
5. Minimize `any` - use proper types
### Naming Conventions
-- Ports: `*.port.ts`
-- Adapters: `*.adapter.ts`
-- Hooks: `use*.ts`
-- Components: `PascalCase.tsx`
-- Utils/Models: `kebab-case.ts`
+- Types: `kebab-case.ts` in `types/`
+- ACP: `kebab-case.ts` in `acp/`
+- Services: `kebab-case.ts` in `services/`
+- Hooks: `use*.ts` in `hooks/`
+- Components: `PascalCase.tsx` in `ui/`
+- Utils: `kebab-case.ts` in `utils/`
### Code Patterns
1. React hooks for state management
2. useCallback/useMemo for performance
-3. useRef for cleanup function access
+3. useRef for cleanup function access and stale closure prevention
4. Error handling: try-catch async ops
5. Logging: Logger class (respects debugMode)
6. **Upsert pattern**: Use `setMessages` functional updates to avoid race conditions with tool_call updates
+7. **Ref pattern for callbacks**: IChatViewContainer callbacks use refs for latest values
+8. **Context value stability**: ChatContext value created once (service instances), wrapped in useMemo
## Common Tasks
@@ -181,46 +229,50 @@ interface ISettingsAccess {
1. Create `hooks/use[Feature].ts`
2. Define state with useState/useReducer
3. Export functions and state
-4. Compose in ChatView.tsx
+4. Call the hook in `ui/ChatPanel.tsx`
+5. Pass state/callbacks to child components as props
### Add Agent Type
-1. **Optional**: Define config in `domain/models/agent-config.ts`
-2. **Adapter**: Implement IAgentClient in `adapters/[agent]/[agent].adapter.ts`
-3. **Settings**: Add to AgentClientPluginSettings in plugin.ts
-4. **UI**: Update AgentClientSettingTab
+1. Add settings type in `types/agent.ts`
+2. Add config and defaults in `plugin.ts`
+3. Add API key injection in `services/session-helpers.ts`
+4. Update `ui/SettingsTab.ts` for configuration UI
### Modify Message Types
-1. Update `ChatMessage`/`MessageContent` in `domain/models/chat-message.ts`
+1. Update `ChatMessage`/`MessageContent` in `types/chat.ts`
2. If adding new session update type:
- - Add to `SessionUpdate` union in `domain/models/session-update.ts`
- - Handle in `useChat.handleSessionUpdate()`
-3. Update `AcpAdapter.sessionUpdate()` to emit the new type
-4. Update `MessageContentRenderer` to render new type
+ - Add to `SessionUpdate` union in `types/session.ts`
+ - Handle in `hooks/useMessages.ts` `handleSessionUpdate()`
+3. Update `acp/acp-client.ts` `sessionUpdate()` to emit the new type
+4. Update `ui/MessageBubble.tsx` `ContentBlock` to render new type
### Add New Session Update Type
-1. Define interface in `domain/models/session-update.ts`
+1. Define interface in `types/session.ts`
2. Add to `SessionUpdate` union type
-3. Handle in `useChat.handleSessionUpdate()` (for message-level updates)
-4. Or handle in `ChatView` (for session-level updates like `available_commands_update`)
+3. Handle in `hooks/useMessages.ts` `handleSessionUpdate()` (for message-level)
+4. Or handle in `hooks/useSession.ts` `handleSessionUpdate()` (for session-level)
+5. Route in `ui/ChatPanel.tsx` session update effect
### Debug
1. Settings → Developer Settings → Debug Mode ON
2. Open DevTools (Cmd+Option+I / Ctrl+Shift+I)
-3. Filter logs: `[AcpAdapter]`, `[useChat]`, `[NoteMentionService]`
+3. Filter logs: `[AcpAdapter]`, `[useMessages]`, `[VaultService]`, `[ChatPanel]`
## ACP Protocol
**Communication**: JSON-RPC 2.0 over stdin/stdout
-**Methods**: initialize, newSession, authenticate, prompt, cancel, setSessionMode, setSessionModel
-**Notifications**: session/update (agent_message_chunk, agent_thought_chunk, tool_call, tool_call_update, plan, available_commands_update, current_mode_update)
+**Methods**: initialize, newSession, authenticate, prompt, cancel, setSessionConfigOption
+**Notifications**: session/update (agent_message_chunk, agent_thought_chunk, user_message_chunk, tool_call, tool_call_update, plan, available_commands_update, current_mode_update, session_info_update, usage_update, config_option_update)
**Requests**: requestPermission
+**Session Management** (unstable): session/list, session/load, session/resume, session/fork
**Agents**:
- Claude Code: `@zed-industries/claude-agent-acp` (ANTHROPIC_API_KEY)
-- Gemini CLI: `@anthropics/gemini-cli-acp` (GOOGLE_API_KEY)
+- Codex: `@zed-industries/codex-acp` (OPENAI_API_KEY)
+- Gemini CLI: `@anthropics/gemini-cli-acp` (GEMINI_API_KEY)
- Custom: Any ACP-compatible agent
---
-**Last Updated**: December 2025 | **Architecture**: React Hooks | **Version**: 0.4.0
+**Last Updated**: March 2026 | **Architecture**: ChatPanel + React Hooks | **Version**: 0.9.1