tileflow 0.1.0-alpha.1 → 0.1.0-alpha.11

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.
@@ -0,0 +1,268 @@
1
+ # Local visual feedback
2
+
3
+ Start with the [tileflow guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/cli/README.md) for installation and a complete first example.
4
+
5
+ Explore with capture itself. The first run may install Playwright's exact pinned Chromium headless
6
+ shell into its versioned per-user cache:
7
+
8
+ ```sh
9
+ npx tileflow capture \
10
+ --map madrid \
11
+ --theme dark \
12
+ --center=-3.69201,40.40871 \
13
+ --zoom=16.15 \
14
+ --width=1200 \
15
+ --height=1200 \
16
+ --json
17
+ ```
18
+
19
+ The successful entry includes a normalized `definition` with its concrete `theme`, `camera`,
20
+ `viewport`, and default map `target`. Copy it under a chosen `scenes.<name>` key; the CLI never rewrites
21
+ executable TypeScript config. Then commit the bounded scene so agents and CI render the same map,
22
+ camera, and viewport:
23
+
24
+ ```ts
25
+ import {defineMap} from '@tileflow/core';
26
+ import {streets} from '@tileflow/maps';
27
+
28
+ export default defineMap({
29
+ id: 'madrid',
30
+ name: 'Madrid',
31
+ version: 1,
32
+ extends: streets,
33
+ scenes: {
34
+ 'madrid-desktop': {
35
+ theme: 'dark',
36
+ camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
37
+ viewport: {width: 1280, height: 800, dpr: 1},
38
+ },
39
+ 'madrid-product': {
40
+ theme: 'dark',
41
+ camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
42
+ viewport: {width: 390, height: 844, dpr: 2},
43
+ target: {
44
+ kind: 'application',
45
+ path: '/maps/madrid',
46
+ captureId: 'product-map',
47
+ },
48
+ },
49
+ },
50
+ });
51
+ ```
52
+
53
+ `scenes` is tooling metadata owned by this map. It is not inherited and does not enter the
54
+ cartographic compiler; tooling supplies the exported map ID when it normalizes a scene internally.
55
+ Every scene stores a concrete theme; omission and browser-only `system` are invalid.
56
+
57
+ ### Live comparison workbench
58
+
59
+ Use the ordinary preview server with a right-hand selection to compare two maps or themes while
60
+ authoring:
61
+
62
+ ```sh
63
+ # Two maps or themes from one config.
64
+ npx tileflow preview \
65
+ --map harad --theme light \
66
+ --against-map ferraris --against-theme light
67
+
68
+ # Or compare against another config.
69
+ npx tileflow preview \
70
+ --config ./candidate.config.ts --map candidate \
71
+ --against-config ./reference.config.ts --against-map reference
72
+ ```
73
+
74
+ Any `--against-*` option opens the comparison workbench. Both previews remain same-origin and share
75
+ one synchronized camera. The toolbar switches between side-by-side, split, opacity overlay, and
76
+ blink modes; the selected mode, camera, split, opacity, and active interaction side survive in the
77
+ URL. Each config has its own watched, last-known-good session, so an invalid edit shows bounded
78
+ diagnostics without discarding the last valid map and recovers automatically after a fix.
79
+
80
+ Enable **Inspect** and click a rendered feature to see its bounded feature data, paint/layout values,
81
+ zoom-curve samples, and compiler contribution sidecar. The sprite gallery shows the compiled atlas,
82
+ not source directories. **Copy scene** produces a concrete-theme scene for the current camera and
83
+ viewport; **Copy command** produces the corresponding capture command. The inspector sidecar exists
84
+ only in memory in this local workbench and is not emitted by `tileflow build`.
85
+
86
+ ### Reproducible two-style review
87
+
88
+ For shareable evidence with exact pixels, use headless capture instead of the live preview:
89
+
90
+ ```sh
91
+ npx tileflow visual compare \
92
+ --config ./candidate.config.ts --map candidate --theme light \
93
+ --against-config ./reference.config.ts --against-map reference --against-theme light \
94
+ --center=-3.7038,40.4168 --zooms=12,14,16 \
95
+ --width=1200 --height=800 --dpr=1 --region=0,0,1200,760 \
96
+ --diff --open --json
97
+ ```
98
+
99
+ `--against-config` is explicit and may name the same config. Both sides use one camera, viewport,
100
+ DPR, ordered zoom matrix, pinned browser session, and concrete theme. At most 16 zooms and 64 MiB
101
+ of aggregate embedded PNG data are accepted; an earlier preflight also caps the matrix at
102
+ 67,108,864 aggregate physical pixels. The default transaction writes
103
+ `.tileflow/comparisons/compare.html`, a machine-readable sibling JSON document, and PNG/receipt-v4
104
+ pairs below `compare.assets`; `--diff` adds a highlighted diff when identities are comparable. The
105
+ HTML embeds every image, uses a restrictive CSP, performs no network request, and offers the same
106
+ side-by-side, wipe, overlay, and blink review modes.
107
+
108
+ `--region=x,y,width,height` selects one shared physical-pixel rectangle for compact appearance
109
+ profiles. Review JSON uses the explicit signed direction `rightMinusLeft`; the region does not crop
110
+ captures, changed-pixel metrics, or diffs.
111
+
112
+ Stdout and the sibling JSON use the same schema-version-1 `visual.compare` document: concrete
113
+ left/right selections, shared camera and viewport, zoom-sorted rows with Review v1 documents and
114
+ relative artifact paths, report/document paths, and sorted warnings. This document and the watch
115
+ NDJSON described below are versioned compatibility boundaries.
116
+
117
+ The asset directory also contains a bounded schema-version-1
118
+ `.tileflow-visual-compare.json` inventory. It assigns ownership only to the listed sibling assets
119
+ and records each exact SHA-256. A later successful transaction removes stale generated rows or
120
+ diffs only when that valid prior inventory names them, preserves unrelated files, and refuses a
121
+ managed asset that has been modified, replaced by a symlink/non-file, or grown beyond its bound.
122
+ `--force` does not weaken those ownership checks. The inventory is capped at 64 KiB and the complete
123
+ report, JSON, inventory, PNGs, receipts, stale cleanup, and optional diffs commit or roll back as one
124
+ file transaction.
125
+
126
+ Different pixels are ordinary review evidence and exit 0. A frame, dimension, runtime, or exact
127
+ data mismatch exits 1 and suppresses pixel metrics because the images are not comparable.
128
+ `--allow-data-mismatch` keeps a data-mismatch report inspectable with exit 0, but does not invent
129
+ pixel metrics. `--watch` observes both configs, cancels stale renders, preserves the last complete
130
+ report through invalid edits, and emits deterministic NDJSON with `--json`. An explicit report path
131
+ requires `--force` in watch mode; the managed default path is safely replaceable.
132
+
133
+ Watch events are `watching`, `building`, `invalid`, `recovered`, `generation-complete`, `failed`,
134
+ and `stopped`, with monotonic comparison generations and source-side generations where applicable.
135
+ Without `--allow-data-mismatch`, an incompatible generation emits `failed`, does not replace the
136
+ last complete report, and remains an unresolved failure; an allowed data mismatch emits
137
+ `generation-complete` but still has no pixel metrics. An orderly stop exits 0 only after at least
138
+ one complete generation and no unresolved invalid or failed state; otherwise it exits 1.
139
+
140
+ Render one checkpoint, or recapture after every edit:
141
+
142
+ ```sh
143
+ npx tileflow capture madrid-desktop --json
144
+ npx tileflow capture --all --json
145
+ npx tileflow capture madrid-desktop --watch --json
146
+ ```
147
+
148
+ Use the first command when you want a single image and the last command while refining a map. Watch
149
+ mode stays open, follows changes to the config, its local imports, and its effective icons and fonts,
150
+ and writes a new PNG after each valid edit. Keeping the browser ready makes repeated renders faster.
151
+ If an edit is invalid, the last good image remains in place and capture resumes automatically after
152
+ the fix. Stop watch mode with `Ctrl+C`. It accepts named committed scenes or `--all`, not exploratory
153
+ camera options.
154
+
155
+ Standalone scenes need no app server, account, API key, MCP service, system Chrome, or visible
156
+ window. The first command—or the first capture—may provision Playwright's pinned Chromium headless
157
+ shell in its versioned per-user cache. `--no-browser-install` makes a prepared/offline environment
158
+ strict. Tileflow never falls back to a system browser or packages the browser in npm.
159
+
160
+ For prepared or offline CI, install ahead of time with
161
+ `npx tileflow setup capture --json`. Use `setup capture --no-browser-install` or
162
+ `capture --no-browser-install` as an enforcement check that the exact shell is already cached.
163
+
164
+ For an application scene, its route and component props must render the committed camera. Tileflow
165
+ uses the camera in scene identity but does not mutate arbitrary application map state.
166
+
167
+ One-shot success JSON is one deterministic schema-version-1 document plus one newline. It contains
168
+ sorted scene/map/theme/target entries, relative output and receipt paths, image hash/dimensions/DPR,
169
+ renderer identity, remote-dependency state, and warnings; it contains no image bytes, credential,
170
+ origin, absolute path, user, or timestamp. Managed output is an atomic pair under
171
+ `.tileflow/captures/<scene>.png` and `<scene>.receipt.json`. `tileflow init` ignores managed captures
172
+ and `.tileflow/diffs` when creating a new `.gitignore`. Explicit output replacement requires
173
+ `--force`; symlinks and path escapes fail closed.
174
+
175
+ Each written receipt uses capture schema v4 and records the concrete theme. A Tileflow World
176
+ capture resolves its TileJSON once for
177
+ the whole capture session and records the exact `world-v1` release plus descriptor, archive, data
178
+ contract, and product-contract hashes. All selected scenes and retries therefore use one immutable
179
+ World release even if `current` moves while the command is running.
180
+
181
+ One-shot `--json` failures leave stdout empty and write exactly one deterministic failure document
182
+ to stderr with `code`, `phase`, bounded diagnostics, and optional origin-only resource metadata.
183
+
184
+ Watch JSON is NDJSON with monotonic `building`, `invalid`, `recovered`, `captured`, `failed`, and
185
+ `stopped` generation events. It watches config, transitive local imports, and effective icon/font
186
+ files, preserves the last good capture through invalid edits, cancels stale work, and reuses one
187
+ headless Browser. An orderly stop exits 0 only after a successful capture with no unresolved
188
+ terminal failure.
189
+
190
+ ### Capture through the real application
191
+
192
+ Application scenes use the app's existing Vite, Next.js, Webpack, or custom loopback server. Start
193
+ only that normal server; Tileflow does not run a package script, scan ports, or start
194
+ `tileflow preview`:
195
+
196
+ ```sh
197
+ npm run dev
198
+ TILEFLOW_APP_ORIGIN=http://127.0.0.1:3000 \
199
+ npx tileflow capture madrid-product --json
200
+ ```
201
+
202
+ The React, Vue, and Svelte wrappers expose `data-tileflow-map`, the resolved concrete
203
+ `data-tileflow-theme`, optional `data-tileflow-capture-id`, and
204
+ `data-tileflow-state="loading|idle|error"`. The headless client selects exactly one target, verifies
205
+ the theme, and waits for idle. Use `--url` for one explicit full loopback URL, `--selector` to
206
+ override target selection, and `--frame map|viewport` to override framing. Origins and URLs must be
207
+ credential-free loopback HTTP(S) without fragments. Each navigation uses a fresh context without a
208
+ reused profile, cookies, local storage, or service workers.
209
+
210
+ ### Match a reference and protect the result
211
+
212
+ While refining a style, compare the current scene with any reference screenshot:
213
+
214
+ ```sh
215
+ npx tileflow visual analyze madrid-desktop \
216
+ --reference ./design-reference.png --region=0,0,1200,760 --json
217
+ ```
218
+
219
+ `visual analyze` renders exactly one committed scene and writes the current image and a JSON report
220
+ under `.tileflow/analysis` by default. The report includes both images' dimensions and dominant
221
+ colors. When their physical dimensions match, it also includes exact and perceptual pixel metrics,
222
+ mean channel difference, a high-contrast `.diff.png`, and signed
223
+ `appearance.actualMinusReference` luminance, OKLab, edge-density, and local-contrast evidence. The
224
+ optional region affects only those appearance metrics. The reference PNG is never modified or
225
+ treated as a baseline.
226
+
227
+ Once the map is approved, save it as the expected baseline and use that image to catch later
228
+ regressions:
229
+
230
+ ```sh
231
+ # Save the reviewed current render as the expected baseline.
232
+ npx tileflow visual update madrid-desktop \
233
+ --baseline-dir test/visual-baselines --json
234
+
235
+ # Compare a fresh render without changing the baseline.
236
+ npx tileflow visual diff madrid-desktop \
237
+ --baseline-dir test/visual-baselines --json
238
+
239
+ # Or apply the operation to every committed scene.
240
+ npx tileflow visual diff --all \
241
+ --baseline-dir test/visual-baselines --json
242
+
243
+ # In CI, return exit 2 when the pixels have changed.
244
+ npx tileflow visual diff madrid-desktop \
245
+ --baseline-dir test/visual-baselines --fail-on changed --json
246
+ ```
247
+
248
+ `visual diff` and `visual update` require either one or more positional scene names or `--all`;
249
+ those forms are mutually exclusive. `visual analyze` always accepts exactly one scene.
250
+
251
+ The baseline directory holds the reviewed expected PNG and its receipt. The files generated by a
252
+ comparison are working evidence: the current `.actual.png`, a transparent high-contrast
253
+ `.diff.png` when comparable, and a `.visual.json` report under `.tileflow/diffs` by default.
254
+ `visual diff` may replace those working files on each run, but it never changes the baseline. This
255
+ separation prevents a check from silently accepting the change it was meant to reveal.
256
+
257
+ Before comparing, `visual diff` validates the baseline PNG and receipt, including their hashes,
258
+ dimensions, runtime, and scene identity, without executing or fetching baseline metadata.
259
+ Statuses are `unchanged`, `changed`, `missing-baseline`, `scene-mismatch`, and `runtime-mismatch`.
260
+ Same-size images report exact RGBA changed pixels and an informational fixed-threshold perceptual
261
+ metric; exact inequality determines changed.
262
+
263
+ Changed pixels exit 0 by default so a person or agent can inspect the working files.
264
+ `--fail-on changed` exits 2 for CI; missing or incompatible baselines and operational failures exit 1.
265
+
266
+ Run `visual update` again only after reviewing and accepting the new render. It is the only
267
+ command that creates or changes baseline pairs, and it never stages, commits, or pushes. Remote
268
+ tiles, glyphs, and sprites are reported because their pixels can change independently of the style.
@@ -0,0 +1,174 @@
1
+ # Native artifacts, local preview and Hosted deployment
2
+
3
+ Use a project-local CLI version that includes the native artifact selector and a trusted
4
+ `tileflow.config.ts`. Follow the [installation instructions](../README.md) first. Native commands
5
+ prepare, serve or publish Tileflow artifacts; they do not install, start or qualify a mobile renderer.
6
+
7
+ ## Validate and build
8
+
9
+ ```sh
10
+ npx tileflow validate --renderer native --target local --json
11
+ npx tileflow build --renderer native --target local --out public/tileflow --json
12
+ ```
13
+
14
+ `validate` prepares and checks the complete theme family without writing production output.
15
+ `build` writes the checked generation beneath `public/tileflow/native`, including a strict version-1
16
+ runtime manifest and a separate schema-v2 `native-build.json` record. Serve the complete renderer
17
+ directory, not just the style. A failed compatibility check leaves existing output unchanged.
18
+
19
+ Both commands accept `--renderer web|native`; omission means `web`. Renderer selection is separate
20
+ from `--target local|hosted`. Existing `validate --target hosted` continues to perform web Hosted
21
+ preflight. Native with `--target hosted` fails with `NATIVE_RENDERER_UNSUPPORTED` before loading the
22
+ config or writing files. `build --target hosted` is not publication and is rejected. The explicit
23
+ combined Hosted publication command below performs its own complete two-renderer preflight.
24
+
25
+ JSON success uses the existing version-1 command envelope. Native results additionally identify
26
+ `renderer: 'native'`, `profile: 'native-v1'` and `validation: 'static-artifacts'`. JSON failure writes
27
+ one structured envelope to stderr, leaves stdout empty and exits nonzero. Native diagnostics have
28
+ safe JSON Pointers, stable codes, severity and suggestions. Unknown renderer names are not echoed.
29
+ The ordinary web commands and their default artifact bytes retain their existing behavior.
30
+
31
+ ## Watch and serve native artifacts
32
+
33
+ `preview` selects the renderer with `--renderer`:
34
+
35
+ ```sh
36
+ npx tileflow preview --renderer native --map stores --theme light
37
+ ```
38
+
39
+ Native mode creates the existing watched artifact session with renderer `native`. It keeps the
40
+ last-known-good native artifact family when a watched generation becomes invalid and replaces it
41
+ only after a later valid generation is prepared. The endpoint is an **assets-only** development
42
+ endpoint: it does not render a browser map, open the visual workbench, start Metro, launch a device,
43
+ or establish visual/native acceptance.
44
+
45
+ With the default host and port, the canonical runtime manifest is:
46
+
47
+ ```text
48
+ http://127.0.0.1:3333/native/manifest.json
49
+ ```
50
+
51
+ The manifest remains the ordinary strict version-1 Tileflow runtime manifest. Its style, sprite,
52
+ font, tileset and generation references use the existing renderer-specific `/native/...` paths; no
53
+ preview-only manifest schema is introduced. `--map` and `--theme` validate a desired map/theme
54
+ against that finalized manifest but do not narrow or fork the served artifact family.
55
+
56
+ Locally built sprites and fonts are served from the preview's own HTTP origin. Native preview checks
57
+ each generation with that exact origin as its development origin, the same exception the application
58
+ configures below. Resources on any other origin still require HTTPS. `validate` and `build` never
59
+ apply this exception.
60
+
61
+ Human output identifies the exact manifest URL, profile `native-v1`, and `assets-only` endpoint.
62
+ Native NDJSON lifecycle events additionally carry `renderer`, `profile`, `manifest` and
63
+ `assetsOnly`; the default and explicit `--renderer web` lifecycle/output contract remains the
64
+ existing browser-preview contract.
65
+
66
+ Metro remains the React Native application's JavaScript development server. The application points
67
+ its Tileflow source at the native manifest URL and sets `source.developmentOrigin` to its exact HTTP
68
+ origin, for example `http://127.0.0.1:3333`. Both addresses must be reachable from the target device;
69
+ an Android emulator may use a different host address. Other resource origins still require HTTPS.
70
+ The application supplies its own development networking setup. Tileflow
71
+ does not add a Metro plugin, device discovery or a tunnel.
72
+
73
+ `preview` still binds to `127.0.0.1` by default. Existing explicit `--host <ip-or-localhost>` behavior
74
+ is preserved. Selecting another host does not add LAN discovery, a public tunnel, an iOS ATS
75
+ exception, an Android cleartext exception, or any native application mutation; the application and
76
+ developer remain responsible for making an explicitly selected development endpoint reachable
77
+ under their own platform/network policy.
78
+
79
+ Browser-only selections fail before config execution in native mode. In particular, committed
80
+ `--scene` preview and every `--against-*` comparison-workbench option are unavailable with
81
+ `--renderer native`. Native mode does not serve the browser preview root, preview JavaScript runtime,
82
+ compiler-inspection sidecars, or the dev handler's status/event control routes. Use ordinary/default
83
+ web preview for those workflows.
84
+
85
+ The `native-lowering-v1` preparation policy records fixed globe-to-Mercator normalization and
86
+ finite cap/dash layer partitioning in `native-build.json`; it does not change web artifacts.
87
+ Native preparation rejects adaptive/unknown projections, terrain, browser PMTiles/contour protocols,
88
+ missing text providers and unsupported style values rather than degrading them silently. It preserves
89
+ shared icon inheritance and independent sprite densities, and maps verified licensed TTF/OTF faces
90
+ into native style declarations. See the
91
+ [profile contract](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/native-artifact-profile.md)
92
+ and [artifact layout](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/native-artifacts.md).
93
+
94
+ ## Publish web and Native together
95
+
96
+ The pre-release source supports an explicit combined deployment against a compatible Hosted API.
97
+ This is a wire and implementation contract, not a statement that a package or server release is
98
+ available. The API must understand renderer-collection deployment schema 2.
99
+
100
+ ```sh
101
+ npx tileflow deploy --with-native --map-id map_0123456789abcdef --map stores
102
+ ```
103
+
104
+ `--map-id` selects the existing managed Tileflow Map. `--map` selects one map from the repository;
105
+ it can be omitted only when the config contains one map. The command uses the ordinary deployment
106
+ credential or account capability, not the publishable mobile credential. Do not put privileged
107
+ credentials in mobile application configuration.
108
+
109
+ Before authentication, upload or publication, the command loads the trusted config once, prepares
110
+ its icon closure, compiles all web themes and lowers all Native themes from that same compiled
111
+ input. It validates profile `native-v1`, hashes, authored identity, source ownership declarations,
112
+ semantic POI provenance and overlay boundaries. Native incompatibility rejects the entire request;
113
+ it cannot publish a new web family alone. Existing Hosted restrictions still apply, including the
114
+ current rejection of package-owned web font bundles. This opt-in does not bypass those restrictions;
115
+ use an explicitly configured glyph provider for this Hosted path.
116
+
117
+ Shared icon packages use the existing content-addressed upload protocol once. The command replaces
118
+ only the confirmed owned sprite URL, without reevaluating config, and sends one strict renderer
119
+ collection to `POST /v1/styles`. The server verifies both complete families and their owned resources,
120
+ stages immutable outputs, and activates them through the existing single deployment authority.
121
+ A Native staging failure does not activate the replacement web family. Retry the same input after an
122
+ interrupted response: the server's existing content identity determines reuse or a new deployment.
123
+
124
+ The successful response has its own explicit schema version 2 and identifies one managed Map and
125
+ deployment plus web and Native theme references. The CLI requires the complete matching response
126
+ before atomically replacing `--manifest`. That local file stays a strict **runtime manifest v1 for
127
+ web**, so existing bundlers do not receive unknown Native fields. The CLI separately prints the
128
+ canonical Native manifest URL. A failed request or invalid response leaves the existing local file
129
+ unchanged; an uncertain response is not proof that the server performed no publication.
130
+
131
+ Ordinary `deploy` without `--with-native` keeps its schema-1 request and response behavior. A later
132
+ web-only deployment does not inherit a Native family from an older deployment.
133
+
134
+ ## Native discovery and protected resources
135
+
136
+ The canonical entry URL is on the application's configured API origin:
137
+
138
+ ```text
139
+ https://api.example.test/maps/map_0123456789abcdef/native/manifest.json
140
+ ```
141
+
142
+ This bounded, one-map runtime manifest is **public metadata**. It contains identity, theme names,
143
+ revisions and references, never Style JSON, credentials or grants. It is served with `no-store` only
144
+ for a coherent active Native deployment. Its GET is not a commercial completion event.
145
+
146
+ Use that URL in the existing `Map` source descriptor, keeping the authored map name in `source.map`.
147
+ The application configures its publishable mobile credential and approved API origin once, using the
148
+ [React Native application configuration contract](../../react-native/docs/native-configuration.md).
149
+ The SDK requires both the requested and final response URLs to be the exact canonical route for the
150
+ declared managed Map. The manifest's `apiUrl` must exactly equal the configured canonical API origin;
151
+ a remote document cannot create a trusted origin or choose where the credential is sent.
152
+
153
+ The credential goes only to the configured API's `POST /v1/sessions/start`. It is not sent to the
154
+ manifest, a CDN, a style URL or a redirect destination. Styles, TileJSON, tiles, sprites, glyphs and
155
+ font resources remain protected by the existing grant and resource-ownership checks.
156
+
157
+ A Native theme references `/maps/<mapId>/native/v<deploymentVersion>/<theme>.json` and requires a
158
+ revision. The server verifies that version against the active deployment and the style's stored
159
+ bytes against the revision. Before resource projection, the SDK checks the protected response URL
160
+ and its Map/theme/deployment/revision metadata. If discovery became stale, it refetches the public
161
+ manifest at most once for that selection, preserving its existing session and admission context.
162
+ Another Map, origin, mode or older deployment cannot replace that ownership. A repeated failure
163
+ enters the existing safe error/rollback path rather than mixing generations or looping indefinitely.
164
+
165
+ Session start, public metadata discovery and Map rendering do not themselves assert a billable
166
+ completion. Completion remains owned by the server's existing eligible protected GET 200/206 path.
167
+ Disabled mobile delivery fails closed; publication does not enable it.
168
+
169
+ ## Qualification boundary
170
+
171
+ Native local preview is artifact preparation and delivery only. A successful artifact or deployment
172
+ response does not establish Hermes execution, iOS/Android rendering, device reachability, visual
173
+ fidelity, package publication or product availability. Qualify the exact compatible SDK/API pair,
174
+ React Native application, platform networking policy and native runtime separately.
@@ -0,0 +1,81 @@
1
+ # Read-only icon comparison
2
+
3
+ Start with the [tileflow guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/cli/README.md) for installation and a complete first example.
4
+
5
+ Compare the exported map with its active hosted revision before deciding whether to deploy.
6
+ These examples assume the configured map ID is `madrid`; replace the sample managed Map ID with
7
+ your Dashboard destination and use personal CLI login or a credential with `status:read`.
8
+ `--against` selects the exact map ID in the configuration, not a deployment-stage keyword:
9
+
10
+ ```sh
11
+ npx tileflow icons diff --map-id map_AbCdEfGhIjKlMnOp --against madrid
12
+ npx tileflow icons diff --map-id map_AbCdEfGhIjKlMnOp --against madrid --json
13
+ npx tileflow icons diff --map-id map_AbCdEfGhIjKlMnOp --against madrid --report ./icon-diff.html
14
+ ```
15
+
16
+ The command compiles only the exported map, performs authenticated read-only control-plane
17
+ requests, and never uploads a package, creates a deployment, or writes the frontend manifest.
18
+ Plain and JSON modes write no report; preparing locked inputs may download and write cache entries. A requested HTML report adds reads of the baseline's four
19
+ public sprite files and
20
+ embeds verified old/new images in one self-contained file. The report groups exact-cell icon
21
+ previews under `Added`, `Modified`, and `Removed`. Added and removed cards show only the affected
22
+ icon; modified cards compare `Before` with `Next`. A script-free 1x/2x selector defaults to 2x,
23
+ switches the embedded density, and renders 1x at half the 2x preview size inside a stable frame so
24
+ the selected state is immediately visible. Package hashes, generated sizes, and icon counts stay in
25
+ collapsed `Details`. A map badge is shown only for a map-scoped report. It exposes no atlas
26
+ implementation UI and contains no API key, remote runtime dependency, original icon source, or
27
+ local source path. Use `--force` to replace different existing report bytes and `--open` only
28
+ together with `--report`.
29
+
30
+ An icon is `modified` when its rendered record changes: normalized 1x/2x pixel hashes, dimensions,
31
+ SDF defaults or stretch/content layout. Fully transparent RGB is ignored in pixel hashes. Metadata
32
+ edits can therefore mark an icon modified even when its pixel hashes are unchanged. Reports render
33
+ SDF with asset defaults; raw downloadable PNGs still encode distance fields.
34
+
35
+ `hasChanges` compares effective package content hashes. The manifest also hashes the four encoded
36
+ artifacts, so a package can change while its per-icon list remains unchanged. Conversely, a fully
37
+ shadowed dependency can change the map's composition receipt without changing its effective sprite.
38
+ A clean icon diff is not evidence that the whole map needs no deployment. The report and digest analysis operate on
39
+ generated sprite data; SVG, raster source files, and absolute paths stay on the local machine.
40
+
41
+ `--json` writes exactly one deterministic schema-version-1 document plus a trailing newline. Its
42
+ stable top-level fields, in order, are:
43
+
44
+ ```json
45
+ {
46
+ "schemaVersion": 1,
47
+ "environment": "madrid",
48
+ "baseline": {"deploymentId": "dep_...", "package": null, "version": 7},
49
+ "proposed": {"package": null},
50
+ "icons": {"added": [], "removed": [], "modified": [], "unchangedCount": 0},
51
+ "generatedBytes": {"before": 0, "after": 0, "delta": 0},
52
+ "artifacts": {"report": null},
53
+ "hasChanges": false
54
+ }
55
+ ```
56
+
57
+ Package summaries, when present, contain `contentHash`, `iconCount`, `label`, and `totalBytes`.
58
+ A missing hosted baseline is an initial comparison against an empty package. The command compares
59
+ delivered icon records, canonical package manifests, and generated bytes; config-directory
60
+ provenance and style-reference analysis belong to `icons list` and final style validation.
61
+
62
+ Normal differences exit 0. Config, compilation, credential, network, response, and report errors
63
+ exit 1 with diagnostics on stderr, even with `--json`. Invalid or unauthorized baselines are errors,
64
+ not empty packages. `--config` selects a config file; `--api-url` and `--api-key` override hosted
65
+ connection settings. `--cache-dir` selects the artifact cache and `--offline` disables only icon
66
+ hydration: the authenticated hosted baseline request still occurs.
67
+
68
+ This workflow does not provide OIDC, automatic secret installation, an official
69
+ GitHub Action, an atomic multi-map deploy set, or a Tileflow-hosted remote
70
+ builder.
71
+
72
+ Docs: https://tileflow.dev/docs/development-and-deployment/icon-inspection-and-diffs
73
+
74
+ ### Team credentials and explicit Map targets
75
+
76
+ A unified Team credential can combine data and Map operations. Use `TILEFLOW_API_KEY` and an
77
+ explicit `--map-id` for deployment, status and hosted icon comparisons. The CLI sends that target
78
+ with style, icon and font requests and verifies the credential covers it. The Hosted API must
79
+ support unified credentials. All Maps includes future Maps; selected Maps is an explicit list.
80
+ A legacy Map key keeps its original binding. Personal CLI login remains separate from machine
81
+ credentials. Credential administration and Team management use the Hosted API or dashboard.
@@ -0,0 +1,130 @@
1
+ # Team Icon Sets and repository locks
2
+
3
+ Start with the [tileflow guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/cli/README.md) for installation and a complete first example.
4
+
5
+ A Team Icon Set is a named catalog resource such as `@acme/brand`. Publishing creates an immutable
6
+ integer revision of the four generated sprite files. Maps in different repositories consume
7
+ independently pinned revisions through `iconSet('@acme/brand')` without copying original artwork.
8
+
9
+ Two command families cover this, and the split is deliberate. `icon-set` is a networked Team
10
+ authority that changes the catalog. `icons list` stays local and needs no credential. `icons diff`
11
+ compares prepared output with an active Hosted Map baseline, so it requires `--map-id`, the
12
+ configured map ID in `--against`, and `status:read` authority through personal login or a scoped
13
+ credential, but never mutates that Map. `install`, `update` and `pin` maintain the exact
14
+ `tileflow.icons.lock.json` beside the selected config.
15
+
16
+ Availability depends on your deployment and your Team's enabled features. Check
17
+ `npx tileflow icon-set --help` for the installed command surface.
18
+
19
+ ## Authorize a Team
20
+
21
+ Catalog commands need Team authority. Either sign in and let the CLI exchange your account session
22
+ for a short-lived Team capability, or pass a Team data key:
23
+
24
+ ```sh
25
+ npx tileflow login
26
+ npx tileflow icon-set list --team @acme --json
27
+ ```
28
+
29
+ Reads request `icons:read`; publication, archive state and purge request `icons:write`. A
30
+ Map-scoped deploy key never gains catalog authority, and `--team` is rejected together with
31
+ `--api-key` because a Team data key already selects its Team.
32
+
33
+ ## Publish a revision
34
+
35
+ ```sh
36
+ npx tileflow icon-set publish ./icons --id brand --team @acme --idempotency-key brand-2026-09-14 --json
37
+ ```
38
+
39
+ `publish` compiles exactly one repository-relative directory into `sprite.json`, `sprite.png`,
40
+ `sprite@2x.json` and `sprite@2x.png`, then uploads those four files under one durable retry key.
41
+ The first successful publication creates the set. Republishing identical bytes reports
42
+ `"publication": "unchanged"` and returns the same revision rather than a new integer. Repeating an
43
+ uncertain request with the same `--idempotency-key` returns its original result instead of creating
44
+ extra history.
45
+
46
+ `--name` and `--description` set bounded catalog display metadata after the immutable revision is
47
+ confirmed. If that metadata update fails, the revision remains published; rerun the same command to
48
+ retry the display update.
49
+
50
+ The JSON receipt reports the Team, the canonical reference, the immutable revision and version ID,
51
+ the package identity, the content hash, the byte count and whether the publication changed
52
+ anything. It contains no credential, source path, or original artwork.
53
+
54
+ ## Inspect and manage the catalog
55
+
56
+ ```sh
57
+ npx tileflow icon-set list --team @acme --json
58
+ npx tileflow icon-set status brand --team @acme --json
59
+ npx tileflow icon-set versions brand --team @acme --json
60
+ npx tileflow icon-set uses brand --team @acme --json
61
+ npx tileflow icon-set archive brand --team @acme --json
62
+ npx tileflow icon-set unarchive brand --team @acme --json
63
+ ```
64
+
65
+ Reads follow bounded cursors and reject a repeated page, a duplicate row or an unsafe page count.
66
+ `uses` reports the known hosted deployments that retain a revision; its
67
+ `includesUndeployedRepositoryLocks` field is always `false`, because a repository lock that was
68
+ never deployed is not discoverable by the catalog. Archiving prevents new publication and implicit
69
+ discovery while retained consumers keep working.
70
+
71
+ ## Purge an exact revision
72
+
73
+ ```sh
74
+ npx tileflow icon-set purge brand --version 3 --team @acme \
75
+ --idempotency-key purge-brand-3 --confirm '@acme/brand@3' --acknowledge-unknown-locks --json
76
+ ```
77
+
78
+ Purge needs all four guards: an exact positive `--version`, a durable `--idempotency-key`, a
79
+ `--confirm` value repeating the exact `@team/set@version`, and `--acknowledge-unknown-locks`.
80
+ The acknowledgement is not a formality. Purge tombstones the publication hold and keeps retained
81
+ deployment holds, including fully shadowed dependencies; it cannot discover undeployed repository
82
+ locks and cannot revoke public bytes that were already downloaded.
83
+
84
+ ## Maintain the repository lock
85
+
86
+ ```sh
87
+ npx tileflow icons install --team @acme --json
88
+ npx tileflow icons update @acme/brand --team @acme --json
89
+ npx tileflow icons pin @acme/brand --version 3 --team @acme --json
90
+ ```
91
+
92
+ These commands read the `iconSet()` references your config declares. `install` resolves the current
93
+ latest revision for declared references that have no pin yet and leaves existing pins alone.
94
+ Both `install [references...]` and `update [references...]` accept an optional subset; omitted
95
+ selection means every declared reference.
96
+ `update` moves the selected references to their current latest revision; omit the references to
97
+ select every declared one. `pin` selects one exact revision and never resolves a head.
98
+
99
+ They are the only build-adjacent commands that resolve `latest` or write the lock. Every requested
100
+ reference is resolved before a single compare-and-swap write of the whole snapshot, so a partial
101
+ lock is never produced and a concurrent writer fails the command rather than losing its pins. An
102
+ undeclared reference is rejected before any request. No lock command rewrites `tileflow.config.ts`;
103
+ add `iconSet('@team/set')` to your config first. Lock commands do not hydrate sprite files.
104
+ Serialization retains pins for other declared references and removes stale undeclared references.
105
+ They accept `--config` (or `-c`), `--team`, `--api-url`, `--api-key` and `--json`.
106
+ Do not combine `--team` with an explicit or ambient `TILEFLOW_API_KEY`.
107
+
108
+ The JSON receipt lists each locked reference with its revision, version ID, package identity and
109
+ content hash, plus any declared reference that is still missing a pin.
110
+
111
+ ## Build, preview and deploy against the lock
112
+
113
+ `validate`, `build`, `preview`, `deploy`, `icons list` and `icons diff` read the lock and compose
114
+ one effective sprite. They never resolve a catalog head, so publishing a newer revision changes
115
+ nothing in a repository until an explicit lock command runs. `--cache-dir` chooses the verified
116
+ artifact cache root on commands that expose it (including `icons list` and `icons diff`);
117
+ `--offline` fails a cache miss instead of hydrating it. These flags are not universal CLI options.
118
+ Other entry points use their documented icon-resolution options or `TILEFLOW_ICON_CACHE_DIR`.
119
+ Offline diff still needs its hosted baseline request.
120
+
121
+ Hydration downloads only the four public files of the exact pinned artifact, from the trusted
122
+ delivery origin, without credentials and without following redirects. Every file's length,
123
+ checksum, sprite index, PNG geometry and per-icon pixel hashes are verified before use.
124
+
125
+ Deploy uploads the effective composed package and sends the same ordered composition receipt in the
126
+ build manifest and the request body, bound to that package's content hash. A referenced source
127
+ revision is never re-uploaded by a deploy.
128
+
129
+ Offline composition is not an offline map. Remote tiles, glyphs and other external resources keep
130
+ their own requirements.