ADR 0012: Vault Abstraction and Local-Only Git Versioning
Status
Accepted
Context
Earlier versions of Jotter operated on a single global data_dir containing all projects. To enable users to collaborate with coworkers on specific projects, Jotter implemented selective per-project Git sync (ADR 0008).
While functional, supporting both a global workspace repository and nested per-project sub-repositories introduced significant architectural complexity:
- Nested Git Repositories: Managing submodules, nested
.gitfolders, and dynamic.gitignorefiles was brittle and error-prone. - Commit Traversal Complexity: The "Time Machine" feature had to heuristically search both project subdirectories and parent directories to resolve commit history.
- Ambiguous Boundaries: Users lacked a clean mental separation between completely private workspaces and shared team environments.
- Networking Out of Scope: Remote management, credential handling, offline detection, and rebase/merge conflict recovery are a large surface area for a tool whose purpose is editing Kanban tasks.
Similar file-based Markdown productivity tools (like Obsidian) solve this problem by organizing workspaces into Vaults.
Decision
We introduce a Vault abstraction above projects, establishing a clean hierarchy: $$\text{Vault} \longrightarrow \text{Projects} \longrightarrow \text{Buckets} \longrightarrow \text{Tasks}$$
Vault as the Unit of Physical Storage & Git Versioning:
- A Vault represents an isolated folder on disk.
- Exactly one Git repository exists per Vault (1:1 mapping).
- Each Vault maintains its own independent SQLite projection index (
tasks.db). - The background filesystem watcher (
FileWatcherService) monitors the active Vault directory.
Local-Only Git Versioning:
- Complex nested repository detection is superseded by a single Vault Git root.
- Jotter no longer performs any Git networking. All remote configuration (
git_remoteon projects and vaults, the globalgitRemoteUrlsetting) and thepull/push/offline-detection logic were removed. commit_changesonly stages and commits local changes, and only if the Vault directory already is a Git repository. Committing never initializes one.- Versioning is enabled per vault by an explicit user action (
POST /api/system/git/init, the sidebar Enable Git button). It is refused when Git is missing or the vault already lies inside another work tree, to avoid nested repositories. - Jotter never overwrites an existing
user.name/user.email; a missing value gets a fallback in the repository's local config only. - The local SQLite index (
tasks.db*) is kept out of commits via.git/info/exclude. - Users who want to share a Vault add a remote themselves and push/pull with their own Git tooling, or use a file sync tool.
- The "Time Machine" queries file revisions (
git log -- <project>/<task>.md) directly from the Vault's single repository. - Existing project-level Git repos are retained as an automatic local fallback for backwards compatibility.
Multi-Vault Switching:
- Vault registrations and the active vault are maintained in a global registry (
~/.config/jotter/vaults.json). - Switching vaults re-targets the active directory, switches the SQLite database connection, runs initial reconciliation, and points the background file watcher to the new vault without restarting the server.
- The frontend provides a dropdown switcher in the sidebar.
- Vault registrations and the active vault are maintained in a global registry (
flowchart TD
subgraph UI ["User Interface"]
VS["Vault Switcher Dropdown"]
end
subgraph VaultPersonal ["Vault: Personal (~/Documents/Jotter)"]
GitA[".git"]
DB_A["tasks.db"]
ProjA1["Project: Default"]
ProjA2["Project: Home"]
end
subgraph VaultWork ["Vault: Work Team (~/Work/client-repo)"]
GitB[".git"]
DB_B["tasks.db"]
ProjB1["Project: Sprint-Backlog"]
ProjB2["Project: Architecture"]
end
VS -->|Active Vault| VaultPersonal
VS -.->|Switch Active| VaultWorkConsequences
Positive
- Architectural Clarity: Decouples project organization from physical Git repository boundaries.
- Git Reliability: Eliminates nested repository conflicts and dynamic
.gitignoreoverwriting. - Small Surface Area: No credentials, network errors, or merge conflicts to handle inside Jotter.
- Collaboration Stays Possible: Users can clone coworker repositories as separate vaults and sync them with their own Git workflow.
- Zero-Friction Migration: Existing installations automatically seed the current
data_diras the"default"vault on startup.
Negative / Trade-offs
- Users who previously relied on nested git repositories inside a single folder are encouraged to separate them into distinct vaults.
- Users who relied on Jotter to push and pull must now do so themselves. Previously configured remote URLs in
settings.jsonand the SQLite index are ignored.
