158</section>160</section>
159```161```
160 162
163<div
164 id="try-annotation-metadata"
165 className="annotation-request-demo not-prose"
166 data-annotation-example="metadata"
167 hidden
168>
169 **Try it**
170
171
172 <section oai-annotation-container>
173 <div
174 className="annotation-contact-preview"
175 data-example-target
176 oai-annotatable="Alex Morgan"
177 oai-annotation-metadata='{"email":"alex@example.com"}'
178 >
179 **Alex Morgan**
180
181Project lead
182
183
184
185 </section>
186
187
188 Select the contact in Annotation mode to see the email address included as
189 context.
190
191
192
193
194
195
196
161Selecting either line selects the entire row. The metadata appears with the197Selecting either line selects the entire row. The metadata appears with the
162annotation and accompanies it in the conversation. Include only context you198annotation and accompanies it in the conversation. Include only context you
163intend to share with both the user and the model. Sending an email would still199intend to share with both the user and the model. Sending an email would still
187Site-initiated requests may require user permission; users can re-enable blocked223Site-initiated requests may require user permission; users can re-enable blocked
188annotation features under **Site tools > Annotation features**.224annotation features under **Site tools > Annotation features**.
189 225
190Add this button beside the chart card from the selection example, then run the226This example opens an annotation on a paragraph without selection attributes.
191script after both elements exist:227Run the script after both elements exist:
192 228
193```html229```html
194<button id="discuss-chart" type="button" hidden>Explain more</button>230<p id="delivery-summary">
231 Standard delivery takes three to five business days.
232</p>
233<button id="discuss-delivery" type="button" hidden>Explain more</button>
195```234```
196 235
197```javascript236```javascript
198const card = document.getElementById("chart-card");237const summary = document.getElementById("delivery-summary");
199const button = document.getElementById("discuss-chart");238const button = document.getElementById("discuss-delivery");
200 239
201button.hidden = typeof document.oai?.annotation?.request !== "function";240button.hidden = typeof document.oai?.annotation?.request !== "function";
202 241
204 const annotation = document.oai?.annotation;243 const annotation = document.oai?.annotation;
205 if (typeof annotation?.request !== "function") return;244 if (typeof annotation?.request !== "function") return;
206 245
207 annotation.request(card, {246 annotation.request(summary, {
208 initialComment: "Explain the latest trend in this graph.",247 initialComment: "Explain this delivery estimate.",
209 });248 });
210});249});
211```250```
212 251
213Selecting **Explain more** asks the browser to open an annotation with the card252Selecting **Explain more** asks the browser to open an annotation with the
214selected and an editable comment. The person can edit, save, and send it with253paragraph selected and an editable comment. The person can edit, save, and send it with
215their message. Opening an annotation doesn't send a message to ChatGPT; only254their message. Opening an annotation doesn't send a message to ChatGPT; only
216the user can submit it.255the user can submit it.
217 256
257<div
258 id="try-annotation-request"
259 className="annotation-request-demo not-prose"
260 data-annotation-request-demo
261 hidden
262>
263 **Try it**
264
265
266
267
268 Standard delivery takes three to five business days.
269
270
271 <button
272 type="button"
273 className="annotation-prompt-button"
274 data-annotation-request-button
275 data-prompt="Explain this delivery estimate."
276 >
277 Explain more
278 </button>
279
280 <p
281 className="annotation-try-it-hint"
282 data-annotation-request-feedback
283 role="status"
284 aria-live="polite"
285 >
286
287
288
289
290
291
218Keep the call within the active user interaction. Awaiting a network request292Keep the call within the active user interaction. Awaiting a network request
219first can lose that interaction.293first can lose that interaction.
220 294
239### Request an annotation for a text range313### Request an annotation for a text range
240 314
241Pass a DOM `Range` to request feedback on a passage without changing the browser's315Pass a DOM `Range` to request feedback on a passage without changing the browser's
242text selection. This example selects the paragraph's contents; it doesn't need316text selection. This example selects only the highlighted phrase; it doesn't need
243an `oai-annotation-container-text` attribute:317an `oai-annotation-container-text` attribute:
244 318
245```html319```html
246<p id="draft-passage">Leave more space between separate groups.</p>320<p>
247<button id="discuss-passage" type="button" hidden>Discuss this passage</button>321 Your trial includes full access for <mark id="draft-passage">14 days</mark>.
322</p>
323<button id="discuss-passage" type="button" hidden>
324 Ask about highlighted text
325</button>
248```326```
249 327
250Run this script after both elements exist:328Run this script after both elements exist:
262 const range = document.createRange();340 const range = document.createRange();
263 range.selectNodeContents(passage);341 range.selectNodeContents(passage);
264 annotation.request(range, {342 annotation.request(range, {
265 initialComment: "Suggest a clearer version of this guidance.",343 initialComment: "Explain when this trial ends.",
266 });344 });
267});345});
268```346```
269 347
348<div
349 id="try-annotation-text-range"
350 className="annotation-request-demo not-prose"
351 data-annotation-example="text-range"
352 hidden
353>
354 **Try it**
355
356
357
358
359 Your trial includes full access for
360 <mark data-example-target>14 days</mark>.
361
362
363
364
365 Open an annotation on just the highlighted words.
366
367
368 <button
369 type="button"
370 className="annotation-prompt-button"
371 data-example-request
372 data-prompt="Explain when this trial ends."
373 >
374 Ask about highlighted text
375 </button>
376
377 <p
378 className="annotation-try-it-hint"
379 data-example-feedback
380 role="status"
381 aria-live="polite"
382 >
383
384
385
386
387
388
270The range must contain nonempty visible text in the current document, with at389The range must contain nonempty visible text in the current document, with at
271least part of the selection in the visible page area. It can contain at most390least part of the selection in the visible page area. It can contain at most
27220,000 UTF-16 code units. Collapsed ranges, whitespace-only text, hidden selected39120,000 UTF-16 code units. Collapsed ranges, whitespace-only text, hidden selected
306 425
307### Preview a shared spacing token426### Preview a shared spacing token
308 427
309Both cards in this example use the same CSS property:428Both cards in this example use the same CSS property. Register the controls on
429the cards directly; selection attributes aren't required:
310 430
311```html431```html
312<style>432<style>
320 }440 }
321</style>441</style>
322 442
323<section id="component-preview" oai-annotation-container>443<section id="component-preview">
324 <article class="preview-card" oai-annotatable="Profile card">444 <article class="preview-card">Profile card</article>
325 Profile card445 <article class="preview-card">Summary card</article>
326 </article>
327 <article class="preview-card" oai-annotatable="Summary card">
328 Summary card
329 </article>
330</section>446</section>
331```447```
332 448
372}488}
373```489```
374 490
491<div
492 id="try-annotation-spacing"
493 className="annotation-request-demo not-prose"
494 data-annotation-example="spacing"
495 hidden
496>
497 **Try it**
498
499
500 **Card padding (pixels)**
501 <div
502 className="annotation-spacing-preview"
503 data-example-preview
504 oai-annotation-container
505 >
506 <article data-example-target oai-annotatable="">
507 Profile card
508 </article>
509 <article data-example-target oai-annotatable="">
510 Summary card
511 </article>
512
513
514
515
516 Select either card in Annotation mode, then change Card padding (pixels).
517 Select Adjust if needed. Both cards update together.
518
519
520
521
522
523
524
375Annotate either card and change **Card padding (pixels)** from 16 to 24. On525Annotate either card and change **Card padding (pixels)** from 16 to 24. On
376hosted sites in ChatGPT, the controls appear automatically; in Codex or on526hosted sites in ChatGPT, the controls appear automatically; in Codex or on
377localhost, select **Adjust** if needed. Both cards update. The annotation records the label, reference,527localhost, select **Adjust** if needed. Both cards update. The annotation records the label, reference,
380 530
381### Collect a choice without a preview531### Collect a choice without a preview
382 532
383Use the contact row from the metadata example to offer an email tone:533This example offers an email tone for a plain paragraph. It doesn't need
534selection attributes or metadata. Run the script after the paragraph exists:
535
536```html
537<p id="email-draft">Draft a follow-up email about the project timeline.</p>
538```
384 539
385```javascript540```javascript
386const contact = document.getElementById("contact-row");541const draft = document.getElementById("email-draft");
387const registration = document.oai?.annotation?.registerControls?.({542const registration = document.oai?.annotation?.registerControls?.({
388 targets: contact,543 targets: draft,
389 controlsHeading: "Email options",544 controlsHeading: "Email options",
390 controlsMode: "replace",545 controlsMode: "replace",
391 controls: [546 controls: [
407This control doesn't need an event handler because it doesn't preview a page562This control doesn't need an event handler because it doesn't preview a page
408change. Omitting `currentValue` tells the browser to include the selected tone563change. Omitting `currentValue` tells the browser to include the selected tone
409even if the user keeps the initial option. Call `registration?.dispose()` when564even if the user keeps the initial option. Call `registration?.dispose()` when
410removing the row.565removing the paragraph.
411 566
412For select controls, preview callbacks receive `option.value`, such as567For select controls, preview callbacks receive `option.value`, such as
413`"professional"`. Annotation history and ChatGPT receive the visible568`"professional"`. Annotation history and ChatGPT receive the visible
424heading isn't included in the context sent to ChatGPT.579heading isn't included in the context sent to ChatGPT.
425 580
426A registration supports up to 12 controls. Each requires a `type`, visible581A registration supports up to 12 controls. Each requires a `type`, visible
427`label`, and `callback` identifier:582`label`, and `callback` identifier.
583
584**Name controls by what they change.** Use a stable property or design-role
585label, such as "Page background," "Primary brand color," or "Accent color,"
586rather than the current value, such as "Warm ivory" or "Deep forest." The label
587should remain meaningful when the value changes. Use `currentValue` for the
588existing value and `reference` for the underlying token or property.
589
590For example, this control definition keeps the label separate from the color:
591
592```javascript
593const pageBackgroundControl = {
594 type: "color",
595 label: "Page background",
596 callback: "setPageBackground",
597 reference: "--color-background",
598 currentValue: "#f6f3ec",
599};
600```
601
602<div
603 id="try-annotation-color"
604 className="annotation-request-demo not-prose"
605 data-annotation-example="color"
606 hidden
607>
608 **Try it**
609
610
611 **Page background**
612 <div
613 className="annotation-color-preview"
614 oai-annotatable=""
615 data-example-target
616 data-example-preview
617 >
618
619Page preview
620
621
622
623
624
625 Select the preview in Annotation mode, then change Page background. Select
626 Adjust if needed. The label stays the same as the color changes.
627
628
629
630
631
632
633
634The supported control types are:
428 635
429| Type | Value | Additional fields |636| Type | Value | Additional fields |
430| -------- | ------------------------------ | ------------------------------------------------- |637| -------- | ------------------------------ | ------------------------------------------------- |
525The host must be a connected HTML element outside shadow DOM in a secure,732The host must be a connected HTML element outside shadow DOM in a secure,
526top-level document. Surface registration isn't supported inside an iframe.733top-level document. Surface registration isn't supported inside an iframe.
527 734
528This example draws a revenue bar and makes it selectable. Put the script after735### Start with picking and identity
529the canvas:736
737This example draws a revenue bar and makes it selectable using only `hitTest`
738and a stable object ID. Put the script after the canvas:
530 739
531```html740```html
532<canvas id="revenue-canvas" width="480" height="240">741<canvas id="revenue-canvas" width="480" height="240">
539const context = canvas.getContext("2d");748const context = canvas.getContext("2d");
540const bar = { x: 40, y: 60, width: 320, height: 100 };749const bar = { x: 40, y: 60, width: 320, height: 100 };
541 750
542function drawRevenue(highlighted = false) {751context.fillStyle = "#2563eb";
543 context.clearRect(0, 0, canvas.width, canvas.height);752context.fillRect(bar.x, bar.y, bar.width, bar.height);
544 context.fillStyle = "#2563eb";
545 context.fillRect(bar.x, bar.y, bar.width, bar.height);
546 if (highlighted) {
547 context.strokeStyle = "#111827";
548 context.lineWidth = 3;
549 context.strokeRect(bar.x, bar.y, bar.width, bar.height);
550 }
551}
552
553drawRevenue();
554 753
555const surface = document.oai?.annotation?.registerSurface?.({754const surface = document.oai?.annotation?.registerSurface?.({
556 element: canvas,755 element: canvas,
574 return null;773 return null;
575 }774 }
576 775
577 return {776 return { id: "revenue-this-quarter" };
578 id: "revenue-this-quarter",
579 name: "Revenue this quarter",
580 role: "chart-bar",
581 metadata: { Metric: "Revenue", Value: 120000 },
582 rect,
583 };
584 },
585 renderSelection({ hoveredId, selectedId }) {
586 drawRevenue(
587 hoveredId === "revenue-this-quarter" ||
588 selectedId === "revenue-this-quarter"
589 );
590 },777 },
591});778});
592```779```
593 780
594Hovering over the bar in Annotation mode highlights it. Selecting it opens an781<div
595annotation with the object's name, metadata, and a screenshot of the selection.782 id="try-annotation-canvas"
783 className="annotation-request-demo not-prose"
784 data-annotation-example="canvas"
785 hidden
786>
787 **Try it**
788
789
790
791
792 <canvas
793 className="annotation-canvas-preview"
794 data-example-canvas
795 oai-annotatable=""
796 width="640"
797 height="360"
798 >
799
800Circle
801
802
803Triangle
804
805
806Square
596 807
597Keep IDs stable within a surface. The optional `name` is visible to the user;808
598`role` gives a short semantic description. The optional `rect` uses CSS pixels809Star
599relative to the visible page area, matching `clientX` and `clientY`. Convert from scene coordinates,810
600including scale, pan, and zoom.811 </canvas>
812
813
814
815
816 Enter Annotation mode, then select a shape to see its name, ID, and fill
817 color in the annotation. The space between shapes is not selectable.
818
819
820
821
822
823
824
825In the code sample, selecting the bar in Annotation mode identifies it as
826`revenue-this-quarter`.
827Keep IDs stable within a surface. The example returns `null` for empty space.
601 828
602`hitTest` can return a promise and receives an `AbortSignal` as `signal` to829`hitTest` can return a promise and receives an `AbortSignal` as `signal` to
603cancel superseded work. The browser allows 250 milliseconds before falling830cancel superseded work. The browser allows 250 milliseconds before falling
610settle the pending promise when its work is aborted; dropping a canceled worker837settle the pending promise when its work is aborted; dropping a canceled worker
611response can block subsequent canvas picking.838response can block subsequent canvas picking.
612 839
613Use the optional `renderSelection` callback for application-specific feedback.840Call `surface?.invalidate()` after moving objects or changing zoom, and
614Clear feedback when both IDs are `null`. Call `surface?.invalidate()` after841`surface?.dispose()` when removing the integration. Both return synchronously
615moving objects or changing zoom, and `surface?.dispose()` when removing the842without a value.
616integration. Both return synchronously without a value.843
844### Add optional object context
845
846The `id` is enough to identify the object. To include a display name, semantic
847role, hidden context, or selection bounds, replace the successful return in
848the preceding `hitTest` callback with:
849
850```javascript
851return {
852 id: "revenue-this-quarter",
853 name: "Revenue this quarter",
854 role: "chart-bar",
855 metadata: { metricId: "quarterly-revenue" },
856 rect,
857};
858```
859
860The optional `name` is visible to the user; `role` gives a short semantic
861description. Include metadata only when it adds context that isn't already
862visible. The optional `rect` uses CSS pixels relative to the visible page area,
863matching `clientX` and `clientY`. The picking example already computes this
864rectangle. Convert from scene coordinates, including scale, pan, and zoom.
865
866### Add optional selection feedback
867
868Use the optional `renderSelection` callback to draw your own hover and
869selection feedback. It doesn't require the optional context fields.
870
871For the canvas above, define this function and add
872`renderSelection: renderRevenueSelection` to the `registerSurface()` options:
873
874```javascript
875function renderRevenueSelection({ hoveredId, selectedId }) {
876 context.clearRect(0, 0, canvas.width, canvas.height);
877 context.fillStyle = "#2563eb";
878 context.fillRect(bar.x, bar.y, bar.width, bar.height);
879
880 if (
881 hoveredId === "revenue-this-quarter" ||
882 selectedId === "revenue-this-quarter"
883 ) {
884 context.strokeStyle = "#111827";
885 context.lineWidth = 3;
886 context.strokeRect(bar.x, bar.y, bar.width, bar.height);
887 }
888}
889```
890
891This callback redraws the bar with an outline while it's hovered or selected.
892When both IDs are `null`, it redraws the bar without an outline.
617 893
618### Add controls to canvas objects894### Add controls to canvas objects
619 895