Comment on any HTML,
hand it back to your agent
A code review for your AI's plans and reports. Select any paragraph, table cell, chart, or diagram in a standalone HTML and comment inline exactly where you mean it, then hit Copy all to hand the agent a structured bundle of every note.
The review loop, in four steps
Turn any HTML plan or report into something you comment on inline and round-trip back to the agent - a tight review loop instead of describing changes in prose.
Select
Highlight any paragraph, table cell, chart, diagram node, or code block.
Comment inline
Leave a note in the window that opens right on your selection.
Copy all
Hand the agent one structured bundle of every open comment.
Reload
The agent edits and marks notes handled; handled comments prune away.
Why Commentable HTML
HTML has become the de-facto standard for planning and reporting with AI agents: plans, reports, design docs, and code reviews increasingly arrive as HTML, not Markdown. It is a spatial, richer medium - diffs and call-graphs as boxes and arrows, data as real charts, long documents as navigable sections. (Anthropic makes the same case in "The unreasonable effectiveness of HTML".) But the moment you want to revise that artifact the loop breaks down: you alt-tab between the render and the chat and describe in prose what to change. The richer the document, the more painful the round-trip.
Commentable HTML closes that loop - it is a code review for your plan. Select any paragraph, table cell, chart, diagram, or code block and comment in place, exactly where you mean it, then one Copy all hands the agent a structured bundle of every note. It drastically shortens the AI planning and iteration loop: you stay in the document and return to the chat only at the end.
Chat, Markdown, HTML - or a tight loop?
Your agent can draft a brilliant plan in seconds. The real bottleneck is everything after - reading it, reacting to it, and getting your changes back into the model. Where that review happens decides how fast you actually move.
| Where you plan | Handles a big plan | Visual richness | Interactive | In-place review | Review loop |
|---|---|---|---|---|---|
| Chat / terminal | Poor | None | No | No | Painful |
| Markdown file | OK / Need viewer | Limited | No | No | Manual |
| Plain HTML | Good | Rich | Yes | No | Out-of-band |
| Commentable HTML | Good | Rich | Yes | Yes | Tight |
- In the chat window. The plan scrolls past in a terminal or chat app and is gone. Long plans are a chore to navigate and hard to keep or share - great for a sentence, hopeless for a spec.
- In a Markdown file. It survives, but raw Markdown is a slog to read and even a viewer stays flat: no real tables, diagrams, or charts. Few teammates read a 300-line
.md. - In plain HTML. Dense, structured, and nice to look at. But review still breaks: you read in the browser, then switch to the chat and describe, in words, what to change and where. Out-of-band and slow.
- With Commentable HTML. An even richer HTML - plus review in place. Comment on the exact sentence, table cell, chart, or diagram node; one Copy all hands the agent every note. The loop collapses to a single tight cycle.
Install
Works with both Claude Code and the GitHub Copilot CLI, and is invokable from each agent's CLI and Desktop app. Pick your agent, add the marketplace, then install the plugin.
copilot plugin marketplace add https://github.com/urikanonov/ai-marketplace
copilot plugin install commentable-html@urikan-ai-marketplace
claude plugin marketplace add https://github.com/urikanonov/ai-marketplace
claude plugin install commentable-html@urikan-ai-marketplace
- Download the ZIP above.
- In Claude (Desktop or claude.ai), open Settings > Features and enable Skills (Pro, Max, Team, or Enterprise with code execution).
- Upload the ZIP to add the
commentable-htmlskill, then invoke it in any chat.
plugin update.
What you get
A full code-review layer that travels with the document; export to one portable HTML file when you want to share.
Comment on anything
Prose, table cells, code blocks, KQL queries, charts, images, and mermaid diagram nodes are all selectable and commentable.
Rich content, not just prose
Author Chart.js charts, Mermaid diagrams, drag-and-drop triage boards, rendered code diffs (inline or side-by-side), and syntax-highlighted code and KQL snippets - all first-class, and every one of them individually commentable.
Self-contained when you share
By default the layer loads from small companion files for cheap iteration. One click inlines the CSS, review UI, and every comment into a single HTML file you can email, archive, or reopen anywhere - no server and no build step.
Round-trip to the agent
One Copy all hands the agent every comment at once - a Markdown bundle plus a machine-readable id list - so it makes a single coordinated, coherent edit across all your notes instead of a one-at-a-time pass, then marks them handled.
Export as Portable or Offline
Export as Portable inlines the review layer and comments so a review travels in one file. Export Offline goes further: it snapshots the mermaid diagrams and charts and strips every remote loader, so the file opens with no network at all.
Handled comments stay gone
Once the agent marks a comment handled, it is pruned on reload. The HTML file is the single source of truth, not the browser cache.
Comments survive a restart
While you iterate, every comment is saved to your browser's localStorage, so closing the tab, restarting the browser, or rebooting your machine never loses your work - reopen the file and your comments are still there.
Deterministic tools
Ships stdlib-only Python tools and a strict validator so generated documents are correct by construction, not by luck.
Commentable decks
Turn a request into an animation-rich, fixed 16:9 slide deck that is also a commentable document: present it full-screen, toggle comment mode to leave notes on the live slides, navigate with the slide bar or arrow keys, and Export Offline for a network-silent handoff.
Deck engine: a curated, vendored subset of frontend-slides by Zara Zhang (MIT). See Credits.
Navigate long documents
A live table of contents, collapsible sections, and one-click jump from any comment straight to its highlight keep even a long report easy to move through.
No extension, runs everywhere
Opening a generated file to read or comment needs no browser extension, plugin, or install - the whole review layer is plain HTML, CSS, and JavaScript inside the file, so anyone you share it with opens it in any modern browser on Windows, macOS, and Linux. (Authoring new documents uses the commentable-html plugin.)
Only as big as it needs to be
Mermaid and Chart.js travel inside the file so diagrams and charts still render offline - but only in a document that actually has one. A prose-and-code review document drops that 1.3 MB renderer entirely, which is about 59 percent of its size, and what remains is kept out of the head so the top of the file stays readable to ordinary tools.
How the review loop works
Feedback goes back to the agent as structured data, not pasted prose.
Self review
Generate
The agent produces a commentable HTML report.
Comment
You select text and leave inline notes in the sidebar.
Copy all
Hand the agent a Markdown bundle of every open comment.
Reload
The agent acts and marks ids handled; handled comments prune away.
Peer review
Self review first
Run the self-review loop to get the artifact in good shape.
Export as Portable
Download one self-contained copy and share it with a peer.
Peer comments
The peer opens it, adds inline comments, and Exports as Portable again - comments now embedded in the HTML.
Back to the agent
You open their Portable file and Copy all to feed their feedback to the agent.
Review someone's plan
Receive a plan
A colleague sends a plan or report as markdown or HTML.
Convert
Ask the agent to turn it into a commentable HTML with this skill.
Review inline
Add your comments in place, on exactly the parts you mean.
Export and send back
Export as Portable and return it with your comments embedded.
Demo
Real commentable examples, including the flagship showcase deck. Select any text and the Add comment popup appears.
Private by design
Your document and every comment stay on your machine. There is no server, no account, no sign-in, and no telemetry - the review layer runs entirely in your browser as a static file.
Your data stays local
Comments live in one of two places only: your browser's localStorage, scoped to that file, while you iterate, or embedded inside the HTML file itself once you Export. Your document text and your notes are never uploaded or transmitted.
Nothing is sent externally
Your content and comments are never sent to us or to any service. They travel only when you choose to - when you share the exported file, or paste the Copy all bundle to your agent yourself.
You own every copy
The HTML file is the single source of truth, not a cloud account. Keep it, archive it, or delete it and the data is gone - no residual copy lives anywhere else. Comments are keyed to a stable id baked into the document, so when it is served from one web origin, moving or renaming it there keeps your browser-side notes; and an exported file always carries every comment inside it, so a shared or archived copy never depends on any browser store.
Offline and compliance-ready
The only optional network use is loading the Mermaid and Chart.js rendering libraries from a public CDN (library code, not your data). Export Offline snapshots those visuals and strips every remote loader, so the file opens with zero network - fit for air-gapped, sensitive, or regulated material.
Three portability modes
Same document, three ways to assemble it - pick based on whether it stays local, travels, or must work with no network. The graph in each card shows where every part comes from.
Non-portable default
References shared companion assets instead of inlining them, so each regeneration is far cheaper in tokens. Best for fast, personal iteration while you and the agent shape the document.
- Styles + runtime
- skill folder
- Mermaid + charts
- CDN
- Comments
- browser storage
Portable
One self-contained file with the CSS, runtime, and comments inlined. Best when you want to share the document with a peer or keep it as a durable, long-term artifact - one click via Export as Portable.
- Styles + runtime
- inlined
- Mermaid + charts
- CDN
- Comments
- seeded from HTMLthen browser storage
Offline
Everything Portable does, plus the mermaid diagrams and charts are rendered and inlined as snapshots and every remote loader is stripped, so the file opens with zero network - archive it or read it on a plane. One click via Export Offline.
- Styles + runtime
- inlined
- Mermaid + charts
- inlined snapshots
- Comments
- seeded from HTMLthen browser storage
CDN marks the parts that load over the network. A Non-portable or Portable report that uses mermaid diagrams or charts therefore needs an internet connection to render them - static images and the rest of the review layer still work with no network. Export Offline to inline those diagrams and charts as snapshots and drop the network dependency entirely.
Credits
The open-source projects this plugin builds on.
Deck engine: frontend-slides
The built-in commentable-decks capability is powered by a curated, hardened, vendored subset of the frontend-slides skill by Zara Zhang, used under the MIT License (© 2025 Zara Zhang). The upstream deploy and PDF-export scripts are excluded, and a CI gate keeps the vendored subtree pristine.
Changelog
Changes to Commentable HTML. See the full changelog on GitHub.
[1.264.0] - 2026-07-28
- A wide table no longer breaks words mid-token. Table cells used
overflow-wrap: anywhere, which participates in intrinsic (min-content) sizing, so every cell reported a min-content width of roughly one character and the table layout algorithm collapsed a column to that and shredded its text even while other columns still had spare room - a 14-column table renderedNAMESPACE MOVEasNAMES/PACE/MOVE. Cells now useoverflow-wrap: break-word, which breaks identically once a line is being laid out but is ignored for min-content, so a cell keeps its longest-word width and is only broken when there is genuinely nowhere left to go. - A table that is genuinely too wide for the page now scrolls horizontally inside its own box instead of pushing the whole document sideways. This is the other half of the fix above:
break-wordis ignored for min-content sizing, which is exactly what stops a column being shredded, but it also means a table whose columns cannot fit reports a min-content width larger than its container and escapes it. Every table is therefore rendered inside a.cmh-table-scrollbox - the containment narrow screens already had, now at every width. The wrapper is a real element rather thandisplay: block; overflow-x: autoon the table itself, becausedisplay: blockwraps the rows in an anonymous table box that shrink-to-fits and collapsed a narrow table's columns to their content width (a 2-column table measured 99px instead of 400px) - which also means narrow screens no longer suffer that collapse. A wrapper that actually scrolls is keyboard-focusable and labelled, so it can be scrolled without a mouse, while a table that fits adds no tab stop. A table built at runtime by an author script is contained too, and a table inside a flex or grid parent keeps its width and its placement. A focused scroll box owns the arrow keys in a deck, so its clipped columns stay reachable by keyboard instead of the arrows changing slides. Printing is unaffected: the wrapper reverts tooverflow: visibleso a wide table is never clipped out of a PDF.
[1.262.0] - 2026-07-28
- A document now carries the vendored rich-libraries payload (the gzip+base64 mermaid and Chart.js bundle the Offline export inlines) only when its content actually uses a diagram or a chart, and never in the document head. It used to be stamped into every document unconditionally: measured on the shipped examples it is 1,363 KB, 55 to 61 percent of a 2.3 MB file, and it sat on line 7 - so a prose-and-code review document paid for a renderer it could never call, and any tool that reads the head of a file hit a megabyte-long line immediately. A prose report now goes from 2,301 KB to 938 KB (59 percent smaller) and its longest line from 1,396,078 characters to 631.
- The decision is re-evaluated on every finalize rather than made once when the document is created, so a document that GAINS a diagram after the payload was dropped gets it back and its Offline export keeps working. A document whose content region cannot be located is left alone entirely - never stripped, and never grown.
Show 5 older releases
[1.261.0] - 2026-07-27
finalizenow reads the document once, threads it through the phase transforms in memory, and writes once. Each phase previously re-read and re-wrote the whole file, so a 1.4 - 2.5 MB document paid a read/write pair plus an independent full-document parse per phase - and becausecontent_replacefinalizes on every write-back, the agent edit loop paid that on every iteration. The phase order and every transform are unchanged, so the output is byte-identical. The per-phase entry points are REMOVED rather than kept: nothing called them once the pipeline threaded the document in memory, and leaving them would let a caller silently reintroduce the per-phase I/O this removes.- Validation and the validated provenance stamp now work on that same in-memory document, so a clean
finalize --strictrun costs one read and one write end to end, instead of re-reading the file to validate it and then re-reading and re-writing it to stamp it.validate.validate()takes an optionalhtml=argument for callers that already hold the document.
- A quadratic scan in the KQL tokenizer. Deciding whether an identifier was a function call sliced the entire remaining query on every token, so cost grew with the square of the query length. It is now a bounded lookahead. A newline before the paren still does not make a call, so highlighted output is unchanged.
[1.260.0] - 2026-07-27
- A diff labelled
config.xml,page.htmlormain.dartnow highlights._EXT_LANGhad no entry for those extensions, so extension inference resolved to no language and the diff rendered monochrome unless the author passeddata-diff-langexplicitly - even though both highlighters fully support them. A guard test now fails when any supported language has an obvious extension the table does not map, so the next language cannot ship with the same hole (CMH-HL-13). - The runtime
sqlfamily accepts double-quoted strings, matching its author-time config (string_stylesissql_singleplusdouble). A double-quoted SQL identifier previously rendered as a string when baked and as plain text at runtime. The other dedicated families' string styles are now spot-checked against their configs by the same guard (CMH-HL-15).
- Runtime keyword lookup is per LANGUAGE rather than one broad bucket shared by 23 languages. The
hashandcfamilies shared a single approximate set, which both over-colored (a lowercasetruein Python,true/false/nullin R,boolin Objective-C) and under-colored (Python's capitalizedTrue/False/Nonenever matched the case-sensitive lookup). Measured cost of splitting, on the builtcommentable-html.jsrather than on the raw keyword text: 9,346 bytes (736,669 -> 746,015, about 1.27 percent), against a divergence visible in every Python block. Each set is compared to its author-time config by test, so a future drift fails there instead of shipping as a block that renders one way baked and another way live (CMH-HL-14).
[1.259.0] - 2026-07-27
- Editing a KQL query no longer leaves its Run in Azure Data Explorer button executing the PRE-EDIT query. The query is encoded inside the link's href, so re-highlighting only the code left a stale payload behind - silently, since nothing validated it.
refresh_block()now recovers the cluster and database from the existing link and rebuilds both the code and the href, preserving the figure frame, caption and cluster copy affordance; the agent edit loop applies it to every KQL figure it writes back (CMH-KQL-10). - The validator classifies a code block through the shared strict scanner instead of probing for the
cmh-code-substring, so a nested, malformed or hand-edited span is reported rather than silently accepted as highlighted (CMH-HL-12).
language-kusto/language-kqlblocks are baked by the same document highlight pass as every other language rather than being skipped. Only the dispatch and the emission are shared - KQL keeps its own tokenizer andcmh-kql-*class vocabulary - so output bytes are unchanged, and the agent edit loop can now serve KQL blocks like any other code (CMH-KQL-09).
[1.258.0] - 2026-07-27
- Content-scoped editing for the agent review loop.
tools/authoring/content_extract.pyprints ONLY the fragment between the CONTENT markers - the 0.1 to 1.3 percent of a document an agent actually edits - instead of making it read a 1.4 to 2.5 MB file, and hands back de-highlighted SOURCE so the agent never has to hand-maintain token spans. A code block the highlight inverse refuses (hand-written markup inside a<pre><code>) is passed through verbatim, so the loop never stalls on the very blocks it exists to repair (CMH-CONTENT-01). tools/authoring/content_replace.pywrites a fragment back as ONE atomic transaction: check, swap, re-bake (typography, section cards, highlighting, an existing TOC), strict-validate, re-stamp the content-bound validated hash, and optionally mark Copy-all ids handled. Either every step succeeds or the original file is left byte-for-byte unchanged with no temporary file surviving - there is no half-finished state and no follow-up step to remember. A replacement that changes nothing does not touch the file at all (CMH-CONTENT-02).- A content edit is LOCAL: a section the agent did not touch keeps a byte-identical hash, so its Mark-reviewed marker survives, while an edited section hashes differently (CMH-CONTENT-03).
tools/authoring/extract_comments.pyprints the embedded comment snapshot as JSON or fenced text for the peer-review path, where a returned Portable file carries comments nobody pasted. It reports what is baked into the file, never "all current comments" - a reviewer's newer edits can still sit in browserlocalStorage, which no command-line tool can read - and it wraps every reviewer note in an untrusted-note fence so a downstream agent treats it as data, not instructions (CMH-CONTENT-04).
[1.257.0] - 2026-07-26
- Syntax highlighting is now reversible. A new shared highlight core exposes
dehighlight(), which recovers the exact source a highlighted code block was built from, for every language including KQL. The inverse is a LEFT inverse over newline-normalized input, because both emitters fold CRLF and lone CR to LF; for content already stored highlighted, dehighlight followed by re-highlight is byte-identical, so an untouched block never churns bytes. Escaped entities, a literal</span>inside a string, multi-line tokens (docstrings, block comments, KQL verbatim strings), tabs and astral Unicode all survive the round trip (CMH-HL-09). - The inverse is a strict single-pass scanner, so content the highlighters did not produce is REFUSED rather than corrupted: hand-written markup in a code block, a nested or malformed span, extra attributes, an unknown token kind, or non-canonical escaping the emitters never write (
",<,é, a bare&) all returnNonefromdehighlight(), andclassify()reportsraw/highlighted/hand-writtenso callers can pass such a block through untouched. The exact-escaping rule also makes an accidental double application refuse instead of silently decoding source that legitimately contains&. A repeated-substitution loop is deliberately not used - it would peel nested spans from the inside out and hand back plausible but corrupted source (CMH-HL-10).
- The code highlighter and the KQL highlighter now share one emission point and one newline normalizer, so the reversibility guarantee covers every language instead of being reimplemented per tokenizer. The two tokenizers stay separate (their grammars genuinely differ) and both the
cmh-code-*andcmh-kql-*class vocabularies are retained, so output bytes are unchanged and already-generated documents keep rendering exactly as before. Emitter output is now asserted flat, pinning the property the inverse depends on (CMH-HL-11).
218 earlier releases in the full changelog on GitHub.