diff --git a/docs/adr/0029-domain-command-builder-pattern.md b/docs/adr/0029-domain-command-builder-pattern.md new file mode 100644 index 00000000..675f53c0 --- /dev/null +++ b/docs/adr/0029-domain-command-builder-pattern.md @@ -0,0 +1,29 @@ +--- +type: ADR +id: "0029" +title: "Domain command builder pattern for useCommandRegistry" +status: active +date: 2026-03-30 +--- + +## Context + +`useCommandRegistry` was a 224-line "brain method" (CodeScene hotspot) that defined all command palette commands inline: navigation, note actions, git operations, view toggles, settings, type management, and filter controls. This monolithic structure scored 39 on CodeScene's complexity scale (target: ≤9.5 for hotspots), making it increasingly hard to add new commands without touching the central file. + +## Decision + +**Split command definitions into focused domain modules under `src/hooks/commands/`, each exporting a `build*Commands(config)` factory function. `useCommandRegistry` becomes a thin assembler that calls each builder and merges the results.** Domain modules: `navigationCommands`, `noteCommands`, `gitCommands`, `viewCommands`, `settingsCommands`, `typeCommands`, `filterCommands`. Shared types live in `commands/types.ts`; public API re-exported from `commands/index.ts`. + +## Options considered + +- **Option A** (chosen): Domain builder modules — each module owns its command shape and receives typed config. `useCommandRegistry` is pure assembly. All new files score 9.58–10.0. Downside: more files to navigate. +- **Option B**: Split by file but keep one large hook calling sub-hooks — sub-hooks still need shared state passed down, similar coupling. No real complexity win. +- **Option C**: Register commands imperatively via a global registry — decouples callers entirely. Downside: harder to trace, no TypeScript inference at the registration site, over-engineering for current scale. + +## Consequences + +- Adding a new command means editing the relevant domain module (e.g. `noteCommands.ts`) only, not touching the assembler. +- Each domain module receives only the config it needs — explicit, typed interface, no hook dependency. +- `useCommandRegistry` reduced from 224 lines to a thin assembler. +- Pattern is consistent with the Rust commands/ module split (ADR-0030). +- Re-evaluation trigger: if command count grows to the point where the assembler itself becomes a complexity hotspot. diff --git a/docs/adr/0030-rust-commands-module-split.md b/docs/adr/0030-rust-commands-module-split.md new file mode 100644 index 00000000..20f8a1d1 --- /dev/null +++ b/docs/adr/0030-rust-commands-module-split.md @@ -0,0 +1,29 @@ +--- +type: ADR +id: "0030" +title: "Rust commands/ module split by domain" +status: active +date: 2026-03-30 +--- + +## Context + +`src-tauri/src/commands.rs` grew to 937 lines as Tauri command handlers accumulated for vault CRUD, git/GitHub sync, AI, system, and window operations. All commands shared a single file with no domain separation, making it hard to navigate, review, and extend. The file was a CodeScene hotspot dragging down overall code health. + +## Decision + +**Replace `commands.rs` with a `commands/` module split by domain: `vault.rs`, `git.rs`, `github.rs`, `ai.rs`, `system.rs`, and `mod.rs` (shared utilities + re-exports).** Each file owns the Tauri command handlers for its domain and the `#[cfg(desktop)]` / `#[cfg(mobile)]` stubs for platform-conditional availability. `mod.rs` is kept thin (≤100 lines) with no command logic — only re-exports and shared helpers (`expand_tilde`, `parse_build_label`). + +## Options considered + +- **Option A** (chosen): Domain-based module split — mirrors the TypeScript `hooks/commands/` pattern (ADR-0029). Each file is independently reviewable and scores well on code health. Downside: more files to navigate. +- **Option B**: Split by platform (`desktop.rs`, `mobile.rs`) — aligns with `#[cfg(...)]` guards but mixes domain concerns. Harder to find a specific command. +- **Option C**: Keep monolith but add section comments — zero file-count cost, but doesn't solve complexity or reviewability. + +## Consequences + +- `github.rs` separates GitHub OAuth/API commands from git sync commands (`git.rs`), matching the underlying Rust module split (`github/` vs `git/`). +- Platform stubs (`#[cfg(mobile)]` error returns) live alongside the desktop implementation in the same domain file. +- `mod.rs` re-exports all command functions so `lib.rs` `invoke_handler!` registration is unchanged. +- New Tauri commands go into the appropriate domain file; if no domain fits, create a new one rather than putting it in `mod.rs`. +- Re-evaluation trigger: if a single domain file (e.g. `vault.rs`) itself grows beyond ~300 lines and becomes a hotspot. diff --git a/docs/adr/README.md b/docs/adr/README.md index 64e52aa7..08711c47 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -82,4 +82,7 @@ proposed → active → superseded | [0024](0024-cache-outside-vault.md) | Vault cache stored outside vault directory | active | | [0025](0025-type-field-canonical.md) | type: as canonical field (replacing Is A:) | active | | [0026](0026-props-down-no-global-state.md) | Props-down callbacks-up (no global state) | active | -| [0027](0027-dual-ai-architecture.md) | Dual AI architecture (API chat + CLI agent) | active | +| [0027](0027-dual-ai-architecture.md) | Dual AI architecture (API chat + CLI agent) | superseded | +| [0028](0028-cli-agent-only-no-api-key.md) | CLI agent only — no direct Anthropic API key | active | +| [0029](0029-domain-command-builder-pattern.md) | Domain command builder pattern for useCommandRegistry | active | +| [0030](0030-rust-commands-module-split.md) | Rust commands/ module split by domain | active |