@tileflow/capture 0.0.0-bootstrap.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/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # @tileflow/capture
2
+
3
+ Pinned, headless local capture and visual comparison for Tileflow scenes. Most users should invoke
4
+ it through the project-local `tileflow` CLI; this package is the public Node integration surface.
5
+
6
+ ## Capture committed scenes
7
+
8
+ ```ts
9
+ import {captureTileflowScenes} from '@tileflow/capture';
10
+
11
+ const result = await captureTileflowScenes({
12
+ scenes: ['madrid-desktop'],
13
+ allowBrowserInstall: true,
14
+ });
15
+
16
+ const [{png, receipt, sha256, networkDependent}] = result.captures;
17
+ ```
18
+
19
+ Standalone scenes compile through `@tileflow/dev`, inject the installed MapLibre JS and CSS into a
20
+ pinned Playwright Chromium headless shell, fulfill generated local icon assets in memory, and wait
21
+ for `load`, `idle`, and two animation frames. They never open a visible window or an HTTP listener.
22
+ One call with multiple scenes uses one Browser and a fresh isolated context per scene. Capture
23
+ installs the exact pinned shell automatically when `allowBrowserInstall` is true; the CLI enables
24
+ that normal happy path by default.
25
+
26
+ For a warm integration, retain and close a session:
27
+
28
+ ```ts
29
+ import {createTileflowCaptureSession} from '@tileflow/capture';
30
+
31
+ const session = createTileflowCaptureSession({cwd: process.cwd()});
32
+ try {
33
+ await session.capture(['madrid-desktop']);
34
+ await session.captureAll();
35
+ } finally {
36
+ await session.close();
37
+ }
38
+ ```
39
+
40
+ Sessions can also capture caller-provided scene definitions or a prepared `TileflowBuildArtifacts`
41
+ snapshot. Results carry PNG bytes, dimensions, hashes, safe runtime identity, the canonical receipt,
42
+ warnings, and a `networkDependent` flag. They never expose a Playwright page or browser as part of
43
+ the capture result.
44
+
45
+ Failures use `TileflowCaptureError` with a stable code and optional bounded details. The phase
46
+ distinguishes style validation, browser start, resource loading, MapLibre load/idle, and screenshot
47
+ production. Resource details contain only a classified kind, sanitized origin, optional HTTP
48
+ status, and safe bounded context; raw Playwright stacks, queries, credentials, response bodies, and
49
+ DOM contents are not part of the public diagnostic contract.
50
+
51
+ ## Application capture
52
+
53
+ Application scenes need only the app's normal loopback server:
54
+
55
+ ```ts
56
+ const result = await captureTileflowScenes({
57
+ appOrigin: 'http://127.0.0.1:3000',
58
+ scenes: ['madrid-product'],
59
+ });
60
+ ```
61
+
62
+ Tileflow does not start or discover the server. It accepts only loopback HTTP(S), creates a fresh
63
+ context without reused profile/cookies/storage or service workers, confines redirects to the exact
64
+ approved origin, finds exactly one target, waits for Tileflow's `data-tileflow-state="idle"` marker,
65
+ and captures the element or viewport. The React, Vue, and Svelte wrappers expose the marker plus
66
+ `data-tileflow-map` and optional `data-tileflow-capture-id`. A full one-off `appUrl`, selector, and
67
+ frame can be supplied through the session options. The route and component props own application
68
+ camera state; capture includes that camera in scene identity but does not rewrite it.
69
+
70
+ ## Prepared/offline browser setup
71
+
72
+ ```ts
73
+ import {setupTileflowCaptureBrowser} from '@tileflow/capture';
74
+
75
+ const renderer = await setupTileflowCaptureBrowser({allowInstall: true});
76
+ ```
77
+
78
+ Playwright provisions its exact platform-specific headless shell in the versioned per-user browser
79
+ cache. Tileflow always launches headlessly, never uses system Chrome, never embeds a browser in the
80
+ npm tarball, and never deletes the shared cache. Set `allowInstall: false` for an offline prepared
81
+ environment. The equivalent CLI command is `tileflow setup capture`; use it to pay installation
82
+ cost in advance or verify prepared CI, not as a prerequisite for normal capture.
83
+
84
+ ## Receipts and visual comparison
85
+
86
+ `createTileflowCaptureReceipt`, `parseTileflowCaptureReceipt`,
87
+ `validateTileflowCaptureReceipt`, and `serializeTileflowCaptureReceipt` implement the strict,
88
+ bounded schema-version-1 receipt contract. A receipt contains image/scene/style hashes, dimensions,
89
+ renderer/platform identity, required nullable `source.tilesetVersion` evidence, and
90
+ remote-dependency state; it contains no time, user, origin,
91
+ repository, absolute path, config source, environment, or credential.
92
+
93
+ ```ts
94
+ import {compareTileflowCaptureToBaseline} from '@tileflow/capture';
95
+
96
+ const comparison = await compareTileflowCaptureToBaseline(result.captures[0], {
97
+ png: baselinePng,
98
+ receipt: baselineReceiptJson,
99
+ });
100
+ ```
101
+
102
+ Comparison validates PNG CRC, hash, dimensions, pixel bounds, and receipt identity before reading
103
+ pixels. It classifies unchanged, changed, missing, scene-mismatch, and runtime-mismatch results,
104
+ reports exact RGBA changed pixels plus a fixed 0.1 perceptual metric, and returns an optional
105
+ transparent high-contrast diff PNG. Exact RGBA inequality determines `changed`.
106
+
107
+ For an external design screenshot that has no Tileflow receipt, use the separate exploratory
108
+ primitive:
109
+
110
+ ```ts
111
+ import {analyzeTileflowCaptureReference} from '@tileflow/capture';
112
+
113
+ const analysis = await analyzeTileflowCaptureReference(result.captures[0], referencePng);
114
+ ```
115
+
116
+ It strictly validates the bounded non-interlaced PNG, reports dimensions and up to 16 deterministic
117
+ quantized palette entries, and—only for equal dimensions—exact/perceptual changed pixels, mean
118
+ absolute RGBA-channel difference, and a high-contrast diff PNG. It does not assert scene/runtime
119
+ compatibility, accept a receipt for the reference, or create a baseline.
120
+
121
+ ## Security and determinism boundary
122
+
123
+ `tileflow.config.ts` is executable trusted repository code, not a sandbox. Chromium receives a
124
+ small non-secret environment allowlist. Standalone capture fulfills only exact compiled synthetic
125
+ assets and reports external HTTP(S) origins; application capture accepts only loopback URLs and
126
+ does not echo query strings or DOM contents in failures. Remote tiles, glyphs, or sprites can change,
127
+ so a remote-dependent result is evidence but not a globally byte-stable golden image.
128
+
129
+ See `THIRD_PARTY_NOTICES.md` for runtime license treatment and
130
+ https://tileflow.dev/docs/agent-workflow for the complete CLI workflow.
@@ -0,0 +1,35 @@
1
+ # Third-party notices
2
+
3
+ `@tileflow/capture` depends on the following separately installed open-source packages. Their
4
+ package distributions contain the complete license texts and notices; this file keeps the relevant
5
+ attribution and browser-provisioning boundary visible in the Tileflow package itself.
6
+
7
+ ## Playwright 1.60.0
8
+
9
+ Copyright (c) Microsoft Corporation. Licensed under the Apache License, Version 2.0. Playwright
10
+ contains code derived from Puppeteer, also available under Apache-2.0. The installed `playwright`
11
+ and `playwright-core` packages include `LICENSE`, `NOTICE`, and `ThirdPartyNotices.txt` files.
12
+
13
+ ## Chromium headless shell revision 1223
14
+
15
+ Chromium is licensed under the BSD license and includes third-party software under additional open
16
+ source licenses. It is not contained in the `@tileflow/capture` npm tarball. Playwright provisions
17
+ the platform-specific headless shell into its versioned per-user browser cache; the downloaded
18
+ distribution carries Chromium's license and credits. Chromium source and license information are
19
+ available at https://www.chromium.org/chromium-projects/.
20
+
21
+ ## MapLibre GL JS 5.24.0
22
+
23
+ Copyright (c) 2023 MapLibre contributors. Licensed under BSD-3-Clause. The installed `maplibre-gl`
24
+ package includes `LICENSE.txt`, including notices for code derived from Mapbox GL JS and other
25
+ third-party works.
26
+
27
+ ## Pixelmatch 7.2.0
28
+
29
+ Copyright (c) 2025 Mapbox. Licensed under the ISC License. The installed `pixelmatch` package
30
+ includes `LICENSE`.
31
+
32
+ ## pngjs 7.0.0
33
+
34
+ Copyright (c) 2015 Luke Page and original contributors; derived work copyright (c) 2012 Kuba
35
+ Niegowski. Licensed under the MIT License. The installed `pngjs` package includes `LICENSE`.
@@ -0,0 +1,270 @@
1
+ import { Browser } from 'playwright';
2
+ import { TileflowCaptureScene } from '@tileflow/core';
3
+ import { TileflowBuildArtifacts } from '@tileflow/dev';
4
+
5
+ type TileflowCaptureRendererIdentity = {
6
+ tileflow: string;
7
+ maplibre: string;
8
+ playwright: string;
9
+ chromiumRevision: string;
10
+ chromiumVersion: string;
11
+ };
12
+ declare const tileflowCaptureRuntime: Readonly<{
13
+ tileflow: string;
14
+ maplibre: string;
15
+ playwright: string;
16
+ chromiumRevision: string;
17
+ chromiumVersion: string;
18
+ }>;
19
+ declare function createTileflowCaptureRendererIdentity(browser?: Pick<Browser, 'version'>): TileflowCaptureRendererIdentity;
20
+
21
+ type TileflowBrowserInstallProgress = 'installing' | 'installed';
22
+ type TileflowBrowserLaunchOptions = {
23
+ allowInstall?: boolean;
24
+ onInstallProgress?: (progress: TileflowBrowserInstallProgress) => void;
25
+ signal?: AbortSignal;
26
+ };
27
+ type TileflowCaptureBrowserDependencies = {
28
+ install(signal?: AbortSignal): Promise<void>;
29
+ launch(): Promise<Browser>;
30
+ };
31
+ declare function createTileflowCaptureBrowserEnvironment(source?: NodeJS.ProcessEnv): Record<string, string>;
32
+ declare function launchTileflowCaptureBrowser(options?: TileflowBrowserLaunchOptions): Promise<Browser>;
33
+ declare function setupTileflowCaptureBrowser(options?: TileflowBrowserLaunchOptions): Promise<TileflowCaptureRendererIdentity>;
34
+ declare class TileflowCaptureBrowserManager {
35
+ #private;
36
+ constructor(options?: TileflowBrowserLaunchOptions, dependencies?: TileflowCaptureBrowserDependencies);
37
+ getBrowser(signal?: AbortSignal | undefined): Promise<Browser>;
38
+ close(): Promise<void>;
39
+ }
40
+
41
+ declare const tileflowCaptureReceiptSchemaVersion: 1;
42
+ declare const tileflowCaptureReceiptLimits: Readonly<{
43
+ maximumBytes: number;
44
+ }>;
45
+ type TileflowCaptureReceipt = {
46
+ schemaVersion: 1;
47
+ scene: {
48
+ name: string;
49
+ map: string;
50
+ target: 'map' | 'application';
51
+ sha256: string;
52
+ };
53
+ style: {
54
+ sha256: string;
55
+ };
56
+ image: {
57
+ sha256: string;
58
+ cssWidth: number;
59
+ cssHeight: number;
60
+ physicalWidth: number;
61
+ physicalHeight: number;
62
+ dpr: 1 | 2;
63
+ };
64
+ renderer: TileflowCaptureRendererIdentity;
65
+ platform: {
66
+ os: string;
67
+ architecture: string;
68
+ };
69
+ source: {
70
+ tilesetVersion: string | null;
71
+ };
72
+ networkDependent: boolean;
73
+ };
74
+ type CreateTileflowCaptureReceiptInput = {
75
+ dpr: 1 | 2;
76
+ height: number;
77
+ map: string;
78
+ networkDependent: boolean;
79
+ pngSha256: string;
80
+ renderer: TileflowCaptureRendererIdentity;
81
+ scene: string;
82
+ sceneSha256: string;
83
+ styleSha256: string;
84
+ sourceVersion?: string;
85
+ target: 'map' | 'application';
86
+ width: number;
87
+ };
88
+ declare function createTileflowCaptureReceipt(input: CreateTileflowCaptureReceiptInput): TileflowCaptureReceipt;
89
+ declare function serializeTileflowCaptureReceipt(receipt: TileflowCaptureReceipt): string;
90
+ declare function parseTileflowCaptureReceipt(source: string | Uint8Array): TileflowCaptureReceipt;
91
+ declare function validateTileflowCaptureReceipt(value: unknown): TileflowCaptureReceipt;
92
+
93
+ declare const tileflowCaptureResultSchemaVersion: 1;
94
+ type TileflowCaptureOptions = {
95
+ allowBrowserInstall?: boolean;
96
+ appOrigin?: string;
97
+ appUrl?: string;
98
+ config?: string;
99
+ cwd?: string;
100
+ onBrowserInstallProgress?: (progress: TileflowBrowserInstallProgress) => void;
101
+ frame?: 'map' | 'viewport';
102
+ scenes: string[];
103
+ signal?: AbortSignal;
104
+ selector?: string;
105
+ timeoutMs?: number;
106
+ };
107
+ type CreateTileflowCaptureSessionOptions = Omit<TileflowCaptureOptions, 'scenes'>;
108
+ type TileflowCapture = {
109
+ scene: string;
110
+ map: string;
111
+ target: 'map' | 'application';
112
+ png: Uint8Array;
113
+ sha256: string;
114
+ sceneSha256: string;
115
+ styleSha256: string;
116
+ width: number;
117
+ height: number;
118
+ dpr: 1 | 2;
119
+ networkDependent: boolean;
120
+ renderer: TileflowCaptureRendererIdentity;
121
+ receipt: TileflowCaptureReceipt;
122
+ warnings: string[];
123
+ };
124
+ type TileflowCaptureResult = {
125
+ schemaVersion: 1;
126
+ captures: TileflowCapture[];
127
+ };
128
+ type TileflowCaptureSession = {
129
+ capture(scenes: string[], signal?: AbortSignal): Promise<TileflowCaptureResult>;
130
+ captureAll(signal?: AbortSignal): Promise<TileflowCaptureResult>;
131
+ captureDefinitions(scenes: Record<string, TileflowCaptureScene>, signal?: AbortSignal): Promise<TileflowCaptureResult>;
132
+ captureArtifacts(artifacts: TileflowBuildArtifacts, requestedScenes: string[], signal?: AbortSignal): Promise<TileflowCaptureResult>;
133
+ close(): Promise<void>;
134
+ };
135
+ declare function captureTileflowScenes(options: TileflowCaptureOptions): Promise<TileflowCaptureResult>;
136
+ declare function createTileflowCaptureSession(options?: CreateTileflowCaptureSessionOptions): TileflowCaptureSession;
137
+
138
+ type TileflowCaptureErrorCode = 'ABORTED' | 'APPLICATION_ORIGIN_REQUIRED' | 'APPLICATION_ORIGIN_INVALID' | 'APPLICATION_NAVIGATION_FAILED' | 'APPLICATION_TARGET_NOT_FOUND' | 'APPLICATION_TARGET_AMBIGUOUS' | 'APPLICATION_ERROR' | 'BASELINE_INVALID' | 'BROWSER_INSTALL_FAILED' | 'BROWSER_MISSING' | 'BROWSER_START_FAILED' | 'CAPTURE_TIMEOUT' | 'INVALID_PNG' | 'MAP_LOAD_FAILED' | 'RENDER_FAILED' | 'RESOURCE_FAILED' | 'SCENE_NOT_FOUND' | 'SCREENSHOT_FAILED' | 'STYLE_INVALID' | 'SYNTHETIC_ASSET_NOT_FOUND';
139
+ type TileflowCapturePhase = 'style-validation' | 'browser-start' | 'resource-load' | 'map-load' | 'map-idle' | 'screenshot';
140
+ type TileflowCaptureDiagnostic = {
141
+ message: string;
142
+ path: string;
143
+ };
144
+ type TileflowCaptureResourceKind = 'glyph' | 'other-http' | 'sprite-image' | 'sprite-json' | 'tilejson' | 'vector-tile';
145
+ type TileflowCaptureResourceDiagnostic = {
146
+ context?: string;
147
+ kind: TileflowCaptureResourceKind;
148
+ origin: string;
149
+ status?: number;
150
+ };
151
+ type TileflowCaptureErrorDetails = {
152
+ diagnostics?: TileflowCaptureDiagnostic[];
153
+ phase?: TileflowCapturePhase;
154
+ resources?: TileflowCaptureResourceDiagnostic[];
155
+ };
156
+ type TileflowCaptureErrorOptions = ErrorOptions & {
157
+ details?: TileflowCaptureErrorDetails;
158
+ };
159
+ declare class TileflowCaptureError extends Error {
160
+ readonly code: TileflowCaptureErrorCode;
161
+ readonly details?: TileflowCaptureErrorDetails;
162
+ constructor(code: TileflowCaptureErrorCode, message: string, options?: TileflowCaptureErrorOptions);
163
+ }
164
+ declare function captureErrorMessage(error: unknown, fallback: string): string;
165
+
166
+ declare const tileflowSyntheticAssetOrigin = "https://tileflow.local.invalid";
167
+
168
+ declare function resolveTileflowApplicationUrl(input: {
169
+ appOrigin?: string;
170
+ appUrl?: string;
171
+ path: string;
172
+ }): {
173
+ origin: string;
174
+ url: string;
175
+ };
176
+
177
+ declare const tileflowVisualComparisonSchemaVersion: 1;
178
+ declare const tileflowVisualPerceptualThreshold = 0.1;
179
+ declare const tileflowVisualArtifactLimits: Readonly<{
180
+ maximumPngBytes: number;
181
+ maximumPaletteColors: 16;
182
+ }>;
183
+ type TileflowVisualComparisonStatus = 'unchanged' | 'changed' | 'missing-baseline' | 'scene-mismatch' | 'runtime-mismatch';
184
+ type TileflowVisualPixelMetric = {
185
+ changedPixels: number;
186
+ totalPixels: number;
187
+ ratio: number;
188
+ };
189
+ type TileflowVisualImageIdentity = {
190
+ sha256: string;
191
+ cssWidth: number;
192
+ cssHeight: number;
193
+ physicalWidth: number;
194
+ physicalHeight: number;
195
+ dpr: 1 | 2;
196
+ networkDependent: boolean;
197
+ };
198
+ type TileflowVisualSceneIdentity = TileflowCaptureReceipt['scene'];
199
+ type TileflowVisualRuntimeIdentity = Pick<TileflowCaptureReceipt, 'renderer' | 'platform'>;
200
+ type TileflowVisualComparison = {
201
+ schemaVersion: 1;
202
+ scene: string;
203
+ map: string;
204
+ target: 'map' | 'application';
205
+ status: TileflowVisualComparisonStatus;
206
+ changeKind: 'pixels' | 'dimensions' | null;
207
+ baselineScene: TileflowVisualSceneIdentity | null;
208
+ actualScene: TileflowVisualSceneIdentity;
209
+ baselineRuntime: TileflowVisualRuntimeIdentity | null;
210
+ actualRuntime: TileflowVisualRuntimeIdentity;
211
+ baseline: TileflowVisualImageIdentity | null;
212
+ actual: TileflowVisualImageIdentity;
213
+ dimensionsMatch: boolean | null;
214
+ exact: TileflowVisualPixelMetric | null;
215
+ perceptual: (TileflowVisualPixelMetric & {
216
+ threshold: number;
217
+ }) | null;
218
+ rendererMatch: boolean | null;
219
+ sceneMatch: boolean | null;
220
+ diffPng?: Uint8Array;
221
+ warnings: string[];
222
+ };
223
+ type TileflowVisualComparisonDocument = Omit<TileflowVisualComparison, 'diffPng'>;
224
+ type TileflowVisualPaletteColor = {
225
+ color: string;
226
+ count: number;
227
+ ratio: number;
228
+ };
229
+ type TileflowVisualReferenceAnalysis = {
230
+ schemaVersion: 1;
231
+ scene: string;
232
+ map: string;
233
+ target: 'map' | 'application';
234
+ dimensionsMatch: boolean;
235
+ reference: {
236
+ sha256: string;
237
+ physicalWidth: number;
238
+ physicalHeight: number;
239
+ palette: TileflowVisualPaletteColor[];
240
+ };
241
+ actual: {
242
+ sha256: string;
243
+ physicalWidth: number;
244
+ physicalHeight: number;
245
+ palette: TileflowVisualPaletteColor[];
246
+ };
247
+ exact: TileflowVisualPixelMetric | null;
248
+ perceptual: (TileflowVisualPixelMetric & {
249
+ threshold: number;
250
+ }) | null;
251
+ meanAbsoluteChannelDifference: number | null;
252
+ diffPng?: Uint8Array;
253
+ warnings: string[];
254
+ };
255
+ type TileflowVisualReferenceAnalysisDocument = Omit<TileflowVisualReferenceAnalysis, 'diffPng'>;
256
+ type TileflowVisualBaseline = {
257
+ png: Uint8Array;
258
+ receipt: string | Uint8Array | TileflowCaptureReceipt;
259
+ };
260
+ declare function validateTileflowVisualReferencePng(png: Uint8Array): {
261
+ width: number;
262
+ height: number;
263
+ };
264
+ declare function analyzeTileflowCaptureReference(actualCapture: TileflowCapture, referencePng: Uint8Array): Promise<TileflowVisualReferenceAnalysis>;
265
+ declare function createTileflowVisualReferenceAnalysisDocument(analysis: TileflowVisualReferenceAnalysis): TileflowVisualReferenceAnalysisDocument;
266
+ declare function compareTileflowCaptureToBaseline(actualCapture: TileflowCapture, baseline?: TileflowVisualBaseline): Promise<TileflowVisualComparison>;
267
+ declare function createTileflowVisualComparisonDocument(comparison: TileflowVisualComparison): TileflowVisualComparisonDocument;
268
+ declare function serializeTileflowVisualComparison(comparison: TileflowVisualComparison | TileflowVisualComparisonDocument): string;
269
+
270
+ export { type CreateTileflowCaptureSessionOptions, type TileflowBrowserInstallProgress, type TileflowBrowserLaunchOptions, type TileflowCapture, TileflowCaptureBrowserManager, type TileflowCaptureDiagnostic, TileflowCaptureError, type TileflowCaptureErrorCode, type TileflowCaptureErrorDetails, type TileflowCaptureOptions, type TileflowCapturePhase, type TileflowCaptureReceipt, type TileflowCaptureRendererIdentity, type TileflowCaptureResourceDiagnostic, type TileflowCaptureResourceKind, type TileflowCaptureResult, type TileflowCaptureSession, type TileflowVisualBaseline, type TileflowVisualComparison, type TileflowVisualComparisonDocument, type TileflowVisualComparisonStatus, type TileflowVisualImageIdentity, type TileflowVisualPaletteColor, type TileflowVisualPixelMetric, type TileflowVisualReferenceAnalysis, type TileflowVisualReferenceAnalysisDocument, type TileflowVisualRuntimeIdentity, type TileflowVisualSceneIdentity, analyzeTileflowCaptureReference, captureErrorMessage, captureTileflowScenes, compareTileflowCaptureToBaseline, createTileflowCaptureBrowserEnvironment, createTileflowCaptureReceipt, createTileflowCaptureRendererIdentity, createTileflowCaptureSession, createTileflowVisualComparisonDocument, createTileflowVisualReferenceAnalysisDocument, launchTileflowCaptureBrowser, parseTileflowCaptureReceipt, resolveTileflowApplicationUrl, serializeTileflowCaptureReceipt, serializeTileflowVisualComparison, setupTileflowCaptureBrowser, tileflowCaptureReceiptLimits, tileflowCaptureReceiptSchemaVersion, tileflowCaptureResultSchemaVersion, tileflowCaptureRuntime, tileflowSyntheticAssetOrigin, tileflowVisualArtifactLimits, tileflowVisualComparisonSchemaVersion, tileflowVisualPerceptualThreshold, validateTileflowCaptureReceipt, validateTileflowVisualReferencePng };