@botiverse/porthole 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/annotate/types.d.ts +120 -0
  2. package/dist/annotate/types.js +42 -0
  3. package/dist/annotate/validate.d.ts +11 -0
  4. package/dist/annotate/validate.js +156 -0
  5. package/dist/bridge/bridge.d.ts +4 -0
  6. package/dist/bridge/bridge.js +12 -1
  7. package/dist/compile/policy.d.ts +4 -3
  8. package/dist/compile/policy.js +7 -4
  9. package/dist/config/capabilities.d.ts +37 -4
  10. package/dist/config/capabilities.js +20 -0
  11. package/dist/conformance/clipboard.d.ts +2 -0
  12. package/dist/conformance/clipboard.js +167 -0
  13. package/dist/conformance/kit.d.ts +1 -1
  14. package/dist/conformance/probes.js +2 -0
  15. package/dist/guide/guide.js +8 -4
  16. package/dist/host/clipboard.d.ts +11 -0
  17. package/dist/host/clipboard.js +43 -0
  18. package/dist/host/create-host.js +141 -0
  19. package/dist/host/types.d.ts +24 -0
  20. package/dist/index.d.ts +6 -3
  21. package/dist/index.js +6 -2
  22. package/dist/mcp/adapter.js +10 -1
  23. package/dist/protocol/mcp-apps.d.ts +5 -0
  24. package/dist/protocol/mcp-apps.js +5 -0
  25. package/dist/protocol/shell-adapter.js +6 -2
  26. package/dist/react/dialog.js +2 -0
  27. package/dist/react/frame.d.ts +18 -0
  28. package/dist/react/frame.js +82 -2
  29. package/dist/react/index.d.ts +2 -1
  30. package/dist/react/index.js +2 -1
  31. package/dist/react/toast.d.ts +33 -0
  32. package/dist/react/toast.js +42 -0
  33. package/dist/shell/annotate.d.ts +20 -0
  34. package/dist/shell/annotate.js +669 -0
  35. package/dist/shell/document.js +4 -1
  36. package/dist/shell/plan.d.ts +2 -0
  37. package/dist/shell/plan.js +2 -1
  38. package/dist/shell/runtime.d.ts +4 -1
  39. package/dist/shell/runtime.js +38 -2
  40. package/package.json +1 -1
  41. package/src/annotate/types.ts +126 -0
  42. package/src/annotate/validate.ts +186 -0
  43. package/src/bridge/bridge.ts +13 -0
  44. package/src/compile/policy.ts +7 -4
  45. package/src/config/capabilities.ts +33 -3
  46. package/src/conformance/clipboard.ts +184 -0
  47. package/src/conformance/kit.ts +1 -1
  48. package/src/conformance/probes.ts +2 -0
  49. package/src/guide/guide.ts +19 -4
  50. package/src/host/clipboard.ts +41 -0
  51. package/src/host/create-host.ts +155 -0
  52. package/src/host/types.ts +25 -0
  53. package/src/index.ts +22 -0
  54. package/src/mcp/adapter.ts +10 -1
  55. package/src/protocol/mcp-apps.ts +6 -0
  56. package/src/protocol/shell-adapter.ts +11 -2
  57. package/src/react/dialog.tsx +2 -0
  58. package/src/react/frame.tsx +117 -1
  59. package/src/react/index.ts +9 -1
  60. package/src/react/toast.tsx +96 -0
  61. package/src/shell/annotate.ts +669 -0
  62. package/src/shell/document.ts +6 -1
  63. package/src/shell/plan.ts +4 -1
  64. package/src/shell/runtime.ts +38 -2
@@ -1,5 +1,5 @@
1
1
  import { JSON_RPC_ERRORS, parseJsonRpc, } from "./jsonrpc.js";
2
- import { MCP_APPS_METHODS as M, MCP_APPS_PROTOCOL_VERSION } from "./mcp-apps.js";
2
+ import { COPY_TEXT_METHOD, MCP_APPS_METHODS as M, MCP_APPS_PROTOCOL_VERSION, } from "./mcp-apps.js";
3
3
  /**
4
4
  * The core's adapter: the host side of the MCP Apps subset the shell speaks, which content using
5
5
  * the official App SDK speaks too. Every message is checked before use; every power goes through
@@ -28,6 +28,7 @@ const REQUESTS = {
28
28
  }),
29
29
  },
30
30
  [M.openLink]: { capability: "openLink", args: (params) => ({ url: params.url }) },
31
+ [COPY_TEXT_METHOD]: { capability: "copyText", args: (params) => ({ text: params.text }) },
31
32
  [M.downloadFile]: {
32
33
  capability: "downloadFile",
33
34
  args: (params) => ({ contents: params.contents }),
@@ -44,7 +45,10 @@ const REQUESTS = {
44
45
  };
45
46
  /** What a capability's outcome looks like to the view. */
46
47
  function answer(capability, outcome) {
47
- const flagged = capability === "sendPrompt" || capability === "openLink" || capability === "downloadFile";
48
+ const flagged = capability === "sendPrompt" ||
49
+ capability === "openLink" ||
50
+ capability === "downloadFile" ||
51
+ capability === "copyText";
48
52
  if (!outcome.ok) {
49
53
  return flagged
50
54
  ? { result: { isError: true } }
@@ -47,6 +47,8 @@ function words(request) {
47
47
  return { heading: "Share this with the agent?", accept: "Share", detail: json(request.args) };
48
48
  case "readData":
49
49
  return { heading: "Let it read this?", accept: "Allow", detail: request.args.uri };
50
+ case "copyText":
51
+ return { heading: "Copy this text?", accept: "Copy", detail: request.args.text };
50
52
  default:
51
53
  return { heading: "Allow this?", accept: "Allow", detail: json(request.args) };
52
54
  }
@@ -1,4 +1,5 @@
1
1
  import { type CSSProperties, type ReactElement, type ReactNode } from "react";
2
+ import type { AnnotateMode, AnnotationMark, AnnotationReport } from "../annotate/types.ts";
2
3
  import type { CapabilityHandlers, Confirm, Refusal } from "../bridge/types.ts";
3
4
  import type { CapabilityName } from "../config/capabilities.ts";
4
5
  import type { ColorScheme, PortholeConfig } from "../config/schema.ts";
@@ -21,6 +22,8 @@ export interface PortholeProviderProps {
21
22
  readonly locator?: InstanceLocator;
22
23
  /** A class for the default dialog, to restyle it. */
23
24
  readonly dialogClassName?: string;
25
+ /** A class for the default copy toast, to restyle it. */
26
+ readonly toastClassName?: string;
24
27
  readonly children?: ReactNode;
25
28
  }
26
29
  export declare function PortholeProvider(props: PortholeProviderProps): ReactElement;
@@ -83,6 +86,21 @@ export interface PortholeFrameProps {
83
86
  * restyle): the running frame reads them again.
84
87
  */
85
88
  readonly themeKey?: string | number;
89
+ /**
90
+ * Annotations, where the config declares `annotate` (P053): what the person may point at now
91
+ * (elements, text, or both), or null to stop. A picked element ends element picking: set it
92
+ * again to pick again.
93
+ */
94
+ readonly annotating?: AnnotateMode | null;
95
+ /** The anchors to mark in the frame (the host's comments). Keep it stable (memoized). */
96
+ readonly annotations?: readonly AnnotationMark[];
97
+ /** The mark to bring into view and make the active one; each new id focuses it. */
98
+ readonly focusedAnnotation?: string | null;
99
+ /**
100
+ * What the frame reported: a selection (an anchor and where it is), its clearing, a cancelled
101
+ * pick, where marks are. The content's word: UI hints, never permissions.
102
+ */
103
+ readonly onAnnotation?: (report: AnnotationReport) => void;
86
104
  /** Draws the placeholder instead of the default one. */
87
105
  readonly renderPlaceholder?: (props: PlaceholderProps) => ReactNode;
88
106
  readonly className?: string;
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createContext, useCallback, useContext, useEffect, useEffectEvent, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, } from "react";
3
+ import { writeClipboard } from "../host/clipboard.js";
3
4
  import { createHost } from "../host/create-host.js";
4
5
  import { ConfirmDialog } from "./dialog.js";
5
6
  import { createLiveFrames } from "./live.js";
7
+ import { CopyToast } from "./toast.js";
6
8
  /**
7
9
  * The React binding: a `<PortholeProvider>` near the root owns what is per page (which frames
8
10
  * run, the heights displays reported, and the default confirmation dialog); a `<PortholeFrame>` per
@@ -28,14 +30,24 @@ export function PortholeProvider(props) {
28
30
  const confirm = useCallback((request, proceed) => own.current !== undefined
29
31
  ? own.current(request, proceed)
30
32
  : new Promise((resolve) => setQueue((waiting) => [...waiting, { request, resolve, proceed }])), []);
33
+ // The default copyText: the host's page writes the clipboard (in the frame's click, which
34
+ // activated this page too) and shows the person what went on it, or offers to copy it.
35
+ const [notice, setNotice] = useState(null);
36
+ const copyText = useCallback(async ({ text }, context) => {
37
+ const copied = await writeClipboard(text, globalThis.document);
38
+ setNotice({ text, copied, title: context.title });
39
+ return copied;
40
+ }, []);
41
+ const closeNotice = useCallback(() => setNotice(null), []);
31
42
  const { hostInfo, locator } = props;
32
43
  const value = useMemo(() => ({
33
44
  live,
34
45
  heights,
35
46
  confirm,
47
+ copyText,
36
48
  ...(hostInfo === undefined ? {} : { hostInfo }),
37
49
  ...(locator === undefined ? {} : { locator }),
38
- }), [live, heights, confirm, hostInfo, locator]);
50
+ }), [live, heights, confirm, copyText, hostInfo, locator]);
39
51
  const current = queue[0] ?? null;
40
52
  return (_jsxs(Page.Provider, { value: value, children: [props.children, props.confirm === undefined ? (_jsx(ConfirmDialog, { request: current?.request ?? null, ...(props.dialogClassName === undefined ? {} : { className: props.dialogClassName }), onAnswer: (accepted) => {
41
53
  // A yes runs the effect inside this very click (a window it opens is the person's).
@@ -44,7 +56,10 @@ export function PortholeProvider(props) {
44
56
  }
45
57
  current?.resolve(accepted ? "accepted" : "declined");
46
58
  setQueue((waiting) => waiting.slice(1));
47
- } })) : null] }));
59
+ } })) : null, _jsx(CopyToast, { notice: notice, ...(props.toastClassName === undefined ? {} : { className: props.toastClassName }), onClose: closeNotice, onCopy: (failed) => {
60
+ // A click on the host's own page: the write has the gesture it needs now.
61
+ void writeClipboard(failed.text, globalThis.document).then((copied) => setNotice({ ...failed, copied }));
62
+ } })] }));
48
63
  }
49
64
  const PLACEHOLDER_STYLE = {
50
65
  position: "absolute",
@@ -163,6 +178,9 @@ function Mount({ frame, page, initialHeightPx, placeholder, run }) {
163
178
  case "outcome":
164
179
  frame.onOutcome?.(detail);
165
180
  return;
181
+ case "annotation":
182
+ frame.onAnnotation?.(detail);
183
+ return;
166
184
  }
167
185
  });
168
186
  useEffect(() => {
@@ -183,6 +201,13 @@ function Mount({ frame, page, initialHeightPx, placeholder, run }) {
183
201
  for (const name of Object.keys(first.handlers ?? {})) {
184
202
  handlers[name] = (args, context) => latest().handlers?.[name]?.(args, named(context));
185
203
  }
204
+ // Copy buttons without a handler of the host's: the provider's, which shows a toast.
205
+ if (config.capabilities.copyText !== undefined && handlers.copyText === undefined) {
206
+ const fallback = page.copyText;
207
+ if (fallback !== undefined) {
208
+ handlers.copyText = (args, context) => fallback(args, named(context));
209
+ }
210
+ }
186
211
  const asks = first.confirm !== undefined || page.confirm !== undefined;
187
212
  let display;
188
213
  try {
@@ -218,10 +243,14 @@ function Mount({ frame, page, initialHeightPx, placeholder, run }) {
218
243
  "refused",
219
244
  "muted",
220
245
  "outcome",
246
+ "annotation",
221
247
  ];
222
248
  for (const event of events) {
223
249
  display.on(event, (detail) => notify(event, detail));
224
250
  }
251
+ if (config.capabilities.annotate !== undefined) {
252
+ applyAnnotations(display, first);
253
+ }
225
254
  if (first.content !== undefined) {
226
255
  display.setContent(first.content, { partial: first.partial === true });
227
256
  }
@@ -237,6 +266,34 @@ function Mount({ frame, page, initialHeightPx, placeholder, run }) {
237
266
  handle.current?.setContent(content, { partial: partial === true });
238
267
  }
239
268
  }, [content, partial]);
269
+ // Annotations follow their props (a new display gets them all when it is made, above).
270
+ const annotates = config.capabilities.annotate !== undefined;
271
+ const { annotating, annotations, focusedAnnotation } = frame;
272
+ const mode = annotating === undefined || annotating === null
273
+ ? ""
274
+ : JSON.stringify([annotating.elements ?? true, annotating.text ?? true]);
275
+ useEffect(() => {
276
+ const display = handle.current;
277
+ if (!annotates || display === null) {
278
+ return;
279
+ }
280
+ const wanted = mode === "" ? null : JSON.parse(mode);
281
+ attempt(() => wanted === null
282
+ ? display.annotations.stop()
283
+ : display.annotations.start({ elements: wanted[0], text: wanted[1] }), latest());
284
+ }, [annotates, mode]);
285
+ useEffect(() => {
286
+ const display = handle.current;
287
+ if (annotates && display !== null) {
288
+ attempt(() => display.annotations.show(annotations ?? []), latest());
289
+ }
290
+ }, [annotates, annotations]);
291
+ useEffect(() => {
292
+ const display = handle.current;
293
+ if (annotates && display !== null && typeof focusedAnnotation === "string") {
294
+ attempt(() => display.annotations.focus(focusedAnnotation), latest());
295
+ }
296
+ }, [annotates, focusedAnnotation]);
240
297
  useEffect(() => {
241
298
  handle.current?.setTheme(scheme);
242
299
  }, [scheme]);
@@ -253,3 +310,26 @@ function Mount({ frame, page, initialHeightPx, placeholder, run }) {
253
310
  ...(ready ? {} : { minHeight: Math.max(startHeight, PLACEHOLDER_HEIGHT_PX) }),
254
311
  }, children: [_jsx("div", { ref: holder, "data-porthole-mount": "", style: { opacity: ready ? 1 : 0, ...(fills ? { height: "100%" } : {}) } }), ready ? null : placeholder({ state: "loading", title: frame.title, run })] }));
255
312
  }
313
+ /** Runs an annotation call, telling the frame's `onError` what it threw (a bad anchor, say). */
314
+ function attempt(run, frame) {
315
+ try {
316
+ run();
317
+ }
318
+ catch (error) {
319
+ frame.onError?.(error instanceof Error ? error.message : String(error));
320
+ }
321
+ }
322
+ /** A new display's annotations, from the props as they are. */
323
+ function applyAnnotations(display, frame) {
324
+ attempt(() => {
325
+ if (frame.annotations !== undefined && frame.annotations.length > 0) {
326
+ display.annotations.show(frame.annotations);
327
+ }
328
+ if (typeof frame.focusedAnnotation === "string") {
329
+ display.annotations.focus(frame.focusedAnnotation);
330
+ }
331
+ if (frame.annotating !== undefined && frame.annotating !== null) {
332
+ display.annotations.start(frame.annotating);
333
+ }
334
+ }, frame);
335
+ }
@@ -2,7 +2,7 @@
2
2
  * `@botiverse/porthole/react`: the thin React binding (TSX: a host's bundler compiles it, or the
3
3
  * package's `dist`). Peer: `react` (19.2+, for `useEffectEvent`).
4
4
  *
5
- * <PortholeProvider> // per page: live-frame cap, heights, the dialog
5
+ * <PortholeProvider> // per page: live-frame cap, heights, the dialog, the copy toast
6
6
  * <PortholeFrame config={widgetConfig} displayId={id} title={title} scheme={scheme}
7
7
  * content={code} partial={streaming} handlers={{ sendPrompt, openLink }}
8
8
  * theme={tokensFor} onError={report} />
@@ -10,4 +10,5 @@
10
10
  */
11
11
  export { PLACEHOLDER_HEIGHT_PX, PortholeFrame, PortholeProvider, type PlaceholderProps, type PortholeFrameProps, type PortholeProviderProps, } from "./frame.tsx";
12
12
  export { ARM_DELAY_MS, ConfirmDialog, type ConfirmDialogProps } from "./dialog.tsx";
13
+ export { CopyToast, TOAST_CHARS, TOAST_MS, clipped, type CopyNotice, type CopyToastProps, } from "./toast.tsx";
13
14
  export { admitFrame, createLiveFrames, type FrameState, type LiveFrames } from "./live.ts";
@@ -2,7 +2,7 @@
2
2
  * `@botiverse/porthole/react`: the thin React binding (TSX: a host's bundler compiles it, or the
3
3
  * package's `dist`). Peer: `react` (19.2+, for `useEffectEvent`).
4
4
  *
5
- * <PortholeProvider> // per page: live-frame cap, heights, the dialog
5
+ * <PortholeProvider> // per page: live-frame cap, heights, the dialog, the copy toast
6
6
  * <PortholeFrame config={widgetConfig} displayId={id} title={title} scheme={scheme}
7
7
  * content={code} partial={streaming} handlers={{ sendPrompt, openLink }}
8
8
  * theme={tokensFor} onError={report} />
@@ -10,4 +10,5 @@
10
10
  */
11
11
  export { PLACEHOLDER_HEIGHT_PX, PortholeFrame, PortholeProvider, } from "./frame.js";
12
12
  export { ARM_DELAY_MS, ConfirmDialog } from "./dialog.js";
13
+ export { CopyToast, TOAST_CHARS, TOAST_MS, clipped, } from "./toast.js";
13
14
  export { admitFrame, createLiveFrames } from "./live.js";
@@ -0,0 +1,33 @@
1
+ import { type ReactElement } from "react";
2
+ /**
3
+ * The default copy notice (P052): when content's `copyText` copied something, the host's page
4
+ * shows the person what went on their clipboard, clipped, in a small toast outside every frame.
5
+ * If the write failed (no recent gesture), it says so and offers a Copy button: a click on the
6
+ * host's own page, which writes it. A host replaces it by passing its own `copyText` handler; it
7
+ * styles it through the `porthole-toast` class and data attributes.
8
+ */
9
+ /** How long a toast for a copy that worked stays. */
10
+ export declare const TOAST_MS = 5000;
11
+ /** The most of the copied text a toast shows. */
12
+ export declare const TOAST_CHARS = 280;
13
+ export interface CopyNotice {
14
+ /** The whole text copied (or to copy). */
15
+ readonly text: string;
16
+ /** Whether it is on the clipboard. */
17
+ readonly copied: boolean;
18
+ /** The display's title, as the host named it. */
19
+ readonly title: string;
20
+ }
21
+ export interface CopyToastProps {
22
+ readonly notice: CopyNotice | null;
23
+ /** The person asked to copy it again (it failed), from this click. */
24
+ readonly onCopy: (notice: CopyNotice) => void;
25
+ readonly onClose: () => void;
26
+ readonly className?: string;
27
+ }
28
+ /** The text as the toast shows it: at most a few lines and `TOAST_CHARS` characters. */
29
+ export declare function clipped(text: string): {
30
+ readonly shown: string;
31
+ readonly clipped: boolean;
32
+ };
33
+ export declare function CopyToast({ notice, onCopy, onClose, className }: CopyToastProps): ReactElement;
@@ -0,0 +1,42 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect } from "react";
3
+ /**
4
+ * The default copy notice (P052): when content's `copyText` copied something, the host's page
5
+ * shows the person what went on their clipboard, clipped, in a small toast outside every frame.
6
+ * If the write failed (no recent gesture), it says so and offers a Copy button: a click on the
7
+ * host's own page, which writes it. A host replaces it by passing its own `copyText` handler; it
8
+ * styles it through the `porthole-toast` class and data attributes.
9
+ */
10
+ /** How long a toast for a copy that worked stays. */
11
+ export const TOAST_MS = 5_000;
12
+ /** The most of the copied text a toast shows. */
13
+ export const TOAST_CHARS = 280;
14
+ const TOAST_LINES = 4;
15
+ /** The text as the toast shows it: at most a few lines and `TOAST_CHARS` characters. */
16
+ export function clipped(text) {
17
+ const lines = text.split("\n");
18
+ let shown = lines.slice(0, TOAST_LINES).join("\n");
19
+ if (shown.length > TOAST_CHARS) {
20
+ shown = shown.slice(0, TOAST_CHARS);
21
+ }
22
+ return shown.length < text.length
23
+ ? { shown: `${shown}…`, clipped: true }
24
+ : { shown, clipped: false };
25
+ }
26
+ const STYLE = `.porthole-toast{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:2147483647;color-scheme:light dark;color:CanvasText;background:Canvas;border:1px solid color-mix(in srgb,CanvasText 18%,transparent);border-radius:12px;padding:12px 14px;width:min(420px,calc(100vw - 32px));box-shadow:0 8px 28px rgb(0 0 0 / .25);font:inherit;font-size:.9em}
27
+ .porthole-toast p{margin:0 0 6px}
28
+ .porthole-toast [data-porthole-source]{opacity:.72}
29
+ .porthole-toast pre{margin:0;white-space:pre-wrap;overflow-wrap:anywhere;max-height:8em;overflow:hidden;padding:8px 10px;border-radius:8px;background:color-mix(in srgb,CanvasText 7%,transparent);font:12px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace}
30
+ .porthole-toast menu{display:flex;justify-content:flex-end;gap:8px;margin:8px 0 0;padding:0}
31
+ .porthole-toast button{font:inherit;min-height:32px;padding:0 12px;border-radius:8px;border:1px solid color-mix(in srgb,CanvasText 25%,transparent);background:transparent;color:inherit;cursor:pointer}`;
32
+ export function CopyToast({ notice, onCopy, onClose, className }) {
33
+ useEffect(() => {
34
+ if (notice === null || !notice.copied) {
35
+ return;
36
+ }
37
+ const timer = setTimeout(onClose, TOAST_MS);
38
+ return () => clearTimeout(timer);
39
+ }, [notice, onClose]);
40
+ const view = notice === null ? null : clipped(notice.text);
41
+ return (_jsxs("div", { role: "status", "aria-live": "polite", className: className === undefined ? "porthole-toast" : `porthole-toast ${className}`, "data-porthole-toast": "", "data-porthole-copied": notice === null ? "" : String(notice.copied), hidden: notice === null, children: [_jsx("style", { children: STYLE }), notice === null || view === null ? null : (_jsxs(_Fragment, { children: [_jsxs("p", { children: [_jsx("strong", { children: notice.copied ? "Copied" : "Not copied yet" }), " ", _jsx("span", { "data-porthole-source": "", children: `from “${notice.title}”` })] }), _jsx("pre", { "data-porthole-copied-text": "", children: view.shown }), view.clipped ? (_jsx("p", { "data-porthole-length": "", children: `${notice.text.length.toLocaleString("en-US")} characters in all` })) : null, _jsxs("menu", { children: [notice.copied ? null : (_jsx("button", { type: "button", "data-porthole-copy": "", onClick: () => onCopy(notice), children: "Copy" })), _jsx("button", { type: "button", "data-porthole-close": "", onClick: onClose, children: notice.copied ? "OK" : "Close" })] })] }))] }));
42
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The shell's annotation runtime (P053), spliced into the shell's runtime (`__ANNOTATE__`) when
3
+ * the config declares `annotate`, so it runs before any content does, with the DOM's own
4
+ * functions as they were. Plain ES2020 like the rest of the shell; no template literals (it sits
5
+ * in one).
6
+ *
7
+ * The host drives it (`porthole/annotate.start|stop|show|focus|clear`) and it reports
8
+ * (`porthole/annotate.selected|unselected|cancelled|anchored`):
9
+ * - picking an element: the element under the pointer (or finger) is outlined with its name; a
10
+ * click or tap picks it, and the content's own handlers never see that click; Escape cancels;
11
+ * - selecting text: each selection that settles is reported, with where it is, and its clearing;
12
+ * - marks: each anchor is found in the content as it is now (its CSS selector, its text where it
13
+ * was, its text anywhere, its words with any spacing) and drawn over it, in a layer of the
14
+ * shell's own (a closed shadow root), again whenever the content changes; what can't be found
15
+ * is reported `orphaned`.
16
+ *
17
+ * It shares the realm with the content, which can interfere with all of it: the host takes its
18
+ * reports as UI hints only.
19
+ */
20
+ export declare const ANNOTATE_RUNTIME: string;