Annotations — capabilities, live
Every demo on this page runs the real library inside its own frame, with a fetch interceptor playing the webhook — the event logs show genuine AFS v1.1 envelopes. Scenarios go from basic to advanced.
Forensic structured capture
Section titled “Forensic structured capture”Every capture attaches a versioned, JSON-safe AnnotationForensics
envelope alongside the flat AFS fields: stable selectors + full DOM
path, geometry (document + viewport), grouped computed styles (layout,
flex/grid, spacing, typography, appearance), ancestor and sibling
context, accessibility (role, accessible name, ARIA, focusability),
React component stack + dev-build source file:line when resolvable, and
page context (URL, viewport, scroll, DPR). Report/prompt formatting is
the application layer’s job — the library guarantees the data:
const forensics = window.__annotations.captureForensics("#cta");// or read annotation.forensics off any created annotationSelection parity — area, keyboard, freeze
Section titled “Selection parity — area, keyboard, freeze”Four tools (element / text / multi / area drag-rectangle), Alt+arrow
keyboard hierarchy navigation from the hovered element, deep
shadow-root hit-testing, and a Pause motion toggle that freezes CSS
animations, WAAPI animations, and playing videos so moving targets hold
still. Each capture records its selectionMethod provenance in the
forensics envelope:
Visual design editing
Section titled “Visual design editing”The Edit mode turns an annotation into a structured design-edit
request. Click an element to open a style panel (text/background color,
font family, size, weight, line height, letter spacing, alignment,
decoration) with live preview, or rewrite its copy inline via
contentEditable. Saving creates a kind: "edit" annotation that keeps
the usual forensic capture and optional comment, plus a versioned
edits envelope precise enough to re-apply programmatically later —
each operation records the property, the before value in both computed
and authored form, the raw after value, and the target’s re-anchor
keys:
{ "kind": "edit", "edits": { "version": 1, "operations": [ { "kind": "style", "property": "color", "before": { "computed": "rgb(24, 24, 27)", "authored": ".hero h2 { color: #18181b }" }, "after": { "value": "#7c3aed", "computed": "rgb(124, 58, 237)" }, "target": { "element": "h2", "selector": "h2.hero", "anchorSelector": "#hero-title", "fullPath": "body > main > h2" } }, { "kind": "text", "before": "Ship faster", "after": "Ship 10x faster", "target": { "element": "h2", "selector": "h2.hero" } } ] }}Cancelling the panel — or deleting the annotation later — restores the
page byte-for-byte (original inline styles and text). Edits flow
through every existing channel unchanged: webhook annotation.created
payloads, controlled setAnnotations/subscribeAnnotations
round-trips, and uncontrolled persistence (a reload re-applies the
saved edits to the page).
Controlled & uncontrolled state
Section titled “Controlled & uncontrolled state”React-style state models. Controlled: the host owns the collection
— seed via __init({ annotations }), replace wholesale with
setAnnotations, observe with subscribeAnnotations, and pass
overlay: false to skip the shadow DOM/picker/visuals entirely and
render your own UI. Uncontrolled: pass persistence: "local" (or a
custom PersistenceAdapter) and the overlay is self-contained —
annotations survive reloads with no host backend:
// controlled — fully headless, host renders everythingwindow.__annotations.__init({ overlay: false });const unsubscribe = window.__annotations.subscribeAnnotations(render);window.__annotations.setAnnotations(hostCopy);
// uncontrolled — self-contained across reloadswindow.__annotations.__init({ persistence: "local" });Severity & custom composer fields
Section titled “Severity & custom composer fields”The default composer always shows severity — blocking selected, one
click to downgrade to suggestion. Hosts replace the whole field set
without forking:
window.__annotations.__init({ composer: { fields: [ { key: "severity", label: "Severity", kind: "chips", options: [{ value: "blocking" }, { value: "suggestion" }], defaultValue: "blocking" }, { key: "area", label: "Area", kind: "select", options: [{ value: "copy" }, { value: "layout" }, { value: "behavior" }] }, ], },});Re-anchoring resilience
Section titled “Re-anchoring resilience”Selectors break the moment a SPA re-renders. The resolver layers a
verified-unique anchor selector → structural full path → context-scored
candidates (stable classes, nearby text, bounding-box proximity, with a
nearby-text contradiction guard) → explicit orphaned state. It never
silently picks an identical-looking sibling.
Action slots
Section titled “Action slots”Host-supplied buttons on the toolbar, composer, pin, or thread.
Programmatic actions get the live re-anchored element; declarative
actions are JSON-safe (they survive CDP injection) and emit
action.requested for the host to handle:
window.__annotations.setActions([ { id: "copy-html", label: "Copy HTML", placement: "pin", run: (ctx) => navigator.clipboard.writeText(ctx.element?.outerHTML ?? "") }, { id: "ask-agent", label: "Ask agent to fix", placement: "pin" }, // declarative]);Agent round-trip
Section titled “Agent round-trip”Feedback is a conversation, not a mailbox. The agent replies in the pin’s thread, resolves annotations, opens threads, and shows a live presence cursor — the exact calls editor-web sends over CDP:
window.__annotations.replyToAnnotation(id, { role: "agent", content: "Fixed — please verify." });window.__annotations.resolveAnnotation(id, "agent");window.__annotations.focusAnnotation(id);window.__annotations.setCursor({ id: "agent-1", kind: "agent", label: "Agent", x: 42, y: 300 });Hydration & scoping
Section titled “Hydration & scoping”The host owns canonical annotation state; the overlay is a derived
view. Every (re)injection re-seeds via __init({ annotations }) —
upserted by id, zero duplicate events — so live-view reloads never lose
pins. Seeds can carry a scope so variant-A feedback never renders on
variant B:
window.__annotations.__init({ annotations: savedAnnotations, // the host's durable copy scope: { variantId: "variant-a" },});Host-captured screenshots
Section titled “Host-captured screenshots”Opt-in, and the library never reads pixels itself: each new annotation
emits an action.requested capture request (element requests include
the clip box). The host captures — editor-web uses CDP
Page.captureScreenshot — and attaches the validated result:
window.__annotations.__init({ screenshots: { onCreate: ["viewport", "element"] } });// host side, after capturing:window.__annotations.attachScreenshot(annotationId, { kind: "element", format: "png", dataUrl,});- Comparison matrix — how these capabilities stack up against Agentation, React Grab, and Cursor.
- Editor-web integration — the
ANNOTATIONS_IMPLflag, CDP injection, webhook mapping, testing. - Full API reference:
packages/coframe-annotations/README.md.