Install
$ agentstack add skill-spunt-markup-mode-markup-mode ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Markup Mode
Markup Mode is a single, self-contained block of HTML/CSS/JS that turns any web page into a reviewable surface. A reviewer starts Markup Mode, then:
- selects text → the selection snaps to word boundaries, is highlighted in place, and the exact quote is captured (good for commenting on a specific claim or sentence), or
- clicks an element → a numbered mark + outline labels it unambiguously (good for design notes on a card, chart, button, etc.).
Notes accumulate in a collapsible dock and compile to tagged Markdown (with a human "Where", the exact quote or a CSS selector, and the comment) that is copied or exported as a .md file to hand to an agent. Everything is client-side: notes live in localStorage; nothing is written to the host's content and no server is involved.
When to use
- The user wants to review an agent-produced document/artifact and leave precise, per-claim feedback.
- The user wants to give design notes on specific components of a live UI.
- The user is building a new HTML artifact and wants a built-in review affordance.
- The user says "markup mode", "add a comment/review layer", "annotate", "let me flag specific parts", or wants location-anchored notes to pass back to an agent.
When NOT to use
- A persistent, multi-user, or queryable feedback backend is required (SQLite + endpoints + a triage UI) → that is a heavier, server-backed tool, not this.
- Non-HTML targets (native apps, PDFs, images).
- Simple "add a feedback button that POSTs somewhere" — Markup Mode hands back Markdown, not a server round-trip.
- Client-rendered app shells — pages whose `
is built at load by a JavaScript bundle rather than shipped as static HTML: **Databricks notebook exports**, Next.js/Nuxt/Angular/React SPA exports, or any "empty+ bundle" page. The host's own JS replaces the body after load, discarding the injected layer — so the marks never appear.scripts/apply.sh**detects and refuses these by default** with an explanation (--forceoverrides). To review one anyway, open it in a browser, let it render, **Save Page As → "Webpage, Complete"**, and apply Markup Mode to that static snapshot — or export the source as Markdown and use the one-command.md` route.
How to apply it to a target
Fast path (default) — one command. Use the bundled applier. It splices the layer, writes the markup-enabled copy to the current working directory, records the original artifact's path in the compiled Source: line, and runs a static self-check — all in one round:
scripts/apply.sh [--accent "#hex"] [--ns name] [--out DIR] [--source PATH]
- Output defaults to
./.markup.html— the original is never touched. --sourcedefaults to the original target's absolute path, so a reviewer who opens the copy still exportsSource:. Override only when the doc will be served from a different real path.- The applier's closing static self-check (block present, closing-`` count preserved by the splice, host bytes intact, config echoed) is the default verification tier — see Verifying below.
Keep narration terse on this routine path: run the command, report the self-check result, stop. No step-by-step play-by-play unless the user asks to be walked through it.
Markdown — one command (same fast path). Point the applier straight at a .md/.markdown file and it does the whole route itself: renders the doc to a self-contained HTML preview, fills assets/templates/markdown-host.html, splices the review layer, writes ./.markup.html to CWD with Source: = the original .md's absolute path, and runs the same static self-check.
scripts/apply.sh doc.md [--safe] [--md-engine pandoc|node|python] [--accent "#hex"] [--ns name] [--out DIR] [--source PATH]
- Converter: auto-detect prefers pandoc (
pandoc -f gfm -t html, best GFM fidelity) → falls back to node (npx --yes marked --gfm).--md-engineforces one. python (python3 -m markdown) is reachable only via--md-engine pythonand prints a one-line reduced GFM fidelity warning (task lists render as literal[ ], bare URLs aren't autolinked). The applier always prints which engine it used. If no usable converter exists for the selected engine, it exits non-zero with an install hint (brew install pandoc, or use Node). - Raw HTML: passed through by default (you trust your own doc).
--safedoes a best-effort post-render strip of `/blocks, inlineon*=handlers, andjavascript:/data:text/html` URLs — for locally-trusted review, not a security boundary. - Title: first
# H1in the doc, else the filename stem (HTML-escaped). - Front-matter: a leading YAML
---…---block is stripped only when---is the very first line; a mid-doc---thematic break is preserved. - Relative links/images are rewritten to absolute, URL-encoded
file://URLs anchored at the source.md's directory, with query/fragment preserved. Pure fragment#…, scheme, protocol-relative//…, and filesystem-absolute/…refs are left untouched. Note:file://rewriting binds images to this machine — a future--embed-imagesflag would inline them for portability. Anchoring/review itself does not need images to resolve.
Markdown — manual 3-step fallback (no python3, or to understand the render→fill→splice flow):
- Render the
.mdto an HTML fragment (no//wrapper). - Drop it into the content slot of
assets/templates/markdown-host.htmland set its `; save as.preview.html`. scripts/apply.sh .preview.html --source /abs/path/to/.md— soSource:points at the original.md, not the preview.
Other artifact types (PDF, images, plain text) are backlog — see references/open-items.md.
No-shell manual splice (any target) — when no shell is available, or to understand the splice: open assets/templates/markup-mode.html, copy everything between the two MARKUP MODE - copy from here / to here markers (both ` elements + the ), paste it immediately before the target's closing tag, and add window.MarkupModeConfig = { sourcePath: "" }; just before it. Do not modify the host's existing markup. For a brand-new artifact you are authoring, bake the block in before ` from the start.
Theme it to the host (optional). The layer reads seven --mm-* variables and auto-detects common host :root tokens (--accent, --bg, --text, --border, …). Override any color — plus namespace, toggle shortcut, and the Source path — via MarkupModeConfig (or apply.sh --accent), with no source editing. Skip it and neutral light/dark defaults are used. See references/adaptation.md for the mapping recipe and full config schema. (Namespace + source name otherwise auto-derive from the filename; localStorage key is markup-mode:.)
> Agent cue — act on this: when a user asks to change any Markup Mode preference or keybinding ("set the markup-mode accent to #6366f1", "rebind the toggle to Ctrl+Shift+M", "default to dark mode"), run scripts/config.sh set — it validates and writes the one config file that governs all future applications. Offer it proactively if they mention wanting a persistent look/shortcut. (scripts/config.sh help lists every key.) For a one-off on a single artifact, pass the matching apply.sh flag instead.
One settings file for every application (markup-mode.config.jsonc). To set defaults once that apply to all future applications (not per-artifact), edit the repo-root markup-mode.config.jsonc. apply.sh reads it as the default source for --accent/--ns/--shortcut/--theme/keymap/behavior and bakes the resolved values in at apply time. Precedence: CLI flag > config file > built-in default (per key); the file ships empty so present-with-defaults == today's behavior; a missing/malformed file is loud-but-non-fatal (warn + fall back, never crash apply). An agent should honor it (read with scripts/config.sh list) and update it on explicit request with the validated, comment-preserving writer:
scripts/config.sh set accent "#6366f1" # validate + write (preserves the header & other keys)
scripts/config.sh set keymap.addRef.mod shift
scripts/config.sh set behavior.themeMode dark
scripts/config.sh unset accent # revert one key to default
scripts/config.sh list # show every key = resolved value
set/unset are strict (bad input exits non-zero, writes nothing). apply.sh also takes --keymap KEY=CHORD, --config PATH, and --no-config. Full key table + schema: references/adaptation.md §0a.
Verifying (tiered — fast by default)
- Default — static (one round, no browser). Trust the applier's static self-check. The layer ships a comprehensive regression suite (
tests/regression.js), so its behavior is already proven; the only per-application risks — injection correctness and theme clash — are statically checkable. - Escalate to a headless browser check only when the layer source was modified or a custom theme was applied — the cases static checks can't cover. Do the whole check in one
browser_evaluatereturning a single object (mount + a text-selection note + an element-mark note + Compile output), not several round-trips. - Force it any time by asking ("verify in a browser", "full verification") or running
apply.sh --verify full. - For a headless check, serve the file (
python3 -m http.server) — Playwright blocksfile:; for a human glance,open. - The full manual checklist lives in
test_cases.md.
Runtime behavior (what the reviewer gets)
- Enter/exit: the dock's "Start Markup Mode" button or
Cmd/Ctrl+Shift+K(configurable);Escexits (or closes an open note first, or clears the selected note). - Text anchor: drag-select → popup beside the selection → type → Save. Draft text is persisted on every keystroke, so clicking away never loses it. The popup is titled "New note" with Discard / Save note.
- Element anchor: hover/click runs through an intent resolver that prefers real controls, semantic/component boundaries, and prose blocks over anonymous wrapper nodes;
↑/↓grows/shrinks along that meaningful target chain; click → popup → Save. A persistent outline + compact unnumbered mark label it. In Markdown previews, clicking prose blocks (p,li, headings,blockquote,pre) creates a whole-block text anchor instead of a generic element anchor, while links/images/tables/controls stay element targets. - Navigate vs. edit (select-then-edit): a single click on a mark or a dock row reveals + selects the note — its anchor scrolls into view and it gets a persistent active highlight (it does not open the editor and does not arm comment mode, so you can browse freely). Minimized marks remain clickable; the Open dock on reveal preference controls whether revealing a mark also expands the dock. To edit, double-click the mark or row, or click the row's ✎ button; the popup is then titled "Editing · Note N" with Cancel / Save. Deleting (the row's ✕) shows an Undo toast for a few seconds.
- Cross-references: while a note is open, hold ⌥ Alt/Option and click an element (or ⌥-drag to select a run of text) to insert a concise
[ref: ]token into the note text at the cursor (text refs carry the full quote, untruncated). The dashed candidate outline only appears while the add-reference modifier is held. A plain click, plain double-click, or plain selection while the popup is open is a no-op — the Alt modifier is what distinguishes adding a reference from the browser's own click/word-select. Copy-ref support copies that token to the clipboard instead of leaving a note when enabled; the default visible workflow is inline Alt insertion. The add-reference modifier is configurable (keymap.addRef.mod). - Hover to view: hovering a mark's pin (when comment mode is off) shows a floating tooltip with the note text — so shared/exported files can be browsed without entering comment mode.
- Dock: opens as a right rail by default; the minimize glyph collapses it to a bottom-right pill. Drag the header to move when floating; resize from the corner in float mode or the rail grip in docked mode. The dock header shows direct position buttons (float / dock-left / dock-right) — click one to move there immediately. On wide screens a rail reserves a resizable column so the page is not covered; below the compact breakpoint it spans the viewport and clears body margins so overlaid marks continue to line up with the page. Minimizing a rail schedules a follow-up reposition after the page margin settles.
- Sort marks: a sort control in the dock lets you order marks by Created (default — insertion order), Document order (DOM position), or Manual (drag-reorder dock rows). Dock row ordinals, edit headers, and compiled
.mdheadings reflect the active sort order. On-page marks are intentionally unnumbered so transient sort order does not obstruct or imply stable document identity. Internal note ids are stable regardless of sort. - Keyboard / a11y: the notes list is a
role="list"with roving arrow-key focus (↑/↓,Enterreveals); the popup is a focus-trappedrole="dialog"that restores focus on close; toasts announce via an ARIA live region. - Output: A Show Compiled / Hide Compiled toggle reveals a read-only Markdown preview generated from the notes. Copy is visible only while compiled is shown; Export .md is always present (disabled when there are no notes). While the compiled view is open, the preview auto-updates on every note add, edit, delete, or sort change.
- Export reviewed HTML: a button beside Export .md downloads a self-contained
.reviewed.html— the same artifact with the current notes baked into an embedded `block. **Opening it anywhere rehydrates the review** (marks, dock rows, highlights), so the file itself becomes the shareable container — email it, reopen it on another machine, hand it to a second reviewer. The export is a **clean snapshot** (runtime dock/overlay/popup nodes and any rail body margins are stripped before saving), so re-opening mounts the layer exactly once and re-exporting replaces the notes block rather than stacking it. This is **complementary to Export .md**: the.mdis the agent task-list handback (the deliverable to action); the reviewed HTML preserves full context for human re-sharing and multi-session review. **Seed-on-mount precedence:** on open, iflocalStorage` already has notes for this namespace it wins (a returning reviewer's in-session edits are never clobbered); the baked block is used only as the fallback for a freshly-opened shared file. Re-export to share new edits. (A shared file can't be rewritten in place — Export reviewed HTML always downloads a new copy.) - Settings / preferences: the gear opens Appearance and Behavior controls. Appearance supports auto/light/dark theme, reduce motion, accent picker/swatches, and a live contrast readout. Behavior currently includes Show marks and Open dock on reveal. Preferences persist per namespace and still respect
MarkupModeConfigcolor overrides. - Copy-ref confirmation: in Copy-ref mode, the confirmation appears as a small pointer chip (near the click point or selection end) instead of the corner toast; the corner toast continues to serve all other messages (inserts, exports, deletes).
Compiled Markdown contract
# Feedback — (review)
Source: · N notes · YYYY-MM-DD
How to apply each note: its Locator is the surrounding text with the exact edit span wrapped in ⟪ ⟫. Find that span in the artifact — whitespace and markup may differ from this rendered text, so match on the words, not byte-for-byte. If it resolves to exactly one place, act on it (edit only what was inside ⟪ ⟫); if it resolves to zero or several places, do no
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [spunt](https://github.com/spunt)
- **Source:** [spunt/markup-mode](https://github.com/spunt/markup-mode)
- **License:** MIT
- **Homepage:** https://www.bobspunt.com/software/markup-mode/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.