# Herdr Annotator Review your agent's code without leaving Herdr. Compare branch, last commit, or uncommitted changes in a native sidebar or full review tab, leave comments on files or lines, and send your review to the agent working in that checkout. Read GitHub PR review threads, reply directly, or queue them for the agent to address. Works with local and SSH worktrees. ![Herdr Annotator](preview.png) ## What you see One compact Changes panel: title/branch, full-tab and refresh icons; view tabs; Files row with Uncommitted/Branch dropdown and local base refs; collapsible file list; file title and range-selection hint; scrollable wrapped unified diff with old/new gutters; bordered inline note cards; boxed multiline composer; fixed session recipient and full-width send action. File rows separate the filename and directory, plus green/red line counts and comment-count badges. Use theme colors and actual sidebar width, not a wide tab. Native draggable dividers separate files, code/comments, and composer; sizes persist. Context buttons expand unchanged lines by 20, request full context, or collapse. The full-tab icon opens a pinned standalone review: collapsible file navigator and wide virtualized unified/split diff with inline comments. A third comments column is optional, off by default. The composer opens on demand. Keep full-review chrome compact: 42px header, 34px view row and file row, 36px footer. Review settings/order/filter controls live behind Review ▾; context/copy/ask/file-comment controls live behind File ▾. Do not shrink code. The general-note field is absent until Review & send… opens confirmation. Full reviews default to Branch and remember comparison mode, diff style, pane visibility, unread filter, context size and widths. Base refs stay checkout-specific. Syntax/Plain is a saved toggle. A bounded lexer colors source tokens with theme colors without changing source text or the diff's add/delete/selection backgrounds. Inline note cards use 8px padding, 4px gaps and 12px body text. No instructional footer repeats beneath each note. Readonly bodies use selectable, HTML-escaped TextView content. Clicking opens Copy/Edit/Delete/read/resolve actions, not editing; text-selection drags must not activate the row. Copy controls preserve exact comment/thread text and diff/selected source lines, even during refresh. Keep a fixed native notes slot before the file and after each source row, even when empty. gpui-shell 0.7.1 splices the entire measured list when item count changes, resetting the reading position. Adding/removing/filtering comments must change slot contents, not list identity, item count or source-row keys. Clicking a slot with multiple notes opens their individual action cards. Added/deleted source has green/red tints; selection has an accent gutter and background. Ready-to-send note cards use accent borders and badges; outdated cards use amber with an explicit exclusion explanation; sent/resolved notes have their own status. Comments lists every file's feedback, including outdated and no-longer-changed files. Saving and sending are separate buttons with distinct placement. Disabled Send explains blockers in its tooltip; avoid repeated static composer/footer help. Copy buttons briefly show ✓ Copied with an opacity transition. No terminal UI or popup picker. ## How it behaves OpenCode logical ordering uses the bound source chat's selected model and reasoning variant. Match a local live OpenCode terminal's title and checkout against root sessions, checking all bounded result pages; never guess newest. Expose an ordering-only model picker with Source chat auto mode. Unresolvable or remote source sessions require an explicit override. Discovery is read-only, returns no provider secrets, and never sends chat messages. Pi retains its own configured model. Never change a session model or the global default. Default to the active space's terminal checkout, with optional pinned scope props. Folders outside Git show a neutral Not a Git checkout state with Refresh, not a red error. No selected folder asks for a checkout. Preserve drafts and block stale review actions; connection, permission and corrupt-repository failures stay errors. File notes use no range. Clicking old-only deletions selects old lines; other source rows select new lines. Shift-click extends on the same side. Saved notes support resolve/reopen; unsaved drafts stay in memory per context. Refresh every 15 seconds except during a draft. Saving includes the reviewed source anchor: unrelated edits and uniquely matched moved lines do not require refresh/reselection. Changed or ambiguous source and branch/comparison changes refuse the save while preserving draft text. Legacy saves without reviewed context still require the original revision. Sending retains its revision guard. List all changed files, loading only selected/annotated diffs. Branch scope includes committed and uncommitted work since merge-base; never fetch refs automatically. Use content-checked raw/numstat metadata for the file list. Read-only Git queries must not list stat-only M entries when worktree edits cancel a branch change. Keep mode-only, binary, empty-file and submodule changes without refreshing the index. Unchanged lines accept annotations; collapsing the preview preserves their anchors. GitHub threads poll every five minutes; Refresh fetches immediately. Show replies, outdated/resolved states, new replies and delivery receipts. Fix thread sends only that thread; Add to review queues it with local notes. Reply opens a separate composer; only Post to GitHub writes a reply, using the signed-in gh account. Keep reply drafts per context/thread. Persist uncertainty before posting; never retry automatically. Explicit retry requires the person to check GitHub first. Missing gh or sign-in shows installation/auth guidance, without blocking local reviews. Read/unread markers persist locally. Unread-only filters comment lists; new GitHub replies become unread. Reading changes neither resolution nor delivery eligibility. Hide resolved and confirmed-sent local notes and GitHub threads by default, including inline notes and file badges. Show sent/resolved / Hide sent/resolved reveals or hides saved history; failed/uncertain sends remain visible. Sending does not resolve threads on GitHub or claim the feedback is fixed; remember this choice across the sidebar and full review. Local notes can reopen. Full tabs pin the source checkout/comparison/chat. Losing that chat disables Send. Opening full review hides the existing Changes sidebar; returning to the source tab restores it. Respect manual sidebar toggles and do not replace a Files panel. Bind to the current tab's focused agent session automatically. Keep that binding when the composer takes focus; never guess between multiple chats or across tabs. Review & send… resolves and pins the current chat, then opens a compact composer with the optional general note, Send review and Cancel. Escape/Cancel retains the note. Final Send revalidates the pinned recipient; never redirect an open confirmation to a replacement chat. Permit note-only reviews. Navigation closes confirmation without losing its note. Disable repeat clicks during delivery. Idle and working agents can receive review feedback. Validate the observed status, then submit through `hc send --if idle` or `hc send --if working` so core checks it atomically. Never use --force or retry a status race automatically. Blocked/unreported terminals cannot receive feedback. Persist uncertain delivery before the core call so a crash/reply loss never silently resends. Requeue requires another explicit decision. Tests never send to a real agent. After confirmed delivery, focus the exact recipient pane. Never focus another chat if it disappeared, and never change focus on failed or uncertain delivery. Cmd+Enter / Ctrl+Enter saves one comment when its composer contains text. With no unfinished comments in the comparison, the shortcut opens confirmation. The next press in its general-note field sends. One press must never save and send, or open confirmation and submit it. Keep nested edit/question/reply/ref-picker shortcuts separate; ignore held/shift/alt chords and retain all normal delivery guards. After a keyboard save, keep the empty native comment composer mounted and focused so the next press still emits submit. Mod focus commands and review controls must also give the review's own keyboard handle focus. Never reclaim focus after an async save if the person switched to another pane. Plain Enter stays multiline; the save action obeys stale/offline/busy guards. Local Edit/Delete require the expected saved body and current revision. Preserve the anchor and delivery history; only open, unsent notes are editable/deletable. Delete asks for confirmation. Concurrent edits must fail rather than overwrite. Last commit compares HEAD's first parent to the working tree, including staged, unstaged and untracked work. Root commits use the empty tree; unborn HEAD has no Last commit scope. Keep annotations isolated by comparison mode. Right-click a source line or hunk to ask a question, or ask about the selection or open diff from a button/command. Questions have their own composer and do not send saved review notes. Validate the snapshot, complete preview, diff side and source bounds. Send only to the idle source chat. Persist content-keyed private uncertain/confirmed receipts before/after delivery; explicit checked retries must keep the original text and target. Cap messages at 64 KiB. File/comparison navigation never blocks on an unfinished comment. Stash and restore text, source side/range, revision and context per checkout/comparison/ file. Keep editor/question drafts with that navigation key too. Changed code requires reselection; navigation never saves, sends or silently re-anchors. Opening full review keeps an unfinished source draft and explains where it is. The branch picker accepts typed refs (develop/origin/develop) with Enter or Compare. Last commit remains selectable after a failed Branch snapshot; only a known unborn HEAD disables it. Delivery guards still reject unsaved comments. Commands annotator.submit/ask/scope/refresh/full use nextCommand and existing guards, with no global key changes. One-shot shared annotator.open accepts {id,at,path,on}; refresh and select only a matching checkout/machine, keep drafts and scope, expire after 30 seconds, and never clear a newer request. Check published stable GitHub releases daily, using public API metadata only; show version, release notes, Update and Later. No code fetch/build without an explicit click. annotator.updates opens manual status/checks. Bootstrap existing installs with one manual installer run. Keep package/mod versions aligned. Use an application-session view registry: hidden drafts and in-flight operations block Update, without heartbeat expiry. Pause editors globally during install. Probe the durable worker status on app restart before accepting new drafts. A detached Python worker holds a file lock through install, verifies receipts, refuses dirty/diverged source and locally modified installs, fetches the fixed official HTTPS upstream/tag, verifies its pinned SHA and version, fast-forwards only, then runs the installer with --expected-commit. Never reset/stash/discard. Check source cleanliness before and after the build; back up replaced files. Keep layouts intact during automatic installation and update private tabs from their receipt. Persist version/commit/binary/updater hashes. Builds need the existing Rust/Python/Git toolchain; no global keys or background daemon install. Auto compares local develop and origin/develop merge-bases and chooses the descendant common ancestor; ties prefer local develop. This excludes upstream work when the local ref is stale without fetching or changing refs. Named base selections stay pinned; Auto clears the pin. Reset legacy origin/develop defaults to Auto, preserve custom refs, and isolate notes by resolved comparison/base. ## Built from - `mods/annotator/main.js`: native GPUI view, TextareaState composer, themed rows, context-scoped drafts, current-tab session binding, per-call async contexts; helpers in `review.js`. - Rust CLI/library: `Engine::handle(Request)` with snapshot/add/resolve/export/send/github. - `src/question.rs`: bounded question context and durable delivery receipts; Edit/Delete and Last commit use the same prepared snapshot and private stores. - Read markers: `read` for local comments and `github_read` for cached thread versions. - `mods/annotator/full.js`: full-tab layout, deferred variable-height diff rows, split-side selection and paged comment cards. `list` comes from gpui-kit. - `scripts/tabs_integration.py` and `mods/annotator/launcher.js`: installer-generated private `annotator-tabs` mod adds a direct full-review button beside Changes. `--layout` updates personal layouts and receipt-checks the private copy; shipped tabs remain untouched. Source checkout/chat are captured before creating the tab. - `mods/annotator/syntax.js`: bounded pure lexical tokens; independent old/new multiline state, reset across hidden context gaps. No HTML or code evaluation. - Read-only Git subprocesses; remote Git argv through `hc exec`. Local hashed JSON stores with exclusive locks, atomic mode-0600 writes and corruption errors. - Exact selected-text/context anchoring; refuse ambiguity, branch mismatches, stale revisions, noncontiguous/out-of-preview selections. - Mod methods: `spaces`, `panes`, `focused`, `agents`, `exec`, `home`, `props`, `theme`, `settings`, `shared`, `setShared`; native `v_resizable`/`resizable_panel`. GitHub API queries run through local authenticated `gh`, only on github.com. ## Watch out Herdr App mods are JavaScript, not Rust dynamic libraries. Rust is a local helper; remote cores do not load this mod. `hc send` includes Enter. The mod's `exec` has a 30-second limit. Native checks render only the initial state; also inspect the live panel. Anchor old/new sides separately and keep literal Git pathspecs. The helper shares a 25-second subprocess budget across each request, including GitHub pagination and Send. Capture drains both pipes with bounded retention; inherited writers cannot keep it waiting indefinitely. Local process-group cleanup does not confirm remote cancellation. Unconfirmed Send stays uncertain. Honor documented preview limits, preserve stale notes and never overwrite corrupt stores. Do not edit shipped mods/layouts or stage/commit review targets. ## Done when Rust/JS tests pass; cargo fmt/clippy pass; Herdr check passes. A real review tab shows changed files and saved inline notes. Notes survive a helper restart, move only to unique exact context, and become outdated for modified/ambiguous anchors. Fake-core tests cover remote reads, confirmed delivery, failure uncertainty, duplicate prevention and busy/blocked refusal. Git index bytes stay unchanged. Large-list tests load a file past index 100; context notes survive collapsing. Fake-GitHub tests cover paging, caching, reply changes, explicit posting, missing gh, and duplicate prevention after a lost confirmation. Tests never post to real PRs.