From 2dd68c170e395465d6dce20897c7ec61837343bb Mon Sep 17 00:00:00 2001 From: Test Date: Sat, 28 Mar 2026 10:55:40 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20backfill=20ADRs=200021=E2=80=930025=20(?= =?UTF-8?q?push-to-main,=20BlockNote,=20repair=20vault,=20cache=20location?= =?UTF-8?q?,=20type=20field)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/adr/0021-push-to-main-workflow.md | 30 ++++++++++++++++++ docs/adr/0022-blocknote-rich-text-editor.md | 32 ++++++++++++++++++++ docs/adr/0023-repair-vault-auto-bootstrap.md | 31 +++++++++++++++++++ docs/adr/0024-cache-outside-vault.md | 30 ++++++++++++++++++ docs/adr/0025-type-field-canonical.md | 30 ++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 docs/adr/0021-push-to-main-workflow.md create mode 100644 docs/adr/0022-blocknote-rich-text-editor.md create mode 100644 docs/adr/0023-repair-vault-auto-bootstrap.md create mode 100644 docs/adr/0024-cache-outside-vault.md create mode 100644 docs/adr/0025-type-field-canonical.md diff --git a/docs/adr/0021-push-to-main-workflow.md b/docs/adr/0021-push-to-main-workflow.md new file mode 100644 index 00000000..74fdecb2 --- /dev/null +++ b/docs/adr/0021-push-to-main-workflow.md @@ -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. diff --git a/docs/adr/0022-blocknote-rich-text-editor.md b/docs/adr/0022-blocknote-rich-text-editor.md new file mode 100644 index 00000000..8d866c9e --- /dev/null +++ b/docs/adr/0022-blocknote-rich-text-editor.md @@ -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. diff --git a/docs/adr/0023-repair-vault-auto-bootstrap.md b/docs/adr/0023-repair-vault-auto-bootstrap.md new file mode 100644 index 00000000..4d46606e --- /dev/null +++ b/docs/adr/0023-repair-vault-auto-bootstrap.md @@ -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. diff --git a/docs/adr/0024-cache-outside-vault.md b/docs/adr/0024-cache-outside-vault.md new file mode 100644 index 00000000..a1ac1c12 --- /dev/null +++ b/docs/adr/0024-cache-outside-vault.md @@ -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/.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/.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). diff --git a/docs/adr/0025-type-field-canonical.md b/docs/adr/0025-type-field-canonical.md new file mode 100644 index 00000000..06f55f70 --- /dev/null +++ b/docs/adr/0025-type-field-canonical.md @@ -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).