docs: add Architecture Decision Records (ADRs) — 5 backfill + process in CLAUDE.md
- docs/adr/README.md: format spec, rules, index - 0001: Tauri v2 + React stack - 0002: filesystem as source of truth - 0003: single note model (no tabs) - 0004: vault vs app settings storage - 0005: Tauri iOS for iPad (vs SwiftUI) - CLAUDE.md: ADR process — when to read, when to create, when to supersede
This commit is contained in:
14
CLAUDE.md
14
CLAUDE.md
@@ -84,6 +84,20 @@ Red → Green → Refactor → Commit. One cycle per commit. For bugs: write a f
|
||||
|
||||
After adding a Tauri command, new component/hook, data model change, or new integration: update `docs/ARCHITECTURE.md`, `docs/ABSTRACTIONS.md`, and/or `docs/GETTING-STARTED.md` in the same commit. Use Mermaid for diagrams (not ASCII). Exception: spatial wireframe layouts.
|
||||
|
||||
## Architecture Decision Records (ADRs)
|
||||
|
||||
ADRs live in `docs/adr/`. Read `docs/adr/README.md` for the format.
|
||||
|
||||
**Before making any architectural choice** (storage strategy, new dependency, platform approach, data model change): check `docs/adr/` for an existing decision that covers it. If one exists and your work aligns with it — proceed. If your work **supersedes** an existing ADR, **do not modify the existing file** — instead:
|
||||
1. Update the existing ADR: set `status: superseded` and `superseded_by: "NNNN"`
|
||||
2. Create a new ADR with the updated decision
|
||||
|
||||
**After completing a task**: if you made a significant architectural decision that isn't already documented in `docs/adr/`, create a new ADR in the same commit. A decision is "significant" if it affects: data storage, platform support, major dependencies, core abstractions, or cross-cutting concerns.
|
||||
|
||||
ADRs use Laputa note format — YAML frontmatter with `type: ADR`, `status: active|superseded|proposed`, `date: YYYY-MM-DD`.
|
||||
|
||||
**Do not create ADRs for**: implementation details, UI styling choices, refactoring decisions, or anything that doesn't affect how future code should be written.
|
||||
|
||||
## Design File (UI tasks)
|
||||
|
||||
1. Open `ui-design.pen` first — study existing frames for visual language.
|
||||
|
||||
31
docs/adr/0001-tauri-react-stack.md
Normal file
31
docs/adr/0001-tauri-react-stack.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0001"
|
||||
title: "Tauri v2 + React as application stack"
|
||||
status: active
|
||||
date: 2026-02-14
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Laputa is a desktop app for macOS (with iPad as a future target) that reads and writes a vault of markdown files. The app needs a native feel, filesystem access, git integration, and a rich text editor. A single developer (with AI assistance) is building it.
|
||||
|
||||
## Decision
|
||||
|
||||
Use **Tauri v2** (Rust backend + WebView frontend) with **React + TypeScript** for the UI, **BlockNote** for the editor, and **Vitest + Playwright** for testing.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Electron**: heavier runtime (~150MB), slower, but more mature ecosystem. Rejected — Tauri is lighter and has better native integration.
|
||||
- **SwiftUI**: best native macOS/iOS experience, but locks to Apple platforms only, no code sharing with a potential web version, and requires rewriting the entire UI. Rejected for the initial version — revisited in ADR-0005.
|
||||
- **Flutter**: cross-platform but WebView-based editor would have been poor; Dart ecosystem is thin for markdown tooling.
|
||||
- **Pure web app**: no filesystem access, no git, would require a backend server. Rejected — offline-first is a core principle.
|
||||
|
||||
## Consequences
|
||||
|
||||
- React frontend can be shared with a future web version
|
||||
- Rust backend provides safe, fast filesystem/git operations
|
||||
- Tauri v2 supports iOS (beta) — see ADR-0005 for iPad strategy
|
||||
- CodeScene code health monitoring applies to both Rust and TypeScript code
|
||||
- Claude Code can work on both layers without context switching
|
||||
- Triggers re-evaluation if: Tauri iOS proves unstable for production, or if SwiftUI becomes the primary target platform
|
||||
30
docs/adr/0002-filesystem-source-of-truth.md
Normal file
30
docs/adr/0002-filesystem-source-of-truth.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0002"
|
||||
title: "Filesystem as the single source of truth"
|
||||
status: active
|
||||
date: 2026-02-14
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Laputa needs a persistence model. The core question: does the app own the data, or does the filesystem? This affects sync, conflict resolution, offline support, portability, and long-term trust with users.
|
||||
|
||||
## Decision
|
||||
|
||||
**The vault is the source of truth.** The app never owns the data — it only reads and writes `.md` files. All cache, React state, and in-memory representations are derived from the filesystem and must be reconstructible by deleting them. When in doubt, the file on disk wins.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Database-first (SQLite)**: faster queries, easier relationships. Rejected — creates lock-in, makes files unreadable outside the app, complicates sync.
|
||||
- **Cloud-first (proprietary sync)**: easier multi-device. Rejected — zero lock-in is a core principle; git handles sync.
|
||||
- **Hybrid (DB + files)**: DB as primary, files as export. Rejected — two sources of truth always diverge.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Notes are plain markdown files, readable and editable by any text editor
|
||||
- Git provides history, sync, and collaboration for free
|
||||
- Vault can be opened/edited externally without app corruption
|
||||
- App rebuilds cache on startup — acceptable cost for integrity guarantees
|
||||
- No "save" button needed — autosave writes to disk immediately
|
||||
- Triggers re-evaluation if: vault size grows to millions of files and filesystem scanning becomes a bottleneck
|
||||
30
docs/adr/0003-single-note-model.md
Normal file
30
docs/adr/0003-single-note-model.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0003"
|
||||
title: "Single note open at a time (no tabs)"
|
||||
status: active
|
||||
date: 2026-03-24
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The app originally had a tab bar allowing multiple notes to be open simultaneously (similar to a browser or code editor). After building and shipping it, the tab model was found to add significant UI complexity, state management overhead, and confusion — without a proportional benefit for a notes app.
|
||||
|
||||
## Decision
|
||||
|
||||
**Remove the tab bar. Only one note is open at a time.** Navigation history (Back/Forward with Cmd+[/]) replaces tabs for moving between recently visited notes. Closed tab history and `useTabManagement` are removed.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Keep tabs**: familiar UX, allows comparing notes side by side. Rejected — adds ~2000 lines of complexity, confusing state (which tab is "active"?), and breaks the "editor is sacred" principle.
|
||||
- **Tabs + single-note toggle**: configurable per user. Rejected — doubles the state surface and testing burden.
|
||||
- **Split pane (two notes at once)**: useful for reference. Deferred — can be added later without tabs, via a dedicated split layout.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Removes ~2000 lines of code (`TabBar`, `useClosedTabHistory`, `useEditorTabSwap`, `tabLayout`)
|
||||
- `handleSelectNote` replaces the current note instead of adding a tab
|
||||
- Cmd+W (close tab) and Cmd+Shift+T (reopen closed tab) removed from shortcuts
|
||||
- Back/Forward navigation (Cmd+[/Cmd+]) preserves history without tab state
|
||||
- Significant simplification of `App.tsx` and editor state
|
||||
- Triggers re-evaluation if: multi-note workflows become a top user request
|
||||
42
docs/adr/0004-vault-vs-app-settings-storage.md
Normal file
42
docs/adr/0004-vault-vs-app-settings-storage.md
Normal file
@@ -0,0 +1,42 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0004"
|
||||
title: "Vault vs app settings for state storage"
|
||||
status: active
|
||||
date: 2026-03-24
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
As features were added, there was recurring ambiguity about where to persist configuration and state: in the vault (as frontmatter in `.md` files) or in app settings (`~/.config/com.laputa.app/settings.json` / localStorage). Without a clear rule, some decisions were inconsistent.
|
||||
|
||||
## Decision
|
||||
|
||||
**Ask: "Would the user want this to follow the vault across all their installations?"**
|
||||
|
||||
- If **yes** → store in the vault (as frontmatter in the relevant `.md` file, using the `_` convention for system properties)
|
||||
- If **no** → store in app settings
|
||||
|
||||
Examples:
|
||||
| Data | Storage | Reason |
|
||||
|------|---------|--------|
|
||||
| Note type icon (`_icon`) | Vault frontmatter | Follows the vault everywhere |
|
||||
| Note type color (`_color`) | Vault frontmatter | Follows the vault everywhere |
|
||||
| Note sort preference | Vault frontmatter (type file) | Per-vault, consistent across devices |
|
||||
| API keys (Anthropic, OpenAI) | App settings | Installation-specific |
|
||||
| GitHub token | App settings | Installation-specific |
|
||||
| Window size / zoom | App settings | Device-specific |
|
||||
| Editor zoom level | App settings | Device-specific |
|
||||
| Telemetry consent | App settings | Installation-specific |
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **Everything in localStorage**: simple, but breaks cross-device sync for vault-level config.
|
||||
- **Everything in vault**: pure, but makes device-specific settings (zoom, window size) propagate to all devices — confusing.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Config that "belongs to a note or type" lives in frontmatter — readable/diffable in git
|
||||
- The `_` prefix convention (see ABSTRACTIONS.md) distinguishes system properties from user properties
|
||||
- App rebuilds from vault state on open — no stale config files to manage
|
||||
- Triggers re-evaluation if: vault files become too polluted with system frontmatter properties
|
||||
38
docs/adr/0005-tauri-ios-for-ipad.md
Normal file
38
docs/adr/0005-tauri-ios-for-ipad.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0005"
|
||||
title: "Tauri v2 iOS for iPad support (vs SwiftUI rewrite)"
|
||||
status: active
|
||||
date: 2026-03-27
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Laputa runs on macOS via Tauri v2. The goal is to also support iPad without changing the stack or redesigning the app from scratch. The core question: extend the existing stack to iOS, or rewrite in SwiftUI for a fully native experience?
|
||||
|
||||
## Decision
|
||||
|
||||
**Use Tauri v2 iOS (beta) for the iPad prototype.** The React frontend stays identical. The Rust backend compiles for iOS with `#[cfg(desktop)]` / `#[cfg(mobile)]` guards for platform-specific features. Desktop-only features (git CLI, macOS menu bar, MCP server, Claude CLI) are stubbed or skipped on mobile.
|
||||
|
||||
The prototype (`feat: add iPad/iOS prototype via Tauri v2 mobile target`, build `b492`) successfully builds and runs on iPad Pro 13" simulator (iOS 18.3.1).
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- **SwiftUI rewrite**: best native macOS/iPad experience, full App Store integration, native TextKit 2 editor. Rejected for now — would discard all existing React code, Rust backend, 2200+ tests, and Claude Code's accumulated context. Worth revisiting if Laputa becomes iOS-first.
|
||||
- **Capacitor**: replaces Tauri layer, keeps React, but the Rust backend is lost entirely — git and file operations would need reimplementation in JS or Swift.
|
||||
- **React Native + WebView**: wraps the React app in a WebView. Too hacky, performance concerns, App Store review risks.
|
||||
|
||||
## Git on iPad
|
||||
|
||||
`git` CLI is unavailable on iOS. Options for production:
|
||||
- **Option A (recommended)**: `isomorphic-git` — pure JS git implementation, no native dependencies, runs in WebView. Replaces Rust git commands on mobile.
|
||||
- **Option B (prototype)**: Working Copy as iOS Files provider — user manages git separately.
|
||||
- **Option C**: iCloud Drive sync — no git history. Not recommended.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Zero frontend changes needed for basic iPad support
|
||||
- Desktop features (git, MCP, Claude CLI) unavailable on iPad until isomorphic-git is integrated
|
||||
- Tauri v2 iOS is still beta — production stability unknown
|
||||
- App Store distribution requires Apple Developer account and TestFlight
|
||||
- Triggers re-evaluation if: Tauri iOS remains unstable after 6 months, or iPad becomes the primary target (in which case SwiftUI rewrite becomes rational)
|
||||
51
docs/adr/README.md
Normal file
51
docs/adr/README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Architecture Decision Records
|
||||
|
||||
This folder contains Architecture Decision Records (ADRs) for the Laputa app.
|
||||
|
||||
## Format
|
||||
|
||||
Each ADR is a Laputa-compatible markdown note with YAML frontmatter:
|
||||
|
||||
```markdown
|
||||
---
|
||||
type: ADR
|
||||
id: "0001"
|
||||
title: "Short decision title"
|
||||
status: active # active | superseded | proposed
|
||||
date: YYYY-MM-DD
|
||||
superseded_by: "0007" # only if status: superseded
|
||||
---
|
||||
|
||||
## Context
|
||||
What situation led to this decision? What forces were at play?
|
||||
|
||||
## Decision
|
||||
What was decided? State it clearly in one or two sentences.
|
||||
|
||||
## Alternatives considered
|
||||
- **Option A** (chosen): pros / cons
|
||||
- **Option B**: pros / cons
|
||||
- **Option C**: pros / cons
|
||||
|
||||
## Consequences
|
||||
What becomes easier or harder as a result of this decision?
|
||||
What triggers re-evaluation of this decision?
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- One decision per file
|
||||
- Files named `NNNN-short-title.md` (monotonic numbering)
|
||||
- Once `active`, never edit — supersede instead
|
||||
- When superseded: update `status: superseded` and add `superseded_by: "NNNN"`
|
||||
- ARCHITECTURE.md reflects the current state (active decisions only)
|
||||
|
||||
## Index
|
||||
|
||||
| ID | Title | Status |
|
||||
|----|-------|--------|
|
||||
| [0001](0001-tauri-react-stack.md) | Tauri v2 + React as application stack | active |
|
||||
| [0002](0002-filesystem-source-of-truth.md) | Filesystem as the single source of truth | active |
|
||||
| [0003](0003-single-note-model.md) | Single note open at a time (no tabs) | active |
|
||||
| [0004](0004-vault-vs-app-settings-storage.md) | Vault vs app settings for state storage | active |
|
||||
| [0005](0005-tauri-ios-for-ipad.md) | Tauri v2 iOS for iPad support (vs SwiftUI rewrite) | active |
|
||||
Reference in New Issue
Block a user