Originally created by: theBGuy
Originally owned by: theBGuy
This change introduces native, file-anchored review threads for GitHub, GitLab, and Bitbucket PRs/MRs, rendering line-anchored review comments (including suggestions) grouped by file in the conversation and directly under their exact lines in the Files diff. It also adds the ability to apply GitHub reviewer suggestions directly to the working tree (with file validation and staging), and brings AI Generate to the PR/MR edit dialog. The goal is to make GitDesktop handle review flows with the fidelity (and more) of the hosted platforms, unifying inline code conversations, suggestions, and bot/AI reviews locally.
src/features/pulls/ReviewThreads.tsx, src/features/pulls/RemotePrView.tsx, src/features/pulls/RemotePrViewParts.tsx), rendering threads grouped by file in the PR conversation and anchored under the exact diff line in the Files view via DiffSurface.tsx.useLocalConversation.ts, DiscussionView.tsx, RemoteIssueView.tsx).ReviewThreadOut structure in src-tauri/src/github/pr.rs and provider glue in src-tauri/src/forge/model.rs, src-tauri/src/forge/mod.rs for reading threads, replying, and resolving—wired for GitHub, GitLab, and Bitbucket.src-tauri/src/forge/bitbucket.rs) and parses GitLab discussions/notes for anchoring and resolution (src-tauri/src/forge/gitlab.rs).forge_pr_review_threads, forge_pr_thread_reply, forge_pr_thread_resolve commands in the Rust API and surfaces their readiness via the Implemented flags.src-tauri/src/git/ops.rs, src/lib/git/api.ts, src/lib/git/queries.ts, src/lib/git/types.ts), validating the suggested lines before touch, preserving encoding and EOLs, and conditionally staging.changelog.d/added-apply-suggestions.md).src/features/conversations/EditTitleBodyDialog.tsx, src/features/pulls/LocalPrView.tsx, src/features/pulls/RemotePrView.tsx), so existing PRs (including those without a checked-out branch) get the same AI title/description flow as creation.changelog.d/added-pr-edit-generate.md, README).README.md, help text, capability lists, and changelogs for new features and usage patterns, including visual and behavioral details of inline threads.changelog.d/fixed-review-threads-polish.md).changelog.d/fixed-bb-thread-pagination.md, changes to Bitbucket backend).lineAnchors extension API in src/features/diff/DiffSurface.tsx for in-diff review thread annotation.These changes collectively bring a full-featured, file-anchored review thread experience—matching and in some areas surpassing hosted platform UIs—for all three providers, and introduce local suggestion-application to the working tree, along with major workflow and polish improvements.
Originally posted by: cloudflare-workers-and-pages[bot]
Deploying gitdesktop with
Cloudflare Pages
5c3cac0View logs
Originally posted by: theBGuy
AI review (sonnet) · automated
This is a large, coherent feature that correctly threads review comments from all three forges through a single neutral type, anchors them in the diff view, and implements a robust local "apply suggestion" primitive on the backend. No blockers. A few real issues below.
Correctness
should-fix ·
src/features/pulls/RemotePrViewParts.tsxPrFilesPane·lineAnchorsuseMemoThe dependency array
[effectivePath, threads, onQuote, onReply, onResolve, apply]includesonReply,onResolve,onQuote, andapply.onApply— all constructed as fresh arrow function literals on everyRemotePrViewrender:Every parent re-render (polling, mutation status updates, etc.) creates new arrow references, which busts the
lineAnchorsmemo, which in turn busts theextendDatamemo inRenderedDiff. The author's own comment says "a fresh object each render would thrash it", so this directly undermines the intended stability guarantee. The fix isuseCallbackon each of the four callbacks inRemotePrViewbefore they're passed down (or stable mutation references from a wrapper).should-fix ·
src-tauri/src/forge/bitbucket.rsreview_threadsOnly the first page (100 comments) is fetched for Bitbucket inline threads. The
group_bb_threadsalgorithm needs the full parent-id topology — including deleted/pending comments — to resolve reply chains. Comments on page 2+ are absent from the topology, so any live reply whose intermediate-parent was on page 2 is silently dropped, and entire threads opened after the first 100 comments disappear.view_prhas the same single-page cap, but at least theregroup_bb_threadssees a consistent slice. Consider adding a simple pagination loop (two or three pages max, like GitLab's five-page cap infetch_mr_discussions).nit ·
src-tauri/src/forge/gitlab.rsreview_threads(line 2245)The fourth branch fires when only
old_pathis present andold_lineisNone— but maps the side as"new". This means a file-level (not line-level) comment on an old-side-only path will be bucketed on the wrong side of a split diff and won't anchor correctly. Should be"old".Edge Cases
should-fix ·
src/features/diff/DiffSurface.tsxRenderedDiffextendDatabuilderIf two
DiffLineAnchorentries share the sameside+line, the second silently overwrites the first.PrFilesPanepre-groups so this never fires today, but the interface (DiffLineAnchor) documents no such uniqueness constraint. Any future caller ofGitDiffViewthat doesn't pre-group would silently lose anchors. At minimum, document the invariant on the interface; ideally, guard or merge insideextendData.Readability / Nits
nit ·
src/features/pulls/ReviewThreads.tsx(line 884)Per the house "clipped text needs a tooltip" rule, a statically-truncated string should carry
title={path}. The reviewer can't see the full path of a long file without expanding the thread.nit ·
src/features/pulls/ReviewThreads.tsxandsrc/features/pulls/RemotePrView.tsxSUBMIT_HINT = formatBinding("mod+enter")is defined identically in both files. Move to a shared location (e.g.,@/lib/hotkeys/bindingor a smallui-constantsmodule) to avoid drift.Originally posted by: theBGuy
AI review (sonnet) · automated
The "changes since last review" bundle three orthogonal bug fixes — trashing an open repo, narrowing a "not found" swallow in branch deletion, and the matching UI-side close-before-trash/reopen-on-failure logic — all on top of the unchanged inline-review-threads feature. The three new fixes are logically correct. The previous review findings on the thread feature itself are unaddressed by these changes.
Resolved since last review
None. The "changes since" diff is entirely about the repo-removal flow, not the review-thread feature. All previous findings carry forward; each is re-verified below.
Correctness
should-fix ·
src/features/pulls/RemotePrViewParts.tsxPrFilesPane·lineAnchorsuseMemo — still presentonReply,onResolve, andapplyare all fresh literals on everyRemotePrViewrender. The newsuggestionApplyobject is constructed inline (not memoized):Every poll tick or
repoStatusupdate creates new references, bustinglineAnchorsand the downstreamextendDatamemo. Wrap the four callbacks andsuggestionApplyinuseCallback/useMemoinRemotePrView.should-fix ·
src-tauri/src/forge/bitbucket.rsreview_threads— could not verifyThe previous review flagged a single-page (100-comment) cap. The
review_threadsbody is truncated in both the main diff and the "changes since" diff. If pagination was not added, threads opened after the first 100 comments remain invisible and their reply chains are silently broken.should-fix ·
src-tauri/src/github/pr.rs·gh_pr_mergecall-site propagation — could not verifyCopilot flagged (marked outdated by them) that
gh_delete_remote_head_branch(...).await?at the call site ingh_pr_mergecauses a successfully-merged PR to appear failed when branch deletion fails. The current diff correctly narrows the inner function's "not found" swallow (removing the too-broad|| lower.contains("not found")arm), but the call-site?is not visible in the diff. Warrants a manual check: if the caller still propagates the delete error with?, a permission failure on deletion will show the user a toast error despite a clean merge.Edge Cases
should-fix ·
src/features/diff/DiffSurface.tsxextendDatabuilder — still presentTwo
DiffLineAnchorentries on the sameside+linesilently overwrite each other (last-write wins).PrFilesPanepre-groups so this never fires today, butDiffLineAnchordocuments no uniqueness contract. Any future caller that doesn't pre-group loses anchors silently. At minimum document the invariant on the interface.Nits
nit ·
src/features/pulls/RemotePrViewParts.tsx·DiffThreadAnchorstale-closure toggleisExpanded(t)reads the render-closureexpanded, notprev. Under rapid batched clicks the second toggle reads stale state and may flip the wrong direction. Fix:!(prev[t.id] ?? !t.isResolved).nit ·
src/features/pulls/ReviewThreads.tsx· truncated path — still presentThe
{path}element withclassName="truncate …"lackstitle={path}. Per the house rule, statically truncated user content needs atitlefor full-text visibility on hover.nit ·
ReviewThreads.tsx+RemotePrView.tsx— still presentSUBMIT_HINT = formatBinding("mod+enter")is now defined identically in both files (theRemotePrView.tsxdefinition was introduced in this change). Move to a shared constant.nit ·
src-tauri/src/forge/gitlab.rs· side"old"/"new"branch — could not verifyPreviously flagged: the fallback branch when only
old_pathis present andold_lineisNonemaps the side as"new"instead of"old". Thegitlab.rsdiff is truncated; cannot confirm whether this was fixed.Originally posted by: theBGuy
AI review (sonnet) · automated
This PR ships file-anchored review threads for GitHub/GitLab/Bitbucket — grouped in the Conversation tab and anchored in the diff — plus local apply-suggestion and AI Generate on the Edit PR dialog. The design is sound, the security controls in
replace_file_linesare thorough, and most of the previous review's findings are resolved. Two real issues remain.Correctness
should-fix —
RemotePrView.tsx, line 1067 — "No activity yet." can appear simultaneously with review threads for GitLab and Bitbucket PRs that have only inline thread activity (no general notes, no reviews). The empty-state guard checkspr.reviews.length === 0 && pr.comments.length === 0but is unaware ofreviewThreads.data. For those providerspr.reviewsis always[](they emit no review entries), so a GitLab/Bitbucket MR with only diff-note threads shows a thread block above an incorrect "No activity yet." line.Fix: add
&& !reviewThreads.data?.lengthto the condition (optional-chain handles loading, empty array, and undefined cleanly).should-fix —
src-tauri/src/github/pr.rs,gh_pr_review_threads— the GraphQL query fetchesreviewThreads(first:100)with no cursor/pagination. A PR with more than 100 review threads (not uncommon on large Copilot reviews or code-heavy PRs with many comments) silently delivers only the first 100. This is a brand-new feature establishing a hard cap on its first version; adding apageInfo { endCursor hasNextPage }loop (or at least a bounded 2–3-page follow) would keep parity with the Bitbucket and GitLab pagination story already present in this PR.Edge cases
should-fix —
src/features/pulls/ReviewThreads.tsx,parseHunk— the guardif (raw === "" && out.length === 0) continue;only skips a blank line at the start of a hunk body. A blank line later in the hunk (e.g. a genuine blank content line that GitHub renders without a leading space) reaches theelsebranch, is treated as a context line, and incrementsnewNo. This would causerecoverOriginalsto compute astartLinethat is off by one per such blank line, makingreplace_file_linesreject the suggestion with a "lines no longer match" error rather than applying it. A conservative fix is to treat lines with no leading marker as context (i.e. add a" "prefix before storing intext, or skip blank raw lines explicitly).Nit
nit —
changelog.d/added-inline-review-threads.md, line 8 — the sentence "Previously they were invisible (GitHub) or shown as context-free flat comments (GitLab/Bitbucket)." starts mid-line immediately after a word-wrapped line, producing a broken visual in editors (though Markdown normalises it at render). Reflow to start that sentence on a fresh line.Resolved since last review
gl_thread_anchornow correctly returns"old"for the old-path-only case (was returning"new"), and the fix is covered by a new four-arm unit test. ✓review_idfield added toReviewThreadOut, allowing review-body copy-markdown to include owned threads. ✓id: r.idinstead ofString::new(), enabling thereview_idlookup. ✓quoteReplyrefs-in-render fix applied consistently acrossRemotePrView,DiscussionView,RemoteIssueView, anduseLocalConversation. ✓view_prnow filters out inline comments (they surface viareview_threads) so they no longer appear context-free in the flat conversation list. ✓Ticket changed by: theBGuy