guanghu/docs/adr/0089-active-vault-filesystem-watcher.md
Guanghu Domestic Migration 9b41d51231
Some checks are pending
Auto-update PR branches / Update open PR branches (push) Waiting to run
CI / Frontend Static Quality Checks (push) Waiting to run
CI / Frontend Tests & Coverage (push) Waiting to run
CI / Rust Tests & Quality Checks (push) Waiting to run
CI / Linux build verification (push) Waiting to run
Deploy docs / Build VitePress site (push) Waiting to run
Deploy docs / Deploy to GitHub Pages (push) Blocked by required conditions
Release (Alpha) / Compute alpha version (push) Waiting to run
Release (Alpha) / Build release artifacts (push) Blocked by required conditions
Release (Alpha) / GitHub Release (alpha) (push) Blocked by required conditions
Release (Alpha) / Update docs and release pages (push) Blocked by required conditions
chore: import sanitized domestic snapshot for REPO-004
Source snapshot: 514ab1975951d94342ea38e64101d5a0f1c51c77
2026-07-17 15:55:28 +08:00

2.8 KiB

type, id, title, status, date
type id title status date
ADR 0089 Active vault filesystem watcher active 2026-04-27

Context

Tolaria treats the filesystem as the source of truth, but before this decision the running app only noticed external file changes after a manual Reload Vault, a Git pull, or an AI-agent-specific refresh callback. Edits from another editor, terminal commands, another Tolaria window, or a non-pull Git operation could leave React state and the editor surface stale.

ADR-0071 already defines the safe reconciliation policy for external vault mutations: reload vault-derived state, protect unsaved local edits, and reopen the clean active note from disk when needed. Filesystem watching needed to reuse that policy instead of adding another ad hoc reload path.

Decision

Tolaria watches the active desktop vault with a native filesystem watcher and routes external change batches through the shared external-refresh reconciler.

The desktop backend exposes start_vault_watcher and stop_vault_watcher commands backed by Rust notify. It watches the active vault recursively, ignores known non-content churn such as .git/, node_modules/, temp files, and .tolaria-rename-txn, then emits vault-changed events with the active vault path and changed paths.

The renderer owns batching and reconciliation. useVaultWatcher starts the backend watcher for the active main-window vault, debounces native events into one refresh, filters out recent app-owned saves, and calls refreshPulledVaultState(). Manual Reload Vault still uses reload_vault directly, but now exposes visible reload feedback.

Options considered

  • Native active-vault watcher plus shared reconciliation (chosen): keeps external changes visible without polling and preserves ADR-0071 behavior for clean and unsaved tabs. Cons: adds one native dependency and a long-lived watcher state.
  • Frontend-only polling: simpler backend surface, but wastes work on idle vaults and still needs careful active-note reconciliation.
  • Direct reloadVault() on every native event: easy to implement, but bypasses clean-tab reopen handling and can clobber the user experience around unsaved edits.
  • Watch every configured vault: could pre-warm state, but burns resources for inactive vaults and complicates event ownership across windows.

Consequences

  • External writes converge automatically into the visible vault state after a short debounce.
  • Active clean notes are refreshed through the same path as pull and AI-agent updates; unsaved local edits remain protected.
  • Tolaria app-owned saves are suppressed briefly so autosave does not trigger a full external refresh loop.
  • The status bar can show reload progress for manual and automatic refreshes.
  • The watcher is a desktop-only integration; mobile builds keep no-op command stubs until a mobile-specific filesystem strategy exists.