Skip to content

Collect visual annotations

The annotated browser is a self-contained iframe that combines a live browser and an annotation overlay into one embeddable view. Your users point at a live page and leave feedback; your app receives each submission via postMessage — no WebSocket or webhook plumbing on your side.

It’s the annotated-browser surface of @coframe/editor-embed, so you drive it with the same EmbedController / <EditorEmbed> API.

  1. Your app mounts the iframe (surface annotated-browser) with a capability token and navigates it to a target URL.
  2. Inside the iframe, Coframe Agent provisions the live browser, injects the annotation overlay on every page navigation, and watches for submissions.
  3. When a user submits an annotation, the iframe re-emits it to your app as an annotation-submitted event over postMessage.

onAnnotationSubmitted is a convenience helper that subscribes to just the annotation events:

import { EmbedController, onAnnotationSubmitted } from "@coframe/editor-embed";
const controller = new EmbedController({
editorBaseUrl: "https://editor.example.com",
sessionId: "my-session",
surface: "annotated-browser",
token: "capability-token",
});
const iframe = document.getElementById("embed") as HTMLIFrameElement;
iframe.src = controller.buildIframeSrc();
controller.attach(iframe);
const unsubscribe = onAnnotationSubmitted(controller, (submission) => {
console.log("Annotated:", submission.url, submission.annotations);
});
controller.navigate("https://example.com/landing");
// Later
unsubscribe();
controller.dispose();
import { EditorEmbed } from "@coframe/editor-embed/react";
function AnnotatedBrowser({ sessionId, token }: {
sessionId: string;
token: string;
}) {
return (
<EditorEmbed
editorBaseUrl="https://editor.example.com"
sessionId={sessionId}
token={token}
surface="annotated-browser"
style={{ width: "100%", height: 600 }}
onEvent={(event) => {
if (event.type === "annotation-submitted") {
// Build a prompt or task from the submission
console.log(event.submission.annotations);
}
}}
/>
);
}
interface AnnotationSubmissionPayload {
id: string;
url: string;
createdAt: string;
annotations: unknown[];
source: string;
authorLabel?: string;
}

See Embed the editor for the full list of commands you can send (navigate, setReadonly, dispose, …) and events you can receive.

The demo mounts the annotated browser and exercises the full pipeline. The “Simulate annotation” button submits an annotation, which flows back to the parent as an annotation-submitted event: