Skip to content

vscode: add review comments from the markdown preview pane (hover-+ per block, no editor mode-switch) #859

Description

@amrmelsayed

Problem

Today the only way to add a review comment to a spec/plan is via the Comments API gutter + in the text editor (packages/vscode/src/comments/plan-review.ts). For long spec/plan documents — which is the typical case, not the exception — raw markdown is significantly harder to read than the rendered preview. The architect's review loop forces a mode-switch every time:

  1. Read in preview (Cmd+K V) — easy
  2. Find the paragraph to comment on
  3. Switch to source editor — context-switch
  4. Find the same paragraph in raw .md — re-find
  5. Click gutter +, type comment, submit
  6. Switch back to preview to continue reading

For a spec with 5+ review comments, that's 10+ context switches. The preview is the natural reading surface for prose-heavy artifacts; the editor is the natural authoring surface. Comments cross the boundary.

Proposal

Add the add-comment affordance directly to the markdown preview pane. v1 scope is authoring only — no thread rendering in the preview. The architect reads in the preview, hovers a paragraph or heading, clicks +, types in an input box, submits. Existing comments still surface in the source editor (architect switches to editor only when they want to see/reply/delete existing threads — uncommon during a first-pass read).

This is deliberately scoped narrow: we add one feature (add) to the preview, leave the other three (read existing threads, reply, delete) where they already live.

Why this works (and the one real limitation)

VS Code's built-in markdown preview is a webview. Extensions can extend it via three contribution points:

Contribution point What it enables
contributes.markdown.previewScripts Inject JavaScript that runs inside the preview webview
contributes.markdown.previewStyles Inject custom CSS into the preview
contributes.markdown.markdownItPlugins Register a markdown-it plugin during AST → HTML rendering

One real limitation: vscode.comments.createCommentController itself doesn't attach to the preview — the Comments API only works on TextEditor surfaces. So we can't reuse the Comments API thread UI in the preview. For authoring only, this is fine — we use a simple vscode.window.showInputBox for the comment text and call into the existing file-write path.

Implementation sketch

1. Preview-side script (new file)

packages/vscode/src/markdown-preview/add-comment.js (loaded via previewScripts):

// On hover over any block element with data-source-line, show a "+" affordance.
// On click, postMessage back to the extension host with the source line.
document.addEventListener('mouseover', (e) => {
  const block = e.target.closest('[data-line]');
  if (!block) return;
  showAddCommentButton(block);
});

function onAddCommentClick(sourceLine) {
  acquireVsCodeApi().postMessage({
    type: 'codev:addComment',
    sourceLine,
  });
}

Markdown-it already emits data-line source-line attributes on block elements when the preview is built (VS Code's own preview uses these for "sync editor to preview" / "sync preview to editor"). We piggyback on the same attribution — no need to add our own.

2. Preview-side styles

packages/vscode/src/markdown-preview/add-comment.css (loaded via previewStyles):

  • Hide the + until hover
  • Position in the gutter or top-right of each block element
  • Use VS Code theme variables (--vscode-textLink-foreground, etc.) for color consistency

3. Extension-host listener

Listen for the postMessage via the markdown preview message-receiver API (vscode.workspace.onDidReceiveMessage is not a thing — actual API path is through markdown.api.v1 if available, or by attaching to the active preview webview's onDidReceiveMessage; verify at implementation time which entry point is current).

On receipt:

const text = await vscode.window.showInputBox({
  prompt: 'Add review comment',
  placeHolder: 'Type your review comment, then Enter to submit',
});
if (!text) return;
await writeReviewMarkerAt(document.uri, sourceLine, text);

writeReviewMarkerAt is a refactor of the existing submitReviewComment in plan-review.ts — extract the file-mutation part so both the editor-side Comments API path and the preview-side path can call it.

4. package.json contribution

"contributes": {
  "markdown.previewScripts": ["./out/markdown-preview/add-comment.js"],
  "markdown.previewStyles": ["./out/markdown-preview/add-comment.css"]
}

These run only for markdown previews, period. No flag, no toggle — purely additive UI affordance gated by hover.

5. Path gating

Limit the + affordance to files matching the same ELIGIBLE_PATH_REGEX from plan-review.ts (/\/codev\/(plans|specs|reviews)\// after #857's regex extension). Other markdown files get the standard preview with no +. Path comes through to the preview script via the existing markdown preview message protocol (the preview already knows the source document URI).

Acceptance criteria

  • Hovering any block element (paragraph, heading, list item, code block) in the markdown preview for a codev/(plans|specs|reviews)/*.md file shows a + affordance
  • Clicking the + opens an InputBox for the comment text
  • Submitting the input writes <!-- REVIEW(@<author>): <text> --> to the source file on the line after the block element clicked (matching the existing editor-side semantics)
  • Author identity matches whatever convention vscode: spec/plan review comments — polish pass (placeholders, reviews/ coverage, author identity, panel discoverability) #857 lands (git config user.name, falling back to architect)
  • No + appears on .md files outside the eligible-path set
  • No regression to the editor-side Comments API path
  • Source-line attribution uses markdown-it's existing data-line attributes — no fork or custom plugin needed for this part
  • writeReviewMarkerAt shared between the editor-side and preview-side submit paths (one mutation function, two entry points)

Out of scope (v1)

  • Rendering existing REVIEW threads inside the preview. Architect switches to the source editor to read/reply/delete existing threads. If this turns out to feel like the bigger friction, that's a Phase 2 (a markdown-it plugin can transform existing REVIEW markers into styled inline call-out HTML).
  • Reply / thread support. Replies are a separate design call (storage format; canReply flag) — handled in the broader Comments-API-side work, not here.
  • Resolve state. Same — separate design call, applies to both editor- and preview-side authoring.
  • Inline diff view of edits to a comment. Not relevant for v1.
  • Touch / mobile support. Hover-triggered affordance assumes a pointing device.

Why this is high leverage

For long documents — which is the typical case for codev specs/plans — the rendered preview is genuinely the right reading surface. Bringing the add-comment action to where the architect is already looking removes 50% of the friction in a review pass. The implementation is well-scoped (three contribution points, one shared mutation function, no Comments API rework) and v1 is intentionally minimal: add-only, no thread rendering. That keeps the PR small and the design decisions deferred (replies, resolve, threading) until the simpler primitive is in users' hands.

Related

Source-line attribution detail (for the implementer)

VS Code's built-in markdown preview uses markdown-it with the linkify, breaks, and source-map options. Block-level tokens carry a map: [startLine, endLine] that gets emitted to HTML as data-line="<startLine>" on the corresponding element. This is how the built-in "sync editor with preview" and "sync preview with editor" features work. We reuse the same attribute — no need to write a custom markdown-it plugin for source-line attribution.

If a future version of VS Code changes this attribute name or removes it, the fallback is to write our own markdown-it plugin that emits a custom data-codev-line attribute. Cheap to add later if needed; not needed for v1.

Metadata

Metadata

Assignees

Labels

area/vscodeArea: VS Code extension

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions