feat: add Markdown Annotator (:MarkdownAnnotate)#743
Open
coderdevin wants to merge 52 commits intoiamcco:masterfrom
Open
feat: add Markdown Annotator (:MarkdownAnnotate)#743coderdevin wants to merge 52 commits intoiamcco:masterfrom
coderdevin wants to merge 52 commits intoiamcco:masterfrom
Conversation
Add a single-step :MarkdownAnnotate command that starts the mkdp server (if not running) and opens an annotator page in the browser. The annotator embeds the live preview in an iframe (same-origin) and allows highlighting text with optional notes. Annotations persist in localStorage and sync with live edits via the existing Socket.io infrastructure. - Add annotator.html to app/_static/ (served by existing /_static/* route) - Add open_annotator RPC notification through the full chain (plugin → rpc → attach → server) - Extract buildUrl/launchInBrowser helpers in server.js to share browser-launching logic between openBrowser and openAnnotator - Add <Plug>MarkdownAnnotate mapping and g:mkdp_open_annotator_on_start flag - Update README with annotator documentation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace dark navy theme with warm cream/beige palette and terracotta accents. Replace browser prompt() with inline note editor panel. Add toast notifications, annotation count badge, entrance animations, typographic curly quotes, and progressive disclosure on hover. Fonts: Source Serif 4 for annotation text, DM Sans for UI elements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move note editor from sidebar bottom to a floating popover that appears near the selected text or annotation item - Click on any annotation's note text to re-edit it in the popover - Annotations without notes show a "Click to add a note" hint on hover - Overlay backdrop to dismiss popover by clicking outside - Enter to save, Escape to cancel, Shift+Enter for newline Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…yboard shortcuts - Toolbar now shows Annotate (H) + Delete (D) with keyboard hints - Delete marks text with red strikethrough in preview + sidebar - Popover wider (420px), taller textarea, larger preview area - Copy All outputs structured AI-friendly format with numbered annotations, [HIGHLIGHT]/[DELETE] tags, blockquotes, and "Please review" prompt footer - Copy All button shows count: "Copy All (3)" - Type badges (Highlight/Delete) on each sidebar annotation - Keyboard shortcuts H/D work when toolbar visible (in both parent and iframe contexts) - Empty state updated with keyboard shortcut hints Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix bug: keyboard shortcuts (H/D) used stale DOM event for position; now uses lastSelectionPos saved during showToolbar - Extract TYPE_HIGHLIGHT/TYPE_DELETE constants, replacing 12+ raw strings - Deduplicate keyboard handler into shared handleToolbarKeydown function used by both parent and iframe document listeners - Fix TreeWalker using parent document instead of iframe document - Remove duplicate const doc declaration in findAndHighlight - Add change guard to updateCount to skip no-op DOM writes - Break long HIGHLIGHT_CSS string into readable array join Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reload button was calling contentWindow.location.reload() which could
fail, falling back to urlInput value which may be stale or incorrect.
Now always resets iframe src to the known /page/{bufnr} path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three annotation types: Modify (M), Add (A), Delete (D) - Modify: select text + note what to change → [MODIFY] in copy output - Add: select anchor text + note what to insert after → [ADD] - Delete: strikethrough + optional reason → [DELETE] Toolbar shows all three with keyboard shortcuts. Copy All uses AI-friendly labels: "Change to:", "Insert after:", "Reason:" Green highlight/badge for Add type, terracotta for Modify, red for Delete. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace ugly native confirm() popup with a styled confirmation dialog matching the warm editorial aesthetic. Includes backdrop overlay, slide-in animation, and red "Clear All" danger button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a selection spans multiple DOM elements (e.g. list items), surroundContents and the deleteContents fallback would break the HTML structure causing empty bullet points and layout corruption. Now wraps individual text nodes within the range instead of trying to wrap the entire range in one <mark>. Each text node gets its own <mark> with the same data-ann-id, and unwrapAllMarks removes all of them on annotation deletion. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add fork notice linking to upstream iamcco/markdown-preview.nvim - Update all install examples to use coderdevin/markdown-preview.nvim - Rewrite annotator section with Modify/Add/Delete types, keyboard shortcuts, and AI-friendly Copy All output format example Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Load and Reload did the same thing since the URL is auto-filled from the plugin. Simplify to just Reload. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Click on any text in the annotator preview and edit it directly.
Changes auto-save to the Vim buffer after 500ms pause or on blur.
- Add update_lines Socket.io handler in server.js that receives
line-level text replacements and writes them back to Vim buffer
via buffer.setLines()
- Enable contentEditable on iframe's .markdown-body
- Snapshot text content per data-source-line element on load
- Detect changes by diffing current text against snapshot
- Emit changes via iframe's existing Socket.io connection
- Green flash animation on saved elements
- Edit counter in topbar ("3 edits")
- Suppress MutationObserver refresh during active editing
- Both inline editing and annotations work simultaneously
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix buffer.setLines() to pass [newLine] array instead of bare string (nvim_buf_set_lines expects a list) - Use WeakMap keyed by DOM element for text snapshots, fixing key collisions when multiple elements share a data-source-line - Remove trim() from oldText/newText to preserve meaningful whitespace (indented code blocks, nested list items) - Remove dead saveFlash keyframes from parent CSS (only used in iframe) - Simplify isEditing flag — clear immediately after save, no setTimeout Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…arch Root cause: collectChanges sent full element textContent as oldText (e.g. "Hello world") but the markdown source has formatting markers (e.g. "Hello **world**"), so server's includes() check failed silently. Fix: compute minimal character-level diff between old and new text, sending only the changed substring (e.g. "wrold" → "world"). This matches the markdown source regardless of surrounding formatting. Also: server now searches up to 10 lines from data-source-line start, handling elements that span multiple markdown lines. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…r API The buffer.getLines/setLines approach was unreliable — textContent from rendered HTML doesn't match markdown source with formatting markers. Now reads/writes the .md file directly via fs, which is simpler and always works. Calls checktime to tell Vim to reload. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace readFileSync/writeFileSync with fs.promises.readFile/writeFile to avoid blocking the Node event loop. Remove TOCTOU existsSync check. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace vague "Please review" prompt with explicit instructions: - Tell AI exactly what each annotation type means - Ask for the complete revised document, not suggestions - "Do not summarize or skip sections" prevents lazy AI output Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Source pane is now hidden by default. A "Source" button in the topbar toggles it on/off. When hidden, preview takes full width. Button highlights with accent color when source pane is active. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix pre-existing bug: disconnect handler used .map() which replaced socket objects with booleans, breaking emitRefreshContent broadcasts - Remove second loadSourceFromServer at 1200ms that could wipe user edits typed between 500ms and 1200ms; guard first load with sourceContentSnapshot check - Replace inline style toggle with .btn-ghost.active CSS class Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename "Source" button to "Edit Source" for clarity - Source only loads when user clicks "Edit Source", not on page load - Add retry logic (up to 5 retries, 1s apart) if socket isn't ready - Remove eager load from iframe load handler Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change from "insert the new content after the quoted anchor text" to "write the new content according to the Insert after instruction" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The label "Insert after" implied a specific position. Changed to "Add" which lets the AI interpret the instruction freely. Also updated popover placeholder from "What to add after this text?" to "What to add here?" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
write_source and update_lines were calling emitRefreshContent before reply(), which could trigger an iframe reload that destroys the socket reference — causing the client's socketRequest callback to never fire (5s timeout → "save failed"). Now reply first, then refresh. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add aria-label to all unlabeled inputs/textareas and aria-hidden to decorative SVGs - Add focus-visible styles, focus traps for dialogs, aria-live regions - Replace hardcoded colors (#fff, #fcfbf9, #2b2f36, #a34845) with CSS custom properties - Add --bg-surface, --bg-surface-warm, --danger-hover, --font-mono tokens - Add responsive breakpoints (768px/480px) and touch target sizing (pointer:coarse) - Add prefers-reduced-motion media query - Fix layout thrashing: single reflow per batch instead of per-element - Upgrade easing to cubic-bezier(0.25, 0, 0, 1) for smoother deceleration Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…l Notes - Add copy icon button on each annotation item (appears on hover) - Extract shared formatAnnotation() and copyToClipboard() helpers - Rename "Copy All" to "Copy All Notes" for clarity Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add document-level annotation type for whole-document directives (e.g. "translate to Chinese", "fix grammar") without requiring text selection. Includes always-visible input with preset chips, G keyboard shortcut, purple/indigo visual identity, and structured Copy All output with Document Instructions section before selection annotations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The small inline input box for document instructions was cramped and hard to use. Replace it with a clickable label that opens a centered modal dialog with a textarea, preset chips, and proper cancel/save actions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Allow switching between markdown files in the same directory without re-running :MarkdownAnnotate. Adds a collapsible tree panel with keyboard shortcut (F) that lists .md files recursively. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
badd only creates the buffer without reading the file. Added bufload to ensure content is available when the preview iframe connects. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Convert --- frontmatter to fenced yaml code block in server before sending to preview, so it renders with syntax highlighting instead of being hidden or displayed as raw text. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…open badd with command escaping and bufnr path matching was fragile. bufadd() returns the buffer number directly and handles paths reliably. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
buildRefreshData now returns null when content is empty, so the retry loop keeps trying until the buffer is fully loaded after bufadd/bufload. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Files in subdirectories failed to preview because bufadd/bufload didn't reliably make content available. New preview_file event reads the file directly from disk and emits refresh_content to the existing socket connection, avoiding iframe navigation entirely. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract convertFrontmatter() to deduplicate frontmatter logic - Remove dead open_md_file handler and navigateToPreviewBufnr - Move state mutations after async success in switchToFile - Parallelize getVar calls with Promise.all in preview_file - Add path validation (absolute + .md extension) to preview_file Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d of literal replacements MODIFY, ADD, and DELETE annotations now treat user notes as direction/suggestions for the LLM rather than exact replacement text. DELETE also now opens a popover for user input instead of committing immediately. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat(annotator): annotation notes as rewrite guidance
doDeleteAction was bypassing the popover save flow by immediately clearing currentSelection before commitFromPopover could run. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Markdown Annotator feature — a browser-based review interface for markdown documents with three annotation types:
M) — select text + note what to changeA) — select anchor text + note what to insert after itD) — mark text for removal with optional reasonThe annotator opens via
:MarkdownAnnotate(single step — starts the server if not running) and embeds the live preview in an iframe (same-origin). Annotations persist in localStorage and sync with live Vim edits.Key features
prompt())M,A,Dwhen toolbar is visible[MODIFY]/[ADD]/[DELETE]tags — designed for the workflow: annotate → copy → paste to AIconfirm())Files changed
app/_static/annotator.html— new self-contained annotator page (served by existing/_static/*route)app/server.js—openAnnotator()+buildUrl()/launchInBrowser()helpersapp/lib/attach/index.js—open_annotatorRPC handlerautoload/mkdp/rpc.vim—mkdp#rpc#open_annotator()autoload/mkdp/util.vim—mkdp#util#open_annotator_page()plugin/mkdp.vim—:MarkdownAnnotatecommand +<Plug>MarkdownAnnotateREADME.md— annotator documentationCopy All output format (for AI)
Test plan
:MarkdownAnnotate— server starts + annotator opensM→ popover opens, add note, save → annotation in sidebarA→ popover with "What to add?" placeholderD→ immediate strikethrough highlight + toastconfirm():MarkdownPreviewstill works independently🤖 Generated with Claude Code