@ingcreators/annot-annotator 0.5.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,300 @@
1
1
  # @ingcreators/annot-annotator
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 6124d59: **`BboxAnnotation` palette extensions: `freehand` + `focusMask`** —
8
+ Phase 3b of `docs/plans/living-spec-authoring-roadmap.md`.
9
+
10
+ Two new variants on the `BboxAnnotation` union expose the rest of
11
+ the Annot visual palette to `bboxAnnotationsToSvg()` and through
12
+ it to `Annotator.toPng()` / `.toSvg()` / `.toEditablePng()`:
13
+
14
+ ```ts
15
+ // Free-form path stroke — `path` is the SVG <path> `d` attribute.
16
+ {
17
+ type: "freehand",
18
+ path: "M100,200 L150,250 L200,210",
19
+ intent: "info", // optional — defaults to "error"
20
+ strokeWidth: 4, // optional — defaults to 2
21
+ fill: "#ffeecc", // optional — defaults to "none"
22
+ }
23
+
24
+ // Dim everything except the cutout region. One <path> with
25
+ // fill-rule="evenodd" combines a full-image rect with the
26
+ // cutout — the even-odd rule cancels overlap.
27
+ {
28
+ type: "focusMask",
29
+ cutout: { x: 200, y: 100, width: 80, height: 40 },
30
+ imageWidth: 1280,
31
+ imageHeight: 800,
32
+ dimColor: "rgba(0,0,0,0.5)", // optional — default same value
33
+ }
34
+ ```
35
+
36
+ `@ingcreators/annot-product-docs`'s `<Screen annotations>`
37
+ Image Service composition (Phase 3c) will map yaml
38
+ `AnnotationSpec` entries to these two primitives plus the
39
+ existing `rect` / `circle` / `arrow` / `text` / `callout` /
40
+ `numberedBadge` shapes. Useful standalone for any annotator
41
+ caller (Playwright fixtures, MCP server, custom test reporters)
42
+ without involving yaml or MDX.
43
+
44
+ **JSON schemas** — `BBOX_ANNOTATION_SCHEMA.oneOf` gains
45
+ `BBOX_FREEHAND` + `BBOX_FOCUS_MASK` entries so MCP callers can
46
+ validate either kind at the boundary.
47
+
48
+ **Out of scope** — redact (mosaic / blur) needs raster pixel
49
+ access and is not implementable as an SVG fragment; it stays
50
+ on the existing destructive `burnRedactions` path in
51
+ `@ingcreators/annot-mcp`. Phase 3a's annotation yaml rejects
52
+ `style: "mosaic" | "blur"` accordingly.
53
+
54
+ **Compatibility** — additive. Existing `BboxAnnotation` callers
55
+ keep working; the new variants are opt-in by setting
56
+ `type: "freehand" | "focusMask"`.
57
+
58
+ - 0c7ac26: **Relocate `burnRedactions` from `@ingcreators/annot-mcp` to
59
+ `@ingcreators/annot-annotator`** — Phase 3e of
60
+ `docs/plans/living-spec-authoring-roadmap.md` (Phase 3 follow-up).
61
+
62
+ The destructive raster burn primitive (solid / mosaic / blur over
63
+ a PNG buffer, built on `@napi-rs/canvas`) historically lived in
64
+ `@ingcreators/annot-mcp` because the MCP server's
65
+ `annot_redact_screenshot` tool was the first caller. The function
66
+ itself has no MCP-specific surface — it's pure
67
+ (`pngBytes + regions → pngBytes`). To let non-MCP callers consume
68
+ it without dragging the MCP server's dep footprint (Playwright,
69
+ `@modelcontextprotocol/sdk`, etc.), the primitive moves to
70
+ `@ingcreators/annot-annotator` — the canonical Node-side raster
71
+ home, which already depends on `@napi-rs/canvas` for its encode
72
+ pipeline (so the move adds **zero** transitive deps).
73
+
74
+ ### `@ingcreators/annot-annotator` — new public surface
75
+
76
+ ```ts
77
+ import {
78
+ burnRedactions,
79
+ type RedactRegion,
80
+ } from "@ingcreators/annot-annotator";
81
+
82
+ const out = await burnRedactions(pngBytes, [
83
+ {
84
+ bbox: { x: 10, y: 20, width: 100, height: 30 },
85
+ style: "solid",
86
+ color: "#000000",
87
+ },
88
+ { bbox: { x: 200, y: 100, width: 80, height: 40 }, style: "mosaic" },
89
+ { bbox: { x: 0, y: 0, width: 64, height: 64 }, style: "blur" },
90
+ ]);
91
+ ```
92
+
93
+ `RedactRegion` is exposed as an alias of `BboxRedactRegion`
94
+ (structurally identical, already declared in the DSL types) so
95
+ existing MCP-side consumers see no shape change.
96
+
97
+ ### `@ingcreators/annot-mcp` — no public API change
98
+
99
+ The existing `burnRedactions` + `RedactRegion` re-exports from
100
+ the package root keep working byte-identical, sourced from the
101
+ annotator instead of the old MCP-local file. MCP's
102
+ `annot_redact_screenshot` / `annot_redact_url` tools continue
103
+ to import from `../redact/burn.js`, which is now a one-line
104
+ re-export from annotator.
105
+
106
+ ### Compatibility
107
+
108
+ Additive on annotator's side; zero behaviour change on MCP's
109
+ side. Tests move with the code (annotator 64 → 71 passed; MCP
110
+ 91 → 84 passed — same scenarios at the new home).
111
+
112
+ ### Out of scope
113
+
114
+ `@napi-rs/canvas` stays as an MCP direct dep — `compare/diff.ts`
115
+ and several other MCP tool tests still use it directly, so
116
+ collapsing it onto a transitive-via-annotator import is a
117
+ separate cleanup.
118
+
119
+ - 64dc6e8: **Relocate `diffScreenshots` from `@ingcreators/annot-mcp` to
120
+ `@ingcreators/annot-annotator`** — Phase 3i of
121
+ `docs/plans/living-spec-authoring-roadmap.md` (Phase 3
122
+ follow-up #2). Same pattern as 3e's `burnRedactions` relocate.
123
+
124
+ The pixelmatch-driven PNG comparison + contiguous-region bbox
125
+ aggregation lived in `@ingcreators/annot-mcp/compare/` for
126
+ historical reasons (the MCP server's
127
+ `annot_compare_screenshots` tool was the first caller). The
128
+ function itself has no MCP-specific surface — it's pure
129
+ (`pngBytes + pngBytes → DiffResult`). Relocating it to
130
+ `@ingcreators/annot-annotator` lets non-MCP callers
131
+ (Playwright visual regression fixtures, Astro pixel drift CI,
132
+ custom test reporters, editor before/after preview) consume
133
+ it without dragging the MCP server's dep footprint.
134
+
135
+ ### `@ingcreators/annot-annotator` — new public surface
136
+
137
+ ```ts
138
+ import {
139
+ diffScreenshots,
140
+ aggregateDiffRegions,
141
+ DimensionMismatchError,
142
+ type DiffResult,
143
+ type DiffOptions,
144
+ } from "@ingcreators/annot-annotator";
145
+
146
+ const result = await diffScreenshots(beforePng, afterPng, { threshold: 0.1 });
147
+ // → { mismatchedPixels: number, regions: BBox[], width, height }
148
+ ```
149
+
150
+ annotator gains `pixelmatch` (~4 KB, no transitive deps) as a
151
+ runtime dep.
152
+
153
+ ### `@ingcreators/annot-mcp` — no public API change
154
+
155
+ The existing `compare/diff.ts` + `compare/aggregate.ts` modules
156
+ become one-line re-export shims forwarding from annotator. MCP's
157
+ internal callers (`tools/compare-screenshots.ts`) and any
158
+ external consumer importing from `@ingcreators/annot-mcp` keep
159
+ working byte-identical.
160
+
161
+ ### Compatibility
162
+
163
+ Additive on annotator's side; zero behaviour change on MCP's
164
+ side. Tests move with the code (annotator 71 → 81 passed; MCP
165
+ 84 → 78 passed — same scenarios at the new home, plus a new
166
+ `diffScreenshots` smoke test that the MCP-side aggregate-only
167
+ test didn't cover).
168
+
169
+ ### Out of scope
170
+
171
+ `pixelmatch` stays as a direct MCP dep — even though MCP no
172
+ longer imports it from the moved code, it's a tiny package
173
+ and removing the explicit dep would force consumers to rely
174
+ on a transitive resolution through annotator, which is more
175
+ fragile than declaring the intent directly.
176
+
177
+ - 691bec5: **Add `flattenEditablePng(pngBytes) → pngBytes`** — Phase 3j of
178
+ `docs/plans/living-spec-authoring-roadmap.md` (Phase 3
179
+ follow-up #2). The editor's editable-PNG format embeds the
180
+ original un-annotated capture + the annotations SVG in PNG
181
+ ancillary chunks for re-edit; "flatten" drops those chunks and
182
+ keeps just the visible (already-annotated) bytes.
183
+
184
+ ### `@ingcreators/annot-annotator` — new public surface
185
+
186
+ ```ts
187
+ import { flattenEditablePng } from "@ingcreators/annot-annotator";
188
+
189
+ const flat = flattenEditablePng(editablePngBytes);
190
+ // → flat PNG: same visible pixels, no Adobe XMP iTXt chunk,
191
+ // no custom svGo chunk. `readEditablePngBytes(flat)` returns
192
+ // null. File size drops significantly (the editable layer
193
+ // roughly doubled the bytes).
194
+ ```
195
+
196
+ ### `@ingcreators/annot-core/xmp-bytes` — new public surface
197
+
198
+ The implementation lives in `@ingcreators/annot-core` as
199
+ `stripPngEditableLayer` — the same chunk-walking helper that
200
+ `writePngWithMetadata` / `writePngWithTagsOnly` already used
201
+ internally to clean stale metadata before re-injecting. Now
202
+ exported so other Tier A consumers (not just annotator) can
203
+ use it directly.
204
+
205
+ annotator's `flattenEditablePng` is a one-line wrapper that
206
+ calls `stripPngEditableLayer` under a more user-facing name.
207
+
208
+ ### Why this is metadata removal, not re-rasterization
209
+
210
+ `toEditablePng` rasterizes the SVG fragment onto the base image
211
+ FIRST and embeds the editable layer as ancillary PNG chunks
212
+ (`iTXt` carrying Adobe XMP + custom `svGo` chunk). The visible
213
+ bytes are already the annotated bitmap. Flattening strips the
214
+ ancillary chunks; the IDAT pixel data stays byte-identical.
215
+ No decode, no re-encode, no `@napi-rs/canvas` round-trip.
216
+
217
+ ### Use cases
218
+ - **Publish-flat** — editor session → distribution-ready PNG;
219
+ the editable layer is dead weight for downstream consumers
220
+ (Slack drop, third-party viewers).
221
+ - **File size** — editable PNG roughly doubles in bytes
222
+ (original + SVG embedded); flattening drops the overhead.
223
+ - **Privacy hardening** — `burnRedactions` is the strong
224
+ version for _redact_ regions; flattening drops the
225
+ recoverable original entirely for _all_ annotations,
226
+ including non-redact ones whose annotated visual the
227
+ publisher wants to keep but whose original capture they
228
+ don't want shippable.
229
+
230
+ ### Internal rename in annot-core
231
+
232
+ The private `removePngMetadata` helper in
233
+ `@ingcreators/annot-core/xmp-bytes` is renamed to
234
+ `stripPngEditableLayer` (clearer name describing what it does
235
+ rather than how it's used). Internal callers in the same
236
+ module updated. No external API change for the rename itself;
237
+ `writePngWithMetadata` + `writePngWithTagsOnly` keep their
238
+ existing signatures + behaviour.
239
+
240
+ ### Compatibility
241
+
242
+ Additive on annotator + core. No behaviour change for existing
243
+ callers (the only internal rename is a private helper).
244
+
245
+ - 9697f27: **Export `burnRegions` as an operation-aligned alias for
246
+ `burnRedactions`** — Phase 3k of
247
+ `docs/plans/living-spec-authoring-roadmap.md`
248
+ (Phase 3 follow-up #2). Closes the follow-up.
249
+
250
+ `burnRedactions` is named for its first caller's intent (MCP's
251
+ `annot_redact_screenshot`), but the underlying primitive is a
252
+ `pngBytes + region[] → pngBytes` raster transform — generic
253
+ over the caller's purpose. The new export surfaces the
254
+ operation-aligned name alongside the intent-named original.
255
+
256
+ ### `@ingcreators/annot-annotator` — new public export
257
+
258
+ ```ts
259
+ import { burnRegions } from "@ingcreators/annot-annotator";
260
+
261
+ // Identical signature + behaviour to burnRedactions.
262
+ const out = await burnRegions(pngBytes, [
263
+ { bbox: { x: 10, y: 20, width: 100, height: 30 }, style: "mosaic" },
264
+ ]);
265
+ ```
266
+
267
+ Identity-equal to `burnRedactions` (`burnRegions === burnRedactions`
268
+ at the export level) — picking one name over the other is purely
269
+ a docs-readability choice.
270
+
271
+ ### Use cases that motivated the alias
272
+
273
+ The function isn't redact-specific — the JSDoc on `burnRedactions`
274
+ now enumerates:
275
+ - Editor-side "highlight this region with a translucent colour
276
+ and ship it baked" workflow.
277
+ - Visual-regression pre-processing — burn dynamic content
278
+ (timestamps, login state badges) into the screenshot so pixel
279
+ diffs stay deterministic.
280
+ - Watermark / overlay burn for downstream distribution.
281
+ - Privacy hardening at non-redact regions (e.g. blur a logo in
282
+ a publicly-shared screenshot).
283
+
284
+ For any of these, `burnRegions` reads as the natural name.
285
+ Redact callers stay on `burnRedactions` (still the recommended
286
+ name when the intent IS redaction); no migration forced.
287
+
288
+ ### `@ingcreators/annot-mcp` — no public API change
289
+
290
+ MCP's `compare/burn.ts` re-export shim + `index.ts` forward both
291
+ names. Existing `burnRedactions` callers see no change.
292
+
293
+ ### Compatibility
294
+
295
+ Additive. `burnRedactions` keeps its public API + JSDoc; the
296
+ alias is purely additive.
297
+
3
298
  ## 0.5.0
4
299
 
5
300
  ### Minor Changes
@@ -0,0 +1,6 @@
1
+ import { BBox } from './dsl/types.js';
2
+ /**
3
+ * Walk a pixelmatch diff mask and return one bbox per contiguous
4
+ * changed region. 4-connected adjacency.
5
+ */
6
+ export declare function aggregateDiffRegions(diffRgba: Uint8Array, width: number, height: number): BBox[];
package/dist/diff.d.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { BBox } from './dsl/types.js';
2
+ export interface DiffResult {
3
+ /** Number of mismatched pixels. */
4
+ mismatchedPixels: number;
5
+ /** Bounding boxes of contiguous changed regions. */
6
+ regions: BBox[];
7
+ /** Dimensions of the input pair. */
8
+ width: number;
9
+ height: number;
10
+ }
11
+ export interface DiffOptions {
12
+ /** Matching threshold (0 to 1); smaller is more sensitive. */
13
+ threshold?: number;
14
+ }
15
+ export declare class DimensionMismatchError extends Error {
16
+ constructor(a: {
17
+ width: number;
18
+ height: number;
19
+ }, b: {
20
+ width: number;
21
+ height: number;
22
+ });
23
+ }
24
+ /**
25
+ * Compare two PNGs pixel-by-pixel and return the changed-region
26
+ * bboxes. Throws `DimensionMismatchError` when the inputs have
27
+ * different sizes — the caller has to capture both at the same
28
+ * viewport for the comparison to make sense.
29
+ */
30
+ export declare function diffScreenshots(before: Uint8Array, after: Uint8Array, options?: DiffOptions): Promise<DiffResult>;
@@ -206,6 +206,74 @@ export declare const BBOX_ANNOTATION_SCHEMA: {
206
206
  type: string;
207
207
  };
208
208
  };
209
+ } | {
210
+ type: string;
211
+ required: string[];
212
+ additionalProperties: boolean;
213
+ properties: {
214
+ type: {
215
+ const: string;
216
+ };
217
+ path: {
218
+ type: string;
219
+ minLength: number;
220
+ };
221
+ intent: {
222
+ $ref: string;
223
+ };
224
+ stroke: {
225
+ type: string;
226
+ };
227
+ strokeWidth: {
228
+ type: string;
229
+ minimum: number;
230
+ };
231
+ fill: {
232
+ type: string;
233
+ };
234
+ color: {
235
+ type: string;
236
+ };
237
+ };
238
+ } | {
239
+ type: string;
240
+ required: string[];
241
+ additionalProperties: boolean;
242
+ properties: {
243
+ type: {
244
+ const: string;
245
+ };
246
+ cutout: {
247
+ $ref: string;
248
+ };
249
+ imageWidth: {
250
+ type: string;
251
+ minimum: number;
252
+ };
253
+ imageHeight: {
254
+ type: string;
255
+ minimum: number;
256
+ };
257
+ dimColor: {
258
+ type: string;
259
+ };
260
+ intent: {
261
+ $ref: string;
262
+ };
263
+ stroke: {
264
+ type: string;
265
+ };
266
+ strokeWidth: {
267
+ type: string;
268
+ minimum: number;
269
+ };
270
+ fill: {
271
+ type: string;
272
+ };
273
+ color: {
274
+ type: string;
275
+ };
276
+ };
209
277
  } | {
210
278
  type: string;
211
279
  required: string[];
@@ -98,7 +98,47 @@ export interface RawAnnotation {
98
98
  type: "raw";
99
99
  svgFragment: string;
100
100
  }
101
- export type BboxAnnotation = BboxRectAnnotation | BboxCircleAnnotation | BboxArrowAnnotation | BboxTextAnnotation | BboxCalloutAnnotation | BboxNumberedBadgeAnnotation | RawAnnotation;
101
+ /**
102
+ * Free-form path. The `path` field carries the SVG `<path>` d
103
+ * attribute verbatim — `M` / `L` / `C` / `Q` / `Z` commands.
104
+ *
105
+ * Style fields are interpreted as stroke-only by default
106
+ * (`fill: "none"` unless overridden) so a freehand stroke
107
+ * doesn't accidentally fill its own enclosed area.
108
+ *
109
+ * Phase 3b of
110
+ * [`docs/plans/living-spec-authoring-roadmap.md`](../../../../docs/plans/living-spec-authoring-roadmap.md).
111
+ */
112
+ export type BboxFreehandAnnotation = AnnotationStyle & {
113
+ type: "freehand";
114
+ path: string;
115
+ };
116
+ /**
117
+ * Focus mask — dim the entire image area EXCEPT the cutout
118
+ * region. The cutout draws crisp; everything outside is filled
119
+ * with `dimColor` (default `rgba(0,0,0,0.5)`).
120
+ *
121
+ * Implemented as a single `<path>` element with `fill-rule="evenodd"`
122
+ * combining a full-image rect with the cutout rect: the
123
+ * even-odd rule cancels overlap, leaving the cutout area
124
+ * transparent on top of the dimmed background.
125
+ *
126
+ * `imageWidth` / `imageHeight` are required so the outer rect
127
+ * covers the full screenshot; the renderer rejects a partial
128
+ * focus mask. Phase 3b of
129
+ * [`docs/plans/living-spec-authoring-roadmap.md`](../../../../docs/plans/living-spec-authoring-roadmap.md).
130
+ */
131
+ export type BboxFocusMaskAnnotation = AnnotationStyle & {
132
+ type: "focusMask";
133
+ /** The crisp cutout region. */
134
+ cutout: BBox;
135
+ /** Full-image dimensions — required so the outer rect covers everything. */
136
+ imageWidth: number;
137
+ imageHeight: number;
138
+ /** CSS colour for the dim layer. Default `rgba(0,0,0,0.5)`. */
139
+ dimColor?: string;
140
+ };
141
+ export type BboxAnnotation = BboxRectAnnotation | BboxCircleAnnotation | BboxArrowAnnotation | BboxTextAnnotation | BboxCalloutAnnotation | BboxNumberedBadgeAnnotation | BboxFreehandAnnotation | BboxFocusMaskAnnotation | RawAnnotation;
102
142
  export type RedactStyle = "solid" | "mosaic" | "blur";
103
143
  export interface BboxRedactRegion {
104
144
  bbox: BBox;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * "Burn" an editable PNG into a flat one — drop the editor's
3
+ * editable layer (Adobe XMP iTXt + custom `svGo` chunk) while
4
+ * keeping the visible bitmap byte-identical.
5
+ *
6
+ * The editable PNG format embeds:
7
+ *
8
+ * - **visible bytes** — the annotated raster the user sees.
9
+ * - **Adobe XMP iTXt chunk** — `<annot:annotations>` (the SVG
10
+ * layer) + `<annot:tags>` (provenance) for re-edit.
11
+ * - **custom `svGo` chunk** — the original un-annotated capture
12
+ * for re-edit.
13
+ *
14
+ * Flattening strips the iTXt + svGo chunks. The result is a
15
+ * regular PNG with the same visible pixels but no recoverable
16
+ * original / no SVG layer. Re-opening in the editor shows the
17
+ * annotated capture as a non-editable bitmap;
18
+ * `readEditablePngBytes` returns `null`.
19
+ *
20
+ * Use cases:
21
+ *
22
+ * - **Publish-flat** — editor session → distribution-ready
23
+ * PNG; the editable layer is dead weight for downstream
24
+ * consumers (Slack drop, third-party viewers).
25
+ * - **File size** — editable PNG roughly doubles in bytes
26
+ * (original + SVG); flattening drops the overhead.
27
+ * - **Privacy hardening** — `burnRedactions` is the strong
28
+ * version for *redact* regions; flattening drops the
29
+ * recoverable original entirely for *all* annotations,
30
+ * including non-redact ones whose annotated visual the
31
+ * publisher wants to keep but whose original capture
32
+ * they don't want shippable.
33
+ *
34
+ * Pure data — no DOM, no canvas. Returns input bytes unchanged
35
+ * when no editable-layer chunks are present (idempotent).
36
+ */
37
+ export declare function flattenEditablePng(pngBytes: Uint8Array): Uint8Array;
package/dist/index.d.ts CHANGED
@@ -2,7 +2,11 @@ export { type Annotator, type AnnotatorInput, type AnnotatorOptions, createAnnot
2
2
  export { decodeAndEncodeImage, encodeRgba } from './encode/encode.js';
3
3
  export { type BrowserEncodeResult, computeResizeTarget, DEFAULT_ENCODE_OPTIONS, type EncodeFormat, type EncodeOptions, type EncodeResult, SAVE_SIZE_LABEL, SAVE_SIZE_MAX_WIDTH, type SaveSizePreset, } from './encode/options.js';
4
4
  export { isPhotoHeavy } from './encode/quantize.js';
5
+ export { burnRedactions, burnRegions, type RedactRegion } from './redact-burn.js';
6
+ export { type DiffOptions, type DiffResult, DimensionMismatchError, diffScreenshots, } from './diff.js';
7
+ export { aggregateDiffRegions } from './diff-aggregate.js';
8
+ export { flattenEditablePng } from './flatten-editable-png.js';
5
9
  export { BBOX_ANNOTATION_SCHEMA, BBOX_REDACT_REGION_SCHEMA, SHARED_DEFS, } from './dsl/schema.js';
6
10
  export { type ArrowOptions, arrowBetween, type BoundingBox, type RectOptions, rectForBoundingBox, type TextOptions, textAt, } from './dsl/svg-primitives.js';
7
11
  export { bboxAnnotationsToSvg } from './dsl/to-svg.js';
8
- export type { AnnotationStyle, BadgePlacement, BBox, BboxAnnotation, BboxArrowAnnotation, BboxCalloutAnnotation, BboxCircleAnnotation, BboxNumberedBadgeAnnotation, BboxRectAnnotation, BboxRedactRegion, BboxTextAnnotation, Intent, Point, RawAnnotation, RedactStyle, } from './dsl/types.js';
12
+ export type { AnnotationStyle, BadgePlacement, BBox, BboxAnnotation, BboxArrowAnnotation, BboxCalloutAnnotation, BboxCircleAnnotation, BboxFocusMaskAnnotation, BboxFreehandAnnotation, BboxNumberedBadgeAnnotation, BboxRectAnnotation, BboxRedactRegion, BboxTextAnnotation, Intent, Point, RawAnnotation, RedactStyle, } from './dsl/types.js';