@ingcreators/annot-core 0.2.1 → 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.
@@ -20,6 +20,23 @@ export declare function joinPath(parent: string, name: string): string;
20
20
  export declare function getParentPath(path: string): string;
21
21
  /** Return the last segment (folder name or filename) from a path. */
22
22
  export declare function getFilename(path: string): string;
23
+ /**
24
+ * Derive the annotations YAML sidecar path paired with the image
25
+ * at `pngPath`. Convention introduced in Phase 4a of the
26
+ * [living-spec authoring roadmap](../../../../docs/plans/living-spec-authoring-roadmap.md):
27
+ * the sidecar sits in the same folder as the PNG with filename
28
+ * `<pngFilename>.annotations.yaml`.
29
+ *
30
+ * Examples:
31
+ * - `shots/login.png` → `shots/login.png.annotations.yaml`
32
+ * - `login.jpg` → `login.jpg.annotations.yaml`
33
+ *
34
+ * The leaf-as-suffix form (keep the full PNG filename including
35
+ * its extension, then append `.annotations.yaml`) keeps the
36
+ * sidecar discoverable next to its image regardless of the image
37
+ * extension.
38
+ */
39
+ export declare function annotationsYamlPathFor(pngPath: string): string;
23
40
  /** Split a path into its segments. Returns [] for the root path. */
24
41
  export declare function splitPath(path: string): string[];
25
42
  /**
@@ -1,9 +1,4 @@
1
- /**
2
- * Shared storage types used by Extension, Web app, and Desktop.
3
- *
4
- * Images and folders are identified by their filesystem-style path.
5
- * Root folder is represented by the empty string "".
6
- */
1
+ import { ElementTree } from '../element-tree/types.js';
7
2
  export interface ImageRecord {
8
3
  /** Primary key: full path, e.g. "Screenshots/Mobile/image-123.png". */
9
4
  path: string;
@@ -18,12 +13,14 @@ export interface ImageRecord {
18
13
  tags: Record<string, string>;
19
14
  createdAt: string;
20
15
  updatedAt: string;
21
- /** Optional DOM structure metadata captured alongside the screenshot
22
- * by the browser extension. Enables "click the Submit button →
23
- * auto-annotate" features in the editor. Undefined when the
24
- * capture came from a non-DOM source (desktop screenshot, paste,
25
- * etc.). Shape may evolve; see `PageMetadata.version`. */
26
- pageMetadata?: PageMetadata;
16
+ /** Canonical screen-capture tree captured alongside the
17
+ * screenshot. Source-agnostic: produced by the browser extension's
18
+ * MAIN-world walker, by Playwright's adapter, or by any future
19
+ * capture source (Figma adapter, OCR-derived, etc.). Undefined
20
+ * when the capture came from a non-DOM source (desktop screenshot,
21
+ * paste). Enables "click the Submit button → auto-annotate"
22
+ * features in the editor. See `@ingcreators/annot-core/element-tree`. */
23
+ elementTree?: ElementTree;
27
24
  }
28
25
  /**
29
26
  * In-place updates allowed via `updateImage`. Note that `folderPath`
@@ -62,81 +59,6 @@ export interface ImageRecord {
62
59
  * dimensions actually changed.
63
60
  */
64
61
  export type ImageRecordUpdate = Partial<Pick<ImageRecord, "annotationsSvg" | "tags" | "updatedAt" | "originalDataUrl" | "width" | "height">>;
65
- export interface PageMetadata {
66
- /** Schema version — bump on breaking changes so consumers can
67
- * gracefully handle old records. Current: 1. */
68
- version: 1;
69
- /** Source URL at capture time (also stored on ImageRecord.sourceUrl
70
- * but duplicated here so metadata is self-contained). */
71
- url: string;
72
- /** Viewport size at capture time, in CSS pixels (not device pixels).
73
- * Used to compute the scale factor when mapping bboxes onto the
74
- * screenshot (which is in device pixels). */
75
- viewport: {
76
- width: number;
77
- height: number;
78
- };
79
- /** `window.devicePixelRatio` at capture time. */
80
- devicePixelRatio: number;
81
- /** For scroll / per-page captures: the scroll offset at capture
82
- * time. Single-viewport captures use `{ x: 0, y: 0 }`. Bboxes in
83
- * `elements` are in document coordinates; subtract this offset +
84
- * add again per-segment for stitched captures. */
85
- scrollOffset: {
86
- x: number;
87
- y: number;
88
- };
89
- /** The rectangle of the document that the SCREENSHOT actually
90
- * covers, in CSS pixels, document coordinates. Used by the editor
91
- * to (a) FILTER `elements` to those visible in the screenshot
92
- * and (b) MAP element bboxes from doc coords → screenshot coords
93
- * via `(elBbox - captureRect.origin) * devicePixelRatio`.
94
- * - visible capture: equals viewport at capture time (scrollX/Y +
95
- * viewport size)
96
- * - area capture: the user-selected sub-region, offset by scroll
97
- * - scroll / per-page: full document (or per-segment) */
98
- captureRect: {
99
- x: number;
100
- y: number;
101
- width: number;
102
- height: number;
103
- };
104
- /** ISO timestamp of when the metadata snapshot was taken (usually
105
- * within a few ms of the screenshot). */
106
- capturedAt: string;
107
- /** Detected elements. Filtered to interactive / labeled items (see
108
- * `interactiveRole` predicate in the content script). */
109
- elements: PageElement[];
110
- }
111
- export interface PageElement {
112
- /** Stable id within this capture — references across annotations. */
113
- id: string;
114
- /** Tag name in lowercase (e.g. "button", "a", "input"). */
115
- tag: string;
116
- /** ARIA role or implicit role ("button", "link", "textbox", ...). */
117
- role?: string;
118
- /** Visible text — textContent for button/link, label text for input. */
119
- text?: string;
120
- /** `aria-label` — explicit accessibility label, prefer over `text`
121
- * when present since it's the a11y ground truth. */
122
- ariaLabel?: string;
123
- /** For form inputs: the input type (text / email / submit / …). */
124
- inputType?: string;
125
- /** For inputs: placeholder text. */
126
- placeholder?: string;
127
- /** For links: the href destination. */
128
- href?: string;
129
- /** Element id attribute (if any) — useful for stable selection. */
130
- domId?: string;
131
- /** Bounding box in DOCUMENT (not viewport) coordinates at capture
132
- * time, in CSS pixels. To draw on the screenshot: multiply by
133
- * devicePixelRatio. [x, y, width, height]. */
134
- bbox: [number, number, number, number];
135
- /** CSS selector (best effort) for re-locating the element later. */
136
- selector?: string;
137
- /** True if element was (partially) visible at capture time. */
138
- visible: boolean;
139
- }
140
62
  export interface FolderRecord {
141
63
  /** Primary key: full path, e.g. "Screenshots/Mobile". */
142
64
  path: string;
@@ -667,6 +589,53 @@ export interface StorageWithDocuments {
667
589
  * @throws `Error` for unstructured backend / IO failures.
668
590
  */
669
591
  updateDocument(path: string, updates: DocumentRecordUpdate): Promise<void>;
592
+ /**
593
+ * Read the annotations YAML sidecar paired with the image at
594
+ * `pngPath`. The store derives the sidecar location internally
595
+ * from the convention `<pngPath>.annotations.yaml` (so
596
+ * `shots/login.png` →`shots/login.png.annotations.yaml`); callers
597
+ * pass the PNG path only.
598
+ *
599
+ * Optional — stores that don't support sidecar yaml files may
600
+ * omit this method. Callers gate on
601
+ * {@link supportsAnnotationsYaml} before invoking.
602
+ *
603
+ * Introduced in Phase 4a of the
604
+ * [living-spec authoring roadmap](../../../../docs/plans/living-spec-authoring-roadmap.md)
605
+ * for the Annot editor's Overlay tool. Each entry in the yaml
606
+ * follows the `AnnotationsFile` schema defined by
607
+ * `@ingcreators/annot-product-docs` (Phase 2a).
608
+ *
609
+ * @returns the YAML source string, or `undefined` when no
610
+ * sidecar exists for that PNG.
611
+ * @throws `Error` for backend / IO / parse failures.
612
+ */
613
+ getAnnotationsYaml?(pngPath: string): Promise<string | undefined>;
614
+ /**
615
+ * Atomically create or replace the annotations YAML sidecar
616
+ * paired with the image at `pngPath`. The store derives the
617
+ * sidecar location internally from the convention
618
+ * `<pngPath>.annotations.yaml`.
619
+ *
620
+ * Optional — stores that don't support sidecar yaml files may
621
+ * omit this method. Callers gate on
622
+ * {@link supportsAnnotationsYaml} before invoking.
623
+ *
624
+ * The write does NOT touch the PNG bytes or any sibling MDX
625
+ * file — only the sidecar yaml is mutated. Idempotent on
626
+ * unchanged input (same `content` twice produces the same
627
+ * on-disk bytes).
628
+ *
629
+ * Introduced in Phase 4a of the
630
+ * [living-spec authoring roadmap](../../../../docs/plans/living-spec-authoring-roadmap.md).
631
+ *
632
+ * @throws `StoragePermissionError` for backend auth / ACL
633
+ * rejection.
634
+ * @throws `StorageQuotaError` when the backend reports
635
+ * out-of-space.
636
+ * @throws `Error` for unstructured backend / IO failures.
637
+ */
638
+ setAnnotationsYaml?(pngPath: string, content: string): Promise<void>;
670
639
  }
671
640
  export declare function supportsResync(store: StorageProvider): store is StorageProvider & StorageWithResync;
672
641
  export declare function supportsForceRefresh(store: StorageProvider): store is StorageProvider & StorageWithForceRefresh;
@@ -675,3 +644,18 @@ export declare function supportsInit(store: StorageProvider): store is StoragePr
675
644
  export declare function supportsRateLimit(store: StorageProvider): store is StorageProvider & StorageWithRateLimit;
676
645
  export declare function supportsThumbnailCache(store: StorageProvider): store is StorageProvider & StorageWithThumbnailCache;
677
646
  export declare function supportsDocuments(store: StorageProvider): store is StorageProvider & StorageWithDocuments;
647
+ /**
648
+ * Narrows a store to one that exposes the Phase 4a annotations
649
+ * YAML sidecar surface (`getAnnotationsYaml` + `setAnnotationsYaml`).
650
+ *
651
+ * Use instead of `if (store.getAnnotationsYaml)` so the narrow is
652
+ * type-safe and the optional behaviour is documented at the call
653
+ * site. The methods sit on
654
+ * {@link StorageWithDocuments} as optional members; this predicate
655
+ * is the dedicated capability check for callers that need both
656
+ * sides of the read / write pair.
657
+ */
658
+ export declare function supportsAnnotationsYaml(store: StorageProvider): store is StorageProvider & StorageWithDocuments & {
659
+ getAnnotationsYaml: NonNullable<StorageWithDocuments["getAnnotationsYaml"]>;
660
+ setAnnotationsYaml: NonNullable<StorageWithDocuments["setAnnotationsYaml"]>;
661
+ };
@@ -0,0 +1,32 @@
1
+ import { ElementTree } from '../element-tree/index.js';
2
+ /**
3
+ * iTXt chunk keyword identifying the ElementTree payload.
4
+ * Differs from the editor's XMP keyword (`XML:com.adobe.xmp`) so
5
+ * the two chunks can coexist on the same PNG.
6
+ */
7
+ export declare const ELEMENT_TREE_ITXT_KEYWORD = "annot:elementTree";
8
+ /**
9
+ * Write `tree` into a PNG's `annot:elementTree` iTXt chunk. The
10
+ * chunk is deflate-compressed (compressionFlag=1) — ElementTree YAML
11
+ * is verbose with high token repetition and compresses well.
12
+ *
13
+ * Any existing `annot:elementTree` chunk is replaced. Other chunks
14
+ * (image data, editor XMP, embedded original via `svGo`) are
15
+ * preserved verbatim.
16
+ */
17
+ export declare function writeElementTreePng(pngData: Uint8Array, tree: ElementTree): Uint8Array;
18
+ /**
19
+ * Read the `annot:elementTree` payload from a PNG. Returns the
20
+ * parsed `ElementTree`, or `null` when the input is not a PNG or
21
+ * has no `annot:elementTree` chunk.
22
+ *
23
+ * Throws on schema-version mismatch (unknown major version) or
24
+ * parse failure — silent ignore would mask data corruption. Callers
25
+ * that need a tolerant read should wrap in try / catch.
26
+ */
27
+ export declare function readElementTreePng(pngData: Uint8Array): ElementTree | null;
28
+ /**
29
+ * Light-weight predicate — does this PNG carry an
30
+ * `annot:elementTree` chunk at all? Doesn't deflate or parse.
31
+ */
32
+ export declare function hasElementTreePng(pngData: Uint8Array): boolean;
@@ -14,7 +14,7 @@
14
14
  * Re-exports of Tier-A symbols below keep existing
15
15
  * `@ingcreators/annot-core/xmp` consumers working unchanged.
16
16
  */
17
- export { type AnnotMetadata, type CreateEditablePngBytesOptions, createEditablePngBytes, readEditableImage, readEditablePngBytes, WELL_KNOWN_TAG_KEYS, writePngWithTagsOnly, } from './xmp-bytes.js';
17
+ export { type AnnotMetadata, type CreateEditablePngBytesOptions, createEditablePngBytes, dataUrlToUint8Array, readEditableImage, readEditablePngBytes, WELL_KNOWN_TAG_KEYS, writePngWithTagsOnly, } from './xmp-bytes.js';
18
18
  export interface EditableImageOptions {
19
19
  /** Rendered image (screenshot + annotations) as Blob */
20
20
  renderedBlob: Blob;
@@ -16,8 +16,37 @@
16
16
  * segments (each prefixed with `annot:OriginalImage\0`).
17
17
  */
18
18
  export declare function buildXmp(annotationsSvg: string, width: number, height: number, tags?: Record<string, string>): string;
19
+ export declare function u32be(n: number): Uint8Array;
20
+ export declare function readU32be(data: Uint8Array, offset: number): number;
21
+ export declare function concat(...arrays: Uint8Array[]): Uint8Array;
22
+ export declare function startsWith(data: Uint8Array, offset: number, prefix: Uint8Array): boolean;
19
23
  export declare function dataUrlToUint8Array(dataUrl: string): Uint8Array;
20
24
  export declare function writeJpegWithMetadata(jpegData: Uint8Array, xmpBytes: Uint8Array, originalData: Uint8Array): Uint8Array;
25
+ export declare function crc32(data: Uint8Array): number;
26
+ export declare function buildPngChunk(chunkType: Uint8Array, data: Uint8Array): Uint8Array;
27
+ /**
28
+ * Strip the Annot editor's editable-layer metadata from a PNG.
29
+ * Walks the chunk stream, drops the Adobe XMP iTXt chunk (the
30
+ * `<annot:annotations>` / `<annot:tags>` carrier) AND the custom
31
+ * `svGo` chunk (the original un-annotated bitmap), keeps every
32
+ * other chunk verbatim (including all critical chunks IHDR /
33
+ * IDAT / IEND so the result stays a valid PNG with the same
34
+ * visible pixels).
35
+ *
36
+ * Returns the input bytes unchanged when no editable-layer
37
+ * chunks are present.
38
+ *
39
+ * Used internally by `writePngWithMetadata` / `writePngWithTagsOnly`
40
+ * to clear stale metadata before re-injecting new chunks. Exposed
41
+ * since Phase 3j of `docs/plans/living-spec-authoring-roadmap.md`
42
+ * (Phase 3 follow-up #2) so `@ingcreators/annot-annotator` can
43
+ * publish a top-level `flattenEditablePng` primitive against the
44
+ * same logic without duplicating chunk-walking code.
45
+ *
46
+ * No re-rasterization — the visible bytes were already the
47
+ * annotated bitmap; this is metadata removal only.
48
+ */
49
+ export declare function stripPngEditableLayer(data: Uint8Array): Uint8Array;
21
50
  export declare function writePngWithMetadata(pngData: Uint8Array, xmpBytes: Uint8Array, originalData: Uint8Array): Uint8Array;
22
51
  /**
23
52
  * Build a tags-only XMP packet — `<annot:tags>` element present,
package/dist/xmp-bytes.js CHANGED
@@ -79,7 +79,7 @@ function g(e, t, r) {
79
79
  return u(o.slice(0, 2), i, a, o.slice(2));
80
80
  }
81
81
  var _ = (() => {
82
- let e = new Uint32Array(256);
82
+ let e = /* @__PURE__ */ new Uint32Array(256);
83
83
  for (let t = 0; t < 256; t++) {
84
84
  let n = t;
85
85
  for (let e = 0; e < 8; e++) n & 1 ? n = 3988292384 ^ n >>> 1 : n >>>= 1;
@@ -262,4 +262,4 @@ function z(e) {
262
262
  return btoa(n);
263
263
  }
264
264
  //#endregion
265
- export { T as WELL_KNOWN_TAG_KEYS, a as buildXmp, C as buildXmpTagsOnly, E as createEditablePngBytes, f as dataUrlToUint8Array, O as readEditableImage, D as readEditablePngBytes, g as writeJpegWithMetadata, S as writePngWithMetadata, w as writePngWithTagsOnly };
265
+ export { T as WELL_KNOWN_TAG_KEYS, y as buildPngChunk, a as buildXmp, C as buildXmpTagsOnly, u as concat, v as crc32, E as createEditablePngBytes, f as dataUrlToUint8Array, O as readEditableImage, D as readEditablePngBytes, l as readU32be, d as startsWith, x as stripPngEditableLayer, s as u32be, g as writeJpegWithMetadata, S as writePngWithMetadata, w as writePngWithTagsOnly };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingcreators/annot-core",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Annot SDK — SVG-first screenshot annotation format, storage abstractions, and editor primitives. The foundation shared by the PWA, Chrome extension, Electron desktop, VSCode extension, and the headless annotator.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "ingcreators",
@@ -46,12 +46,14 @@
46
46
  "access": "public"
47
47
  },
48
48
  "devDependencies": {
49
+ "@types/js-yaml": "^4.0.9",
49
50
  "@types/pako": "^2.0.4",
50
51
  "typescript": "^6.0.3",
51
- "vite": "^8.0.13",
52
- "vite-plugin-dts": "^5.0.1"
52
+ "vite": "^8.0.16",
53
+ "vite-plugin-dts": "^5.0.3"
53
54
  },
54
55
  "dependencies": {
56
+ "js-yaml": "^4.1.1",
55
57
  "pako": "^2.1.0"
56
58
  },
57
59
  "scripts": {