From 42b76d85d0db8ca06e716ae7ef2668f20c04d412 Mon Sep 17 00:00:00 2001 From: Test Date: Sat, 28 Mar 2026 10:51:47 +0100 Subject: [PATCH] =?UTF-8?q?docs:=20backfill=20ADRs=200006=E2=80=930010=20(?= =?UTF-8?q?flat=20vault,=20title=20sync,=20underscore=20convention,=20keyw?= =?UTF-8?q?ord=20search,=20dynamic=20relationships)?= 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/0006-flat-vault-structure.md | 30 +++++++++++++++++++ docs/adr/0007-title-filename-sync.md | 30 +++++++++++++++++++ docs/adr/0008-underscore-system-properties.md | 29 ++++++++++++++++++ docs/adr/0009-keyword-only-search.md | 30 +++++++++++++++++++ ...dynamic-wikilink-relationship-detection.md | 30 +++++++++++++++++++ 5 files changed, 149 insertions(+) create mode 100644 docs/adr/0006-flat-vault-structure.md create mode 100644 docs/adr/0007-title-filename-sync.md create mode 100644 docs/adr/0008-underscore-system-properties.md create mode 100644 docs/adr/0009-keyword-only-search.md create mode 100644 docs/adr/0010-dynamic-wikilink-relationship-detection.md diff --git a/docs/adr/0006-flat-vault-structure.md b/docs/adr/0006-flat-vault-structure.md new file mode 100644 index 00000000..30f92e0b --- /dev/null +++ b/docs/adr/0006-flat-vault-structure.md @@ -0,0 +1,30 @@ +--- +type: ADR +id: "0006" +title: "Flat vault structure (no type-based folders)" +status: active +date: 2026-03-15 +--- + +## Context + +Originally, notes were organized into type-based subfolders (`project/`, `person/`, `topic/`, etc.). Changing a note's type required moving it between folders, which broke wikilinks, complicated wikilink resolution (paths vs titles), and created friction for users who wanted to reorganize their knowledge. It also made vault scanning more complex and introduced edge cases around folder creation/deletion. + +## Decision + +**All user notes live as flat `.md` files at the vault root. Type is determined solely from the `type:` frontmatter field — never inferred from folder location.** Only a small set of protected folders exist: `type/` (type definition documents), `config/` (meta-configuration), and `attachments/`. + +## Options considered + +- **Option A** (chosen): Flat vault with frontmatter-only type — simple wikilink resolution (title/filename only), no file moves on type change, vault scanning restricted to root + protected folders. Downside: large vaults may look cluttered in Finder. +- **Option B**: Keep type-based folders — familiar Obsidian-like structure. Downside: type changes require file moves, wikilink resolution needs path awareness, scanning is recursive and slower. +- **Option C**: Hybrid (folders optional, type still from frontmatter) — maximum flexibility. Downside: two ways to do the same thing, confusing for AI agents and automation. + +## Consequences + +- Wikilink resolution is simplified to multi-pass title/filename matching — no path-based matching needed. +- Changing a note's type is a frontmatter edit, not a file move. +- A `flatten_vault` migration command and wizard were added for existing vaults with type folders. +- `vault_health_check` detects stray files in non-protected subfolders. +- `scan_vault` only indexes root-level `.md` files plus protected folders — non-protected subdirectories are ignored. +- Re-evaluation trigger: if users need nested folder hierarchies for non-type organization (e.g., project-specific subdirectories). diff --git a/docs/adr/0007-title-filename-sync.md b/docs/adr/0007-title-filename-sync.md new file mode 100644 index 00000000..c857d959 --- /dev/null +++ b/docs/adr/0007-title-filename-sync.md @@ -0,0 +1,30 @@ +--- +type: ADR +id: "0007" +title: "Title equals filename (slug sync)" +status: active +date: 2026-03-15 +--- + +## Context + +With the move to a flat vault structure (ADR-0006), filenames became the primary identifier for notes. Previously, titles were extracted from the first H1 heading, which was fragile (users could delete or change the H1 without realizing it affected the note's identity). A clear, deterministic mapping between title and filename was needed. + +## Decision + +**Every note's filename is `slugify(title).md`. The `title` frontmatter field is the source of truth for the human-readable title. On note open, the system syncs the title field to match the filename if they've diverged (filename wins). On rename, both title and filename are updated atomically.** + +## Options considered + +- **Option A** (chosen): `title = slugify(filename)` with bidirectional sync — deterministic, predictable, wikilinks resolve by title/filename stem. Downside: titles with special characters get simplified in filenames. +- **Option B**: UUID-based filenames with title only in frontmatter — filenames never change. Downside: vault is unreadable in Finder/terminal, breaks the "plain markdown files" principle. +- **Option C**: H1-based title extraction — no explicit title field. Downside: fragile, H1 can be accidentally deleted or changed, decoupled from filename. + +## Consequences + +- `extract_title` reads from frontmatter `title:` field, never from H1. Falls back to `slug_to_title()` (hyphens → spaces, title-case). +- `sync_title_on_open` auto-corrects desynced frontmatter on note open. +- `rename_note` updates both `title:` frontmatter and filename atomically, plus cross-vault wikilink updates. +- The H1 block inside BlockNote is hidden via CSS; a dedicated `TitleField` component above the editor is the primary title editing surface. +- Slug collision detection prevents duplicate filenames. +- Re-evaluation trigger: if users need filenames that don't match titles (e.g., short slugs for long titles). diff --git a/docs/adr/0008-underscore-system-properties.md b/docs/adr/0008-underscore-system-properties.md new file mode 100644 index 00000000..44a96425 --- /dev/null +++ b/docs/adr/0008-underscore-system-properties.md @@ -0,0 +1,29 @@ +--- +type: ADR +id: "0008" +title: "Underscore convention for system properties" +status: active +date: 2026-03-24 +--- + +## Context + +As Laputa added more features that store configuration in note frontmatter (pinned properties, type icons, colors, sidebar labels, sort order), the Properties panel became cluttered with internal fields that users shouldn't normally edit. A convention was needed to distinguish user-visible properties from system-internal ones. + +## Decision + +**Any frontmatter field whose name starts with `_` is a system property. It is hidden from the Properties panel, not exposed in search/filters, but remains editable in the raw editor. The frontmatter parser filters out `_*` fields before passing properties to the UI.** + +## Options considered + +- **Option A** (chosen): Underscore prefix convention (`_icon`, `_color`, `_order`, `_pinned_properties`) — simple, readable in raw files, universal rule. Downside: users must know the convention to access system fields. +- **Option B**: Separate YAML block or nested `_system:` key — cleaner separation. Downside: more complex parsing, breaks flat key-value frontmatter model. +- **Option C**: Store system properties in a separate sidecar file (`.meta.yml`) — complete separation. Downside: doubles the number of files, harder to keep in sync. + +## Consequences + +- All future system-level frontmatter fields must use the `_field_name` convention. +- Both Rust (`vault/mod.rs`) and TypeScript (`utils/frontmatter.ts`) parsers filter `_*` fields before passing `properties` to the UI. +- Power users can still access and edit system properties via the raw editor. +- Type documents use `_icon`, `_color`, `_order`, `_sidebar_label`, `_pinned_properties`. +- Re-evaluation trigger: if the number of system properties grows large enough to warrant a structured sub-object. diff --git a/docs/adr/0009-keyword-only-search.md b/docs/adr/0009-keyword-only-search.md new file mode 100644 index 00000000..f7bb2b22 --- /dev/null +++ b/docs/adr/0009-keyword-only-search.md @@ -0,0 +1,30 @@ +--- +type: ADR +id: "0009" +title: "Keyword-only search (remove semantic indexing)" +status: active +date: 2026-03-24 +--- + +## Context + +Laputa previously used QMD (a Go binary) for semantic vector indexing, enabling similarity-based search. This added significant complexity: a bundled Go binary requiring code-signing, an indexing step on vault open, status bar progress tracking, auto-install logic, and a separate `tools/qmd/` directory. The semantic search quality did not justify the operational burden, especially as the AI agent (with MCP vault tools) became a more natural way to do exploratory queries. + +## Decision + +**Remove QMD semantic indexing entirely and keep only keyword-based search. Search uses `walkdir` to scan all `.md` files, matching against titles and content with case-insensitive substring matching and relevance scoring.** + +## Options considered + +- **Option A** (chosen): Keyword-only search via `walkdir` — zero dependencies, no indexing step, instant results, no binary to sign/bundle. Downside: no fuzzy or semantic matching. +- **Option B**: Keep QMD semantic search — richer search results, similarity matching. Downside: bundled Go binary, code-signing, indexing latency, maintenance burden. +- **Option C**: Replace QMD with a Rust-native embedding library — no external binary. Downside: large model files, cold start time, still needs indexing. + +## Consequences + +- No external search binary to bundle, sign, or install. +- No indexing step on vault open — search is instant. +- `search_vault` Tauri command scans files directly with `walkdir`, runs in a blocking Tokio task. +- Title matches rank higher than content-only matches; exact title matches rank highest. +- The AI agent (via MCP `search_notes` tool) provides an alternative for exploratory/semantic queries. +- Re-evaluation trigger: if users report keyword search is insufficient for large vaults (9000+ notes). diff --git a/docs/adr/0010-dynamic-wikilink-relationship-detection.md b/docs/adr/0010-dynamic-wikilink-relationship-detection.md new file mode 100644 index 00000000..153c578a --- /dev/null +++ b/docs/adr/0010-dynamic-wikilink-relationship-detection.md @@ -0,0 +1,30 @@ +--- +type: ADR +id: "0010" +title: "Dynamic wikilink relationship detection" +status: active +date: 2026-03-08 +--- + +## Context + +Laputa needs to support arbitrary relationship types between notes (e.g., `Topics:`, `Key People:`, `Depends on:`). Initially, a hardcoded list `RELATIONSHIP_KEYS` identified which frontmatter fields were relationships. This was fragile — adding a new relationship type required a code change, and users couldn't define their own. + +## Decision + +**The Rust parser dynamically detects relationship fields by scanning all frontmatter keys for values containing `[[wikilinks]]`. Any field with wikilink values is captured in the `relationships` HashMap — no hardcoded field name list needed.** + +## Options considered + +- **Option A** (chosen): Dynamic detection via `[[wikilink]]` presence — zero configuration, extensible, any field name works. Downside: fields with bracket-like content could false-positive (mitigated by the `[[...]]` double-bracket syntax). +- **Option B**: Hardcoded `RELATIONSHIP_KEYS` list — simple, predictable. Downside: inflexible, requires code changes for new relationship types. +- **Option C**: User-configurable relationship field list in vault config — flexible. Downside: configuration burden, doesn't work out of the box. + +## Consequences + +- Users can define arbitrary relationship types by adding wikilink values to any frontmatter field. +- No code change needed when adding new relationship types — convention over configuration. +- All relationship fields appear in the Inspector's RelationshipsPanel automatically. +- The `relationships` HashMap in `VaultEntry` captures all dynamic relationships. +- Standard fields (`belongs_to`, `related_to`) are still recognized for backward compatibility but not privileged. +- Re-evaluation trigger: if false-positive detection becomes a problem (e.g., fields with literal `[[` content that aren't relationships).