Wardstone: Building a Desktop UI for a Developer Methodology
The methodology was working. But I kept switching to the terminal to check sprint status, then losing my place in the code.
In the last post, I described building Dev Context Methodology — a Claude Code plugin that gives AI-assisted development sessions full project awareness through structured context layers, lifecycle stages, and automated hooks.
DCM works well. But it lives in markdown files and CLI commands. You can run /track-status to see where a project stands, but you can't glance at it. You can't drag a task between priorities. You can't see your sprint board while you're deep in code without switching windows and losing your train of thought.
DCM needed a face. So I built one.
Why a Desktop App (and Why Tauri)
I considered four options:
CLI-only. Already had it. Works for queries, not for dashboards. You can't maintain ambient awareness through a terminal — you have to actively ask for information every time you need it.
Web app. Adds a server, a deploy, a URL to remember. That's overhead for a single-user tool that reads files on your local machine. The data doesn't belong on a network.
VS Code extension. Ties the tool to one editor, and VS Code's extension API is limiting for complex dashboards. I didn't want the methodology bound to an editor choice.
Tauri 2. A Rust backend with a web frontend, compiled to a native desktop app. The binary is roughly 5MB — not the 200MB+ Electron tax. It ships as a real .dmg, sits in the Dock, and feels like a native tool.
Tauri won because of the architecture it enables. The Rust backend handles all file I/O directly — there's no HTTP layer, no API server, no database. Tauri commands call Rust functions, Rust reads and writes markdown files, and the React frontend reflects the state. The entire data layer is the filesystem.
File-System-as-Database
This is the most unusual architectural decision in the project, and it's inherited from DCM itself. All project state is stored as plain markdown with YAML frontmatter. Here's what a project's context file looks like:
---
name: wardstone
stage: developing
priority: high
updated: 2026-03-28
---
## Stage Gate: Developing
- [x] All Must items are done
- [ ] All Should items are done or deferred
- [ ] No active impediments
## To-Dos
- [x] Atomic write for all file mutations
- [x] File watcher with pause support
- [ ] Undo system for all edit operations
No SQLite. No binary format. Just files you can open in any text editor, diff with git, and read with AI.
The tradeoff is real: you give up query flexibility (no SELECT * FROM projects WHERE priority = 'high'). But you gain something worth more for this use case — every change is git-diffable, the data is human-readable, and Claude Code can read and write it with the same tools it uses for everything else. No ORM, no migrations, no schema versioning.
The constraint shaped the architecture. Every "query" Wardstone performs is really a file read plus YAML frontmatter parse. The Rust parser walks the .tracker/ directory, reads each project's CONTEXT.md, parses the frontmatter, and returns typed data to the frontend.
Making File State Feel Real-Time
Here's the problem that made this project interesting: Wardstone reads tracker files, but Claude Code also writes them. Two writers, one filesystem.
When you run /track-todo in Claude Code to add a backlog item, that change needs to appear in Wardstone's UI. When you edit a project's priority in Wardstone, Claude Code needs to see the updated file on its next read. Both tools must see the same truth, and neither can own a lock.
The solution has two parts: atomic writes and a file watcher with self-awareness.
Every file write in Wardstone goes through an atomic_write function — write to a .tmp file first, then rename into place:
fn atomic_write(path: &Path, content: &str) -> Result<(), String> {
let tmp = path.with_extension("tmp");
fs::write(&tmp, content)
.map_err(|e| format!("write failed: {}", e))?;
fs::rename(&tmp, path).map_err(|e| {
let _ = fs::remove_file(&tmp);
format!("rename failed: {}", e)
})
}
Rename is atomic on POSIX systems, so there's no window where a reader (Claude Code or Wardstone's own watcher) sees a half-written file. If the process crashes mid-write, the original file is untouched and the .tmp is orphaned.
The watcher monitors the entire .tracker/ directory recursively. When a .md file changes, it emits an event to the frontend, which re-fetches the affected data. But there's a subtlety: when Wardstone itself writes a file through the UI, the watcher sees that write and tries to reload — which would overwrite the pending UI state.
The fix is a pause set — a thread-safe HashSet of file paths that the watcher should ignore. Before Wardstone writes a file, it adds the path to the set. The watcher checks every event against it and skips paused paths. After the write completes, the path is removed. This prevents the watcher from reacting to Wardstone's own edits while still catching external changes from Claude Code.
Bidirectional Editing
Wardstone isn't read-only. You can create projects, edit priorities, add backlog items, manage sprints, and log incidents — all from the UI. Every change writes back to the same markdown files that the CLI reads.
This means you can have Wardstone open in one window and Claude Code running in another, both working with the same data. Add a to-do in Wardstone — Claude sees it next time it reads the project context. Run /track-advance in Claude Code — Wardstone's dashboard updates within a second.
The UX challenge was making markdown editing feel like a native app without hiding the underlying format. The UI doesn't pretend the data isn't markdown — it just makes the common operations (changing a stage, reordering a backlog, toggling a gate checkbox) fast and visual. For anything complex, you can always open the file directly.
Nine themes with CSS custom properties, keyboard-first navigation, and a command palette round out the experience. These are the quality-of-life features nobody asks for but everybody notices when they're missing.
What I Learned
Atomic writes are non-negotiable for any app that shares state with external processes. I learned this early, not the hard way — but only because I'd already been burned by file corruption in other projects. The pattern is simple (write to temp, rename into place) and the cost is almost zero. Every write command in Wardstone uses it — all 40+ of them.
File watchers are deceptively complex. The watcher itself is simple. The interaction between a watcher, a UI that writes the same files, and an external tool (Claude Code) that also writes those files — that's where the race conditions live. The pause set pattern solved it cleanly, but I wouldn't have predicted needing it from the design doc.
Tauri 2 is genuinely good for single-user developer tools. The Rust-to-JavaScript bridge is clean, the binary is small, and you get native OS integration (file dialogs, notifications, menu bar) without fighting a framework. The mental model is straightforward: Rust owns the data, React owns the display, Tauri commands connect them.
The gap between "renders data" and "feels like an app" is mostly keyboard shortcuts, transitions, and error states. The dashboard that shows project data took a day. Making it feel like a tool you'd actually keep open took a week. That last mile is where taste lives — and it's the part AI is worst at generating for you.
The Stack
| Layer | Technology | Purpose |
|---|---|---|
| Backend | Rust (Tauri 2) | File I/O, frontmatter parsing, atomic writes, watcher |
| Frontend | React 19 + TypeScript | Dashboard views, sprint boards, backlog editor |
| Styling | Tailwind + CSS custom properties | 9 themes, responsive layout |
| Build | Vite + Tauri CLI | Dev server, .dmg packaging |
The entire app ships as a single .dmg. No install wizard, no runtime dependencies, no server. Download, drag to Applications, open.
The terminal is where you do the work. The dashboard is where you see the work. Having both — a CLI methodology that Claude Code enforces and a visual dashboard where you can monitor and edit state — turns project management from something you have to actively do into something you can passively see.
DCM is the nervous system. Wardstone is the dashboard.
Sometimes the best way to understand a problem is to build a GUI for it.
View Wardstone on GitHub Read Post 1: Building DCM