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:
- Read in preview (
Cmd+K V) — easy
- Find the paragraph to comment on
- Switch to source editor — context-switch
- Find the same paragraph in raw
.md — re-find
- Click gutter
+, type comment, submit
- 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
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
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.
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:Cmd+K V) — easy.md— re-find+, type comment, submitFor 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:
contributes.markdown.previewScriptscontributes.markdown.previewStylescontributes.markdown.markdownItPluginsOne real limitation:
vscode.comments.createCommentControlleritself doesn't attach to the preview — the Comments API only works onTextEditorsurfaces. So we can't reuse the Comments API thread UI in the preview. For authoring only, this is fine — we use a simplevscode.window.showInputBoxfor 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 viapreviewScripts):Markdown-it already emits
data-linesource-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 viapreviewStyles):+until hover--vscode-textLink-foreground, etc.) for color consistency3. Extension-host listener
Listen for the postMessage via the markdown preview message-receiver API (
vscode.workspace.onDidReceiveMessageis not a thing — actual API path is throughmarkdown.api.v1if available, or by attaching to the active preview webview's onDidReceiveMessage; verify at implementation time which entry point is current).On receipt:
writeReviewMarkerAtis a refactor of the existingsubmitReviewCommentinplan-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
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 sameELIGIBLE_PATH_REGEXfromplan-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
codev/(plans|specs|reviews)/*.mdfile shows a+affordance+opens anInputBoxfor the comment text<!-- REVIEW(@<author>): <text> -->to the source file on the line after the block element clicked (matching the existing editor-side semantics)architect)+appears on.mdfiles outside the eligible-path setdata-lineattributes — no fork or custom plugin needed for this partwriteReviewMarkerAtshared between the editor-side and preview-side submit paths (one mutation function, two entry points)Out of scope (v1)
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
packages/vscode/src/comments/plan-review.ts— existing editor-side Comments API wiring; this issue adds a parallel authoring surface, doesn't replace itviewSpecFile/viewReviewFilecommands; orthogonal but referenced for contextviewSpecDiff/viewPlanDiff; long-doc review companion (this issue is "comment on the doc you're reading", vscode: viewSpecDiff / viewPlanDiff — side-by-side diff of current vs prior revision for spec/plan files #858 is "see what changed since last review")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 amap: [startLine, endLine]that gets emitted to HTML asdata-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-lineattribute. Cheap to add later if needed; not needed for v1.