docs: backfill ADRs 0011–0015 (MCP server, Claude CLI agent, remove theming, git cache, auto-save)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
30
docs/adr/0011-mcp-server-for-ai-integration.md
Normal file
30
docs/adr/0011-mcp-server-for-ai-integration.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0011"
|
||||
title: "MCP server for AI tool integration"
|
||||
status: active
|
||||
date: 2026-02-28
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Laputa's AI features (agent panel, chat) need structured access to vault data — searching notes, reading content, editing frontmatter, and steering the UI. Rather than building a bespoke API, the Model Context Protocol (MCP) provides a standardized tool interface that works with Claude Code, Cursor, and any MCP-compatible client.
|
||||
|
||||
## Decision
|
||||
|
||||
**Laputa ships a Node.js MCP server (`mcp-server/`) that exposes vault operations as 14 tools. It runs on stdio for external clients and on two WebSocket ports (9710 for tool calls, 9711 for UI actions) for the embedded Laputa frontend. Tauri spawns the server on startup and auto-registers it in Claude Code and Cursor configs.**
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): Node.js MCP server with stdio + WebSocket dual transport — standard MCP compatibility, works with Claude Code/Cursor out of the box, WebSocket enables real-time UI steering. Downside: Node.js dependency, two extra ports.
|
||||
- **Option B**: Rust-native MCP server — no Node.js dependency. Downside: MCP SDK is JavaScript-first, Rust implementation would be custom and harder to maintain.
|
||||
- **Option C**: Custom REST/gRPC API — full control. Downside: no compatibility with existing AI tool ecosystems, each client needs a custom integration.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Vault tools (search, read, create, edit, delete, link) are available to any MCP-compatible client.
|
||||
- Auto-registration in `~/.claude/mcp.json` and `~/.cursor/mcp.json` means zero setup for users.
|
||||
- The WebSocket bridge enables real-time UI actions (highlight elements, open notes, set filters) from AI tools.
|
||||
- `mcp-server/` is bundled into release builds and spawned as a child process by `mcp.rs`.
|
||||
- Port conflicts on 9710/9711 are handled gracefully (EADDRINUSE tolerance).
|
||||
- Re-evaluation trigger: if MCP SDK gains a Rust implementation that eliminates the Node.js dependency.
|
||||
30
docs/adr/0012-claude-cli-for-ai-agent.md
Normal file
30
docs/adr/0012-claude-cli-for-ai-agent.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0012"
|
||||
title: "Claude CLI subprocess for AI agent (replacing direct API)"
|
||||
status: active
|
||||
date: 2026-03-01
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The AI agent panel initially called the Anthropic API directly from Rust, managing tool calling loops manually. This required implementing tool execution, conversation state, and streaming — all complex to maintain. Claude CLI (`claude` binary) handles all of this natively, including MCP tool integration, conversation history, and streaming NDJSON output.
|
||||
|
||||
## Decision
|
||||
|
||||
**The AI agent panel spawns Claude CLI as a subprocess via `claude_cli.rs`, passing messages with `--output-format stream-json` and vault MCP config via `--mcp-config`. The frontend parses the NDJSON event stream (Init, TextDelta, ThinkingDelta, ToolStart, ToolDone, Result, Done) for real-time display.**
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): Claude CLI subprocess with NDJSON streaming — built-in tool calling, MCP integration, conversation management, no API key needed (CLI handles auth). Downside: requires Claude CLI installed, subprocess management complexity.
|
||||
- **Option B**: Direct Anthropic API with manual tool loop — full control, no external dependency. Downside: must implement tool calling, retries, conversation state, MCP tool bridging.
|
||||
- **Option C**: Use Anthropic Agent SDK from Rust — structured agent framework. Downside: SDK is Python/TypeScript, no Rust support.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The AI agent gets full tool access (MCP vault tools + shell access) without custom tool-calling code.
|
||||
- `claude_cli.rs` manages subprocess lifecycle: spawn, stream events, kill on cancel.
|
||||
- The frontend (`useAiAgent` hook) processes NDJSON events for reasoning blocks, tool action cards, and response display.
|
||||
- File operation detection (from Write/Edit tool inputs) triggers automatic vault reload.
|
||||
- The simpler AI Chat panel still uses the Anthropic API directly for lightweight, no-tools conversations.
|
||||
- Re-evaluation trigger: if Anthropic releases a Rust Agent SDK or if Claude CLI streaming format changes significantly.
|
||||
29
docs/adr/0013-remove-theming-system.md
Normal file
29
docs/adr/0013-remove-theming-system.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0013"
|
||||
title: "Remove vault-based theming system"
|
||||
status: active
|
||||
date: 2026-03-23
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Laputa had a vault-based theming system where themes were markdown notes in `theme/` with `type: Theme` frontmatter. Each property became a CSS variable. This included a `ThemeManager` hook, theme property editor, dark mode detection, live preview on save, and three built-in themes. The system was complex (spanning Rust seed/create/defaults modules, TypeScript hooks, and CSS variable bridging) and added significant maintenance burden for a feature that most users never customized beyond the defaults.
|
||||
|
||||
## Decision
|
||||
|
||||
**Remove the vault-based theming system entirely. The app uses a single, hardcoded light theme defined in CSS variables (`src/index.css`) and editor theme (`src/theme.json`).** The `theme/` folder, `ThemeManager` hook, theme Rust modules, theme property editor, and dark mode support were all deleted.
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): Remove theming, ship a single polished light theme — drastically reduced complexity, fewer files to maintain, no theme-related bugs. Downside: no user customization, no dark mode.
|
||||
- **Option B**: Keep theming but simplify — reduce to light/dark toggle only. Downside: still requires theme loading, CSS variable bridging, and live preview infrastructure.
|
||||
- **Option C**: Keep the full theming system — maximum flexibility. Downside: high maintenance cost for a rarely-used feature, frequent source of bugs (WKWebView reflow issues, CSS var sync).
|
||||
|
||||
## Consequences
|
||||
|
||||
- Deleted: `src-tauri/src/theme/`, `src/hooks/useThemeManager.ts`, `ThemePropertyEditor.tsx`, theme-related commands, `_themes/` legacy support.
|
||||
- Single theme defined in `src/index.css` (CSS variables) and `src/theme.json` (editor typography).
|
||||
- No dark mode support — the app is light-only.
|
||||
- Protected folders reduced: `theme/` is no longer scanned by `scan_vault`.
|
||||
- Re-evaluation trigger: if dark mode becomes a hard requirement for accessibility or user demand.
|
||||
31
docs/adr/0014-git-based-vault-cache.md
Normal file
31
docs/adr/0014-git-based-vault-cache.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0014"
|
||||
title: "Git-based incremental vault cache"
|
||||
status: active
|
||||
date: 2026-03-08
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Scanning a vault of 9000+ markdown files on every app launch takes several seconds. A caching strategy was needed that could detect which files changed since the last scan and only re-parse those, while remaining correct even after external edits (e.g., from a text editor or git pull).
|
||||
|
||||
## Decision
|
||||
|
||||
**Use git as the change detection mechanism. The cache stores all `VaultEntry` objects in a JSON file at `~/.laputa/cache/<vault-hash>.json`. On load, it compares the cached git HEAD commit hash with the current one: if the same, only re-parse uncommitted changed files; if different, use `git diff` to find changed files and selectively re-parse. Full rescan only on cache miss or version bump.**
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): Git-based incremental cache — leverages existing git infrastructure, precise change detection, handles both committed and uncommitted changes. Downside: requires git-tracked vault, cache invalidation logic is complex.
|
||||
- **Option B**: File modification time (`mtime`) based cache — works without git. Downside: unreliable across filesystems (iCloud, Dropbox), clock skew issues.
|
||||
- **Option C**: File hash (content-based) cache — always correct. Downside: must read every file to compute hash, defeating the purpose of caching.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Cache file stored outside the vault at `~/.laputa/cache/<vault-hash>.json` — never pollutes the user's git repo.
|
||||
- Writes are atomic (write to `.tmp` then rename) to prevent corruption.
|
||||
- Cache version (v5) is bumped on `VaultEntry` field changes to force full rescan.
|
||||
- Legacy `.laputa-cache.json` files inside the vault are auto-migrated and deleted on first run.
|
||||
- `reload_vault` command deletes the cache file before rescanning, guaranteeing fresh data.
|
||||
- Stale cache entries are pruned on vault open (files that no longer exist on disk).
|
||||
- Re-evaluation trigger: if non-git vaults (e.g., iCloud-only) need to be supported.
|
||||
29
docs/adr/0015-auto-save-with-debounce.md
Normal file
29
docs/adr/0015-auto-save-with-debounce.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0015"
|
||||
title: "Auto-save with 500ms debounce"
|
||||
status: active
|
||||
date: 2026-03-19
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Manual save (Cmd+S) was the only way to persist editor changes. Users occasionally lost work when switching notes or closing the app without saving. An auto-save mechanism was needed that balanced responsiveness (no perceived lag) with disk I/O efficiency (not writing on every keystroke).
|
||||
|
||||
## Decision
|
||||
|
||||
**Notes auto-save with a 500ms debounce after the last keystroke. The `useEditorSave` hook watches for editor content changes and triggers a save after 500ms of inactivity. The same `save_note_content` Rust command is used for both auto-save and manual save.**
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): 500ms debounce auto-save — fast enough to feel instant, slow enough to batch rapid keystrokes. Downside: 500ms window where unsaved changes exist.
|
||||
- **Option B**: Save on every change (no debounce) — zero data loss risk. Downside: excessive disk writes, poor performance, frequent git diffs.
|
||||
- **Option C**: Save on note switch / app blur only — minimal disk writes. Downside: data loss if app crashes mid-edit, no live preview of changes in other views.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Users never need to manually save (Cmd+S still works as an immediate save).
|
||||
- Auto-save triggers vault entry updates, keeping the note list, search, and relationships current.
|
||||
- The same save path handles wikilink extraction and frontmatter parsing after save.
|
||||
- Secondary windows (multi-window mode) each have their own auto-save via `useEditorSaveWithLinks`.
|
||||
- Re-evaluation trigger: if 500ms is too aggressive for low-powered devices or network-synced vaults.
|
||||
Reference in New Issue
Block a user