feat(panel): deep links to review threads in the Pull request tab
Deep links for review comments in an agent's Pull request tab: press a thread in the Conversation to open it in Files changed, and back again. Each thread also gets a Luke link of its own.
What it does
A link place for one thread. It's LINK_PLACE.PULL_REQUEST_THREAD { planId, agentId, threadId, view }, written under the agent's Changes link:
https://tryluke.dev/<ws>/plan/<planId>/agent/<agentId>/changes/conversation/thread/<threadId>
https://tryluke.dev/<ws>/plan/<planId>/agent/<agentId>/changes/files/thread/<threadId>
threadId is GitHub's GraphQL node id for the thread (ReviewThread.id), the same id reply and resolve already use. Opening the link:
openLink(use-tasks-tab) opens the agent's Pull request tab, reusing a shared tab the wayCHANGESdoes, on the view the link names.- It leaves a counted
threadRevealask on a context (thread-reveal.tsx), the same pattern asMessageReveal. - The tab takes the ask, finds the thread in its conversation read, and brings it into view. If the read no longer holds that thread, a toast says so.
Both in-tab jumps call openPlace with this place, so they take the same route as a link from anywhere. Copy link in a thread's ⋯ copies the link for the view the thread is drawn in, through useCopyLinkItem.
Conversation → Files changed. Pressing a thread's file/lines header, or ⋯ › View in Files changed:
- opens the file if it is collapsed or folded as viewed (new
ChangesList.expand), and clears a filter that hides it - shows a minimized line's chip and opens the thread, even a resolved or outdated one
- scrolls Pierre's list to the thread's line (new
DiffListHandle.revealLine, usingCodeView.scrollTo({ type: "line" })) - for a line no hunk shows, or an outdated thread, scrolls to the file header, where the above-the-diff list stands
- lights the thread's lines in the lit range from #3818 until its ring ends
- rings the card (
data-search-landed) and focuses its summary withpreventScroll
A thread whose file is no longer in the diff gets a toast saying so, and is shown in the Conversation instead.
Files changed → Conversation. ⋯ › View in Conversation scrolls the Conversation's own scroller to the card (centreWithin, never scrollIntoView), then opens, rings and focuses it. A thread brought into view stays visible even if Hide resolved or Hide bots would hide it.
How the scroll stays right while things expand
The jump is a pending state that resolves over renders, not in one call:
useListLandingopens the file, shows the chip, and records the place.PierreDiffList's reveal effect runs after its update effect, so Pierre already has the file open and its notes laid out. Only then does it callrevealLineorreveal.- Pierre keeps the target pending and re-resolves it every frame, and on every measurement, until it settles. Files above the thread that mount and measure taller on the way still land it in the right place.
The card's focus and ring wait, through focusSeek, until the card stands inside the list's box.
Screenshots
| Light | Dark | |
|---|---|---|
| Conversation (press src/expiry.ts lines 34–35) | ![]() | ![]() |
| → Files changed: file opened, scrolled to line 35, lines lit, thread ringed and focused | ![]() | ![]() |
| ⋯ › View in Conversation: the card centred, ringed and focused | ![]() | ![]() |
| A thread's file and lines under the pointer: underlined, with the tooltip "View in Files changed" | ![]() |
Tests
luke-link.test: both views round-trip, plus refusals for a bad view, a misspelled step, a missing or trailing id, a taskless agent, and an id with characters outside the pattern.use-tasks-tab.test: the route opens the Pull request tab, sets the view, and leaves counted asks; letting go of an older ask leaves the newer one standing.agent-pull-request-tab.test:- a collapsed file plus a minimized thread, reached through the window's route: the file opens, the list scrolls to line 13, the lines light, the thread is ringed and focused, and the lines go out as the ring ends
- a line outside the hunks, and an outdated thread: the list scrolls to the file header, and the folded thread opens above the diff
- a file no longer in the diff: the thread stays in the Conversation and is shown there
- Files changed → Conversation through Hide resolved
- Copy link
Checks
pnpm check:affectedran out of memory on this sandbox (exit 137 on the service and web typecheck, and features workers timing out). I ran the fallback set instead, and it passed:tscfor features, panel, desktop, ui and web- features vitest and the touched panel suites
./scripts/oxlint.sh, biomepnpm knip./scripts/repository-checks.sh
- Desktop/panel and
@luke/features/luke-linkonly, with no service change, so no Preview run is needed.
The stacked follow-up will open GitHub anchors (#discussion_r…, #issuecomment-…, #pullrequestreview-…) inside the tab.
Made with Luke






