Anchored review comments in MarkEdit's preview.
Select text in the preview, start typing, and the comment attaches to what you selected. Comments are stored in the Markdown file itself, so an agent you are working with can read your review straight from the document.
Because the comments live in the Markdown, an agent can read a review and answer in the document. Replies are Markdown too, and render as such:
There is no standard for anchored comments in Markdown. There is a standard for
anchored annotations in general: the
W3C Web Annotation Data Model
(Recommendation, 2017), whose TextQuoteSelector identifies a passage by quoting
it — exact, plus the prefix and suffix that disambiguate it when the quoted
words repeat.
That is also, in substance, how GitHub anchors a pull request review comment: not
inside the file, but by path + line + side, alongside a saved copy of the
surrounding diff_hunk used to re-anchor the comment when the file moves on (and
to mark it outdated when it cannot).
This extension takes that model and puts it in the file:
- Should the ceiling apply to service accounts?
<!-- annotation
id=c3 author="reviewer" created="2026-08-27T16:52:00Z" line=39
exact="service accounts" prefix="Open questions Should the ceiling apply to " suffix="? What happens to a websocket"
They should be exempt — they have no interactive session.
-->
<!-- annotation
id=c4 author="claude" created="2026-08-27T16:58:00Z" reply-to=c3
Agreed. Exempt anything with `grant_type=client_credentials`, and keep the
**idle** timeout for interactive sessions only.
-->A reply carries reply-to and no selector of its own: it inherits the anchor of
the comment it answers.
Nothing is written inline. The highlight is re-derived on each render by finding the quote again.
The alternatives write into the prose, and that is where they break. Rendering every candidate through the same markdown-it configuration MarkEdit-preview uses, across twelve Markdown constructs:
| Format | Result |
|---|---|
CriticMarkup {==x==} |
Visible braces leak into the output in 12 of 12 constructs. Invisible only where a CriticMarkup processor is installed; on GitHub and in VS Code it is literal junk. |
Paired HTML comments <!--@c1-->x<!--/@c1--> |
Clean in 9 of 12. Breaks in fenced code, indented code and inline code, where it renders verbatim as const x = "<!--@c1-->brown fox<!--/@c1-->". |
| This format (nothing inline) | Cannot break any construct, because it never touches the content. |
No inline scheme can annotate code at all. This one can.
Placement matters too. A comment block is only inert when it sits at column zero between two top-level blocks: indented into a list it changes the paragraph structure, dropped between table rows it truncates the table, nested in a blockquote it mangles it. The extension always writes at column zero, with a blank line either side.
- MarkEdit — download
or
brew install --cask markedit. Developed and tested against 1.34; 1.29 or later is recommended, since that is wheregetDirectoryPath(used to derive the default comment author) arrived. - MarkEdit-preview — install from the extension registry. This extension draws into the preview pane that one creates, so it does nothing without it.
Download dist/markedit-comments.js, then:
mkdir -p ~/Library/Containers/app.cyan.markedit/Data/Documents/scripts
cp markedit-comments.js ~/Library/Containers/app.cyan.markedit/Data/Documents/scripts/Restart MarkEdit. That folder is MarkEdit's user-script directory; every .js
file in it is loaded at launch.
To open the folder in Finder instead:
open ~/Library/Containers/app.cyan.markedit/Data/Documents/scriptsRequires Node 20 or later.
git clone https://github.com/anchitrao/markedit-comments.git
cd markedit-comments
npm install
npm run build # builds, and copies the script into the scripts folder for you
npm run reload # quit and relaunch MarkEditOpen a Markdown file, switch to the preview (Shift-Command-V), and look for
Comments under the Extensions menu. Then select a few words in the preview —
a composer should open under them.
If the menu is missing, confirm the file is in the scripts folder and that MarkEdit was restarted; scripts are read only at launch.
rm ~/Library/Containers/app.cyan.markedit/Data/Documents/scripts/markedit-comments.jsRestart MarkEdit. Comments already written stay in your Markdown files as inert HTML comments; they will simply stop being drawn.
- Comment — select text in the preview and start typing. Return saves, Shift-Return adds a line, Escape cancels.
- Open a thread — click a highlight. Reply, resolve, or delete from there. Deleting a comment deletes its replies.
- See every thread at once —
Extensions ▸ Comments ▸ Show Comments Sidebar(Shift-Command-\). A rail beside the document holds one card per thread, each level with the text it annotates and moving with it as you scroll. Click a card to focus it and reveal its reply box; click a highlight to jump to its card. Set"sidebar": trueto have it open by default. - Navigate —
Extensions ▸ Comments ▸ Next / Previous Comment. - Hand the review to an agent — the comments are in the file, so "read my
comments in
notes.md" is enough.Copy All Commentsputs them on the clipboard as plain text when you would rather paste than point at a path.
Highlight colors are taken from the editor theme you have set — the extension measures the theme's own search-match and accent colors rather than hard-coding its own, and re-measures when you change theme or when the system switches between light and dark.
If the quoted text is edited away, the comment is not lost. It falls back to the block it was written against, is drawn as an underline instead of a highlight, and is badged outdated in its thread — the same thing GitHub does with a review comment whose diff has moved. The recorded selector stays in the file, so the comment still says what it was about.
In MarkEdit's settings.json:
{
"extension.markeditComments": {
"author": "anchit.rao",
"openOnSelect": true,
"showResolved": true,
"sidebar": false
}
}author— name recorded on new comments. Defaults to your account name. Set it to distinguish a reviewer, or an agent, from you.openOnSelect— open the composer as soon as text is selected. Set tofalseto use onlyComments ▸ Comment on Selection.showResolved— keep drawing resolved comments in the preview.sidebar— open the comments rail when a document opens. The toggle is remembered per machine once you use it.
An annotation is an HTML comment block. Attributes run from the opening line to the first blank line; everything after it is the comment body, which may contain blank lines of its own.
| Attribute | Meaning |
|---|---|
id |
Identifier, unique within the document. |
exact |
The quoted text, whitespace-normalized. |
prefix, suffix |
Surrounding context, used to pick the right occurrence. |
line |
Source line when written. A hint for tie-breaking; not authoritative. |
author, created |
Who wrote it, and when (ISO 8601). |
reply-to |
Present on replies; the id being answered. Replies carry no selector. |
resolved |
true when resolved. |
Quoted values are JSON strings, so quotes, backslashes and pipes round-trip
unchanged. A literal --> inside a body is escaped as --\>.
See example/sample-with-comments.md.
The comments are plain text in the file. To act on a review:
- Read the document.
- Each
<!-- annotation ... -->block is one comment.exactis the text it refers to; the body is the request. - Blocks with
reply-toare replies to the block with thatid. - Blocks with
resolved=trueare already dealt with. - After addressing one, either delete its block or add a reply block with
reply-toset — both are valid, and MarkEdit will re-render either way.
Editing the quoted text will mark the comment outdated rather than orphan it, so it is safe to revise the prose first and clean up the comments afterwards.
npm test # format, anchoring and placement tests
npm run lint
npm run typecheck
npm run buildAnchoring is tested against markdown actually rendered by markdown-it in the same configuration the preview uses, and placement is tested against an in-memory document, because those are the two places where a bug would corrupt a file.
MIT

