Deep-linkable comment anchors on issues and proposals
Overview
Clicking a comment's timestamp should navigate to a stable URL that scrolls directly to that comment and highlights it. This is standard behavior on GitHub, Linear, and most modern issue trackers. Without it, sharing a specific comment requires copy-pasting prose context — the link is always to the top of the page.
Phase 1 — Audit current comment rendering
Before writing any code, answer these questions by reading the templates and routes:
- What is the current HTML structure of a rendered comment? Does each comment
<div>have anidattribute? - What does the timestamp element look like — is it a
<span>,<time>, or<a>? - Do issue comments and proposal comments share a template fragment, or are they separate?
- What is the
comment_idformat? (SHA256 prefix, integer, UUID?) - Does the router already handle fragment URLs (
/issues/42#comment-abc123) or does it strip the fragment server-side?
Produce a written audit. Do not write any code in this phase.
Phase 2 — Anchor IDs on every comment
Add a stable id attribute to each rendered comment container. Convention:
<div class="comment" id="comment-{{ comment.comment_id }}">
Rules:
- ID must be derived from
comment_idonly — never from position, timestamp, or author, which can change - Must be present on initial page load (no JS required to generate it)
- Must work for both issue comments and proposal comments
- Must survive template refactors — the id lives on the outermost comment element, not a nested child
Phase 3 — Timestamp becomes a deep link
Replace the comment timestamp element with a self-referencing anchor:
<a href="#comment-{{ comment.comment_id }}" class="comment-ts-link" title="Link to this comment">
<time datetime="{{ comment.created_at }}">{{ comment.created_at | timeago }}</time>
</a>
Behavior:
- Clicking the timestamp updates the URL hash and scrolls the comment into view (native browser
#fragmentbehavior — no JS needed) - Hovering reveals a subtle link cursor and a copy-link affordance (CSS only, no JS)
- The link is unobtrusive — it must not look like a navigation link at rest, only on hover
CSS for the hover affordance:
.comment-ts-link {
color: inherit;
text-decoration: none;
&:hover {
text-decoration: underline;
text-decoration-style: dotted;
&::after {
content: ' #';
opacity: 0.4;
}
}
}
Phase 4 — Scroll-to and highlight on page load
When a page loads with a #comment-<id> fragment, the targeted comment should be visually distinguished so it is immediately obvious which comment was linked to.
CSS approach (no JS, uses :target pseudo-class):
.comment:target {
background: var(--bg-elevated);
border-left: 3px solid var(--color-accent);
border-radius: 4px;
transition: background 0.3s ease;
}
Scroll behavior (CSS only):
:root {
scroll-behavior: smooth;
}
The :target highlight must:
- Activate on page load when the fragment matches (no JS)
- Not persist after the user scrolls away and clicks elsewhere
- Work in both light and dark themes (use CSS variables, not hardcoded colors)
Phase 5 — Copy-link button (optional JS enhancement)
Progressive enhancement on top of Phases 2–4. Add a copy icon that appears on comment hover and copies the full absolute URL (not just the fragment) to the clipboard.
<button class="comment-copy-link" data-href="{{ request.url }}#comment-{{ comment.comment_id }}" aria-label="Copy link to comment">
{{ icon('link', 12) }}
</button>
document.querySelectorAll('.comment-copy-link').forEach(btn => {
btn.addEventListener('click', () => {
navigator.clipboard.writeText(btn.dataset.href);
btn.classList.add('comment-copy-link--copied');
setTimeout(() => btn.classList.remove('comment-copy-link--copied'), 1500);
});
});
Visual feedback: icon swaps to a checkmark for 1.5s after copy. No toast, no modal — the button itself is the feedback.
This phase is gated on Phases 2–4 being shipped and stable. Do not implement until the anchor + timestamp link behavior is confirmed working end-to-end.
Acceptance criteria
[ ] Every comment has a stable id="comment-<comment_id>" on its container
[ ] Clicking the timestamp updates the URL to #comment-<id> and scrolls to the comment
[ ] Page loaded with #comment-<id> in the URL highlights the target comment
[ ] Works on issue detail page
[ ] Works on proposal detail page
[ ] No JavaScript required for Phases 2–4
[ ] Timestamp link is visually subtle at rest, reveals affordance on hover
[ ] :target highlight uses CSS variables (theme-safe)
[ ] Copy-link button (Phase 5) copies full absolute URL to clipboard