@domandigital/craft 0.11.0 → 0.13.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/dist/audit.d.cts CHANGED
@@ -11,7 +11,13 @@
11
11
  * to a report and judged again later without a browser. Capturing one needs a
12
12
  * browser (see `@domandigital/craft/audit`); judging one does not.
13
13
  */
14
- declare const SNAPSHOT_VERSION = 1;
14
+ /**
15
+ * 2 adds `role` and `geometry` per section, `rhythmVariance` for the page and
16
+ * the screenshot measures in `visual`. All four are optional: a version 1
17
+ * snapshot reads as version 2 without them (see `readSnapshot`), and nothing
18
+ * that judged a v1 snapshot judges it differently.
19
+ */
20
+ declare const SNAPSHOT_VERSION = 2;
15
21
  interface SnapshotColour {
16
22
  /** `rgb(r, g, b)` or `rgba(r, g, b, a)`, as the browser computed it. */
17
23
  value: string;
@@ -50,6 +56,27 @@ interface SnapshotControl {
50
56
  text: string;
51
57
  }
52
58
  type SectionKind = "hero" | "logos" | "stats" | "cards" | "marquee" | "text" | "other";
59
+ /**
60
+ * What a section is for, finer than `kind`. `kind` keeps its version 1
61
+ * meaning because the rendered tells, the null models and the estate were
62
+ * calibrated on it; `role` is what component-level comparison reads.
63
+ */
64
+ type SectionRole = SectionKind | "cta-band" | "footer-cta" | "pricing" | "testimonials" | "faq" | "process" | "features" | "team" | "contact";
65
+ /** How a section is laid out, measured, not judged. */
66
+ interface SectionGeometry {
67
+ /** Share of the section's text blocks that are centred, 0 to 1. */
68
+ centredShare: number;
69
+ /** Content area balance about the vertical centre line: 1 mirror-even, 0 all on one side. */
70
+ mirrorSymmetry: number;
71
+ /** Share of the section's area with no text, image or control in it, 0 to 1. */
72
+ whitespaceRatio: number;
73
+ /** Width the content spans, over the viewport width. */
74
+ contentWidthRatio: number;
75
+ /** The painted background behind the section, as computed. */
76
+ background: string;
77
+ /** Buttons and button-styled links in the section. */
78
+ controls: number;
79
+ }
53
80
  interface SnapshotSection {
54
81
  top: number;
55
82
  height: number;
@@ -62,6 +89,70 @@ interface SnapshotSection {
62
89
  iconCards: number;
63
90
  /** Hidden at load, waiting for a scroll to reveal it. */
64
91
  hiddenAtLoad: boolean;
92
+ /** Version 2. */
93
+ role?: SectionRole;
94
+ /** Version 2. */
95
+ geometry?: SectionGeometry;
96
+ /** Version 2: short pill or label texts in the section ("Most popular", "New"), at most 5. */
97
+ badges?: string[];
98
+ /** Version 2: small round portraits (at most 96px, roughly square, radius 40% or more). */
99
+ avatars?: number;
100
+ /** Version 2: a horizontal scroller, snap track or prev/next control holds the content. */
101
+ carousel?: boolean;
102
+ /** Version 2: large standalone figures ("500+", "98%", "4.9/5") at 24px or more. */
103
+ figures?: number;
104
+ }
105
+ type ImageRole = "photo" | "illustration" | "icon" | "avatar" | "logo";
106
+ /**
107
+ * What the image's own bytes say about where it came from. Only a positive
108
+ * is evidence: most images carry no metadata at all, and a re-encode strips
109
+ * it, so no marker proves nothing.
110
+ */
111
+ interface ImageProvenance {
112
+ /** The IPTC digital source type found, if any: "trainedAlgorithmicMedia" or "compositeWithTrainedAlgorithmicMedia". */
113
+ digitalSourceType: string | null;
114
+ /** Where the marker sat: an XMP packet, a C2PA manifest, or somewhere else in the file. */
115
+ source: "xmp" | "c2pa" | "other" | null;
116
+ /** The file carries a C2PA manifest (Content Credentials), whatever it says. */
117
+ c2pa: boolean;
118
+ /** Bytes read, from the start of the file. */
119
+ bytes: number;
120
+ }
121
+ /** One image as the visitor sees it: an `img`, or a CSS background large enough to be a picture. */
122
+ interface SnapshotImage {
123
+ /** The URL the browser loaded, unwrapped from an image optimiser (`/_next/image?url=`), or a `data:` URI cut to 64 characters. */
124
+ src: string;
125
+ /** Host of `src`, or null for a `data:` URI. */
126
+ host: string | null;
127
+ width: number;
128
+ height: number;
129
+ top: number;
130
+ role: ImageRole;
131
+ alt: string | null;
132
+ /** Empty alt, `aria-hidden` or `role="presentation"`. */
133
+ decorative: boolean;
134
+ /** Index into `sections`, or null when the image sits outside them. */
135
+ section: number | null;
136
+ /** A CSS `background-image`, not an `img`. */
137
+ background: boolean;
138
+ /** Present when the bytes were read. */
139
+ provenance?: ImageProvenance;
140
+ }
141
+ /**
142
+ * Measures of the rendered pixels, from a full-page screenshot (capped at
143
+ * `height`). None of them passes or fails anything: they describe.
144
+ */
145
+ interface SnapshotVisual {
146
+ /** Hasler and Süsstrunk (2003) colourfulness, M = σ_rgyb + 0.3 μ_rgyb, on 0-255 channels. Grey is 0. */
147
+ colourfulness: number;
148
+ /** Share of sampled pixels on a luminance edge: a complexity proxy, 0 to 1. */
149
+ edgeDensity: number;
150
+ /** 1 minus the mean luminance difference between each pixel and its left-right mirror, 0 to 1. */
151
+ symmetry: number;
152
+ width: number;
153
+ height: number;
154
+ /** Pixels actually read: large pages are sampled on a grid. */
155
+ sampled: number;
65
156
  }
66
157
  interface SnapshotGradient {
67
158
  /** Colour stops, as computed. */
@@ -116,6 +207,17 @@ interface Snapshot {
116
207
  sections: SnapshotSection[];
117
208
  effects: SnapshotEffects;
118
209
  motion: SnapshotMotion;
210
+ /** Version 2: coefficient of variation of the section heights. Null with fewer than two sections. */
211
+ rhythmVariance?: number | null;
212
+ /** Version 2: absent when the page could not be screenshotted. */
213
+ visual?: SnapshotVisual;
214
+ /**
215
+ * Version 2, craft 0.14 and later: every image on the page, largest first, at
216
+ * most 60. Absent in older snapshots, which the imagery tells never judge.
217
+ */
218
+ images?: SnapshotImage[];
219
+ /** Set when a version 1 snapshot was read and upgraded in memory. */
220
+ migratedFrom?: 1;
119
221
  /** The design-system measures trawl has collected since v7, kept so it can switch to this. */
120
222
  metrics: {
121
223
  fontSizes: number[];
@@ -266,9 +368,31 @@ type FontClass = "serif" | "sans" | "mono" | "script" | "system";
266
368
  * Pure. Built from a Snapshot, so it describes what a visitor gets.
267
369
  */
268
370
 
269
- declare const FINGERPRINT_VERSION = 1;
371
+ /** One section as layout comparison sees it. */
372
+ interface FingerprintSection {
373
+ role: SectionRole;
374
+ /** Share of centred text, 0 to 1. */
375
+ centred: number;
376
+ /** Content width over viewport width, 0 to 1. */
377
+ width: number;
378
+ /** Left-right balance of the content, 0 to 1. Component comparison only. */
379
+ symmetry?: number;
380
+ /** Buttons in the section. */
381
+ controls?: number;
382
+ /** Cards side by side. */
383
+ cards?: number;
384
+ /** The section's own background, or null when it is the page ground. */
385
+ background?: Oklch | null;
386
+ /** Badge texts ("Most popular"), lower-cased. */
387
+ badges?: string[];
388
+ /** Small round portraits. */
389
+ avatars?: number;
390
+ carousel?: boolean;
391
+ /** Large standalone figures. */
392
+ figures?: number;
393
+ }
270
394
  interface Fingerprint {
271
- version: typeof FINGERPRINT_VERSION;
395
+ version: 1 | 2;
272
396
  /** The most prominent chromatic colour: button fills first, then painted area, then text. */
273
397
  accent: Oklch | null;
274
398
  ground: Oklch;
@@ -288,6 +412,8 @@ interface Fingerprint {
288
412
  effects: string[];
289
413
  /** Section kinds in running order, hero first. */
290
414
  layout: SectionKind[];
415
+ /** Version 2: present when every section carries a role and geometry. */
416
+ sections?: FingerprintSection[];
291
417
  }
292
418
 
293
419
  /**
@@ -329,6 +455,10 @@ interface SnapshotOptions {
329
455
  /** A Chromium to launch instead of Playwright's own. Also read from CRAFT_CHROMIUM. */
330
456
  executablePath?: string;
331
457
  timeoutMs?: number;
458
+ /** Take a screenshot and measure its pixels (`visual`). Default true. */
459
+ visual?: boolean;
460
+ /** Read the first bytes of the page's larger images for AI provenance markers. Default true. */
461
+ provenance?: boolean;
332
462
  }
333
463
  interface MinimalPage {
334
464
  close?(): Promise<void>;
@@ -338,7 +468,34 @@ interface MinimalPage {
338
468
  }): Promise<unknown>;
339
469
  evaluate<R>(fn: () => R): Promise<R>;
340
470
  waitForTimeout(ms: number): Promise<void>;
471
+ /** Optional: a driver without it gets a snapshot with no `visual` measures. */
472
+ screenshot?(options: {
473
+ fullPage?: boolean;
474
+ type?: "png";
475
+ clip?: {
476
+ x: number;
477
+ y: number;
478
+ width: number;
479
+ height: number;
480
+ };
481
+ }): Promise<Uint8Array>;
482
+ /** Optional: Playwright's request context, which fetches outside the page's CORS rules. Without it, no image gets `provenance`. */
483
+ request?: {
484
+ get(url: string, options?: {
485
+ headers?: Record<string, string>;
486
+ timeout?: number;
487
+ }): Promise<{
488
+ ok(): boolean;
489
+ body(): Promise<Uint8Array>;
490
+ }>;
491
+ };
341
492
  }
493
+ /** At most this many images are read for provenance, largest first. */
494
+ declare const PROVENANCE_MAX_IMAGES = 24;
495
+ /** The most time the reads may take for one page, in ms, so a machine offline waits seconds, not minutes. */
496
+ declare const PROVENANCE_BUDGET_MS = 20000;
497
+ /** The most of a page the screenshot measures read, in CSS px. A long page is judged on its opening. */
498
+ declare const VISUAL_MAX_HEIGHT = 6000;
342
499
  interface MinimalBrowser {
343
500
  newPage(options?: {
344
501
  viewport?: {
@@ -388,4 +545,4 @@ interface AuditResult {
388
545
  /** Snapshot a URL, judge it against the rendered tells, and fingerprint it. */
389
546
  declare function audit(url: string, options?: SnapshotOptions & CheckOptions): Promise<AuditResult>;
390
547
 
391
- export { type AuditResult, type SnapshotOptions, type SnapshotResult, audit, collectInPage, loadPlaywright, overlayInPage, snapshotPage, snapshotUrl, snapshotUrls };
548
+ export { type AuditResult, PROVENANCE_BUDGET_MS, PROVENANCE_MAX_IMAGES, type SnapshotOptions, type SnapshotResult, VISUAL_MAX_HEIGHT, audit, collectInPage, loadPlaywright, overlayInPage, snapshotPage, snapshotUrl, snapshotUrls };
package/dist/audit.d.ts CHANGED
@@ -11,7 +11,13 @@
11
11
  * to a report and judged again later without a browser. Capturing one needs a
12
12
  * browser (see `@domandigital/craft/audit`); judging one does not.
13
13
  */
14
- declare const SNAPSHOT_VERSION = 1;
14
+ /**
15
+ * 2 adds `role` and `geometry` per section, `rhythmVariance` for the page and
16
+ * the screenshot measures in `visual`. All four are optional: a version 1
17
+ * snapshot reads as version 2 without them (see `readSnapshot`), and nothing
18
+ * that judged a v1 snapshot judges it differently.
19
+ */
20
+ declare const SNAPSHOT_VERSION = 2;
15
21
  interface SnapshotColour {
16
22
  /** `rgb(r, g, b)` or `rgba(r, g, b, a)`, as the browser computed it. */
17
23
  value: string;
@@ -50,6 +56,27 @@ interface SnapshotControl {
50
56
  text: string;
51
57
  }
52
58
  type SectionKind = "hero" | "logos" | "stats" | "cards" | "marquee" | "text" | "other";
59
+ /**
60
+ * What a section is for, finer than `kind`. `kind` keeps its version 1
61
+ * meaning because the rendered tells, the null models and the estate were
62
+ * calibrated on it; `role` is what component-level comparison reads.
63
+ */
64
+ type SectionRole = SectionKind | "cta-band" | "footer-cta" | "pricing" | "testimonials" | "faq" | "process" | "features" | "team" | "contact";
65
+ /** How a section is laid out, measured, not judged. */
66
+ interface SectionGeometry {
67
+ /** Share of the section's text blocks that are centred, 0 to 1. */
68
+ centredShare: number;
69
+ /** Content area balance about the vertical centre line: 1 mirror-even, 0 all on one side. */
70
+ mirrorSymmetry: number;
71
+ /** Share of the section's area with no text, image or control in it, 0 to 1. */
72
+ whitespaceRatio: number;
73
+ /** Width the content spans, over the viewport width. */
74
+ contentWidthRatio: number;
75
+ /** The painted background behind the section, as computed. */
76
+ background: string;
77
+ /** Buttons and button-styled links in the section. */
78
+ controls: number;
79
+ }
53
80
  interface SnapshotSection {
54
81
  top: number;
55
82
  height: number;
@@ -62,6 +89,70 @@ interface SnapshotSection {
62
89
  iconCards: number;
63
90
  /** Hidden at load, waiting for a scroll to reveal it. */
64
91
  hiddenAtLoad: boolean;
92
+ /** Version 2. */
93
+ role?: SectionRole;
94
+ /** Version 2. */
95
+ geometry?: SectionGeometry;
96
+ /** Version 2: short pill or label texts in the section ("Most popular", "New"), at most 5. */
97
+ badges?: string[];
98
+ /** Version 2: small round portraits (at most 96px, roughly square, radius 40% or more). */
99
+ avatars?: number;
100
+ /** Version 2: a horizontal scroller, snap track or prev/next control holds the content. */
101
+ carousel?: boolean;
102
+ /** Version 2: large standalone figures ("500+", "98%", "4.9/5") at 24px or more. */
103
+ figures?: number;
104
+ }
105
+ type ImageRole = "photo" | "illustration" | "icon" | "avatar" | "logo";
106
+ /**
107
+ * What the image's own bytes say about where it came from. Only a positive
108
+ * is evidence: most images carry no metadata at all, and a re-encode strips
109
+ * it, so no marker proves nothing.
110
+ */
111
+ interface ImageProvenance {
112
+ /** The IPTC digital source type found, if any: "trainedAlgorithmicMedia" or "compositeWithTrainedAlgorithmicMedia". */
113
+ digitalSourceType: string | null;
114
+ /** Where the marker sat: an XMP packet, a C2PA manifest, or somewhere else in the file. */
115
+ source: "xmp" | "c2pa" | "other" | null;
116
+ /** The file carries a C2PA manifest (Content Credentials), whatever it says. */
117
+ c2pa: boolean;
118
+ /** Bytes read, from the start of the file. */
119
+ bytes: number;
120
+ }
121
+ /** One image as the visitor sees it: an `img`, or a CSS background large enough to be a picture. */
122
+ interface SnapshotImage {
123
+ /** The URL the browser loaded, unwrapped from an image optimiser (`/_next/image?url=`), or a `data:` URI cut to 64 characters. */
124
+ src: string;
125
+ /** Host of `src`, or null for a `data:` URI. */
126
+ host: string | null;
127
+ width: number;
128
+ height: number;
129
+ top: number;
130
+ role: ImageRole;
131
+ alt: string | null;
132
+ /** Empty alt, `aria-hidden` or `role="presentation"`. */
133
+ decorative: boolean;
134
+ /** Index into `sections`, or null when the image sits outside them. */
135
+ section: number | null;
136
+ /** A CSS `background-image`, not an `img`. */
137
+ background: boolean;
138
+ /** Present when the bytes were read. */
139
+ provenance?: ImageProvenance;
140
+ }
141
+ /**
142
+ * Measures of the rendered pixels, from a full-page screenshot (capped at
143
+ * `height`). None of them passes or fails anything: they describe.
144
+ */
145
+ interface SnapshotVisual {
146
+ /** Hasler and Süsstrunk (2003) colourfulness, M = σ_rgyb + 0.3 μ_rgyb, on 0-255 channels. Grey is 0. */
147
+ colourfulness: number;
148
+ /** Share of sampled pixels on a luminance edge: a complexity proxy, 0 to 1. */
149
+ edgeDensity: number;
150
+ /** 1 minus the mean luminance difference between each pixel and its left-right mirror, 0 to 1. */
151
+ symmetry: number;
152
+ width: number;
153
+ height: number;
154
+ /** Pixels actually read: large pages are sampled on a grid. */
155
+ sampled: number;
65
156
  }
66
157
  interface SnapshotGradient {
67
158
  /** Colour stops, as computed. */
@@ -116,6 +207,17 @@ interface Snapshot {
116
207
  sections: SnapshotSection[];
117
208
  effects: SnapshotEffects;
118
209
  motion: SnapshotMotion;
210
+ /** Version 2: coefficient of variation of the section heights. Null with fewer than two sections. */
211
+ rhythmVariance?: number | null;
212
+ /** Version 2: absent when the page could not be screenshotted. */
213
+ visual?: SnapshotVisual;
214
+ /**
215
+ * Version 2, craft 0.14 and later: every image on the page, largest first, at
216
+ * most 60. Absent in older snapshots, which the imagery tells never judge.
217
+ */
218
+ images?: SnapshotImage[];
219
+ /** Set when a version 1 snapshot was read and upgraded in memory. */
220
+ migratedFrom?: 1;
119
221
  /** The design-system measures trawl has collected since v7, kept so it can switch to this. */
120
222
  metrics: {
121
223
  fontSizes: number[];
@@ -266,9 +368,31 @@ type FontClass = "serif" | "sans" | "mono" | "script" | "system";
266
368
  * Pure. Built from a Snapshot, so it describes what a visitor gets.
267
369
  */
268
370
 
269
- declare const FINGERPRINT_VERSION = 1;
371
+ /** One section as layout comparison sees it. */
372
+ interface FingerprintSection {
373
+ role: SectionRole;
374
+ /** Share of centred text, 0 to 1. */
375
+ centred: number;
376
+ /** Content width over viewport width, 0 to 1. */
377
+ width: number;
378
+ /** Left-right balance of the content, 0 to 1. Component comparison only. */
379
+ symmetry?: number;
380
+ /** Buttons in the section. */
381
+ controls?: number;
382
+ /** Cards side by side. */
383
+ cards?: number;
384
+ /** The section's own background, or null when it is the page ground. */
385
+ background?: Oklch | null;
386
+ /** Badge texts ("Most popular"), lower-cased. */
387
+ badges?: string[];
388
+ /** Small round portraits. */
389
+ avatars?: number;
390
+ carousel?: boolean;
391
+ /** Large standalone figures. */
392
+ figures?: number;
393
+ }
270
394
  interface Fingerprint {
271
- version: typeof FINGERPRINT_VERSION;
395
+ version: 1 | 2;
272
396
  /** The most prominent chromatic colour: button fills first, then painted area, then text. */
273
397
  accent: Oklch | null;
274
398
  ground: Oklch;
@@ -288,6 +412,8 @@ interface Fingerprint {
288
412
  effects: string[];
289
413
  /** Section kinds in running order, hero first. */
290
414
  layout: SectionKind[];
415
+ /** Version 2: present when every section carries a role and geometry. */
416
+ sections?: FingerprintSection[];
291
417
  }
292
418
 
293
419
  /**
@@ -329,6 +455,10 @@ interface SnapshotOptions {
329
455
  /** A Chromium to launch instead of Playwright's own. Also read from CRAFT_CHROMIUM. */
330
456
  executablePath?: string;
331
457
  timeoutMs?: number;
458
+ /** Take a screenshot and measure its pixels (`visual`). Default true. */
459
+ visual?: boolean;
460
+ /** Read the first bytes of the page's larger images for AI provenance markers. Default true. */
461
+ provenance?: boolean;
332
462
  }
333
463
  interface MinimalPage {
334
464
  close?(): Promise<void>;
@@ -338,7 +468,34 @@ interface MinimalPage {
338
468
  }): Promise<unknown>;
339
469
  evaluate<R>(fn: () => R): Promise<R>;
340
470
  waitForTimeout(ms: number): Promise<void>;
471
+ /** Optional: a driver without it gets a snapshot with no `visual` measures. */
472
+ screenshot?(options: {
473
+ fullPage?: boolean;
474
+ type?: "png";
475
+ clip?: {
476
+ x: number;
477
+ y: number;
478
+ width: number;
479
+ height: number;
480
+ };
481
+ }): Promise<Uint8Array>;
482
+ /** Optional: Playwright's request context, which fetches outside the page's CORS rules. Without it, no image gets `provenance`. */
483
+ request?: {
484
+ get(url: string, options?: {
485
+ headers?: Record<string, string>;
486
+ timeout?: number;
487
+ }): Promise<{
488
+ ok(): boolean;
489
+ body(): Promise<Uint8Array>;
490
+ }>;
491
+ };
341
492
  }
493
+ /** At most this many images are read for provenance, largest first. */
494
+ declare const PROVENANCE_MAX_IMAGES = 24;
495
+ /** The most time the reads may take for one page, in ms, so a machine offline waits seconds, not minutes. */
496
+ declare const PROVENANCE_BUDGET_MS = 20000;
497
+ /** The most of a page the screenshot measures read, in CSS px. A long page is judged on its opening. */
498
+ declare const VISUAL_MAX_HEIGHT = 6000;
342
499
  interface MinimalBrowser {
343
500
  newPage(options?: {
344
501
  viewport?: {
@@ -388,4 +545,4 @@ interface AuditResult {
388
545
  /** Snapshot a URL, judge it against the rendered tells, and fingerprint it. */
389
546
  declare function audit(url: string, options?: SnapshotOptions & CheckOptions): Promise<AuditResult>;
390
547
 
391
- export { type AuditResult, type SnapshotOptions, type SnapshotResult, audit, collectInPage, loadPlaywright, overlayInPage, snapshotPage, snapshotUrl, snapshotUrls };
548
+ export { type AuditResult, PROVENANCE_BUDGET_MS, PROVENANCE_MAX_IMAGES, type SnapshotOptions, type SnapshotResult, VISUAL_MAX_HEIGHT, audit, collectInPage, loadPlaywright, overlayInPage, snapshotPage, snapshotUrl, snapshotUrls };