Skip to content

Line-number anchor deeplinks#31

Open
tarekrached wants to merge 1 commit into
oxidecomputer:mainfrom
tarekrached:line-number-anchors
Open

Line-number anchor deeplinks#31
tarekrached wants to merge 1 commit into
oxidecomputer:mainfrom
tarekrached:line-number-anchors

Conversation

@tarekrached

@tarekrached tarekrached commented Jun 4, 2026

Copy link
Copy Markdown

Line-number anchor deeplinks

Adds GitHub-style line deeplinks to the diff viewer. Click any line number to highlight that line and write an anchor into the URL; open or paste such a URL to re-select and scroll to the line. Works on both sides of the diff, in split and unified views.

pr-02-anchor-highlight

(screenshots are from a diff of this PR 😄)

What you get

  • Click a line number → the line highlights (amber, GitHub-style) and the URL gains a #<file>R<n> anchor (R = additions/right side, L = deletions/left). Click the same line again to clear it.
  • Open or paste a deeplink → the target file expands if collapsed, scrolls into view, and the line is selected. Back/forward and edits to the URL are handled too.
  • The anchor path is human-readable (#src/App.tsxR76), not a hash — slashes stay literal; only genuinely unsafe characters are percent-encoded.

How it works

The highlight uses @pierre/diffs' native selectedLines prop rather than hand-rolled DOM bookkeeping. The library owns the tagging and re-applies it across its virtualized re-renders, so scrolling a long diff never drops the highlight.

The one wrinkle is that the library reserves the data-selected-line attribute for that selection — its renderer clears every one of them before repainting — and the existing keyboard (j/k) cursor was painting onto the same attribute. The cursor now lives on its own data-cursor-line attribute, so the two highlights coexist instead of clobbering each other: the anchor reads as amber, the cursor as a neutral gray.
anchor-cursor-coexist

The selection's color is themed amber via a small stylesheet injected into each diff's shadow root. This is deliberate: the library's selection-color CSS variables are registered non-inheriting, so they can't be overridden from the host element the way --diffs-modified-color-override can — a scoped style is the reliable hook.

URL routing (buildAnchor / parseAnchor, the hashchange listener) and scroll-to-line stay app-side. The library exposes no "reveal line N" API for an off-screen virtualized row, so on deeplink load we scroll the file into view and then poll for the row in the shadow DOM once it mounts. This is the exact two-phase workaround described in pierrecomputer/pierre#452 (request for a native Virtualizer.scrollToLine()); if that lands, scrollToAnchor can be simplified to a single call.

Verification

Driven through a real browser against a live diff:

  • Click an addition line → #…R<n> set, native selection painted, legacy hand-rolled attribute absent
  • Deeplink on initial load → selects and scrolls (including a line ~3/4 down a long file)
  • j/k cursor and anchor on different lines → neither clobbers the other
  • Toggle off (re-click) → selection and hash cleared
  • Deletions side in both split and unified views → highlights correctly

npm run tsc, npm run lint, npm test, and npm run build are all green.

Notes for review

  • Rebuilt dist/ is included, consistent with how this repo tracks build artifacts.
  • An earlier prototype hand-rolled the highlight via shadow-DOM tagging and a MutationObserver; adopting the native selectedLines deleted that machinery and inherited virtualization handling for free.

Click any line number to highlight it and write a GitHub-style anchor
into the URL (#<file>R<n> for the additions side, L<n> for deletions);
opening or pasting such a URL re-selects and scrolls to that line.

The highlight uses the @pierre/diffs native `selectedLines` prop, so the
library owns the tagging and re-applies it across its virtualized
re-renders -- no manual shadow-DOM bookkeeping. Because the library
reserves `data-selected-line` for that selection (its renderer clears all
of them before repainting), the keyboard cursor moves to its own
`data-cursor-line` attribute so the two highlights never clobber each
other. The selection's color vars are registered non-inheriting and
can't be reached from the host, so the amber re-theme is a scoped
stylesheet injected into each diff's shadow root.

URL <-> selection routing (buildAnchor/parseAnchor, hashchange) and
scroll-to-line stay app-side: the library exposes no reveal API for an
off-screen virtualized row, so scrolling still locates the row in the
shadow DOM once it mounts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@david-crespo

david-crespo commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Thank you! I am in the process of trying to switch over to the new CodeView thing they discussed in https://pierre.computer/writing/on-rendering-diffs, but I ran into trouble keeping the keyboard shortcuts working, so I'm not sure if I'll be able to finish hat. If I do, this would probably have to be rethought. I'll have to decide which direction. In the meantime, anyone can enjoy this feature by running your fork locally.

I like the idea overall, and it's not costly to have around even if you're not using it, but I'm curious to hear more how you use it. Since the tool is meant to be used locally, you're the only one generating these links and linking yourself to them. In order for the links to work, you have to be running the server on the same revset/commit range as when you generated the link, right? It makes me think that the commit range shouldn't have to be baked into the server run command — what if you could specify it in the query string? That way you could have a single server running, and the links to a particular line could build in the commit range that makes the fragment links work. I'm not suggesting you add this functionality in this PR, just thinking out loud about future directions.

@tarekrached

Copy link
Copy Markdown
Author

All of that makes sense. I'm using it to help me with local agent-driven code reviews before pushing up to github - the basic flow is "explain your changes with links to the relevant lines in sk". It's pretty helpful, though switching between tabs is kind of a pain so I'm considering also adding something to show the markdown explanation in the same view, with the diff alonside.

@david-crespo

Copy link
Copy Markdown
Collaborator

Oh! That makes so much sense. There are two local users, you and the agent. I do this with Github permalinks all the time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants