# How Tuicr Handles Session Persistence: Architecture and Implementation

> Learn how Tuicr handles session persistence using JSON serialization and atomic file writes. Discover its robust architecture for crash safety and concurrent TUI CLI access.

- Repository: [Almog Gavra/tuicr](https://github.com/agavra/tuicr)
- Tags: architecture
- Published: 2026-08-01

---

**Tuicr persists review sessions by serializing the in-memory `ReviewSession` to JSON files under a user-specific data directory, using atomic writes with file locking to ensure crash safety and concurrent access between the TUI and CLI.**

The `agavra/tuicr` repository implements a robust session persistence mechanism that allows code review state—including comments, file-level flags, and diff views—to survive application restarts. By combining JSON serialization with atomic file operations and a manifest-based index, Tuicr ensures that both interactive TUI sessions and non-interactive CLI commands can safely read and write review data without corruption.

## Core Persistence Architecture

### ReviewSession and Data Models

The foundation of how Tuicr handles session persistence lies in the `ReviewSession` struct defined in [`src/model/review.rs`](https://github.com/agavra/tuicr/blob/main/src/model/review.rs). This type holds the complete review state, including per-file `FileReview` instances, line-level comments, and reviewed hunks. When the application serializes state to disk, it converts this in-memory representation into JSON format for human-readable storage and easy debugging.

### ReviewStore Public API

[`src/review_store.rs`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs) exposes the `ReviewStore` facade, which serves as the primary interface for listing, loading, and saving sessions. The store abstracts the underlying file operations, providing methods like `get_review(repo_or_slug)` to retrieve existing sessions and `add_comment()` to merge new data. This layer ensures that both the TUI application and CLI utilities interact with persistence through a consistent, type-safe API.

### Storage Layer with Atomic Writes

Low-level I/O operations reside in [`src/persistence/storage.rs`](https://github.com/agavra/tuicr/blob/main/src/persistence/storage.rs), which implements `save_session()` using atomic write patterns. The implementation writes to a temporary file first, acquires an exclusive lock using `flock`, then renames the temporary file to the final destination. This approach prevents data corruption if the process crashes mid-write and handles concurrent modification attempts from multiple Tuicr instances.

### Manifest and Index Management

The [`src/persistence/manifest.rs`](https://github.com/agavra/tuicr/blob/main/src/persistence/manifest.rs) module maintains an [`index.json`](https://github.com/agavra/tuicr/blob/main/index.json) file that maps repository paths and PR slugs to their corresponding session file names. This manifest tracks active TUI sessions via PID and timestamp metadata, enabling fast lookups and automatic cleanup of stale entries. The index allows Tuicr to locate session files quickly without scanning the entire data directory.

## Session Persistence Workflow

### Initializing a New Session

When the TUI initializes via `App::new()` in [`src/app/session.rs`](https://github.com/agavra/tuicr/blob/main/src/app/session.rs), the application checks for an existing session file in the user data directory—`$XDG_DATA_HOME/tuicr/reviews/` on Unix or `%APPDATA%\tuicr\reviews\` on Windows. If no session exists for the current repository and PR combination, `ReviewStore` creates a new empty JSON file that will store subsequent review activity.

### Autosave and Atomic Write Mechanics

Tuicr implements a background autosave mechanism driven by the `review_watch_interval_ms` configuration parameter, defaulting to 1000 milliseconds. At each interval, the system serializes the current `ReviewSession` to JSON and invokes `storage::save_session()` to perform an atomic write. The file locking mechanism ensures that if a CLI command modifies the session simultaneously, the TUI waits for the lock before writing, preventing race conditions.

### Merging External Changes

Before each autosave operation, Tuicr reloads the session file from disk using `load_session()`. This step detects modifications made by external processes, such as `tuicr review add` CLI invocations, and merges them into the in-memory state via `ReviewStore::add_comment()`. This three-way merge strategy ensures that comments added from the command line appear immediately in the TUI without overwriting existing in-memory changes.

### Session Retrieval and Cleanup

CLI commands like `tuicr review list` and `tuicr review comments` call `ReviewStore::get_review()` to access persisted data. The store consults the manifest to locate the correct JSON file, then deserializes it using `serde_json`. On normal TUI exit, the application removes its active session entry from the manifest and deletes empty session files that contain no comments or reviewed flags, preventing disk pollution.

## Code Examples

### Saving Comments from the TUI

When users add comments through the interface, [`src/app/comments.rs`](https://github.com/agavra/tuicr/blob/main/src/app/comments.rs) delegates to the persistence layer:

```rust
// Inside App::save_comment()
let req = AddCommentRequest {
    file: path.clone(),
    line: Some(line_no),
    body: comment_body.clone(),
    // … other fields …
};
add_comment_to_session(&mut self.session, req);

```

The `add_comment_to_session()` function updates the in-memory `ReviewSession`, which the background autosave thread then persists to disk via `storage::save_session()`.

### Adding Comments via CLI

External tools can append comments to active sessions without launching the TUI:

```bash

# Add a comment to an existing session

tuicr review add --repo . --input '{"file":"src/main.rs","line":42,"body":"Consider refactoring"}'

```

Behind the scenes, `ReviewStore::add_comment()` loads the existing session JSON, merges the new comment, and triggers an atomic write to ensure the TUI sees the change on its next autosave cycle.

### Loading Sessions Programmatically

Scripts and integrations can access review data directly through the Rust API:

```rust
use tuicr::ReviewStore;

fn main() -> Result<()> {
    // Load the active session for the current repo
    let store = ReviewStore::new()?;
    let session = store.get_review(".")?;
    println!("Loaded {} comments", session.total_comment_count());
    Ok(())
}

```

This approach leverages the same `ReviewStore` facade used internally, ensuring consistent path resolution and file locking behavior.

### Cleaning Up Empty Sessions

For maintenance operations, the manifest API provides cleanup utilities:

```rust
use tuicr::persistence::manifest::Manifest;

fn purge_empty() -> Result<()> {
    let mut manifest = Manifest::load()?;
    manifest.remove_empty_sessions()?; // walks the data dir and deletes files with no content
    Ok(())
}

```

This function removes stale session files that lack comments or review markers, reclaiming disk space while maintaining the integrity of the [`index.json`](https://github.com/agavra/tuicr/blob/main/index.json) manifest.

## Summary

- Tuicr stores review sessions as JSON files in platform-specific data directories (`$XDG_DATA_HOME/tuicr/reviews/` or `%APPDATA%\tuicr\reviews\`).
- The `ReviewSession` struct in [`src/model/review.rs`](https://github.com/agavra/tuicr/blob/main/src/model/review.rs) defines the persistence schema, while `ReviewStore` in [`src/review_store.rs`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs) provides the public API.
- Atomic writes in [`src/persistence/storage.rs`](https://github.com/agavra/tuicr/blob/main/src/persistence/storage.rs) use temporary files and `flock` locking to prevent corruption during concurrent access.
- The manifest system in [`src/persistence/manifest.rs`](https://github.com/agavra/tuicr/blob/main/src/persistence/manifest.rs) maintains an [`index.json`](https://github.com/agavra/tuicr/blob/main/index.json) index for fast session lookups and tracks active TUI processes.
- Autosave intervals (default 1000ms) coupled with pre-write reloading ensure that CLI and TUI modifications merge without data loss.

## Frequently Asked Questions

### Where does Tuicr store session persistence files?

Tuicr stores session data in JSON format under the user data directory, specifically `$XDG_DATA_HOME/tuicr/reviews/` on Unix systems or `%APPDATA%\tuicr\reviews\` on Windows. Each session file corresponds to a specific repository and PR combination, with an [`index.json`](https://github.com/agavra/tuicr/blob/main/index.json) manifest tracking active sessions.

### How does Tuicr prevent data corruption when multiple processes access the same session?

The persistence layer implements atomic writes using temporary files and POSIX `flock` advisory locks in [`src/persistence/storage.rs`](https://github.com/agavra/tuicr/blob/main/src/persistence/storage.rs). Before writing, Tuicr acquires an exclusive lock on the session file, writes to a temporary location, then renames the file into place, ensuring that concurrent CLI and TUI processes cannot corrupt the JSON structure.

### Can I access Tuicr review sessions from external scripts?

Yes, the `ReviewStore` API exposed in [`src/review_store.rs`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs) allows programmatic access to persisted sessions. You can instantiate `ReviewStore::new()` and call `get_review(repo_path)` to load the current session state, enabling integration with CI pipelines or custom review tooling that reads comment data from the JSON files.

### What happens to session files when the TUI crashes?

Tuicr's storage layer detects orphaned lock files left by crashed processes and removes them before attempting new writes. Empty session files containing no comments or reviewed flags are automatically cleaned up on the next normal exit, while sessions with content persist indefinitely until explicitly deleted or the review is completed.