@zumer/snapdom-plugins 2.2.2 → 3.0.0-beta.0

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ZumerLab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,13 +1,22 @@
1
1
  # @zumer/snapdom-plugins
2
2
 
3
- Official plugins for [SnapDOM](https://github.com/zumerlab/snapdom) — extend and transform DOM captures with zero core changes.
3
+ Official plugins for [SnapDOM](https://github.com/zumerlab/snapdom), a capture engine for
4
+ web apps. Core exports images and canvases; this package adds HTML, structured context,
5
+ element maps, PDF, ASCII, visual transforms and live GIF/video recording.
6
+
7
+ Image, HTML and structured exports use captured state; GIF and video start a sequence of
8
+ live captures when their export method is called.
4
9
 
5
10
  ## Install
6
11
 
12
+ Install the plugins next to a matching core:
13
+
7
14
  ```bash
8
- npm install @zumer/snapdom-plugins
15
+ npm install @zumer/snapdom @zumer/snapdom-plugins
9
16
  ```
10
17
 
18
+ Plugins 3.x need a 3.x core; npm refuses to install them next to an older core.
19
+
11
20
  ## Usage
12
21
 
13
22
  Import plugins individually (recommended for tree-shaking):
@@ -23,7 +32,7 @@ const result = await snapdom(element, {
23
32
  const png = await result.toPng();
24
33
  ```
25
34
 
26
- Or import everything at once:
35
+ Or use the package entry point:
27
36
 
28
37
  ```js
29
38
  import { filter, timestampOverlay, replaceText } from '@zumer/snapdom-plugins';
@@ -42,7 +51,8 @@ import { filter } from 'https://esm.sh/@zumer/snapdom-plugins/filter';
42
51
 
43
52
  ### `filter`
44
53
 
45
- Applies CSS filter effects to the captured clone.
54
+ Applies CSS filter effects to the clone, overriding authored filters. `preset` takes
55
+ precedence over `filter`; an unknown preset logs a warning and `filter` applies.
46
56
 
47
57
  ```js
48
58
  import { filter } from '@zumer/snapdom-plugins/filter';
@@ -59,7 +69,7 @@ snapdom(el, { plugins: [filter({ preset: 'grayscale' })] });
59
69
 
60
70
  ### `timestamp-overlay`
61
71
 
62
- Adds a translucent timestamp label to the captured clone.
72
+ Adds a timestamp label. The clock or custom formatter runs for every capture.
63
73
 
64
74
  ```js
65
75
  import { timestampOverlay } from '@zumer/snapdom-plugins/timestamp-overlay';
@@ -79,7 +89,8 @@ snapdom(el, { plugins: [timestampOverlay({ position: 'top-right', format: 'date'
79
89
 
80
90
  ### `replace-text`
81
91
 
82
- Find-and-replace text in the captured clone. Supports strings and regex.
92
+ Replaces DOM text in the clone using strings or regular expressions. Stylesheet and script
93
+ text are left unchanged.
83
94
 
84
95
  ```js
85
96
  import { replaceText } from '@zumer/snapdom-plugins/replace-text';
@@ -100,9 +111,85 @@ snapdom(el, {
100
111
 
101
112
  ---
102
113
 
114
+ ### `redact-inputs`
115
+
116
+ Masks input and textarea values, excludes selected blocks, and removes named attributes
117
+ from captured outputs. Core already masks password fields to match their visible bullets;
118
+ other field values remain visible unless you redact them. The live page stays unchanged.
119
+
120
+ ```js
121
+ import { redactInputs } from '@zumer/snapdom-plugins/redact-inputs';
122
+
123
+ // Defaults: email, tel, and the cc-* / current-password / new-password / one-time-code
124
+ // autocomplete tokens.
125
+ snapdom(el, { plugins: [redactInputs()] });
126
+
127
+ // Everything, including textareas:
128
+ snapdom(el, { plugins: [redactInputs({ all: true })] });
129
+
130
+ // App-specific fields, blanked rather than bulleted:
131
+ snapdom(el, { plugins: [redactInputs({ selector: '[data-private]', mask: () => '' })] });
132
+
133
+ // Exclude whole blocks and remove metadata attributes:
134
+ const redaction = redactInputs({
135
+ blocks: ['.private-panel', '[data-private-block]'],
136
+ attributes: [
137
+ { selector: '[data-token]', names: ['data-token'] },
138
+ { selector: '.customer', names: ['title', 'aria-label'] }
139
+ ]
140
+ });
141
+ snapdom(el, { plugins: [redaction] });
142
+
143
+ // Remove those blocks from the layout as well:
144
+ snapdom(el, { excludeMode: 'remove', plugins: [redaction] });
145
+ ```
146
+
147
+ The default mask keeps the string's length. Glyph widths can differ, so wrapping and truncation may change.
148
+ A `<select>` is not masked (its visible option label stays); hide it with a `blocks` rule instead.
149
+
150
+ | Option | Type | Default | Description |
151
+ |--------|------|---------|-------------|
152
+ | `types` | `string[]` | `['email', 'tel']` | Input `type` attribute values to redact |
153
+ | `autocomplete` | `string[]` | `['cc-*', 'current-password', 'new-password', 'one-time-code']` | Autocomplete tokens; a trailing `*` matches by prefix |
154
+ | `selector` | `string` | `''` | Extra CSS selector for inputs and textareas |
155
+ | `all` | `boolean` | `false` | Redact every input and textarea, regardless of `types`, `autocomplete` or `selector` |
156
+ | `mask` | `(value, el) => string` | same-length bullets | Custom masker |
157
+ | `blocks` | `string \| string[]` | `[]` | CSS selectors for whole subtrees to exclude; uses the capture's `excludeMode` |
158
+ | `attributes` | `Array<{ selector: string, names: string[] }>` | `[]` | Remove exact named attributes from clones of matching source elements and their semantic projections; names are not wildcard patterns |
159
+
160
+ `blocks` keeps an invisible space by default (`excludeMode: 'hide'`); `'remove'` drops the
161
+ subtree from the captured layout. It combines with the capture's existing `exclude` rules.
162
+ If the capture root itself belongs to a blocked subtree, `'remove'` throws; use `'hide'`
163
+ for that capture. Selectors match source elements, including nodes in open shadow trees.
164
+
165
+ `attributes` removes the listed DOM attributes and their projections in `agent-map` and
166
+ `context-export`, such as accessible names or structured state. It does not erase copies
167
+ of those values already materialized as visible text, CSS `content`, or bitmap pixels.
168
+ Use `blocks` to hide the subtree that paints visible content. A `value` attribute rule on
169
+ an input or textarea clears its displayed value and omits its structured `state.value`.
170
+ Existing default field masks still apply when you add block or attribute rules.
171
+
172
+ HTML exports use the sanitized clone. `agent-map` and `context-export` apply the same
173
+ block, attribute and field rules to their source-derived data, regardless of official
174
+ plugin order. The rules do not search arbitrary text or images for sensitive content.
175
+
176
+ The built-in mask without custom selectors permits reuse of unchanged captures. Custom
177
+ masks and nonempty `selector`, `blocks` or `attributes` rules run on every capture. Changed
178
+ content runs the full pipeline so no field skips redaction.
179
+
180
+ Nonempty `blocks` or `attributes` rules add a final `beforeRender` pass. In builds with the
181
+ experimental `engine: 'html-in-canvas'` enabled, that pass makes the capture use the SVG
182
+ renderer. Field-only redaction does not add this pass.
183
+
184
+ `agent-map` and `context-export` omit sensitive values and mask other typed values in their
185
+ structured state fields. Ordinary page text and labels remain. An attached image still
186
+ follows core's capture policy; use this plugin to mask its visible fields too.
187
+
188
+ ---
189
+
103
190
  ### `color-tint`
104
191
 
105
- Tints the entire capture to a specified color using a `mix-blend-mode` overlay.
192
+ Tints an HTML capture with a `mix-blend-mode` overlay. Opacity `0` leaves it unchanged.
106
193
 
107
194
  ```js
108
195
  import { colorTint } from '@zumer/snapdom-plugins/color-tint';
@@ -135,11 +222,14 @@ console.log(art);
135
222
  | `charset` | `string` | `' .:-=+*#%@'` | Characters from lightest to darkest |
136
223
  | `invert` | `boolean` | `false` | Invert luminance mapping |
137
224
 
225
+ `toAscii({ width, charset, invert })` overrides any of the three per call.
226
+
138
227
  ---
139
228
 
140
229
  ### `pdf-image`
141
230
 
142
- Exports the capture as a PNG embedded in a downloadable PDF (A4). Adds a `toPdfImage()` method.
231
+ Adds `toPdfImage()`, which downloads a single-page A4 PDF containing a JPEG of the capture.
232
+ Text is part of the image. The method returns a temporary object URL, revoked after five seconds.
143
233
 
144
234
  ```js
145
235
  import { pdfImage } from '@zumer/snapdom-plugins/pdf-image';
@@ -156,23 +246,10 @@ await result.toPdfImage(); // triggers download
156
246
 
157
247
  ---
158
248
 
159
- ### `html-in-canvas`
160
-
161
- Uses the experimental [WICG `drawElementImage`](https://github.com/WICG/canvas-place-element) API for direct DOM-to-canvas rendering where supported. Falls back gracefully.
162
-
163
- ```js
164
- import { htmlInCanvas } from '@zumer/snapdom-plugins/html-in-canvas';
165
-
166
- snapdom(el, { plugins: [htmlInCanvas()] });
167
- ```
168
-
169
- > This plugin uses an experimental browser API and may not work in all environments.
170
-
171
- ---
172
-
173
249
  ### `agent-map`
174
250
 
175
- Produces a Set-of-Mark package for **visual agents**: an annotated screenshot with numbered badges on interactive elements, plus a compact JSON map from badge index → role / accessible name / bbox / state. One call, fully client-side.
251
+ Adds `toAgentMap()`: an image with numbered badges on interactive elements and a JSON map
252
+ of their names, roles, bounds and state. This format is often called Set-of-Mark.
176
253
 
177
254
  ```js
178
255
  import { agentMap } from '@zumer/snapdom-plugins/agent-map';
@@ -181,7 +258,7 @@ const result = await snapdom(el, { plugins: [agentMap()] });
181
258
  const { image, map, dimensions } = await result.toAgentMap();
182
259
 
183
260
  // image: data URL of the screenshot with numbered red badges overlaid
184
- // map: [{ i, n, r, b, s? }, …] — index, name, role, bbox, state
261
+ // map: [{ i, n, r, b, s? }, …]: index, name, role, bbox, state
185
262
  // Agent says "click element 2" → map[2].b gives [x, y, w, h]
186
263
  ```
187
264
 
@@ -190,16 +267,17 @@ Map entry shape (default `fields: 'minimal'`):
190
267
  | Key | Type | Description |
191
268
  |-----|------|-------------|
192
269
  | `i` | `number` | Index matching the badge drawn on the image |
193
- | `n` | `string` | Accessible name (aria-label → labelledby → alt → title → labels → textContent, truncated to 60 chars) |
194
- | `r` | `string` | ARIA-style role (`button`, `link`, `checkbox`, `radio`, `textbox`, `combobox`, `slider`, `heading`, …) — derived from `role` attribute or implicit role of the element |
195
- | `b` | `[x, y, w, h]` | Bounding box in pixels, scaled against `maxImageWidth` |
196
- | `s` | `object?` | State: included only when at least one key is meaningful — `checked`, `disabled`, `focus`, `expanded`, `pressed`, `selected`, `value`, `open`, `selectedText`, `covered` |
270
+ | `n` | `string` | Accessible name: aria-label → labelledby → alt → title → labels → textContent. The textContent fallback is capped at 60 characters |
271
+ | `r` | `string` | Explicit `role` or an implicit role such as `button`, `link`, `checkbox`, `radio`, `textbox`, `combobox`, `slider` or `heading` |
272
+ | `b` | `[x, y, w, h]` | Bounding box in output pixels, scaled with the image and `maxImageWidth` |
273
+ | `s` | `object?` | Meaningful state: `checked`, `disabled`, `focus`, `expanded`, `pressed`, `selected`, `value`, `hasValue`, `open`, `selectedText`, `covered` |
274
+ | `isSemanticOnly` | `true?` | Structural entry added by `semantic: true`; no badge is drawn for it |
197
275
 
198
276
  Example map for a checkout form:
199
277
 
200
278
  ```js
201
279
  [
202
- { i: 0, n: 'Email', r: 'textbox', b: [28, 80, 280, 34], s: { value: 'ada@example.com' } },
280
+ { i: 0, n: 'Email', r: 'textbox', b: [28, 80, 280, 34] },
203
281
  { i: 1, n: 'Send product updates', r: 'checkbox', b: [28, 134, 13, 13], s: { checked: true } },
204
282
  { i: 2, n: 'Apply coupon', r: 'button', b: [28, 176, 114, 38], s: { expanded: false } },
205
283
  { i: 3, n: 'Remove coupon', r: 'button', b: [150, 176, 140, 38], s: { disabled: true } },
@@ -211,36 +289,87 @@ Example map for a checkout form:
211
289
 
212
290
  | Option | Type | Default | Description |
213
291
  |--------|------|---------|-------------|
214
- | `image` | `'annotated' \| 'raw' \| false` | `'annotated'` | `'annotated'` overlays numbered badges; `'raw'` skips badges; `false` skips image generation entirely (no canvas draw, no toDataURL — cheapest path). |
292
+ | `image` | `'annotated' \| 'raw' \| false` | `'annotated'` | Annotated image, raw image or map only. `false` skips this export's image generation; see `needs` to skip capture rendering too |
215
293
  | `fields` | `'minimal' \| 'full'` | `'minimal'` | `'full'` adds `t` (raw text content) and `a` (meaningful attributes) per entry. |
216
- | `semantic` | `boolean` | `false` | Include non-interactive structural elements (headings, paragraphs, landmarks). Off by default — agents act on interactive. |
294
+ | `semantic` | `boolean` | `false` | Include structural elements such as headings, paragraphs and landmarks |
217
295
  | `maxImageWidth` | `number` | `1024` | Downscale target for the image; bboxes rescale to match. |
218
296
  | `imageFormat` | `'png' \| 'jpg' \| 'webp'` | `'png'` | Image format (only used when image is rendered). |
219
297
  | `imageQuality` | `number` | `0.8` | Quality for lossy formats. |
220
298
  | `interactiveSelector` | `string` | see below | CSS selector for interactive elements. |
221
299
  | `semanticSelector` | `string` | see below | CSS selector for semantic elements (used when `semantic: true`). |
222
300
  | `labelStyle` | `object` | `{}` | Override badge styles. |
301
+ | `needs` | `'clone' \| 'render'` | `'render'` | Use `'clone'` with `image: false` to skip rendering; only valid on a per-capture plugin |
223
302
 
224
303
  Defaults:
225
- - **interactive**: `a[href], button, input, select, textarea, [role="button"|"link"|"tab"|"menuitem"|"checkbox"|"radio"|"switch"|"slider"|"combobox"|"textbox"], [tabindex]:not([tabindex="-1"]), summary, [contenteditable="true"]`
226
- - **semantic**: `h1–h6, nav, main, article, section, header, footer, figcaption, blockquote, legend, p`
227
304
 
228
- Per-call options override constructor options (e.g. `result.toAgentMap({ image: false })`).
305
+ - Interactive elements: links with `href`, buttons, inputs, selects, textareas, summaries,
306
+ `[contenteditable="true"]`, and elements with `tabindex` other than `-1`. Also matches
307
+ roles `button`, `link`, `tab`, `menuitem`, `checkbox`, `radio`, `switch`, `slider`, `combobox`, `textbox`.
308
+ - Structural elements: `h1`–`h6`, `nav`, `main`, `article`, `section`, `header`, `footer`,
309
+ `figcaption`, `blockquote`, `legend`, `p`.
310
+
311
+ Per-call overrides: `image`, `imageFormat`, `imageQuality`, `maxImageWidth`, plus image sizing
312
+ (`width`, `height`, `scale`, `dpr`) and `backgroundColor`. Selectors, fields, semantics and badge
313
+ styles are frozen during capture. For a map without rendering:
314
+
315
+ ```js
316
+ const result = await snapdom(el, { plugins: [agentMap({ image: false, needs: 'clone' })] });
317
+ const { map, dimensions } = await result.toAgentMap();
318
+ ```
319
+
320
+ Such a result has no image; `result.url` and image exports throw. The map includes open shadow
321
+ roots and slotted content and respects core's exclusion policy. Sensitive input values are
322
+ omitted; other input/textarea values become up to 12 bullets. This does not redact the image.
323
+
324
+ Badges are applied only to `toAgentMap({ image: 'annotated' })`; raw images and other exports
325
+ keep the captured page. Bounding boxes are not exact with perspective, rotated ancestors,
326
+ or a root rotation stripped by `outerTransforms: false`.
229
327
 
230
328
  #### When to use
231
329
 
232
- - Visual agents using Set-of-Mark prompting — one call gives you both the labelled image and the coordinate lookup table.
233
- - Computer-use / browser-agent harnesses that need click coordinates for a vision model's output.
234
- - Visual QA with an LLM judge — compare before/after captures with structured element identity.
235
- - Dataset generation for vision-LLM fine-tuning — (image, map) pairs.
330
+ Use it for visual agents that need element coordinates, visual QA, or image/map datasets.
331
+ It runs inside the web app, including browser extensions and Electron pages, without
332
+ requiring an external browser automation process.
333
+
334
+ ---
335
+
336
+ ### `context-export`
337
+
338
+ Adds `toContext()`: the captured UI as a text outline or JSON tree containing structure,
339
+ roles, visible text, state and optional bounds. It skips hidden content and collapses
340
+ wrappers that add no information.
341
+
342
+ ```js
343
+ import { contextExport } from '@zumer/snapdom-plugins/context-export';
344
+
345
+ const result = await snapdom(el, { plugins: [contextExport()] });
346
+ const outline = await result.toContext();
347
+ const { root, truncated, nodes } = await result.toContext({ format: 'json' });
348
+ ```
349
+
350
+ | Option | Type | Default | Description |
351
+ |--------|------|---------|-------------|
352
+ | `format` | `'outline' \| 'json'` | `'outline'` | Text outline or `{ root, truncated, nodes }` |
353
+ | `maxTextLength` | `number` | `120` | Per-node text limit; longer text ends with an ellipsis |
354
+ | `maxNodes` | `number` | `800` | Node limit; output reports truncation |
355
+ | `geometry` | `boolean` | `true` | Include `[x, y, width, height]` relative to the capture root |
356
+ | `needs` | `'clone' \| 'render'` | `'render'` | `'clone'` skips rendering; only valid on a per-capture plugin |
357
+
358
+ The tree is frozen during capture. Per-call options can change `format`, `maxTextLength`,
359
+ or omit captured geometry. They cannot increase `maxNodes` or restore geometry that was
360
+ not captured. Open shadow roots and slots follow core's exclusion policy. Sensitive input
361
+ values are omitted; other input/textarea values are masked. Ordinary page text and attributes
362
+ remain in the output.
236
363
 
237
- Because it runs entirely in the browser, it works in contexts where Playwright / Puppeteer can't: Chrome extensions, SaaS web apps capturing the user's own page, Electron apps capturing their own window.
364
+ Use `contextExport({ needs: 'clone' })` when you only need structured output. That result
365
+ has no image, so `result.url` and image exports throw.
238
366
 
239
367
  ---
240
368
 
241
369
  ### `html-export`
242
370
 
243
- Adds a `toHtml()` export that returns the capture as a self-contained, **re-renderable HTML document** (clone + inlined styles/fonts) instead of pixels. It unwraps the SVG `<foreignObject>` SnapDOM already produced, so the markup and CSS match the capture byte-for-byte — nothing is rasterized.
371
+ Adds `toHtml()`, which returns the frozen clone with its captured styles and fonts as an
372
+ HTML document. You can reopen the captured layout without exporting it as a bitmap.
244
373
 
245
374
  ```js
246
375
  import { htmlExport } from '@zumer/snapdom-plugins/html-export';
@@ -257,13 +386,17 @@ await result.toHtml({ download: 'snapshot.html' });
257
386
  | `fullDocument` | `boolean` | `true` | Wrap output in `<!DOCTYPE html>…`; if `false`, return just `<style>` + the fragment |
258
387
  | `filename` | `string` | `'capture.html'` | Download filename when `opts.download` is `true` |
259
388
 
260
- Per-call `opts`: `download` (`boolean \| string` — `true` triggers download, a string sets the filename), plus `fullDocument` / `filename` overrides. Returns the HTML `string`.
389
+ Per-call `opts` accepts `fullDocument`, `filename` and `download`. Set `download: true` to
390
+ download, or pass a filename string. The method returns the HTML string either way.
391
+
392
+ The document retains source markup, including event-handler attributes; it is not sanitized.
261
393
 
262
394
  ---
263
395
 
264
396
  ### `gif-export`
265
397
 
266
- Adds a `toGif()` export that records an **animated GIF** by re-capturing the live element over time and encoding the frames. The GIF89a encoder (median-cut quantization + LZW) is built in — no dependencies. Returns a `Blob` (`image/gif`).
398
+ Adds `toGif()`, which records the live element and returns an `image/gif` Blob. Its built-in
399
+ GIF89a encoder uses median-cut quantization and LZW, with no encoding dependency.
267
400
 
268
401
  ```js
269
402
  import { gifExport } from '@zumer/snapdom-plugins/gif-export';
@@ -282,17 +415,20 @@ await result.toGif({ download: 'animation.gif' });
282
415
  | `frames` | `number` | — | Explicit frame count (overrides `duration`) |
283
416
  | `maxColors` | `number` | `256` | Palette size per frame (2–256) |
284
417
  | `background` | `string` | `'#ffffff'` | Color composited under transparent pixels |
285
- | `scale` | `number` | `1` | Capture scale |
418
+ | `scale` | `number` | capture's scale | Capture scale |
286
419
  | `repeat` | `number` | `0` | Loop count (`0` = forever, `-1` = play once) |
287
420
  | `filename` | `string` | `'capture.gif'` | Download filename |
288
421
 
289
- Per-call `opts` override any constructor option, plus `download` (`boolean \| string`). Each frame is captured live, so CSS animations / dynamic content are recorded as they play.
422
+ Per-call `opts` override constructor options and accept `download` (`boolean \| string`).
423
+ Recording starts when `toGif()` runs, including its first frame. It preserves the original
424
+ capture policy, including exclusions and plugins. Frame capture speed limits the sampling rate.
290
425
 
291
426
  ---
292
427
 
293
428
  ### `video-export`
294
429
 
295
- Adds a `toMp4()` export that records a **video** by re-capturing the live element over time and encoding the frames with the native `MediaRecorder`. Returns a `Blob` whose type reflects what was actually produced.
430
+ Adds `toMp4()`, which records the live element through `MediaRecorder`. The returned Blob's
431
+ type identifies the recorded container.
296
432
 
297
433
  ```js
298
434
  import { videoExport } from '@zumer/snapdom-plugins/video-export';
@@ -304,7 +440,9 @@ const blob = await result.toMp4();
304
440
  await result.toMp4({ download: true });
305
441
  ```
306
442
 
307
- > **Codec reality:** `MediaRecorder` output depends on the browser. Safari produces MP4 (H.264); Chromium typically produces WebM (VP8/VP9). When MP4 isn't supported the plugin falls back to WebM, warns in the console, and the downloaded file extension is set to `.mp4` / `.webm` accordingly.
443
+ The plugin tries supported MP4 codecs first, then WebM. A WebM fallback logs a warning;
444
+ the default download filename matches the recorded `.mp4` or `.webm` container. Explicit
445
+ filenames and `download` strings are used as supplied.
308
446
 
309
447
  | Option | Type | Default | Description |
310
448
  |--------|------|---------|-------------|
@@ -312,11 +450,14 @@ await result.toMp4({ download: true });
312
450
  | `duration` | `number` | `2000` | Total duration in ms (ignored if `frames` is set) |
313
451
  | `frames` | `number` | — | Explicit frame count (overrides `duration`) |
314
452
  | `background` | `string` | `'#ffffff'` | Color composited under transparent pixels |
315
- | `scale` | `number` | `1` | Capture scale |
453
+ | `scale` | `number` | capture's scale | Capture scale |
316
454
  | `bitrate` | `number` | — | `videoBitsPerSecond` passed to `MediaRecorder` |
317
- | `filename` | `string` | — | Download filename (extension auto-set to `.mp4` / `.webm`) |
455
+ | `filename` | `string` | — | Download filename; the default follows the recorded `.mp4` / `.webm` container |
318
456
 
319
- Requires `MediaRecorder` (unavailable in some headless environments). Per-call `opts` override any constructor option, plus `download` (`boolean \| string`).
457
+ Requires `MediaRecorder` and `canvas.captureStream()`, which may be unavailable in headless
458
+ environments. Per-call `opts` override constructor options and accept `download`
459
+ (`boolean \| string`). Recording starts at the call and preserves capture options as GIF
460
+ recording does. MediaRecorder uses a real-time clock, so slow captures can stretch duration.
320
461
 
321
462
  ---
322
463
 
@@ -345,10 +486,11 @@ Per-capture plugins run before global ones. Duplicate plugin names are skipped a
345
486
 
346
487
  ## Build your own plugin
347
488
 
348
- Use the template to scaffold a new plugin in seconds:
489
+ Start from the plugin template:
349
490
 
350
491
  ```bash
351
492
  npx degit zumerlab/snapdom/packages/plugin-template my-plugin
493
+ cd my-plugin && npm install --save-dev @zumer/snapdom
352
494
  ```
353
495
 
354
496
  See [PLUGIN_SPEC.md](../../PLUGIN_SPEC.md) for the full hook specification and [CONTRIBUTING_PLUGINS.md](../../CONTRIBUTING_PLUGINS.md) to get your plugin listed on the community page.
package/agent-map.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Type declarations for `@zumer/snapdom-plugins/agent-map`.
3
+ *
4
+ * The plugin instance type comes from the core package: one definition of what a plugin IS,
5
+ * so a plugin that stops matching the host's contract fails to typecheck instead of failing
6
+ * at capture time.
7
+ */
8
+ import type { SnapdomPlugin } from '@zumer/snapdom';
9
+
10
+ export interface AgentMapOptions {
11
+ /** Image output; 'annotated' overlays numbered badges on interactive elements. */
12
+ image?: 'annotated' | 'raw' | false;
13
+ /** Per-entry shape. */
14
+ fields?: 'minimal' | 'full';
15
+ /** Also include non-interactive semantic elements. */
16
+ semantic?: boolean;
17
+ maxImageWidth?: number;
18
+ imageFormat?: 'png' | 'jpg' | 'webp';
19
+ imageQuality?: number;
20
+ interactiveSelector?: string;
21
+ semanticSelector?: string;
22
+ labelStyle?: Record<string, string>;
23
+ /** How far the capture runs. */
24
+ needs?: 'clone' | 'render';
25
+ }
26
+
27
+ /**
28
+ * Adds `result.toAgentMap()` for visual agents: an annotated screenshot plus a structured map
29
+ * of the interactive elements and their bounding boxes. Published as `prompt-export` until 2.12.0 renamed it.
30
+ */
31
+ export declare function agentMap(options?: AgentMapOptions): SnapdomPlugin;