Select line(s) in a PR diff to create inline comment threads, with a jump-to-comments index at the conversation top #546
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#546
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What's requested
On a PR, let a reviewer select line(s) in the diff and create an inline comment thread anchored exactly there, plus a jump-to-comments index at the top of the conversation page listing every anchored thread (path:line, resolved state) that scrolls to the thread when clicked.
Current state (static read, no local repro)
Much of the machinery exists; three gaps remain:
web/src/components/DiffTable.jsximplements full drag/shift line selection over the diff (issue #244) but exposes no comment affordance — the only comment entry point is the conversation page's inline diff.web/src/pages/Pull.jsxstageLine(~line 311) builds acount: 1anchor per line and collects the body viawindow.prompt, staged into the finish-review modal. There is no multi-line selection → range anchor path, and the composer should be the realCommentComposer(components/CommentComposer.jsx), not a browser prompt.threads_unresolved, ~line 94) and rendersThreadCards inline viaplacement()/threadsAt()(~lines 328–392), but there is no index of anchored threads at the top of the conversation to jump to them; on a large diff, inline threads are only findable by scrolling.Architecture notes
internal/review/model.go(ThreadAnchorwithold_start/old_lines/new_start/new_lines, validation at ~line 282 requiresnew_start/new_lines >= 1for NEW-side). No backend change needed for range anchors — only the client ever buildscount: 1today.lib/diff.jsanchorContextShais the ONLY §4 hash implementation and has pinned vectors; any range selection must feed it inputs byte-identically (drift-hash twin withDriftHashininternal/review/model.go). If range anchors change hash inputs, that is a pinned-contract change and needs an explicit decision.lib/diff-lines.js(issue #244) already provides headless per-line numbering and the file-scoped hash codec — reuse it for selection → anchor conversion rather than re-deriving numbering in the component.threadFreshness,placement()in Pull.jsx). The index must reflect the same placement truth (mark drifted/outdated entries rather than linking to a line that no longer exists).side: "NEW"for context/additions,side: "OLD"for deletions (Pull.jsxstageLine). Range selection in DiffTable is already clamped to one chunk + one side (DiffTable drag logic), which maps cleanly onto the anchor model.Acceptance criteria
CommentComposer(nowindow.promptanywhere on this path)new_lines/old_lines> 1 as appropriate); single-line keeps the current shapepulls.threads.*SDK client)anchorContextShainputs and pinned vector tests stay byte-identical, or the change is flagged as a pinned-contract decision for reviewFixed by PR #551 (#551, branch fix/issue-546): diff line selection creates inline threads + jump-to-comments index. Client-only, no backend change.
Review of PR #551 (fix/issue-546) — verified in scratch worktree /tmp/pr551
Verdict: ready to merge. All 7 acceptance criteria hold; no fixes pushed (nothing broken found).
(1) Anchor conversion — correct.
buildAnchor(review-anchor.js:53) preserves the zero-pair convention (NEW zeroes old_, OLD zeroes new_, lines===1 single-line — identical to oldstageLineshape) and clamps viamatchAnnotated(null on empty → "pick again" path, no throw). Byte-identity verified by reading both numberings:numberedLines(Pull.jsx:292) andannotateHunkLines(diff-lines.js:35) use identical counter logic over the samehunk.linesorder, so annotated index == oldrow.idx; single-line spans hash{start: idx, count: 1}exactly as before. Pinned Go-twin vector test untouched and passing;freshnessOfreduces to the old check for count:1. No drift-hash change — correctly NOT flagged as a pinned-contract change.(2) DiffTable affordance — correct. Opt-in (
onCommentSelect+canComment, DiffTable.jsx:284); Commit.jsx passes neither, so the commit page is unaffected. Consumer-ownedCommentComposer, zerowindow.prompt, zero directanchorContextSha(calls in the component (comment-mentions only) — both pinned by tests.(3) PullFiles staging — correct.
pulls.threads.create(num, {anchor, body}, {noPopupAuth:true})matches the SDK signature (reviews.js:60). Invalidatesthreads:{full}:{num}— byte-identical key shape to the conversation'sthreadsKey(Pull.jsx:703), so the new thread appears on navigation. #502 gaterole() !== null && !anon()matches Pull.jsx:746 exactly. Non-blocking note: the review summary (threads_unresolved) is TTL-staled after a Files-tab create until refetch — standard app-wide staleness, not a defect.(4) Conversation composer — correct.
window.prompt("Comment on…")gone (remaining prompt at Pull.jsx:141 is the pre-existing dismissal-reason path, out of scope). DraftCommentComposer→onStage→pending→ FinishReview (Pull.jsx:814/965/997) withanchorLabelrange rendering. Shift+click gated on same file+side+hunkIdx (the DiffTable clamp convention).(5) ThreadIndex — correct.
path:line/path:start-endvia sharedanchorLabel, resolved/outdated suffixes, unresolved-first viasortThreadsForIndex(same order as inline cards). Jump scrolls tothread-<tid>with emerald flash + expand (flashed()forcesopen()); drifted entries target their collapsed file-end card (same id), file-gone entries render· gonewith no target. No dead-line links.(6) Placement truth — single source. Both cards (
threadFreshness→ delegates tofreshnessOf) and index use the same helper; anchors never relocate.(7) Guideline — compliant. Only canonical idioms (
.card/.card-header/.pill/.btn/.muted/.err-line, sharedCommentComposer);ui.cssuntouched (no new CSS, correctly no guideline extension — law 11 carried by the two doc amendments).outline-emerald-500flash is a Tailwind token (cf.tab-badgeprecedent), not a literal;flex-wrapindex/affordance rows degrade gracefully at ~390px by construction.(8) No backend, no deps. File list is docs +
web/only (the onegrep gohit isdocs/go/12_web_ui.md); no.go, nopackage.json/go.modchange. Law-12 decisions appended in both docs in the same change.Tests:
node --test web/test/unit/*.test.js→ 1277 pass / 1 fail; the 1 failure is the live-server smoke subtest ("built SPA shell is served at / and /setup", /setup 403 from the standing instance) — unrelated to this client-only change (a main-worktree head-to-head run was attempted but the smoke test hangs without a live server, itself confirming environment-dependence). Newreview-anchor-546.test.js: 18/18 pass.vite buildgreen (only the standard chunk-size warning).No browser drive per task instructions (node tests + reasoning; layout reasoned from shared card/pill/btn classes at desktop + ~390px).
Fixed by PR #551 (review clean — all 7 criteria pass, hash parity + placement truth verified), merged. Closing.