docs: backfill ADRs 0021–0025 (push-to-main, BlockNote, repair vault, cache location, type field)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
30
docs/adr/0021-push-to-main-workflow.md
Normal file
30
docs/adr/0021-push-to-main-workflow.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0021"
|
||||
title: "Push directly to main (no PRs or branches)"
|
||||
status: active
|
||||
date: 2026-03-02
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Initially, the project used feature branches and PRs. With a single developer (assisted by Claude Code), the PR overhead — branch creation, rebase churn, merge conflicts from long-lived branches — slowed development without adding review value. The pre-commit and pre-push hooks already enforce tests, linting, type checking, and code health gates.
|
||||
|
||||
## Decision
|
||||
|
||||
**Push directly to main — no PRs, no feature branches. The pre-push hook runs all quality gates (tests, lint, type check, coverage, CodeScene health). Never use `--no-verify`.**
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): Push to main with hook-enforced quality gates — fastest iteration, no rebase churn, hooks provide automated review. Downside: no PR-based review, harder to roll back a batch of changes.
|
||||
- **Option B**: Feature branches with PRs — standard team workflow, code review. Downside: rebase churn for a solo developer, PR overhead with no reviewer.
|
||||
- **Option C**: Feature branches without PRs (merge to main locally) — branch isolation without review overhead. Downside: still has merge conflicts, branches diverge.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Commit every 20-30 minutes with conventional commit prefixes (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`).
|
||||
- Pre-commit hook: vitest + CodeScene health check.
|
||||
- Pre-push hook: same + Playwright smoke tests.
|
||||
- No `--no-verify` ever — the hooks are the quality gate.
|
||||
- Reverting changes requires `git revert` (not force push).
|
||||
- Re-evaluation trigger: if a second developer joins and needs code review.
|
||||
32
docs/adr/0022-blocknote-rich-text-editor.md
Normal file
32
docs/adr/0022-blocknote-rich-text-editor.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0022"
|
||||
title: "BlockNote as the rich text editor"
|
||||
status: active
|
||||
date: 2026-02-15
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Laputa needs a rich text editor that can render markdown with YAML frontmatter, support custom inline content types (wikilinks), and provide a modern editing experience. The editor must handle the markdown-to-blocks-to-markdown round-trip without data loss.
|
||||
|
||||
## Decision
|
||||
|
||||
**Use BlockNote as the primary rich text editor, with CodeMirror 6 as an alternative raw editing mode. Custom wikilink inline content is defined via `createReactInlineContentSpec`. Markdown round-tripping uses a pre/post-processing pipeline with placeholder tokens for wikilinks.**
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): BlockNote + CodeMirror 6 raw mode — BlockNote provides modern block-based editing, CodeMirror gives power users direct markdown access. Downside: wikilink round-tripping requires custom preprocessing pipeline.
|
||||
- **Option B**: ProseMirror directly — maximum control. Downside: much more boilerplate, no block-level abstractions, harder to maintain.
|
||||
- **Option C**: CodeMirror only (no rich text) — simplest, no round-trip issues. Downside: poor UX for non-technical users, no inline previews.
|
||||
- **Option D**: Monaco Editor — rich features, VS Code-like. Downside: heavy, designed for code not prose, no block-level structure.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Custom wikilink type defined in `editorSchema.tsx` via `createReactInlineContentSpec`.
|
||||
- Markdown-to-BlockNote pipeline: `splitFrontmatter()` → `preProcessWikilinks()` → `tryParseMarkdownToBlocks()` → `injectWikilinks()`.
|
||||
- BlockNote-to-Markdown pipeline: `blocksToMarkdownLossy()` → `postProcessWikilinks()` → prepend frontmatter.
|
||||
- Placeholder tokens use `‹` and `›` (U+2039/U+203A) to avoid colliding with markdown syntax.
|
||||
- Raw editor (CodeMirror 6) toggled via Cmd+K → "Raw Editor" or breadcrumb bar button.
|
||||
- The H1 block is hidden via CSS in favor of a dedicated `TitleField` component.
|
||||
- Re-evaluation trigger: if BlockNote's markdown round-tripping degrades or a better block editor emerges.
|
||||
31
docs/adr/0023-repair-vault-auto-bootstrap.md
Normal file
31
docs/adr/0023-repair-vault-auto-bootstrap.md
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0023"
|
||||
title: "Repair Vault auto-bootstrap pattern"
|
||||
status: active
|
||||
date: 2026-03-07
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
As Laputa adds features that depend on vault files (type definitions, config files, agents), users with existing vaults would miss these files. Manually creating them is error-prone. Features must work on both new and existing vaults without user intervention.
|
||||
|
||||
## Decision
|
||||
|
||||
**Every feature that depends on vault files must auto-bootstrap: check if file/folder exists on vault open, create with defaults if missing (silent, idempotent). All bootstrap functions are registered with the central `Cmd+K → "Repair Vault"` command for manual re-creation.**
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): Auto-bootstrap on vault open + manual Repair Vault command — works for new and existing vaults, idempotent, no user action needed. Downside: vault may accumulate files the user didn't explicitly create.
|
||||
- **Option B**: Require users to run a setup wizard — explicit, user-controlled. Downside: friction, users forget, new features don't work until setup is run.
|
||||
- **Option C**: Store defaults in app bundle, not vault — no vault files created. Downside: breaks the "vault as source of truth" principle, custom configs can't override defaults.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Type definitions (`type/project.md`, etc.) are seeded on vault open if missing.
|
||||
- Config files (`config/agents.md`, etc.) are seeded on vault open if missing.
|
||||
- `Repair Vault` command (Cmd+K) re-creates all expected files — useful after manual deletion or vault corruption.
|
||||
- All bootstrap operations are silent and idempotent — running twice has no effect.
|
||||
- `getting_started.rs` creates the Getting Started demo vault with all expected structure.
|
||||
- The `vault_health_check` command detects missing or misconfigured vault files.
|
||||
- Re-evaluation trigger: if the number of auto-created files becomes excessive or confusing for users.
|
||||
30
docs/adr/0024-cache-outside-vault.md
Normal file
30
docs/adr/0024-cache-outside-vault.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0024"
|
||||
title: "Vault cache stored outside vault directory"
|
||||
status: active
|
||||
date: 2026-03-08
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The vault cache was originally stored as `.laputa-cache.json` inside the vault directory. This caused problems: the cache file appeared in git status, polluted the user's repo, and could be accidentally committed. It also confused vault scanning (the cache file was itself a file in the vault).
|
||||
|
||||
## Decision
|
||||
|
||||
**Store the vault cache at `~/.laputa/cache/<vault-hash>.json`, outside the vault directory. The vault path is hashed (via `DefaultHasher`) to produce a deterministic filename. Legacy cache files inside the vault are auto-migrated and deleted on first run.**
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): External cache at `~/.laputa/cache/` — never pollutes the vault, no git issues, deterministic filename from vault path hash. Downside: separate cleanup needed if vault is deleted.
|
||||
- **Option B**: Cache inside vault with `.gitignore` — simpler, travels with the vault. Downside: .gitignore can be overridden, users may not have one, still appears in file listings.
|
||||
- **Option C**: No persistent cache (in-memory only) — simplest, no file management. Downside: full rescan on every app launch, slow for large vaults.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Cache path: `~/.laputa/cache/<vault-hash>.json` (e.g., `~/.laputa/cache/12345678.json`).
|
||||
- Writes are atomic: write to `.tmp` then rename.
|
||||
- Legacy `.laputa-cache.json` files inside the vault are auto-migrated and deleted.
|
||||
- `reload_vault` command deletes the cache file before rescanning.
|
||||
- The `.laputa/` directory also stores other app data (future: vault metadata, indexes).
|
||||
- Re-evaluation trigger: if vaults need to be portable between machines (cache would need to travel with the vault or be regenerated).
|
||||
30
docs/adr/0025-type-field-canonical.md
Normal file
30
docs/adr/0025-type-field-canonical.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
type: ADR
|
||||
id: "0025"
|
||||
title: "type: as canonical field (replacing Is A:)"
|
||||
status: active
|
||||
date: 2026-03-08
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
The entity type field was originally stored as `Is A:` in frontmatter (e.g., `Is A: Project`), following a natural-language naming convention. This caused problems: the space and colon made it awkward to parse, `is_a` was used internally as the snake_case variant, and `type:` is the standard YAML convention for metadata classification. The field name also confused AI agents that expected standard YAML conventions.
|
||||
|
||||
## Decision
|
||||
|
||||
**Use `type:` as the primary frontmatter field for entity types (e.g., `type: Project`). The legacy `Is A:` field is accepted as an alias for backward compatibility but new notes always use `type:`.** The internal TypeScript/Rust property remains `isA` for backward compatibility.
|
||||
|
||||
## Options considered
|
||||
|
||||
- **Option A** (chosen): `type:` as canonical with `Is A:` as legacy alias — clean, standard YAML convention, AI-readable. Downside: must maintain backward compatibility with existing vaults.
|
||||
- **Option B**: Keep `Is A:` as canonical — no migration needed. Downside: non-standard, awkward parsing, confusing for AI agents.
|
||||
- **Option C**: `kind:` or `category:` — avoids potential YAML type conflicts. Downside: less intuitive, still requires migration from `Is A:`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- New notes use `type: Project` (not `Is A: Project`).
|
||||
- The Rust parser checks `type:` first, falls back to `Is A:` for legacy notes.
|
||||
- `VaultEntry.isA` property name kept for internal backward compatibility.
|
||||
- Type documents in `type/` folder use `type: Type` in their own frontmatter.
|
||||
- Repair Vault migrates legacy `Is A:` fields to `type:` when run.
|
||||
- Re-evaluation trigger: if YAML reserved word `type` causes parsing issues (not observed so far).
|
||||
Reference in New Issue
Block a user