Architecture
parc is built library-first. parc-core is a pure Rust library with no terminal I/O. Everything else — the CLI, the JSON-RPC server, the Tauri desktop GUI — is a thin consumer of the same engine.
# Crate layout
parc/
├── parc-core/ # Library — no println!, no TTY, returns Result<T, ParcError>
├── parc-cli/ # CLI binary — terminal formatting, $EDITOR, clap
├── parc-server/ # JSON-RPC 2.0 server (stdio / Unix socket)
└── parc-gui/ # Tauri v2 desktop app — vanilla TypeScript web components
# parc-core
The library. Contains the data model, the Markdown frontmatter parser, the schema engine, the search DSL parser and compiler, the SQLite + FTS5 index, the history snapshot engine, the wiki-link resolver, and the lifecycle hook dispatcher.
Rules:
- No
println!, noprint!, no TTY assumptions - All operations return
Result<T, ParcError>with structured error variants - Takes a
VaultPathas input — never assumes a location, never reads$HOME - No global state — every operation takes the vault and config it needs
This is what makes the CLI, server, and GUI so thin. None of them re-implement business logic; they just translate between their respective transports and parc-core.
# parc-cli
The parc binary. Built on clap (derive). Adds terminal-specific concerns:
- Markdown rendering with
termimad - Diff rendering with
similar $EDITORinvocation forparc newandparc edit- Colour and TTY detection
- JSON output mode (
--json) for scripts
Every CLI command is a thin function that calls parc-core and formats the result.
# parc-server
The parc-server binary, also reachable as parc server. A JSON-RPC 2.0 server with two transports: newline-delimited JSON over stdio, or the same protocol over a Unix domain socket. Twenty methods covering the full core API — see JSON-RPC server.
The server is built on tokio. Each request handler is a thin wrapper around a parc-core call. Errors from parc-core are mapped to JSON-RPC error codes.
# parc-gui
A Tauri v2 desktop app. The frontend is vanilla TypeScript with web components and zero runtime npm dependencies — the only node_modules is the build pipeline (Vite + Tauri CLI). The Rust side links parc-core directly via tauri::generate_handler! invocations; there is no IPC layer beyond Tauri's normal command bridge.
# Files first, index second
The Markdown files in <vault>/fragments/ are the source of truth. The SQLite index in <vault>/index.db is fully derivable from them — parc reindex rebuilds it from scratch in seconds for a vault of thousands of fragments.
This gives you four useful properties:
- Backups are simple.
tarthe vault, you have everything that matters. Restore on any machine, runparc reindex, you're back. - Git transport works. Pull a collaborator's changes, run
parc reindex, see their fragments. - External edits are safe. Edit fragments by hand, with vim, with a script —
parc reindexcatches up. - Recovery is local. A corrupted index is a single command to fix; there is no remote anything to coordinate with.
# The search pipeline
A query string flows through these stages:
"type:todo #backend due:this-week"
│
▼ parc_core::search::parser
SearchQuery AST
│
▼ parc_core::search::compiler
FTS5 MATCH + SQL WHERE
│
▼ rusqlite (FTS5)
Result rows
│
▼ parc_core::search::resolver
Fragment summaries
│
▼ consumer (CLI / RPC / GUI)
The parser produces an AST that the consumer can inspect (parc search ... --explain). The compiler is the only stage that knows about SQL — every transformation above it is pure data.
# Plugins
Plugins are WebAssembly modules loaded into a wasmtime sandbox. The runtime is gated behind the wasm-plugins cargo feature so default builds carry zero overhead. Plugin manifest types and the management subcommands work without the feature; only loading and execution require it.
Plugins talk to parc through a parc_host namespace exposing fragment CRUD, search, logging, and output. The capability set declared in each plugin's manifest is enforced at link time — a plugin without write_fragments cannot import the corresponding host function at all, not just at runtime.
# Why this shape
The library-first design exists because parc has three frontends from day one (CLI, JSON-RPC, GUI) and a fourth on the horizon (plugins). Putting all logic in parc-core and keeping the binaries thin means:
- A bug fix in the search compiler benefits every frontend at once
- New frontends are cheap to add — the GUI is roughly 1500 lines of glue
- Tests for the engine don't need to spin up a binary or a server
- The JSON-RPC surface and the CLI surface are guaranteed to be in sync, because both are calling the same functions
It also makes the codebase pleasant to work in. Almost every interesting question has a single answer — read the function in parc-core.