4. Annotate the PR description and comments, feeding the agent-feedback pipeline
Date: 2026-06-30
Status
Accepted
Context
The PR Overview panel shows the PR description and the comments timeline, but both are read-only. We want to select and comment on any part of the description (like the plan/annotate app), and to comment on individual PR comments, and have those notes flow into the feedback we send the agent — alongside the existing diff annotations.
Two annotation systems already exist:
- Code review (
CodeAnnotation) anchors to diff lines (file + line + side). It cannot anchor to prose. - Plan/annotate (
Annotation) anchors to selected text in rendered markdown, using web-highlighter (startMeta/endMeta+originalText). Its engine —useAnnotationHighlighter+AnnotationToolbar+CommentPopover+FloatingQuickLabelPicker— lives entirely inpackages/uiand has no plan-specific dependencies. It is already reused on a second surface (useHtmlAnnotation.ts, over an iframe), proving it is surface-agnostic.
The PR description/comments render through MarkdownBody in PRSummaryTab.tsx — a trimmed-down copy of our custom markdown engine that only handles 5 block types (heading, code, list-item, blockquote, hr) and has no real rendering for tables, HTML blocks, directives, or alerts. The full renderer (BlockRenderer + the blocks/ components) is the same custom parser (parseMarkdownToBlocks) with the complete component set, and it emits the data-block-id DOM the highlighter needs.
Decision
-
Use the prose engine, not
CodeAnnotation. Annotate the PR description/comments with the plan/annotate text-anchoredAnnotationsystem (useAnnotationHighlighter+AnnotationToolbar+CommentPopover+FloatingQuickLabelPicker). Reuse the hook + those three components — not the whole planViewer(it carries ~50 plan-only props). -
Upgrade the renderer. Replace the trimmed
MarkdownBodyfor PR prose with the full shared block renderer. Extract the block-dispatch out ofViewerinto a small sharedRenderedMarkdowncomponent so the plan viewer and the PR panels share one renderer (no duplication). This fixes HTML/table/alert rendering and makes the text annotatable in the same move. -
Two interaction styles — comment-only (intentionally simpler than plan/annotate).
- Description → select text and the comment box opens immediately. Comment-only: no toolbar, no quick-labels, no delete/redline picker. This maps to the highlighter's
commentmode (select →CommentPopoverdirectly). This is a deliberate divergence from plan/annotate, which shows a multi-option toolbar. - Each comment → a small "comment" button on the card that opens the same
CommentPopoverand attaches a note to that comment. No text-selection inside comment cards (avoids fighting the cards' click/collapse handlers).
- Description → select text and the comment box opens immediately. Comment-only: no toolbar, no quick-labels, no delete/redline picker. This maps to the highlighter's
-
One comment popover, with Ask AI. Use the existing shared
CommentPopover— the same component the code review uses for file comments. Wire its Ask AI action so you can ask AI about the selected description text (reusing the review editor's existing Ask AI backbone). No new popover. -
Separate store. Keep a new
Annotation[](prose) store, lifted toreview-editor/App.tsxand threaded viaReviewStateContext, distinct from the existingCodeAnnotation[]. The two models stay separate in memory. -
Show it in the Annotations sidebar under "PR description". Description comments render in the existing review Annotations sidebar, in their own "PR description" group, alongside diff comments — select, edit, and delete there, just like any annotation. The sidebar already renders a second annotation type (
editorAnnotations, with its own delete path), so this follows that established pattern rather than a novel merge. Delete/select therefore come "for free" from the sidebar — no separate affordance on the highlight. -
Feed the existing pipeline, no server change. Prose annotations count toward
totalAnnotationCount(so "Send Feedback" appears) and are appended tofeedbackMarkdownviaexportAnnotations(description) /exportMessageAnnotations(comments). Rides the existing/api/feedbackPOST — no new endpoints. -
Phasing. Description first (proves the full pipeline end to end — select → comment → sidebar → send), comments second.
Consequences
- The review editor will hold two annotation models at once (
CodeAnnotation[]for the diff,Annotation[]for prose). They stay separate in memory and are joined only at the presentation edges — the Annotations sidebar (a new "PR description" group, mirroring the existingeditorAnnotationsrender/delete pattern) and the exported feedback markdown. - Swapping the description renderer to
RenderedMarkdownwill shift the panel's spacing/styling; that needs to be matched back. - Prose annotations have no real file/line; the export labels them by source ("PR description" / "comment by @x") instead of
file:line.exportAnnotationsalready degrades gracefully without line numbers. - Extracting
RenderedMarkdowntouches the planViewer(it starts consuming the shared component). Low risk, but it is a shared-code change, not isolated to the review editor. - No server, endpoint, or Pi-runtime changes — the work is contained to
packages/ui(shared renderer + reused annotation components) andpackages/review-editor(state, wiring, the per-comment button). - The comment-card click conflict is designed out (button instead of selection), so it is not a runtime risk.