Skip to content

Latest commit

 

History

History
295 lines (201 loc) · 13.5 KB

File metadata and controls

295 lines (201 loc) · 13.5 KB

annot — Product Features

annot: ephemeral space for human-AI thinking

A native annotation tool that opens content in a window, lets humans add structured feedback with line-level precision, and outputs results for AI consumption when closed.


Core Value Proposition

annot is a structured dialogue medium — a space where human and AI take turns refining content. It's designed to:

  • Pull humans into the loop at decision points
  • Capture rich, located feedback — not just yes/no, but what and where
  • Shape content through reaction — propose replacements, sketch alternatives, add context
  • Exit cleanly — ephemeral by design, zero persistence burden

Review Modes

File Review

Open any source file for annotation. Syntax highlighting adapts to language. Navigate by line numbers.

Diff Review

Review changes from a git or jj repository, or raw unified diffs. Color-coded: additions green, deletions red. Annotations capture both old and new line numbers.

Targets work from the CLI (annot diff, annot diff 4f7d4491, annot diff main..HEAD -- src/) and from MCP (review_diff with target/pathspecs) — the same comparisons either way: working copy (uncommitted work vs its base), revision (one revision vs its parent), range (... diffs from the merge base), and staged (git only — jj has no index). Raw diffs come via stdin (git diff | annot) or MCP diff_content.

Revision strings are the repository's own dialect: git revspecs (HEAD~2, abc1234) in a git repo, jj revsets (@-, trunk(), your own aliases) in a jj one — resolved by jj's own engine against your own config, so a revset means here what it means in jj.

Which repository — the CLI reviews the repo enclosing your cwd; a human standing in a directory means "here". The MCP server can't rely on that: it's a sidecar of the calling agent and inherits the directory that agent was launched from, which stays fixed while the agent moves between repos. review_diff takes working_dir to name the repo explicitly (any directory inside it works), and it roots the session — the file picker and :save follow it. Without it, annot falls back to its own process directory and reviews whatever repo lives there.

Short change-id prefixes work exactly as jj log displays them: jj highlights the shortest prefix that is unique among the commits you can see (often one letter), and annot resolves prefixes against the same revset (revsets.short-prefixes, defaulting to revsets.log) — so a prefix jj shows you is a prefix you can type.

In jj a revset may name several commits. If they form one contiguous stack — one root, one head, no gaps — annot reviews them as a single changeset: the base the stack sits on versus its tip. So annot diff 'trunk()..@' reviews your whole branch as one diff, and a revset alias for it ('ready()' = 'trunk()..@') works the same way — annot diff 'ready()'. Anything else is an error naming the candidates, never a silent pick: mutable() & mine() names several unrelated stacks, and there is no single diff of that.

Jujutsu (jj)

jj repositories are first-class, colocated or not. Three things differ from git, deliberately:

  • The working copy is snapshotted. jj's working copy is a commit (@) that only matches the filesystem because every jj command snapshots on startup — so annot does too, and your uncommitted edits show up even if no jj command has run since you made them. This is the one and only write annot performs on a repository: a snapshot working copy entry in jj op log, the same one jj status would leave. A clean working copy produces no operation at all.
  • Conflicts are content, not errors. A rebase can leave a committed conflict, and "what did this rebase break?" is exactly the review you want. Conflicted files render as jj's own marker text (<<<<<<<, %%%%%%%, +++++++, >>>>>>>), with the marker lines tinted and a striped gutter down the conflict region. They annotate like any other line. (In git, an unmerged path still errors — there it means "you're mid-merge, go fix it".)
  • Change IDs lead. Window labels name a revision by its change ID (@ · kmxyzqrs), which survives rewrites — quote it back after an amend and it still resolves, where a commit ID may already be dangling.

Pathspecs become jj filesets in a jj repo (src/ as a bare path, glob:"*.rs" as an expression).

File treeCmd+B toggles a sidebar listing every changed file with its +/− counts. Clicking a file scrolls to it (expanding it if collapsed); the row for the file currently in view stays highlighted. The : palette's Files namespace does the same jump by fuzzy search.

Per-file collapse — every file gets a header bar (chevron, path, +/− counts) that sticks to the top while its lines scroll. Clicking the bar collapses the file to just its header; the titlebar shows the changeset's +A −D totals with a fold-all/unfold-all toggle. Files with more than 500 changed lines start collapsed. Search hits and file jumps auto-expand collapsed files. Collapse is pure presentation — annotations keep resolving to the same lines.

Split view — a titlebar button (two-column icon) or the : palette's View namespace switches the diff between unified (default) and side-by-side rendering. Context lines span both columns; each change run pairs its deletions and additions row by row, padding the shorter side with hatched filler. Selections drag within one column and annotate that side; annotations created in either view render in both and survive toggling. The choice is per-session — every review opens unified.

Unfold context — in repository-backed diffs (git or jj), fold arrows stacked in the @@ hunk-header gutter (GitHub-style, Octicon fold-down/fold-up: ▼ on top continues the hunk above downward, ▲ below grows the hunk beneath upward; each arrow is a full-width band one row tall, making the header two rows when both show) expand hidden context: each reveals 20 lines toward its edge of the gap, and a gap of 20 lines or fewer collapses to a single expand-all button. The fold after a file's last hunk has no header below it, so it keeps a standalone arrow row. The folded line count lives in the arrow tooltips. A fully unfolded gap's arrows disappear and its neighboring hunks merge. Unfolded lines are ordinary context rows — annotatable, searchable, and included in output like any other line. Raw pasted diffs have no file contents to slice, so no unfold chevrons appear there. Expansion is per-session.

Content Review

Review agent-generated content — plans, drafts, analysis. Markdown rendering with Mermaid diagrams and portal links that embed live code.


Annotation System

Line Selection

  • Click line numbers to select individual lines
  • Shift+Drag for ranges
  • Multi-line annotations supported

Rich Annotation Content

  • Plain text with inline formatting
  • Tags: Semantic mini-prompts like [# SECURITY] or [# TODO] — composable with prose and each other
  • Images: Paste screenshots directly into annotations
  • Excalidraw diagrams: Sketch ideas visually (can also convert Mermaid diagrams to Excalidraw)
  • Replace blocks: Propose code replacements with before/after display
  • References: Link to annotations, sections, and project files

Slash Commands in Annotation Editor

  • / triggers command menu (Excalidraw, Replace block, etc.)
  • # triggers tag autocomplete
  • @ triggers reference autocomplete (annotations, sections, files)

Tag System

  • Create custom tags with names and LLM instructions
  • Tags persist across sessions
  • Usage tracking for smart suggestions

Exit Modes

Declare your intent before closing the window:

  • Tab/Shift+Tab cycles through modes
  • Selected mode colors the window footer
  • Each mode has instruction text for AI interpretation

Types

  • Persistent: Saved modes like "Apply", "Reject", "Revise"
  • Ephemeral: Agent-provided modes for context-specific workflows (via MCP)
  • Command-linked: Modes that trigger Claude Code slash commands

Portal Links (Live Code Embeds)

In content review, embed live code snippets:

See the validation logic in [validate](src/lib.rs#L45-L60)

This fetches and displays the actual code inline, syntax-highlighted. Max 50 portals, 500 lines each. Sensitive paths blocked.


Content Shaping

annot isn't a document editor — humans shape content through reaction, not direct authorship:

Replace Blocks

  • Propose code replacements via /replace command
  • Shows original (tinted) alongside your replacement (green accent)
  • Output includes diff-style format for AI to apply changes

Save to File

  • Export content as markdown via command palette
  • Portal links expand to embedded code blocks
  • Optionally overwrite source or create new file

Export to Obsidian

  • Send content to Obsidian vault
  • Auto-names note from first H1 heading

Diagrams

Excalidraw

  • Open diagram editor from annotation
  • Saves as JSON + PNG
  • Embedded inline in annotation content

Mermaid

  • Rendered from markdown code blocks
  • Convert to Excalidraw for sketching

Session Context

Press Shift+C to add a high-level comment that applies to the entire review (not a specific line). Appears in the SESSION block of output.


Command Palette (:)

Press : (colon) to open. Eight namespaces:

Tags

  • Browse, create, edit, delete tags
  • Examples: TODO, SECURITY, REFACTOR, BUG, PERF
  • Press e to edit selected tag

Exit Modes

  • Browse, set, create, edit, delete, reorder modes
  • Examples: Apply, Revise, Reject, Discuss
  • Press s to set as active
  • Press r to reorder (drag with arrow keys)

Files

  • Fuzzy-jump to a changed file (diff review only)

Copy

  • Copy content only
  • Copy annotations only
  • Copy both together

Save

  • Opens save modal for file export

Obsidian

  • Export to configured vaults
  • Add/edit/delete vault references

Theme

  • System / Light / Dark toggle

Keyboard-First Design

Key Action
Click line # Select/deselect line
Shift+Drag Select range
/ Slash command menu (Excalidraw, Replace)
# Tag autocomplete
@ Reference autocomplete (annotations, sections, files)
Tab/Shift+Tab Cycle exit modes
Shift+C Session context editor
: Command palette
Alt+Tab Command palette → Exit modes
Ctrl+F Search
Cmd+B Toggle file tree (diffs)
e Edit item (in command palette)
r Reorder items (exit modes only)
Cmd+D Delete item (in command palette)

Output Format

Structured for AI consumption:

TAGS:
  [# SECURITY] Review for security vulnerabilities
  [# TODO] Items needing follow-up

CONTEXT: plan.md [embeds: src/lib.rs, src/main.rs]

GENERAL:
  Please focus on the error handling approach

NEXT: Apply — Proceed with this plan as written

---

src/lib.rs:45-52:
   44 | fn previous_line() {  // context
>  45 | fn example() {
>  46 |     // code
      └──> [# SECURITY] Check for injection vulnerabilities here
           Consider validating the input before processing

Saved to /path/to/file.md

Section Meanings

  • TAGS: Tag definitions used in annotations (only if tags are present)
  • CONTEXT: What's being reviewed, with any embedded portal files
  • GENERAL: High-level comment about the entire review (not line-specific)
  • NEXT: What action the human wants (exit mode name + instruction)

MCP Integration

Three tools exposed via Model Context Protocol:

  1. review_file — Open file at path
  2. review_diff — Review diffs from a git or jj repo (structured target: working copy / revision / range / staged) or raw diff content
  3. review_content — Review agent-generated content

All block until window closes, returning structured output with annotations, exit mode, and any images.

Ephemeral exit modes: Agents can provide context-specific exit modes each time they invoke annot — these appear alongside persistent modes but don't persist after the session.


CLI Flags

Flag Description
--json Output annotations as JSON with base64 images (for tool integrations)
--exit-mode "Name:Instruction" Add a transient exit mode button (repeatable)
-l, --label <LABEL> Label for stdin content (syntax highlighting)

--json returns {"text": "...", "images": [{"figure": 1, "data": "base64...", "mime_type": "image/png"}]} instead of plain text. Used by the Pi extension to pipe images back to the agent.

--exit-mode can be repeated to add multiple buttons. Colors auto-cycle.


Configuration

Persisted in the OS config directory (~/.config/annot/ on Linux, ~/Library/Application Support/annot/ on macOS, %APPDATA%\annot\ on Windows):

  • Tags and exit modes
  • Usage statistics
  • Theme preference
  • Content zoom level
  • Obsidian vault paths

Atomic writes with file locking for concurrent safety.


Platform Support

Native Tauri app for:

  • macOS — overlay title bar, traffic light positioning, native Cmd+W close
  • Linux — borderless window with custom chrome (in-app title bar, close button, and resize handles); tested on NixOS with Wayland (via XWayland); requires GDK_BACKEND=x11 and WEBKIT_DISABLE_DMABUF_RENDERER=1 for correct WebKit2GTK rendering (baked into the Nix-installed binary automatically)
  • Windows — standard decorated OS window with the native title bar (no custom chrome); builds and runs from source via WebView2; config stored in %APPDATA%\annot\