Repository navigation
Add selected local Review diff text to the chat draft with patch provenance #455
Description
User problem and benefit
While reviewing an agent's changes, asking “why was this removed?” or “keep this part” requires copying patch text, switching to Chat, and reconstructing its filename and diff provenance. Opening the saved file loses deleted lines; editor excerpts cannot describe both sides of a change.
Add Add selection to chat to a loaded single-file local Review diff. Append an editable, safely fenced patch excerpt to the current draft, then focus Chat. This spends no model tokens until the user sends and performs no Git operation.
Source inspiration — code evidence, not app screenshots
Reviewed both repositories at pinned commits; neither source application was launched or visually inspected.
- Synara:
DiffPanel.tsx:1120–1157accepts a selection only when both ends belong to the same file container, captures its path and text, and appends a reference to the active thread's composer. Borrow the direct selection-to-draft workflow, not its floating web toolbar. - T3 Code:
AnnotatableCodeView.tsx:194–215builds a diff comment and adds it to an explicitcomposerDraftTarget.buildDiffReviewComment:402–448resolves selected diff lines, distinguishes old/new coordinates, and preserves change markers in a fenced diff. Borrow explicit destination and patch provenance; do not import its persistent annotation store or line-comment UI.
This is a smaller, feasible improvement than a new review-comment system: Threadlane already has selectable patches and guarded, undoable draft insertion.
Current Threadlane implementation
Inspected upstream main at c3a6f09107e742df36c55d97795ff95e903b3bb0, rather than assuming the automation worktree was current.
ReviewDiffDocumentowns theTextViewState, loaded patch presentation, find state and focus. Its toolbar currently exposes Find in diff, not draft insertion. The shared diff renderer is selectable.RightPanelView::apply_review_diff_resultvalidates request/project/session identity before installing a patch. Reuse this ownership model for excerpt capture. The existing Files selection command explicitly excludes Review/diff documents.WorkspaceView::insert_files_selection_to_chatis the host-to-chat precedent.ChatListView::append_draft_text_forchecks theComposerKey, makes an undoable input edit, reveals Chat and focuses the composer. No new draft store or daemon command is needed.
Proposed UX and states
- Entry point: reuse the familiar label Add selection to chat as a quiet labeled kit Button in the Review document toolbar and a Review-scoped Action reachable by keyboard. No hover-only control, new global shortcut, or automatic sending. Keep Find, Previous/Next file, whitespace toggle and Back available. At narrow widths, wrap or use a standard keyboard-operable overflow menu.
- Result: append
Review diff excerpt: src/config.rs, aDisplayed patch snapshotlabel, and the exact selected display text inside a safedifffence. Preserve selected+/-markers, indentation, Unicode and partial lines. If Ignore whitespace is enabled, explicitly label the excerptWhitespace-filtered patch snapshot. Do not invent old/new line numbers, infer omitted markers, claim it is an applicable patch, or reread disk to replace what the user selected. Existing draft text/images and clipboard remain untouched; one Undo removes the insertion. - Availability: v1 supports a ready, nonempty single-file local Review patch only. Disable with an accessible textual reason for empty/whitespace-only selection, loading/error/parse-pending state, missing chat target, checkout mismatch, or more than 32 KiB of selected UTF-8 text (matching existing editor/terminal excerpt policy). Reject oversized selections without truncation. All changes says “Open a single-file diff to add an excerpt.” Binary/metadata-only output without textual hunks is not eligible. No implicit whole-file fallback.
- Identity and staleness: capture the source document, loaded request revision, file target, whitespace mode, checkout, selection and destination
(work_dir, session_id)before toolbar/menu focus transfer. Revalidate on activation. File navigation, reload, whitespace change, checkout/session switch, closing Review or a replacement text document invalidates the capture. A changed target must never receive the excerpt; explain “The diff or chat changed. Select the text again.” If only focus moves to the command, retain the selection. A patch can age on disk after loading: snapshot labeling is deliberate, not a promise of live Git freshness. - Accessibility: keyboard users must be able to select text in the diff and invoke the same command; verify the pinned TextView selection/focus APIs rather than assuming mouse selection suffices. Provide visible focus, accessible names and disabled reasons, with no color-only states. Escape closes the topmost menu/find layer and restores focus without insertion. On success focus the composer; on rejection keep the draft and source intact. Use semantic theme tokens and standard compact controls per the project Design Guides.
Out of scope: All changes/cross-file selections, GitHub PR review comments, commit/stash diff previews, editor-tab diff exports, persistent annotations, line remapping, extra Git reads, staging/discarding, sending/queueing/steering, and model permission changes.
Scoped implementation plan
- Shared document and formatting: extend
crates/threadlane-ui-kit/src/review_find.rs/review.rswith selection capture, availability and a host-facing action for the existingReviewDiffDocument; keep Git/session services outside the kit. Add bounded pure excerpt formatting, reusingsafe_fencefromeditor.rs. Reuse the TextView ownership/focus-capture lessons inthreadlane-ui-chat/src/quote_selection.rs, but do not reuse assistant-quote trimming for literal patch text. Use a patch-content identity distinct from the find-search generation (typing a search query alone must not pretend the patch changed). - Guarded host request: in
threadlane-ui-right-panel/src/view.rsandtypes.rs, derive eligibility fromReviewDiffRequest,ReviewDiffState, target and active checkout. Capture a narrowly scoped excerpt request and recheck the live document/request at consumption, including selections extending outside the diff. Clear captures through existing load/reset/navigation paths; do not materialize the whole patch on every render or selection notification. - Draft handoff: subscribe in
threadlane-ui-workspace/src/view.rs, following the Files-selection route, and call the existingappend_draft_text_for. Keep source and destination validation at both boundaries. Leave chat draft storage, daemon/protocol, Git and the application binary shell unchanged. - Shared preview and regression fixtures: update
threadlane-ui-kit/examples/preview/review.rsfor the controlled component contract. Add ready, filtered, empty, loading, stale and disabled fixtures; a preview without an explicit draft destination stays disabled and must not probe the filesystem. Add tests alongside the shared document, right-panel request guards and workspace/chat handoff tests.
Acceptance criteria and validation
- Selected added, deleted and context text reaches the correct draft with path and snapshot/whitespace provenance, safe fences and exact content; no fabricated line numbers or silent truncation.
- Existing draft/images/clipboard survive; Undo restores the previous draft. No send, queue, steer, Git mutation, filesystem write or new network request occurs.
- Tests cover Unicode/CRLF/backticks, partial lines, 32 KiB boundaries, empty/oversized/binary selections, parse readiness and selections crossing outside the document.
- Deterministic tests reject races across file/revision/whitespace/checkout/session changes, including identical session IDs in different projects. Focus-only transfer succeeds; find highlights/navigation never masquerade as user selection.
- Mount GPUI tests under the initialized kit
Root, useAppState::for_tests(), and verify mouse plus keyboard-only selection → command → composer → Undo, menu Escape, and disabled/error behavior. Test same-project different-worktree routing and active agent turns without sending. - During implementation run focused
cargo nextest runfor affected kit/right-panel/workspace/chat tests,cargo check -p threadlane-gpui, andgit diff --check. Exercise the real window at minimum width, larger text and light/dark themes; check preview build/fixtures. These are proposed validations, not tests performed by this research run.
Duplicate screening
Reviewed all 106 existing issues, open and closed, searched titles/bodies/comments by diff, patch, excerpt, annotation, selected text, add-to-chat and review-comment concepts, and read nearby discussion. Rechecked before filing.
- #416 covers editable file buffers and explicitly excludes Review/diff-side selection. This proposal supplies that missing source, not another editor excerpt implementation.
- #405 and #320 cover assistant text and terminal output; their draft handoff behavior should be reused.
- #434, #402 and #333 cover local diff find, adjacent-file navigation and whitespace filtering—not excerpts.
- #156 concerns GitHub/PR integration. #271's two inspiration images were inspected: the visible Add to chat command is in a PR object menu, not a selected local-patch workflow. #160 concerns browser DOM annotations. No source-app visual inspection is implied.
- #350 covers insertion identity hardening; reuse the project/session guard rather than reopen that issue.
Risks and tradeoffs
The main risk is selection loss or stale ownership during focus transfer, not Git plumbing; prove that behavior in real GPUI before expanding scope. Snapshot excerpts can omit surrounding context or include secrets, so require explicit insertion and a separate send, with no new telemetry/persistence channel. Restricting v1 to one file and omitting inferred line coordinates loses T3 Code's richer annotation precision, but avoids introducing a diff parser, annotation database and remapping machinery for a simple draft handoff.
Activity