Skip to content

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:

One toolbar, six ways to point. Click the tabs to see each mode:

localhost:3000/pricing
Starter
GrowthPopular
Scale
Enterprise
"Annotate the highlighted phrase itself — the exact wording travels with your note."
ViewCommentElementTextMultiArea
Hover any element — the picker outlines it live.

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.

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).

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.

localhost:3000/pricing
Starter
GrowthPopular
Scale
Enterprise
"Annotate the highlighted phrase itself — the exact wording travels with your note."
#plan-starter
Every target gets a stable selector an agent can grep for.

Under 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.

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.

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.

KeysAction
Alt+Select the parent of the hovered element
Alt+Descend back into the child you came from
Alt+ / Previous / next selectable sibling
EnterAnnotate the current keyboard target
EscCancel 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.

React-style state models, your choice:

  • Controlled — the host owns the collection: seed with __init({ annotations }), replace wholesale with setAnnotations, observe with subscribeAnnotations. Pass overlay: false to 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.

Live controlled-state demo →

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.

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.

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:

localhost:3000/pricing
Starter
GrowthPopular
Scale
Enterprise
"Annotate the highlighted phrase itself — the exact wording travels with your note."
1
You leave feedback and the agent gets the AFS event…

Live agent round-trip demo →

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.

Live screenshots demo →

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.

Live severity demo →

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/rearrange protocol kinds exist, but the editing UI is the roadmap.