Dependencies rewritten 2026-08-19 in a backlog staleness pass. The design and acceptance criteria are unchanged and still sound; only the blocker status was wrong.
Background
Split out from the descoped #115 Phase D. Phase A + B.1 of the diff viewer have shipped; this is the remaining "polish" item that's still relevant.
Problem
When a Watcher user wants to share a specific change with someone (a colleague, a stakeholder, a comment in another tool), they currently have to send the whole /changes/{id} URL — there's no way to point at a specific line within the diff. For long diffs that means the recipient has to scroll-hunt for what's being referenced.
Goal
Each line in the diff (and ranges) gets a stable, copyable URL fragment, e.g.:
https://watcher.exe.xyz/changes/{id}#L42 — single line
https://watcher.exe.xyz/changes/{id}#L42-L57 — range
When the URL with a fragment is loaded, the page should:
- Auto-scroll to the referenced line(s)
- Visually highlight the referenced span (subtle background tint)
Approach sketch
diff2html-ui renders each line as .d2h-code-line / .d2h-code-side-line with an associated line number cell. Two pieces:
- Anchor IDs at render time (
src/dashboard/static/js/diff-viewer.js): after Diff2HtmlUI.draw(), walk the rendered DOM and add id="L{n}" to each line number cell. Add a small "copy link" affordance on hover (chip near the line number that copies ${location.pathname}${location.search}#L{n} to clipboard).
- Range selection — shift-click a second line number to extend a selection;
#L{a}-L{b} form. Standard pattern from GitHub.
- Fragment handling on load — read
location.hash, scroll the line into view, apply a .d2h-line-anchor-target class for highlight.
Acceptance
Out of scope
- Cross-mode anchor preservation (line 42 in Extracted ≠ line 42 in Raw — that's a different feature)
- Persisting selections in
localStorage
Dependencies
Blocked by #222. The original said "None — purely client-side enhancement on top of existing diff2html-ui mount." That is now backwards — there is no mount. Verified 2026-08-19:
| Prerequisite |
State |
/changes/{id} route |
Gone — no changes route in src/dashboard/routes/ |
.diff-mount / data-unified-diff / #diff-content in templates |
Zero occurrences |
Any template loading diff-viewer.js |
None |
Diff2HtmlUI.draw() ever running |
Never — nothing mounts it |
Every acceptance criterion below targets DOM that is not rendered. The chain is:
#126 (this) → #222 (restore unified diff + chunk semantics) → a persistent content store
ChangeRevision stores a fingerprint and nothing else, so there is no previous content to diff and therefore no diff page to anchor into.
Note also #273 — the orphaned viewer assets (diff2html-ui.min.js, diff-viewer.js, the input.css .diff-mount overrides) are proposed for deletion, recoverable from 89d64eb. If that lands first, this issue's approach sketch needs re-vendoring diff2html as a step, and whoever restores it should confirm diff2html is still the right choice rather than restoring 3.4.52 reflexively.
Related
Background
Split out from the descoped #115 Phase D. Phase A + B.1 of the diff viewer have shipped; this is the remaining "polish" item that's still relevant.
Problem
When a Watcher user wants to share a specific change with someone (a colleague, a stakeholder, a comment in another tool), they currently have to send the whole
/changes/{id}URL — there's no way to point at a specific line within the diff. For long diffs that means the recipient has to scroll-hunt for what's being referenced.Goal
Each line in the diff (and ranges) gets a stable, copyable URL fragment, e.g.:
https://watcher.exe.xyz/changes/{id}#L42— single linehttps://watcher.exe.xyz/changes/{id}#L42-L57— rangeWhen the URL with a fragment is loaded, the page should:
Approach sketch
diff2html-ui renders each line as
.d2h-code-line/.d2h-code-side-linewith an associated line number cell. Two pieces:src/dashboard/static/js/diff-viewer.js): afterDiff2HtmlUI.draw(), walk the rendered DOM and addid="L{n}"to each line number cell. Add a small "copy link" affordance on hover (chip near the line number that copies${location.pathname}${location.search}#L{n}to clipboard).#L{a}-L{b}form. Standard pattern from GitHub.location.hash, scroll the line into view, apply a.d2h-line-anchor-targetclass for highlight.Acceptance
id="L{n}"afterDiff2HtmlUI.draw()/changes/{id}#L42scrolls to line 42 and visually highlights it/changes/{id}#L42-L57highlights the rangeoutputFormat: "side-by-side"and"line-by-line"Out of scope
localStorageDependencies
Blocked by #222. The original said "None — purely client-side enhancement on top of existing diff2html-ui mount." That is now backwards — there is no mount. Verified 2026-08-19:
/changes/{id}routechangesroute insrc/dashboard/routes/.diff-mount/data-unified-diff/#diff-contentin templatesdiff-viewer.jsDiff2HtmlUI.draw()ever runningEvery acceptance criterion below targets DOM that is not rendered. The chain is:
ChangeRevisionstores a fingerprint and nothing else, so there is no previous content to diff and therefore no diff page to anchor into.Note also #273 — the orphaned viewer assets (
diff2html-ui.min.js,diff-viewer.js, theinput.css.diff-mountoverrides) are proposed for deletion, recoverable from89d64eb. If that lands first, this issue's approach sketch needs re-vendoring diff2html as a step, and whoever restores it should confirm diff2html is still the right choice rather than restoring 3.4.52 reflexively.Related