annotations 0.1.0

A minimal terminal tool for annotating code: select lines, write a note, get a Markdown file
# Annotator — Implementation Plan & Log

A small Rust TUI application for annotating code files. You open a file, "paint"
(select) a range of lines, write a review-style note, and the app saves the
annotation to a file named `<UUID>.md` containing the note plus metadata.

## Requirements

- Open a code file and view it in a terminal UI.
- Select ("paint") a range of lines, like a review comment.
- Write a free-text note about the selected lines.
- Save the annotation to `<UUID>.md` (UUID generated by the app).
- The saved file stores metadata in addition to the comment:
  - git commit hash (if available)
  - hash of the file contents
  - the file name
  - the lines in question (start–end range)

## Design decisions

- **UI**: Terminal UI (TUI) built with `ratatui` + `crossterm`. "Painting" lines
  maps naturally to a vim-like visual selection in the terminal.
- **Interaction model** (modal, vim-inspired), one TUI session for everything.
  The second header line always shows the main keys for the current mode
  (kept within 80 columns); `?` opens a help screen listing every key.
  - File view: `j`/`k` or arrows move, `g`/`G` top/bottom, `v` paint (select)
    lines, `c`/`Enter` comment the line(s), `f` comment the whole file,
    `b` open the file browser (in the open file's directory), `q` quit
  - Painting: move to extend, `c`/`Enter` comment the selection, `v`/`Esc` stop
  - File browser: `j`/`k` `g`/`G` move, `Enter` open file or directory,
    `l`/`→` enter directory, `h`/`←`/`Backspace` parent, `c` comment the
    selected file or directory, `C` comment the current directory, `Esc` back
    to the open file, `q` quit
  - Note editor: title says what is being commented on; `Esc` save, `Ctrl+Q`
    discard (back to where you were), `Enter` newline, `←`/`→` move; text
    word-wraps and the box grows up to half the screen, then scrolls
  - Mouse: the wheel scrolls the file or browser (the cursor is pulled along
    to stay on screen, so while painting it extends the selection); in the
    file view a click moves to a line and dragging with the left button paints
    lines (dragging past the top/bottom edge scrolls); in the browser a click
    selects an entry and clicking the selected entry opens it. Ignored while
    writing a comment. Mouse capture means terminal text selection needs the
    terminal's modifier (e.g. Shift, or Option in iTerm2)
  - `annotator` starts in the browser at the working directory,
    `annotator <file>` starts in the file view
- **Scopes**: an annotation is about a line range (TUI painting or
  `<file> --lines A-B`), a whole file (TUI `f` / browser `c`, or `--path FILE`)
  or a whole directory (browser `c`/`C`, or `--path DIR`). A directory gets one
  annotation, not one per file. Paths inside the working directory are stored
  relative to it.
- **Storage**: one Markdown file per annotation, named `<UUID>.md`, written to
  an `annotations/` directory in the current working directory (configurable via `--output-dir`).
- **Metadata**: collected at save time:
  - `git rev-parse HEAD` of the repo containing the file (optional, if in a repo)
  - SHA-256 of the *whole* file, also for line ranges, so a working tree that
    has drifted from `git_commit` can be detected
  - for directories: SHA-256 of a manifest with one `<sha256>  <relative path>`
    line per file, sorted by path — the same as
    `git ls-files -co --exclude-standard | sort | xargs sha256sum | sha256sum`.
    Inside a repo the file list is tracked + untracked-but-not-ignored files
    (so `target/` is skipped, deleted files are listed as `deleted`); outside a
    repo it is a recursive walk that skips hidden entries
  - scope: the path plus, for line annotations, the line range, as one
    code-link style reference — `src/` (directory, trailing slash),
    `src/foo.rs` (file), `src/foo.rs#L3-7` (lines, 1-based inclusive; a single
    line is `#L3`)
  - timestamp (UTC)
- **Output format** (`<UUID>.md`):

  ````markdown
  # Annotation

  - scope: `path/to/file.rs#L12-18`   (or: `path/to/file.rs`, `path/to/dir/`)
  - git_commit: <hash or "unavailable">
  - content_sha256: <hash>
  - created_at: <RFC3339 UTC>

  ## Context                (only for line ranges)

  ```rs                     (language tag = file extension)
  <the annotated lines>
  ```

  ## Comment

  <note text>
  ````

## Dependencies

- `ratatui` + `crossterm` — TUI
- `uuid` (v4) — annotation file names
- `sha2` — content hashing
- `chrono` — timestamps

## Milestones

1. [x] Plan written
2. [x] Project scaffolded (`cargo new`, deps compile)
3. [x] TUI: load + display file, cursor movement
4. [x] TUI: visual line selection ("painting")
5. [x] TUI: note input mode
6. [x] Save annotation `<UUID>.md` with all metadata
7. [x] End-to-end test: annotate a sample file, verify output file
8. [x] Plan updated as implementation log
9. [x] Fix issues from user-testing annotations (see below)
10. [x] Test suite: unit, TUI (in-memory terminal) and CLI integration tests

## Testing

`cargo test` runs:

- `src/annotation.rs` — hashing, Markdown output (incl. the `Context` code block), git detection, file and
  directory annotations, `.gitignore`-aware directory hash.
- `src/app.rs` — syntax highlighting (Rust, Markdown and code fences, non-ASCII text), comment wrapping (`wrap_note`), key
  handling for every action (painting, mouse wheel/click/drag, line/file/directory comments, discard,
  empty comments, browser navigation and switching files, help screen) and
  rendering into ratatui's `TestBackend` (comment box wrap/grow/scroll and
  cursor placement, key hints per mode and their 80-column limit, help screen
  lists every key).
- `tests/cli.rs` — runs the built binary in temp dirs and git repos: all
  metadata fields, dirty-tree detection, line-range clamping, `--path` for
  files and directories, and every argument/runtime error path.

## Implementation log

- 16:55 — Started: explored environment (Rust 1.98.1, git 2.50), wrote initial PLAN.md
- 16:58 — Scaffolded `cargo init annotator`, added deps: ratatui 0.30, crossterm 0.29, uuid{v4}, sha2, chrono
- 17:02 — Wrote `src/annotation.rs`: `Annotation` struct, git commit detection, SHA-256 content hash, UUID filename, Markdown serialization, 7 unit tests
- 17:07 — Wrote `src/app.rs`: TUI state (`Normal` / `Visual` / `Editing`), cursor + line selection (painting), note editor widget, rendering with ratatui
- 17:14 — Wrote `src/main.rs`: CLI parsing (`--lines A-B`, `--note TEXT`, `--output-dir DIR`), batch mode, TUI entry via `app::run`
- 17:22 — Fixed `sha256_hex`: sha2 0.11 returns `GenericArray` without `LowerHex`, switched to manual byte formatting; all 7 unit tests pass
- 17:24 — End-to-end batch mode test passed: `annotator src/main.rs --lines 1-5 --note "test"` produced valid `<UUID>.md` with file, lines, git_commit (unavailable), content_sha256, created_at, and note
- 17:30 — Updated PLAN.md as implementation log
- 20:14 — Added `Ctrl+Q` to cancel comment editing without saving
- 20:14 — Added Rust syntax highlighting for keywords, strings, and comments in TUI
- 20:48 — Implemented `--path` flag for recursive directory annotation
- 20:48 — Implemented `--hash-scope` flag for configurable hash scope (file/range)
- 20:48 — Added file browser UI with navigation (j/k move, Enter select, h/Left parent)
- 2026-10-20 — Fixed open issues from annotations: removed `--hash-scope` (whole-file hash always),
  `--path` now writes one annotation (scope file/directory, manifest hash for directories) and
  requires a non-empty `--note`; lowercase `v` paints; comment box word-wraps, grows and scrolls
- 2026-10-20 — Added TUI and CLI integration tests. They found two bugs, now fixed: saving from
  the TUI cleared the painted selection before reading it (multi-line selections were saved as
  the cursor line only), and running with no arguments printed usage instead of opening the
  file browser
- 2026-10-20 — Made every action reachable from the TUI (user feedback: "I could not see how to
  navigate the directories and select another file"): single TUI session with `b` to browse
  from a file and `Esc` back, file/directory comments from the TUI (`f`, browser `c`/`C`),
  per-mode key hints on their own header line, `?` help screen. Fixed: quitting after opening
  a file from the browser reopened that file in a second session
- 2026-10-21 — Line annotations save the annotated lines in a `## Context` section. Mouse
  support: wheel scrolling, click to move/select, drag to paint lines, click again to open a
  browser entry; listed under "Mouse" in the `?` help. Checked end to end by driving the binary
  in a pty with SGR mouse sequences
- 2026-10-21 — Unified the `scope`, `file`/`path` and `lines` fields into one `scope` reference
  (`src/`, `src/foo.rs`, `src/foo.rs#L3-7`)
- 2026-10-21 — Markdown highlighting for `.md`/`.markdown` files (other files keep the Rust
  highlighter): headings, rules, quote and list markers, inline code, links, bold, italic, and
  fenced code blocks, with Rust highlighting inside `rs`/`rust` blocks. Fixed: the Rust
  highlighter sliced lines by char index as if it were a byte index, so lines with non-ASCII
  text panicked (e.g. 32 lines of this file) or would show the wrong text
- 2026-10-21 — Prepared publishing on crates.io as `annotations`, matching the repository name
  (`annotator` is taken there); the installed command stays `annotator`. Added package metadata,
  `rust-version = "1.89"` and an `exclude` list for `annotations/` and `.DS_Store`

## Feedback from User Testing

Issues and feature requests collected from annotation files in `annotations/`
(source annotation ID prefix in brackets).

### Open issues
- **Default output directory** [`e089cf26`]: the default (`annotations/` relative to where
  the tool is run) is not really visible. Requested: `<repository root>/annotations/` when inside
  a git repository; otherwise the working directory the tool was started from. To clarify:
  whether outside a repo that means the working directory itself or `<cwd>/annotations/`.
  (Related: the header shows `saves to: annotations`, a relative path, not the resolved
  location.)

### UX Improvements (Addressed)
- **Visual mode key** [`2792dba9`]: lowercase `v` starts/stops painting (`V` still works)
- **Line wrapping in the comment box** [`437fa259`]: the editor word-wraps, grows up to half
  the screen and then scrolls to keep the cursor visible
- **Output location visibility** [`9e367b20`]: Added `saves to: <output_dir>` in the header
- **Help text** [`9e367b20`]: Clarified "v paint multiple lines" in the header
- **Cancel comment** [`5b79496a`]: Added `Ctrl+Q` in editing mode to discard a comment without saving
  (requested as a special `/exit` command; `Ctrl+Q` chosen as more idiomatic for a TUI)
- **Syntax highlighting** [`9e367b20`]: Added basic Rust syntax coloring for keywords (fn, let, pub, etc.), strings, and comments (line and block)

### Feature Requests (Addressed)
- **One unified scope** [`64bca40a`]: `scope`, `file`/`path` and `lines` were redundant; now a
  single `- scope:` field holds `src/`, `src/foo.rs` or `src/foo.rs#L3-7` (GitLab-style range,
  as suggested; a single line is `#L3`). Older annotation files keep the previous fields
- **Copy the painted lines into the annotation** [`998108af`]: line annotations (TUI and
  `--lines`) get a `## Context` section with the lines in a code block, before `## Comment`;
  the fence is lengthened when the code itself contains backtick runs
- **Mouse support** [`38f00d92`]: the wheel scrolls, click moves to a line, left-drag paints
  lines; in the browser click selects and a second click opens
- **Support commenting entire directories or paths** [`6e12dd0d`]: `--path FILE|DIR --note TEXT`
  writes a single annotation for the whole file or directory; an empty note is rejected
  (the earlier version wrote one empty annotation per file: `03fb2d0f`, `4ef8b1af`, `a879f431`)
- **Hash scope** [`41ee6bdc`, `6e12dd0d`]: `--hash-scope` removed; file and line annotations
  always hash the whole file, directories hash a manifest of their files (see Design decisions)
- **Built-in file selection/navigation** [`b3ef44ab`]: Implemented file browser mode in TUI with
  `j/k` navigation, `Enter` to select, `h/Left` to go to parent
- **Comment whole files from the TUI** [`8b628f99`]: `f` in the file view (the annotation
  suggested `w`), or `c` on a file in the browser
- **All actions available in the UI** (user feedback, not an annotation): the browser was only
  reachable by starting without a file and its keys were not shown. Now `b` opens it from any
  file, `Esc` returns, whole-file and directory comments work from the TUI, each mode shows its
  keys and `?` lists them all

### Test artifacts (no action)
- [`53c8ebe2`, `bf5e6bb4`]: "test batch mode" — batch-mode smoke tests
- [`03fb2d0f`, `4ef8b1af`, `a879f431`]: empty comments from a `--path` test run
- [`8965fffe`]: "Testing commenting the file" — confirms `f` saves a `scope: file` annotation