# How Comments Are Managed in Tuicr: Complete Lifecycle and Architecture Guide

> Explore the complete comment lifecycle in Tuicr. Learn how comments are managed from draft to submission, including their architecture and storage.

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

---

**Tuicr manages code review comments through a structured lifecycle that tracks each remark from local draft creation through remote submission, storing them as `Comment` objects with UUID-based IDs, lifecycle states, and optional line-range metadata in JSON-backed review sessions.**

Tuicr is an open-source terminal-based code review tool that treats every review remark as a structured `Comment` object. Understanding how comments are managed in Tuicr requires examining its Rust-based architecture, from the data models defined in [`src/model/comment.rs`](https://github.com/agavra/tuicr/blob/main/src/model/comment.rs) through the persistence layer in [`src/review_store.rs`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs) to the remote submission pipeline. This article breaks down the complete comment lifecycle, including creation, storage, UI rendering, and synchronization with GitHub or GitLab.

## Core Comment Architecture in [`src/model/comment.rs`](https://github.com/agavra/tuicr/blob/main/src/model/comment.rs)

Every comment in Tuicr is defined by the **`Comment`** struct in [`src/model/comment.rs`](https://github.com/agavra/tuicr/blob/main/src/model/comment.rs). This structure captures not only the content but also spatial context within a diff and synchronization state with remote forges.

### Comment Structure and Fields

The `Comment` type contains the following fields:

| Field | Purpose |
|-------|---------|
| `id` | UUID generated at creation time |
| `content` | Markdown text of the comment |
| `comment_type` | User-defined classification (e.g., `note`, `issue`, `suggestion`) |
| `created_at` | UTC timestamp of creation |
| `line_context` | Optional `LineContext` storing line numbers and source text |
| `side` | Diff side (`LineSide::Old` or `LineSide::New`) |
| `line_range` | Optional multi-line range (`LineRange { start, end }`) |
| `author` | Defaults to `"user"` (`DEFAULT_AUTHOR`) |
| `lifecycle_state` | Current state: `LocalDraft`, `PushedDraft`, or `Submitted` |
| `remote_review_id` / `remote_comment_id` | Remote forge IDs post-submission |
| `commit_id` | SHA for commit-scoped comments |

### Comment Lifecycle States

The **`CommentLifecycleState`** enum controls editability and synchronization status:

- **`LocalDraft`** → Fully editable; exists only locally
- **`PushedDraft`** → Locked; comment exists on remote but not yet finalized
- **`Submitted`** → Locked; permanently synchronized with the remote forge

The `Comment::is_locked()` method returns `true` for `PushedDraft` and `Submitted` states, preventing modifications to comments that have left the local environment.

## Adding Comments via `add_comment_to_session`

All comment insertions funnel through **`add_comment_to_session`** in [`src/review_store.rs`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs). This function receives an **`AddCommentRequest`** that specifies the attachment target via the `CommentTarget` enum:

```rust
pub enum CommentTarget {
    Review,
    File { path: PathBuf },
    Line { path: PathBuf, line: u32, side: LineSide },
    LineRange { path: PathBuf, range: LineRange, side: LineSide },
}

```

Depending on the target variant, the comment is stored in one of three locations within the session:

- `session.review_comments` → Review-level general comments
- `session.files[path].file_comments` → File-level comments
- `session.files[path].line_comments[line]` → Line-specific or range-end comments

The function automatically stamps the author, attaches optional `commit_id` values, and updates the session's `updated_at` timestamp to track modification time.

### Programmatically Creating a Comment

```rust
use tuicr::{
    model::{Comment, CommentType, LineSide},
    review_store::{AddCommentRequest, CommentTarget, ReviewStore},
};

let store = ReviewStore::new();
let session_ref = /* obtain a SessionRef from ReviewStore */;
let request = AddCommentRequest {
    target: CommentTarget::Line {
        path: "src/main.rs".into(),
        line: 42,
        side: LineSide::New,
    },
    content: "Consider renaming this variable".to_string(),
    comment_type: CommentType::from_id("suggestion"),
    author: "alice".to_string(),
    commit_id: None,
};

let comment = store.add_comment(&session_ref, request).unwrap();
println!("Added comment {} with id {}", comment.content, comment.id);

```

### CLI-Based Comment Creation

The Tuicr CLI constructs the same `AddCommentRequest` internally:

```bash
tuicr review add \
  --input '{"content":"Looks good!","type":"praise"}' \
  --target review

```

## Local Storage and the `ReviewStore` Facade

**`ReviewStore`** ([`src/review_store.rs`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs)) provides a thin façade over the file-based storage layer ([`src/persistence/storage.rs`](https://github.com/agavra/tuicr/blob/main/src/persistence/storage.rs)). When you call `ReviewStore::add_comment`, the system:

1. Loads the session JSON from disk
2. Invokes `add_comment_to_session` to mutate the in-memory structure
3. Writes the amended session back to the platform data directory (e.g., `~/.local/share/tuicr/reviews/`)

This ensures atomic updates to the local review state while maintaining durability across application restarts.

## Rendering Comments in the Diff View

When Tuicr renders a side-by-side diff, it injects line and file-level comments into the UI stream via **`add_comments_to_line`** in [`src/ui/diff_side_by_side.rs`](https://github.com/agavra/tuicr/blob/main/src/ui/diff_side_by_side.rs). This function:

- Walks the `line_comments` map for the current file
- Creates `AnnotatedLine::LineComment` entries for each remark
- Updates the Comment Navigator sidebar with badges showing the comment type and author

The renderer inserts visual badges directly after the associated diff lines, allowing reviewers to see remarks in their original code context.

## Synchronizing with Remote Forges

The submit pipeline in **[`src/forge/submit.rs`](https://github.com/agavra/tuicr/blob/main/src/forge/submit.rs)** handles the transition from local drafts to remote pull request or merge request comments. During submission:

1. **Pre-flight mapping**: Local `Comment` objects are translated into GitHub's "anchor" format or GitLab's discussion format
2. **Filtering**: Comments that cannot be mapped to valid line anchors are discarded
3. **API execution**: The forge backends (`src/forge/github/*` and `src/forge/gitlab/*`) execute `gh api` or `glab` commands
4. **State advancement**: Upon successful remote creation, `remote_review_id` and `remote_comment_id` are populated, and `lifecycle_state` advances from `LocalDraft` → `PushedDraft` → `Submitted`

This progression ensures that once a comment receives a remote ID, it becomes immutable in the local session to prevent divergence between local and remote states.

## Commit-Scoped Comments and Visibility

Tuicr supports **commit-scoped comments** for reviews targeting specific commits rather than entire branches. When the inline commit selector shows a single commit, `App::save_comment` (in [`src/app/comments.rs`](https://github.com/agavra/tuicr/blob/main/src/app/comments.rs)) calls `Comment::with_commit_id` to store the comment with that specific SHA.

The visibility logic in `ReviewSession::comment_visible` filters comments based on the current selector state:

- If the selector shows a single commit, only comments with matching `commit_id` values are visible
- If the selector shows a range that excludes the stored SHA, the comment is hidden
- General comments without a `commit_id` remain visible across all selection modes

This keeps the UI consistent with the selected commit range while preserving remarks for future reference.

## Summary

- **Comment Model**: Tuicr uses a comprehensive `Comment` struct in [`src/model/comment.rs`](https://github.com/agavra/tuicr/blob/main/src/model/comment.rs) that tracks content, location, author, and lifecycle state
- **Lifecycle Tracking**: Comments progress through `LocalDraft` → `PushedDraft` → `Submitted` states, with `is_locked()` preventing edits to synchronized remarks
- **Flexible Targeting**: The `CommentTarget` enum supports review-level, file-level, line-specific, and multi-line range comments
- **Atomic Persistence**: `ReviewStore` in [`src/review_store.rs`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs) provides atomic load-modify-write cycles to JSON session files
- **Integrated Rendering**: The UI layer in [`src/ui/diff_side_by_side.rs`](https://github.com/agavra/tuicr/blob/main/src/ui/diff_side_by_side.rs) injects comments directly into diff views via `add_comments_to_line`
- **Remote Synchronization**: The submit pipeline manages API translation and state advancement for GitHub and GitLab integration

## Frequently Asked Questions

### What is the difference between LocalDraft and Submitted comments in Tuicr?

**LocalDraft** comments exist only in your local filesystem and can be edited or deleted freely. Once a comment is pushed to a remote forge (GitHub or GitLab), it transitions to **Submitted** status, at which point `Comment::is_locked()` returns `true` and the comment becomes immutable in the local session to maintain consistency with the remote state.

### How does Tuicr handle multi-line comments?

Tuicr supports multi-line comments through the `LineRange` variant of `CommentTarget`, which accepts `start` and `end` line numbers. These comments are stored in `session.files[path].line_comments` using the range end line as the key, and the `line_range` field in the `Comment` struct preserves the full span for accurate rendering and remote submission.

### Where are Tuicr comments stored locally?

According to the `agavra/tuicr` source code, comments are persisted as part of review session JSON files under the platform-specific data directory, typically `~/.local/share/tuicr/reviews/` on Linux. The `ReviewStore` facade in [`src/review_store.rs`](https://github.com/agavra/tuicr/blob/main/src/review_store.rs) manages all read and write operations to this location.

### Can comments be added to specific commits rather than the entire PR?

Yes. When viewing a single commit in the inline selector, Tuicr attaches the current commit SHA to the comment via `Comment::with_commit_id`. These commit-scoped comments are filtered by `ReviewSession::comment_visible` and only appear when that specific commit is selected, ensuring review remarks stay relevant to the current diff context.