@ingcreators/annot-core 0.2.0 → 0.3.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,152 @@
1
1
  # @ingcreators/annot-core
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 691bec5: **Add `flattenEditablePng(pngBytes) → pngBytes`** — Phase 3j of
8
+ `docs/plans/living-spec-authoring-roadmap.md` (Phase 3
9
+ follow-up #2). The editor's editable-PNG format embeds the
10
+ original un-annotated capture + the annotations SVG in PNG
11
+ ancillary chunks for re-edit; "flatten" drops those chunks and
12
+ keeps just the visible (already-annotated) bytes.
13
+
14
+ ### `@ingcreators/annot-annotator` — new public surface
15
+
16
+ ```ts
17
+ import { flattenEditablePng } from "@ingcreators/annot-annotator";
18
+
19
+ const flat = flattenEditablePng(editablePngBytes);
20
+ // → flat PNG: same visible pixels, no Adobe XMP iTXt chunk,
21
+ // no custom svGo chunk. `readEditablePngBytes(flat)` returns
22
+ // null. File size drops significantly (the editable layer
23
+ // roughly doubled the bytes).
24
+ ```
25
+
26
+ ### `@ingcreators/annot-core/xmp-bytes` — new public surface
27
+
28
+ The implementation lives in `@ingcreators/annot-core` as
29
+ `stripPngEditableLayer` — the same chunk-walking helper that
30
+ `writePngWithMetadata` / `writePngWithTagsOnly` already used
31
+ internally to clean stale metadata before re-injecting. Now
32
+ exported so other Tier A consumers (not just annotator) can
33
+ use it directly.
34
+
35
+ annotator's `flattenEditablePng` is a one-line wrapper that
36
+ calls `stripPngEditableLayer` under a more user-facing name.
37
+
38
+ ### Why this is metadata removal, not re-rasterization
39
+
40
+ `toEditablePng` rasterizes the SVG fragment onto the base image
41
+ FIRST and embeds the editable layer as ancillary PNG chunks
42
+ (`iTXt` carrying Adobe XMP + custom `svGo` chunk). The visible
43
+ bytes are already the annotated bitmap. Flattening strips the
44
+ ancillary chunks; the IDAT pixel data stays byte-identical.
45
+ No decode, no re-encode, no `@napi-rs/canvas` round-trip.
46
+
47
+ ### Use cases
48
+ - **Publish-flat** — editor session → distribution-ready PNG;
49
+ the editable layer is dead weight for downstream consumers
50
+ (Slack drop, third-party viewers).
51
+ - **File size** — editable PNG roughly doubles in bytes
52
+ (original + SVG embedded); flattening drops the overhead.
53
+ - **Privacy hardening** — `burnRedactions` is the strong
54
+ version for _redact_ regions; flattening drops the
55
+ recoverable original entirely for _all_ annotations,
56
+ including non-redact ones whose annotated visual the
57
+ publisher wants to keep but whose original capture they
58
+ don't want shippable.
59
+
60
+ ### Internal rename in annot-core
61
+
62
+ The private `removePngMetadata` helper in
63
+ `@ingcreators/annot-core/xmp-bytes` is renamed to
64
+ `stripPngEditableLayer` (clearer name describing what it does
65
+ rather than how it's used). Internal callers in the same
66
+ module updated. No external API change for the rename itself;
67
+ `writePngWithMetadata` + `writePngWithTagsOnly` keep their
68
+ existing signatures + behaviour.
69
+
70
+ ### Compatibility
71
+
72
+ Additive on annotator + core. No behaviour change for existing
73
+ callers (the only internal rename is a private helper).
74
+
75
+ ## 0.2.1
76
+
77
+ ### Patch Changes
78
+
79
+ - f485646: **Expose `./xmp-bytes` subpath on the published tarball.**
80
+ The workspace `package.json#exports` declared
81
+ `"./xmp-bytes": "./src/xmp/xmp-bytes.ts"` from day one, but
82
+ `publishConfig.exports` only declared `.` + `./styles/*` —
83
+ so every workspace subpath (`./headless`, `./editor/*`,
84
+ `./xmp-bytes`, etc.) was stripped from the published
85
+ tarball.
86
+
87
+ This patch unblocks the `./xmp-bytes` subpath specifically.
88
+ `@ingcreators/annot-product-docs-astro@0.2.1`'s playwright
89
+ fixture imports `@ingcreators/annot-core/xmp-bytes` (via
90
+ the bundled `writePngWithTagsOnly` helper); without this
91
+ patch, any consumer doing
92
+ `import { test } from "@ingcreators/annot-product-docs-astro/playwright"`
93
+ hits `Package subpath './xmp-bytes' is not defined`.
94
+
95
+ ## Fix
96
+
97
+ `vite.config.ts` switches to multi-entry library mode:
98
+
99
+ ```ts
100
+ lib: {
101
+ entry: {
102
+ index: resolve(__dirname, "src/index.ts"),
103
+ "xmp-bytes": resolve(__dirname, "src/xmp/xmp-bytes.ts"),
104
+ },
105
+ formats: ["es"],
106
+ },
107
+ ```
108
+
109
+ `package.json#publishConfig.exports` gains the matching
110
+ `./xmp-bytes` entry:
111
+
112
+ ```json
113
+ "./xmp-bytes": {
114
+ "types": "./dist/xmp/xmp-bytes.d.ts",
115
+ "default": "./dist/xmp-bytes.js"
116
+ }
117
+ ```
118
+
119
+ Bundle landing in the tarball:
120
+
121
+ ```
122
+ dist/xmp-bytes.js 7.55 kB │ gzip: 2.67 kB
123
+ dist/xmp/xmp-bytes.d.ts declarations colocated by the dts plugin
124
+ ```
125
+
126
+ ## Other subpaths still missing
127
+
128
+ The workspace `exports` map declares 17+ subpaths (`./headless`,
129
+ `./editor`, `./editor/*`, `./icons`, `./xmp`, `./zip`,
130
+ `./utils`, `./desktop-bridge`, `./storage`, `./encode/*`,
131
+ `./auto-capture-options`, …). Most are Tier C (browser-only)
132
+ and shouldn't ship as standalone published bundles — Tier C
133
+ code lives in `@ingcreators/annot-editor` / `-render` for
134
+ npm consumers. The few Tier A subpaths that DO make sense
135
+ on npm (`./headless`, `./storage`, `./utils`, `./zip`,
136
+ `./zip-bytes`) can ship in a separate follow-up patch
137
+ when a concrete downstream consumer needs them.
138
+
139
+ ## After this PR republishes
140
+
141
+ `@ingcreators/annot-product-docs-astro` needs a `0.2.2`
142
+ republish that bumps its `@ingcreators/annot-core` dep
143
+ from `0.2.0` to `^0.2.1` so the playwright fixture can
144
+ finally resolve `./xmp-bytes` at consumer install time.
145
+ Once both land, the `examples/workflow-app/` tour can
146
+ migrate from the hybrid `captureScreen + page.screenshot`
147
+ pair to the unified `page.screenshot({ annot: { mdx } })`
148
+ single call.
149
+
3
150
  ## 0.2.0
4
151
 
5
152
  ### Minor Changes
package/README.md CHANGED
@@ -40,7 +40,7 @@ headless tool against the SDK.
40
40
  |---------|------|---------|---|
41
41
  | `@ingcreators/annot-core` | A | Re-exports everything from `/headless` — DOM-free root entry | ✅ |
42
42
  | `@ingcreators/annot-core/headless` | A | Same as root, kept as an alias for callers that want to be explicit | workspace only (v0.1.0) |
43
- | `@ingcreators/annot-core/storage` | A | `ImageRecord`, `FolderRecord`, `PageElement`, `PageMetadata`, `StorageProvider` | workspace only (v0.1.0) |
43
+ | `@ingcreators/annot-core/storage` | A | `ImageRecord`, `FolderRecord`, `StorageProvider` | workspace only (v0.1.0) |
44
44
  | `@ingcreators/annot-core/utils` | A | `assertNonNull`, `computeDasharray`, `newIdB58`, defaults | workspace only (v0.1.0) |
45
45
  | `@ingcreators/annot-core/zip` | A | ZIP builder used by PPTX export | workspace only (v0.1.0) |
46
46
  | `@ingcreators/annot-core/encode` | A | Image encode helpers | workspace only (v0.1.0) |
@@ -1,4 +1,5 @@
1
1
  export { createHistoryCore, DEFAULT_HISTORY_DEPTH, type HistoryCore, type HistoryHooks, } from './history-core.js';
2
+ export type { OverlayRegionPickDetail } from './overlay-pick-types.js';
2
3
  export { CATEGORY_CONTROL_SHAPE, classifyPropertyElement, classifyPropertySelection, PROPERTY_CONTROL_IDS, PROPERTY_CONTROLS, PROPERTY_EFFECT_IDS, type PropertyCategory, type PropertyControlDef, type PropertyControlId, type PropertyControlOption, type PropertyControlType, type PropertyEffectId, } from './property-schema.js';
3
4
  export { computeSnap, cursorForAngle, type Rect, rotateAround, type SnapGuide, type SnapInput, type SnapResult, } from './selection-geometry.js';
4
5
  export { ANNOT_SVG_VERSION, ANNOT_SVG_VERSION_ATTR, ANNOT_SVG_VERSION_UNSTAMPED, getAnnotVersionFromString, readAnnotVersion, stampAnnotVersion, } from './svg-format.js';
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Shared types for the snapshot region pick flow — Phase 4d of
3
+ * [`docs/plans/living-spec-authoring-roadmap.md`](../../../../docs/plans/living-spec-authoring-roadmap.md).
4
+ *
5
+ * The `<annot-snapshot-overlay>` Lit element (host-ui) and the
6
+ * `OverlayTool` (editor) both deal with snapshot-region picks but
7
+ * live in packages that don't depend on each other (host-ui →
8
+ * editor, not the reverse). Lifting the detail type into Tier A
9
+ * core lets both sides import it without forcing a cycle.
10
+ *
11
+ * Pure data types — no DOM, no Node-specific APIs.
12
+ */
13
+ /**
14
+ * Payload of the `overlay-region-pick` CustomEvent fired by
15
+ * `<annot-snapshot-overlay>` when the user clicks a region.
16
+ * Consumed by `OverlayTool` to build an `OverlayProposal` for the
17
+ * intent picker dialog.
18
+ */
19
+ export interface OverlayRegionPickDetail {
20
+ /** Tree-unique identifier of the picked node (e.g. `"e3"`).
21
+ * Stable within one capture only — annotation persistence keys
22
+ * on `match: { role, name }` instead. */
23
+ ref: string;
24
+ /** ARIA role of the picked node. Always present. */
25
+ role: string;
26
+ /** Accessible name, when the node carries one. */
27
+ name?: string;
28
+ /** Page-space bounding box of the picked node. CSS px. */
29
+ bbox: {
30
+ x: number;
31
+ y: number;
32
+ width: number;
33
+ height: number;
34
+ };
35
+ }
@@ -316,5 +316,5 @@ export declare function normalizeVariantSideFields(toolId: string, newVariant: s
316
316
  export declare const TOOL_REGISTRY: Readonly<Record<string, ToolRegistryEntry>>;
317
317
  /** All tool ids the toolbar exposes. Frozen tuple form for tests
318
318
  * that assert the registry covers exactly this set. */
319
- export declare const TOOL_REGISTRY_IDS: readonly ["arrow", "shape", "highlight", "text", "freehand", "marker", "redact", "crop"];
319
+ export declare const TOOL_REGISTRY_IDS: readonly ["arrow", "shape", "highlight", "text", "freehand", "marker", "redact", "crop", "overlay"];
320
320
  export type ToolRegistryId = (typeof TOOL_REGISTRY_IDS)[number];
@@ -0,0 +1,4 @@
1
+ export { parseElementTreeFromJson, serializeElementTreeToJson, validateElementTree, } from './json.js';
2
+ export { type BBox, type ElementNode, type ElementTree, type ElementTreeSource, type ElementTreeViewport, isElementTreeShape, } from './types.js';
3
+ export { type ElementMatch, type ElementTreeVisitor, findByMatch, findByRef, flattenTree, walkTree, } from './walk.js';
4
+ export { parseElementTreeFromYaml, serializeElementTreeToYaml } from './yaml.js';
@@ -0,0 +1,21 @@
1
+ import { ElementTree } from './types.js';
2
+ /**
3
+ * Serialize an ElementTree to a JSON string. Uses 2-space indent for
4
+ * readability; pass `compact: true` for a single-line minified
5
+ * form. Optional / undefined fields are omitted from the output to
6
+ * keep the serialization round-trip stable.
7
+ */
8
+ export declare function serializeElementTreeToJson(tree: ElementTree, options?: {
9
+ compact?: boolean;
10
+ }): string;
11
+ /**
12
+ * Parse a JSON string into an ElementTree. Throws `Error` with a
13
+ * descriptive message on malformed input — the parser is strict to
14
+ * surface schema drift early.
15
+ */
16
+ export declare function parseElementTreeFromJson(json: string): ElementTree;
17
+ /**
18
+ * Strict structural validation of a parsed `unknown` payload.
19
+ * Exported so the YAML parser can share the same validation pass.
20
+ */
21
+ export declare function validateElementTree(value: unknown): ElementTree;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Top-level ElementTree wire format. Stored in PNG XMP as iTXt
3
+ * (`annot:elementTree` keyword, deflate-compressed YAML by default —
4
+ * see `@ingcreators/annot-core/xmp` extension landed in Phase 1d).
5
+ */
6
+ export interface ElementTree {
7
+ /** Schema version. Currently 1. Reader version-dispatches; writers
8
+ * always emit the current schema. */
9
+ version: 1;
10
+ /** Metadata about who produced this capture. */
11
+ source: ElementTreeSource;
12
+ /** Capture viewport. CSS px; `scale` is the device-pixel-ratio. */
13
+ viewport: ElementTreeViewport;
14
+ /** Root node. Always present. Often `role: "main"` or `"document"`
15
+ * for browser captures; for partial captures (area / element-scoped)
16
+ * the root is the captured element itself. */
17
+ root: ElementNode;
18
+ }
19
+ export interface ElementTreeSource {
20
+ /** Which capture tool produced this tree. Common values:
21
+ * `"extension"` (browser extension MAIN-world walker),
22
+ * `"playwright"` (ariaSnapshot adapter). Open-ended so future
23
+ * Figma / screen-recorder / OCR adapters slot in cleanly. */
24
+ kind: "extension" | "playwright" | (string & {});
25
+ /** ISO 8601 timestamp of when the tree was produced. */
26
+ capturedAt: string;
27
+ /** Optional human-readable tool identifier (e.g.
28
+ * `"annot-playwright@0.4.0"`). Used in `annot show-xmp` output for
29
+ * debug; not consumed programmatically. */
30
+ agent?: string;
31
+ /** Source page URL at capture time, if applicable. */
32
+ url?: string;
33
+ }
34
+ export interface ElementTreeViewport {
35
+ /** Viewport width in CSS px. */
36
+ width: number;
37
+ /** Viewport height in CSS px. */
38
+ height: number;
39
+ /** `window.devicePixelRatio` (or equivalent) at capture time. Used
40
+ * to map CSS-px bboxes onto device-px screenshot coordinates. */
41
+ scale: number;
42
+ }
43
+ /**
44
+ * Per-node payload. Children are nested in `children`; the tree is
45
+ * walked depth-first.
46
+ */
47
+ export interface ElementNode {
48
+ /** ARIA role. Always present — decorative nodes use `"generic"` or
49
+ * `"presentation"`. Browser captures fall back to the implicit
50
+ * role for the underlying tag when ARIA doesn't apply. */
51
+ role: string;
52
+ /** Accessible name. Omitted when empty. */
53
+ name?: string;
54
+ /** Page-space bounding box in CSS px. Omitted for hidden / abstract
55
+ * nodes (e.g. document root with no measurable extent). */
56
+ bbox?: BBox;
57
+ /** Tree-unique identifier in `e<n>` format (depth-first numbering
58
+ * starting at `e1`). Stable WITHIN one capture; NOT stable across
59
+ * re-captures — see OQ-11 in the roadmap. */
60
+ ref: string;
61
+ /** ARIA-derived state tokens. Single tokens like `"checked"`,
62
+ * `"pressed"`, `"expanded"`, `"selected"`, `"disabled"`,
63
+ * `"required"`, `"invalid"`. Also accepts `key=value` tokens for
64
+ * scalar states: `"level=2"`, `"valuetext=10"`. */
65
+ states?: readonly string[];
66
+ /** HTML attribute snapshot (whitelist-filtered at capture time).
67
+ * Keys are lowercase attribute names; values are the attribute
68
+ * strings. Empty when no attributes were captured. */
69
+ attributes?: Readonly<Record<string, string>>;
70
+ /** Direct text content (heading body, paragraph text, textbox
71
+ * value). Distinct from `name` for elements where they differ
72
+ * (e.g. a textbox with placeholder `"Email"` and current value
73
+ * `"alice@example.com"` has `name: "Email"`, `text:
74
+ * "alice@example.com"`). */
75
+ text?: string;
76
+ /** Children in document order. Omitted for leaf nodes. */
77
+ children?: readonly ElementNode[];
78
+ }
79
+ /**
80
+ * Axis-aligned bounding box. CSS px, page-space coordinates.
81
+ */
82
+ export interface BBox {
83
+ x: number;
84
+ y: number;
85
+ width: number;
86
+ height: number;
87
+ }
88
+ /**
89
+ * Convenience type guard for callers that receive `unknown` payloads
90
+ * and need to check whether they look like an ElementTree before
91
+ * narrowing. Light validation only — checks for the presence of the
92
+ * three required top-level keys plus `version === 1`. The full
93
+ * parsers (`parseElementTreeFromYaml` / `parseElementTreeFromJson`)
94
+ * do exhaustive structural validation.
95
+ */
96
+ export declare function isElementTreeShape(value: unknown): value is ElementTree;
@@ -0,0 +1,49 @@
1
+ import { ElementNode, ElementTree } from './types.js';
2
+ /**
3
+ * Visitor callback. Return `false` to stop traversal early; return
4
+ * `void` / `true` to keep walking. The `parents` array gives the
5
+ * ancestor chain from the root down to (but excluding) the visited
6
+ * node — empty for the root itself.
7
+ */
8
+ export type ElementTreeVisitor = (node: ElementNode, parents: readonly ElementNode[]) => boolean | void;
9
+ /**
10
+ * Depth-first walk over every node in the tree. Stops early when the
11
+ * visitor returns `false`.
12
+ */
13
+ export declare function walkTree(tree: ElementTree, visit: ElementTreeVisitor): void;
14
+ /**
15
+ * Find the unique node with the given `ref`, or `null` if none.
16
+ * Refs are tree-unique within one capture (depth-first numbering),
17
+ * so the first match is the only match.
18
+ */
19
+ export declare function findByRef(tree: ElementTree, ref: string): ElementNode | null;
20
+ /**
21
+ * Match descriptor used by annotation yaml's `match: { role, name }`
22
+ * resolver. Both fields optional independently — a `{ role: "main" }`
23
+ * match catches every node with that role regardless of name; a
24
+ * `{ name: "Sign in" }` catches every node with that name regardless
25
+ * of role. When both are set, both must match (AND semantics). `name`
26
+ * comparison is exact.
27
+ */
28
+ export interface ElementMatch {
29
+ role?: string;
30
+ name?: string;
31
+ }
32
+ /**
33
+ * Find every node satisfying the match. Returns nodes in
34
+ * depth-first document order.
35
+ *
36
+ * Returning all matches (not just the first) lets callers detect
37
+ * ambiguity — drift detection flags `duplicated` when a unique
38
+ * `match` resolves to more than one node.
39
+ */
40
+ export declare function findByMatch(tree: ElementTree, match: ElementMatch): ElementNode[];
41
+ /**
42
+ * Produce a flat array of every node in depth-first document order.
43
+ * Each entry is the live `ElementNode` reference (no copying); the
44
+ * caller must treat the result as read-only.
45
+ *
46
+ * Useful for consumers that prefer flat iteration (search index
47
+ * builders, etc.). Prefer `walkTree` when ancestor context matters.
48
+ */
49
+ export declare function flattenTree(tree: ElementTree): ElementNode[];
@@ -0,0 +1,22 @@
1
+ import { ElementTree } from './types.js';
2
+ /**
3
+ * Serialize an ElementTree to YAML.
4
+ *
5
+ * The output uses 2-space indent, sorted top-level keys, and
6
+ * deterministic field ordering inside each node so the same tree
7
+ * always serializes to byte-identical YAML. This is important for
8
+ * the XMP write path — re-saving a PNG without semantic changes
9
+ * must produce identical bytes, otherwise git history thrashes
10
+ * pointlessly.
11
+ *
12
+ * Empty optional fields (`states`, `attributes`, `children`) are
13
+ * omitted entirely. The reader treats absence and empty as
14
+ * equivalent.
15
+ */
16
+ export declare function serializeElementTreeToYaml(tree: ElementTree): string;
17
+ /**
18
+ * Parse a YAML string into an ElementTree. Throws `Error` with a
19
+ * descriptive message on malformed input (parse failure or schema
20
+ * mismatch).
21
+ */
22
+ export declare function parseElementTreeFromYaml(yaml: string): ElementTree;
@@ -14,16 +14,18 @@ export { normalizeVariantSideFields, TOOL_PANEL_EXTRA_CONTROL_IDS, TOOL_REGISTRY
14
14
  export { readUniversalStyleAttrs, resolveStyleReadSource, } from './editor/tool-style-reader.js';
15
15
  export { writeUniversalStyleAttrs } from './editor/tool-style-writer.js';
16
16
  export { type AffineMatrix, applyInverseAffine, clampZoom, computeFitZoom, computeRenderedSize, DEFAULT_MAX_ZOOM, DEFAULT_MIN_ZOOM, FIT_VIEW_PADDING, } from './editor/viewport-math.js';
17
+ export { type BBox, type ElementMatch, type ElementNode, type ElementTree, type ElementTreeSource, type ElementTreeViewport, type ElementTreeVisitor, findByMatch, findByRef, flattenTree, isElementTreeShape, parseElementTreeFromJson, parseElementTreeFromYaml, serializeElementTreeToJson, serializeElementTreeToYaml, validateElementTree, walkTree, } from './element-tree/index.js';
17
18
  export { builtinIcon, type IconSpec, isBuiltinIcon, isSvgIcon, isUrlIcon, svgIcon, urlIcon, } from './icons/types.js';
18
19
  export { StorageConflictError, StorageError, type StorageErrorCode, StorageNotFoundError, StoragePermissionError, StorageQuotaError, } from './storage/errors.js';
19
20
  export { type ListingEntry, type ListingEntryKind, type MetadataCache, MetadataCacheError, MetadataCacheQuotaError, type StorageWithMetadataCache, supportsMetadataCache, } from './storage/metadata-cache.js';
20
21
  export { ancestorPaths, getFilename, getParentPath, isDescendantOrSame, joinPath, ROOT_PATH, rewritePathPrefix, splitExt, splitPath, uniquifyFilename, uniquifyFilenameAsync, validateName, } from './storage/path.js';
21
22
  export { type CachedThumbnail, type ThumbnailCache, ThumbnailCacheError, type ThumbnailCacheGetRequest, ThumbnailCacheQuotaError, } from './storage/thumbnail-cache.js';
22
- export type { DocumentRecord, DocumentRecordUpdate, FolderRecord, ImageRecord, ImageRecordUpdate, PageElement, PageMetadata, StorageProvider, StorageWithDocuments, StorageWithForceRefresh, StorageWithInit, StorageWithRateLimit, StorageWithResync, StorageWithTokenRefresher, } from './storage/types.js';
23
+ export type { DocumentRecord, DocumentRecordUpdate, FolderRecord, ImageRecord, ImageRecordUpdate, StorageProvider, StorageWithDocuments, StorageWithForceRefresh, StorageWithInit, StorageWithRateLimit, StorageWithResync, StorageWithTokenRefresher, } from './storage/types.js';
23
24
  export { supportsDocuments, supportsForceRefresh, supportsInit, supportsRateLimit, supportsResync, supportsTokenRefresher, } from './storage/types.js';
24
25
  export { assertNonNull } from './utils/assert.js';
25
26
  export { DEFAULT_FILL_COLOR, DEFAULT_FONT_SIZE, DEFAULT_STROKE_COLOR, DEFAULT_STROKE_WIDTH, JPEG_QUALITY, MOSAIC_BLOCK_SIZE, } from './utils/constants.js';
26
27
  export { computeDasharray, detectDashKey } from './utils/dash-utils.js';
27
28
  export { ANNOT_FILENAME_PREFIX, defaultAnnotFilenameStem, defaultAnnotImageFilename, formatLocalTimestamp, } from './utils/filename.js';
28
29
  export { newIdB58 } from './utils/id.js';
30
+ export { ELEMENT_TREE_ITXT_KEYWORD, hasElementTreePng, readElementTreePng, writeElementTreePng, } from './xmp/element-tree-payload.js';
29
31
  export { buildZip, dataUrlExt, dataUrlToBytes } from './zip/zip-builder.js';