Decision record

ADR-0007: Comment anchors are sections and tasks

Written by

ADR-0007: Comment anchors are sections and tasks, not editor ranges

Status: accepted · 2026-09-11

Context

The comment record has carried an anchor field since Phase 4, documented as a “ProseMirror/Yjs-relative range anchor; null = page-level thread”. Nothing ever wrote it. The web use case and the MCP add_comment tool both created page-level comments, so every comment on every page sat in one flat list at the bottom, and an approver reviewing a plan could not point at the task they meant.

Two things forced the question at once. A rejection carried no reason — the status moved and the submitter learned nothing — which made the review gate a wall rather than a conversation. And an agent that had published a brief over MCP needed to read what a person objected to and where, so it could change that part and resend, rather than guess.

The field’s original intent was the Google Docs model: a Yjs relative position that survives concurrent edits and lets a comment sit beside a sentence. That is the right model for prose reviewed by people in an editor. It is the wrong model for this product, for three reasons that do not go away with more engineering:

Decision

A comment anchors to a section, a task, or the page. Never to a range.

{ kind: 'section', anchor: 'the-decision' }
{ kind: 'task', index: 2, text: 'Author identity — …' }
null

These are the identifiers the product already uses to address parts of a page: update_section takes a heading anchor, set_task takes a task index or text prefix. A reviewer in the app and an agent over MCP are therefore pointing at the same thing with the same name. The task anchor stores both index and a text snapshot, the same pair set_task matches on, so a reordered list can still be re-matched.

An anchor is validated against the page as it is now, on write. A comment pointing at a heading or task that does not exist is refused, not silently demoted to page level: the reviewer meant a specific place.

Two things ride on the same change, because they are what the anchoring was for:

Alternatives rejected

Editor-range anchoring, as originally intended. Rejected for the three reasons in Context. The strongest version of the case for it — a comment on a specific sentence — is a real feature for prose, and it is not what a reviewer of a plan or brief needs, which is “this task” or “this section”.

Anchoring by line number. Trivially expressible, trivially broken by any edit above the line, and meaningless once the Markdown is rendered.

A separate review-note record on the page. Cleaner in the data model, worse for every reader: a person would read notes in one panel and comments in another, and an agent would call two tools to learn one thing.

Threads of arbitrary depth. Deeper nesting makes resolution ambiguous and buys nothing a plan review needs. One level is a conversation; two is a forum.

Consequences

Not decided here

Suggested edits — a reviewer proposing specific replacement text — are a different feature with a different data model, and the section anchor is probably its natural home. Nothing in this decision forecloses it.

References

Written and maintained by , who made this decision — about · GitHub.

← All decisions