How Tuicr Implements Session Persistence and Auto-Save in Rust
Tuicr persists review sessions by serializing the ReviewSession struct to JSON files under the user's data directory, using atomic file operations with flock locking and a background timer that auto-saves every 1000ms to ensure no review state is lost between TUI restarts or CLI interactions.
Tuicr is a terminal-based code review tool that maintains state across interactive TUI sessions and non-interactive CLI commands. According to the agavra/tuicr source code, the session persistence layer combines Rust structs for in-memory state with atomic filesystem operations to prevent data corruption when multiple processes access the same review.
Core Architecture of Tuicr Session Persistence
The persistence system spans four primary modules that handle everything from data models to filesystem atomicity.
The ReviewSession Data Model
At the heart of the system lies ReviewSession, defined in src/model/review.rs. This struct encapsulates the complete review state, including:
- File-level metadata: Per-file
FileReviewinstances tracking reviewed hunks - Line-level comments: Structured comment data attached to specific lines
- Review progress: Flags indicating which files have been fully reviewed
When the TUI initializes via App::new(), it either loads an existing session or creates a fresh in-memory structure that will later synchronize to disk.
The ReviewStore API Facade
The ReviewStore struct in src/review_store.rs provides the public interface for all persistence operations. Key methods include:
get_review(repo_or_slug): Locates and deserializes a session by repository path or PR identifieradd_comment(): Merges new comments into an existing session and triggers persistence- Listing and enumeration: Supports the
tuicr review listCLI command
This facade abstracts the underlying storage mechanics from both the TUI (src/app/comments.rs) and the CLI entry points.
Atomic Storage Implementation
Low-level I/O resides in src/persistence/storage.rs, implementing crash-resistant write semantics. The storage::save_session function executes a three-phase commit:
- Write to temporary file: Serializes the
ReviewSessionto a temp path usingserde_json - Acquire lock: Uses Unix
flock(or equivalent) to obtain an exclusive file lock, preventing concurrent writes from the TUI and CLI processes - Atomic rename: Moves the temp file into place, ensuring readers never encounter partially written JSON
If a stale lock file exists from a crashed process, the storage layer detects and removes it before proceeding, eliminating deadlock scenarios.
Session Manifest and Indexing
To avoid scanning the entire data directory, Tuicr maintains an index.json managed by src/persistence/manifest.rs. The manifest:
- Maps repository paths and PR slugs to specific session filenames
- Tracks active sessions with process IDs and last-seen timestamps
- Provides fast lookup for
manifest::load_indexwithout filesystem globbing
On graceful TUI exit, the application removes its active session entry from the manifest and deletes empty session files (those with no comments or reviewed flags).
The Auto-Save Mechanism
Tuicr eliminates manual save operations through a background polling system integrated into the TUI event loop.
Background Polling and Configuration
The review_watch_interval_ms configuration parameter (defaulting to 1000ms) drives a timer in src/app/session.rs. Every interval, the TUI executes an auto-save sequence:
- Read current state: Calls
load_sessionto fetch any external modifications - Merge changes: Incorporates comments added via concurrent
tuicr review addCLI invocations usingadd_comment_to_session - Atomic write: Persists the merged state via
storage::save_session
This merge-before-write strategy ensures that CLI additions surface immediately in the TUI without overwriting in-memory changes.
Handling Concurrent Modifications
When both the TUI and a CLI process modify the same session, Tuicr prevents lost updates by strictly following read-modify-write semantics. Before each auto-save, the system reloads the JSON from disk, merges the in-memory delta, and writes the combined state. The file lock acquired during storage::save_session guarantees that no two writers interleave their JSON serialization.
Failure Recovery and Lock Handling
The persistence layer implements several resilience patterns for production reliability:
- Orphaned lock recovery: If
flockfails because a lock file exists without a live process, Tuicr removes the stale lock and retries - Empty session cleanup: On normal exit,
Manifest::remove_empty_sessions()deletes JSON files containing no comments or review progress, preventing disk clutter - Crash recovery: Abnormal termination leaves the session file intact; the next TUI launch loads the recovered state from the data directory (
$XDG_DATA_HOME/tuicr/reviews/on Unix,%APPDATA%\tuicr\reviews\on Windows)
Practical Code Examples
Adding a Comment from the TUI
When users save a comment in the interface, App::save_comment() delegates to the store:
// src/app/comments.rs (simplified)
let req = AddCommentRequest {
file: path.clone(),
line: Some(line_no),
body: comment_body.clone(),
// … additional metadata …
};
add_comment_to_session(&mut self.session, req);
// Autosave timer triggers storage::save_session asynchronously
Appending Comments via CLI
Non-interactive usage modifies the same session file:
# Adds comment to existing session or creates new
tuicr review add --repo . --input '{"file":"src/main.rs","line":42,"body":"Consider refactoring"}'
Behind the scenes, ReviewStore::add_comment loads the session JSON, merges the new comment, and invokes storage::save_session.
Loading Sessions Programmatically
use tuicr::ReviewStore;
fn main() -> Result<()> {
let store = ReviewStore::new()?;
// Load by repository path or PR slug
let session = store.get_review(".")?;
println!("Loaded {} comments", session.total_comment_count());
Ok(())
}
Manual Cleanup of Empty Sessions
use tuicr::persistence::manifest::Manifest;
fn purge_empty() -> Result<()> {
let mut manifest = Manifest::load()?;
manifest.remove_empty_sessions()?; // Deletes files with no content
Ok(())
}
Summary
- Data model:
ReviewSessioninsrc/model/review.rsholds all review state, whileReviewStoreinsrc/review_store.rsprovides the public API - Atomic writes:
src/persistence/storage.rsimplementsstorage::save_sessionusing temp files,flocklocking, and atomic rename for crash safety - Auto-save: A 1000ms timer in the TUI (
src/app/session.rs) triggers periodic saves, merging external CLI changes before writing - Indexing:
src/persistence/manifest.rsmaintainsindex.jsonfor fast session lookup and tracks active process sessions - Concurrency: File-level locks and read-modify-write cycles prevent data loss when TUI and CLI access the same session simultaneously
Frequently Asked Questions
Where does Tuicr store session data on disk?
Tuicr stores JSON session files in the user’s data directory: $XDG_DATA_HOME/tuicr/reviews/ on Unix systems or %APPDATA%\tuicr\reviews\ on Windows. An index.json file in the same directory maps repository paths to specific session filenames.
How does Tuicr handle concurrent edits from the TUI and CLI?
Before every auto-save, Tuicr reloads the session file via load_session to fetch any comments added by tuicr review add CLI commands. It merges these external changes into the in-memory ReviewSession before calling storage::save_session, which acquires an exclusive flock to prevent write conflicts.
What happens if Tuicr crashes during a review?
The session file remains intact on disk due to the atomic write semantics (temp file + rename) in storage::save_session. When Tuicr restarts, it loads the recovered state from the data directory. Any orphaned lock files are automatically detected and removed during the next save operation.
How can I programmatically clean up empty review sessions?
Import the Manifest struct from src/persistence/manifest.rs and call Manifest::load() followed by remove_empty_sessions(). This walks the data directory, deletes JSON files containing no comments or review flags, and updates the index accordingly.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →