Annotations — features
@coframe/annotations turns clicks on a live page into structured,
agent-ready feedback. Point at anything — an element, a phrase, a
region — say what’s wrong, and an agent gets everything it needs to fix
it: stable selectors, styles, React sources, and a thread to reply in.
It works on any page — no framework, no build step, zero runtime dependencies. Or just try it right here:
Annotation modes
Section titled “Annotation modes”One toolbar, six ways to point. Click the tabs to see each mode:
Prefer the real thing? The same modes run live in the selection demo.
- Element — click any element; the picker outlines it live, even inside open shadow roots.
- Text — select a phrase; the exact quoted text rides along in the payload.
- Multi-select — click elements into a group and comment on all of them at once.
- Area — drag a rectangle; everything it touches becomes one grouped annotation.
- Keyboard — walk the DOM hierarchy with Alt+arrows when the mouse can’t reach.
- Pause motion — freeze CSS animations, WAAPI animations and videos so moving targets hold still.
Toolbar & launcher
Section titled “Toolbar & launcher”The overlay mounts as a small draggable puck that expands into the toolbar. Everything lives in a closed shadow root, so your page’s CSS never bleeds in (and vice versa).
- ⠿ Drag grip — move the toolbar anywhere; it stays on screen.
- 👁 View / Comment — flip between browsing the page and annotating it.
- 🔢 Counter & navigator — annotation count, plus ‹ › to jump between pins (with a separate count of agent-authored ones).
- 🧹 Clear — remove every annotation (and presence cursor) in one click.
- ⏸ Pause motion — freeze page animations to annotate a specific state.
- 🔘 Element / Text / Multi / Area — switch selection tools.
- ➕ Action slots — hosts add their own buttons to the toolbar, composer, pins, or threads (live demo).
Smart identification & forensic capture
Section titled “Smart identification & forensic capture”Every annotation identifies its target the way an agent would want it to: a stable selector it can grep the codebase for, plus the full context around it.
#plan-starterUnder the hood, each capture attaches a versioned AnnotationForensics
envelope. Trimmed real payload:
{ "version": 1, "selectionMethod": "element", "target": { "tag": "button", "selector": "#checkout-cta", "anchorSelector": "#checkout-cta", "fullPath": "html > body > main > section.hero > button#checkout-cta", "text": "Start free trial", "rect": { "x": 412, "y": 980, "width": 180, "height": 44 }, "viewportRect": { "x": 412, "y": 380, "width": 180, "height": 44 }, "styles": { "layout": { "display": "inline-flex", "position": "relative" }, "spacing": { "padding": "12px 24px", "margin": "0px" }, "typography": { "font-size": "15px", "font-weight": "600" }, "appearance": { "background-color": "rgb(124, 92, 255)", "border-radius": "8px" } }, "ancestors": [{ "tag": "section", "selector": "section.hero" }], "siblings": { "index": 2, "count": 3 }, "accessibility": { "role": "button", "name": "Start free trial", "focusable": true }, "react": { "components": [{ "name": "CtaButton" }, { "name": "Hero" }], "source": { "fileName": "src/components/hero.tsx", "lineNumber": 42 } } }, "page": { "url": "https://localhost:3000/pricing", "viewport": { "width": 1280, "height": 800 }, "scroll": { "x": 0, "y": 600 }, "devicePixelRatio": 2 }}Multi-select and area annotations carry a per-element targets array
and the drag rectangle. See it live in the
forensic capture demo.
React source resolution
Section titled “React source resolution”On React dev builds, captures resolve the component stack and the JSX
source file:line — through three channels (_debugSource, the babel
__source prop, and React 19’s _debugStack), walking owner chains
when the direct fiber has none. When a location can’t be resolved, the
payload says why (production build, no fiber, no debug info) instead
of guessing.
Computed styles capture
Section titled “Computed styles capture”Styles arrive pre-grouped into five buckets — layout, flex/grid, spacing, typography, appearance — so an agent (or a report template) can pull “what does this look like” without diffing hundreds of raw computed properties.
Keyboard shortcuts
Section titled “Keyboard shortcuts”| Keys | Action |
|---|---|
| Alt+↑ | Select the parent of the hovered element |
| Alt+↓ | Descend back into the child you came from |
| Alt+← / → | Previous / next selectable sibling |
| Enter | Annotate the current keyboard target |
| Esc | Cancel area drag / clear keyboard target / close |
Mouse movement resets the walk to the element under the cursor, so you can mix pointing and keying freely.
Controlled & uncontrolled state
Section titled “Controlled & uncontrolled state”React-style state models, your choice:
- Controlled — the host owns the collection: seed with
__init({ annotations }), replace wholesale withsetAnnotations, observe withsubscribeAnnotations. Passoverlay: falseto go fully headless and render your own UI. - Uncontrolled — pass
persistence: "local"and the overlay is self-contained; annotations survive reloads with no host backend.
Persistence
Section titled “Persistence”Uncontrolled persistence is versioned and pluggable: the built-in
localStorage adapter works out of the box, or supply any
PersistenceAdapter (load/save) to store annotations wherever you
like. Hydrated seeds can carry a scope (URL, session, variant) so
variant-A pins never render on variant B.
Webhook & AFS output
Section titled “Webhook & AFS output”Every interaction emits an AFS v1.1
event envelope to your webhook — annotation.created,
annotation.updated, thread.message, action.requested,
presence.cursor, and session lifecycle events:
{ "type": "annotation.created", "timestamp": "2026-07-26T12:00:00.000Z", "sessionId": "session_123", "sequence": 4, "payload": { "annotation": { "comment": "Tighten this copy", "severity": "blocking" } }}sequence is monotonic per session — detect gaps, request replay.
Delivery failures are never silent: the overlay shows a “sync failed”
toast and fires onWebhookError. Validate envelopes server-side with
parseEnvelope from @coframe/annotations/server.
Agent round-trip & presence cursors
Section titled “Agent round-trip & presence cursors”Feedback is a conversation. Agents reply in the pin’s thread,
acknowledge and resolve annotations, and show a live labeled presence
cursor while they work — all via plain JSON calls on
window.__annotations, safe to send over CDP:
Screenshots
Section titled “Screenshots”Opt-in and host-captured — the library never reads pixels itself. Each
new annotation emits a capture request (element requests include the
clip box); the host answers with attachScreenshot, which validates
kind, format and size before accepting.
Severity & composer fields
Section titled “Severity & composer fields”The composer always shows severity — blocking by default, one click
to downgrade to suggestion — and severity lands as a first-class
field on the annotation. Hosts can replace the entire field set
(chips, select, text, textarea, toggle, with required
validation) without forking.
Limitations
Section titled “Limitations”Honest edges, mirrored from the comparison matrix:
- Desktop-focused — selection is mouse/keyboard-driven; there is no touch-optimized flow.
- No same-origin iframe selection — the picker sees the top document (and open shadow roots) only. Planned.
- Animation freeze covers CSS, WAAPI and video — JS-driven animation libraries (e.g. GSAP) keep running.
- No freehand drawing — annotations anchor to elements, text and regions, not sketch strokes. Planned.
- No MCP server, by design — agent integration is the application layer’s job (editor-web, Jarvis).
- No visual layout editing yet — the
placement/rearrangeprotocol kinds exist, but the editing UI is the roadmap.
- Capabilities, live — every feature above running for real, in embedded demos.
- Comparison matrix — how this stacks up against Agentation, React Grab, and Cursor.
- Editor-web integration — injection, webhook mapping, screenshots over CDP.