tileflow 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,660 @@
1
+ # tileflow
2
+
3
+ CLI-only tools for Tileflow config, preview, validation, capture, and hosted deploy. The package
4
+ requires Node.js 22 or newer and intentionally exposes only the `tileflow` binary; importing
5
+ `tileflow` as a JavaScript library is not a supported surface.
6
+
7
+ Local config, validation, build, preview, and hosted-compatibility checks require
8
+ no Tileflow account or API key:
9
+
10
+ ```sh
11
+ npm install @tileflow/core@alpha @tileflow/maps@alpha
12
+ npm install --save-dev --save-exact tileflow@bootstrap
13
+ npx tileflow init
14
+ npx tileflow validate
15
+ npx tileflow validate --target hosted
16
+ npx tileflow build
17
+ npx tileflow preview
18
+ ```
19
+
20
+ The generated config imports `streets`, extends it, materializes a custom dark theme from
21
+ `streetsThemes.dark`, and declares the starter's theme family, modules, and view. Streets already
22
+ owns its complete icon and glyph providers, so the leaf does not repeat font stacks or know a
23
+ delivery URL.
24
+
25
+ Install the exact project dependency first; then `npx tileflow` runs its local executable. Other
26
+ package managers work; use the equivalent command for the lockfile already owned by the project.
27
+
28
+ `tileflow preview` previews the map exported by `tileflow.config.ts` and uses its configured `view`.
29
+ Select one of that map's committed standalone scenes explicitly:
30
+
31
+ ```sh
32
+ npx tileflow preview --scene madrid-mobile
33
+ ```
34
+
35
+ To work on another map, point `--config` at another singular config. Multi-map catalogs are internal
36
+ orchestration used by this repository's workbench and are not a second public config shape.
37
+
38
+ `tileflow dev` remains a compatibility alias. The `preview` name distinguishes this SDK preview
39
+ server from an application's own `npm run dev` process.
40
+
41
+ `--api-base-url` selects the origin used by official World, Bathymetry vector, Bathymetry DEM,
42
+ Nautical, and terrain TileJSON sources in the generated preview style. In particular, a hybrid
43
+ Bathymetry map resolves `/tiles/bathymetry/tiles.json` and `/tiles/bathymetry/dem/tiles.json` from
44
+ that one origin. `TILEFLOW_API_URL` supplies the same value when the flag is omitted, which lets a
45
+ local tiles stack serve all sidecars without changing map authoring.
46
+
47
+ The preview binds only `127.0.0.1` by default. Network exposure is never implicit. To bind another
48
+ interface, pass one explicit IP literal (or `localhost`) with `--host`, for example
49
+ `tileflow preview --host 192.0.2.10`. URL-, path-, and hostname-shaped values are rejected.
50
+
51
+ A map preview accepts `--theme <concrete-name>`; omission selects its declared default. A scene
52
+ preview applies its committed concrete theme, camera, and CSS viewport dimensions. Capture remains the
53
+ authority for exact DPR and pixels. Scenes whose target is an application must be viewed through
54
+ that application's normal development server.
55
+
56
+ ## Build artifacts
57
+
58
+ `tileflow build` resolves the exported map lineage, validates executable config and MapLibre
59
+ semantics, compiles its ordered package/local icon directories and selected text provider, and
60
+ writes `manifest.json`, one `styles/<map>/<theme>.json` per concrete appearance, and every referenced
61
+ sprite/font asset. The manifest records `defaultTheme`, optional `systemThemes`, and the exact theme
62
+ catalog; it never flattens a default into a second style alias. A
63
+ map with text has exactly one provider: local/package `fonts` directories or a `glyphs` descriptor
64
+ whose `fontStacks` are explicit. The default output is `dist/tileflow`; choose another directory
65
+ explicitly with `--out`:
66
+
67
+ ```sh
68
+ npx tileflow build --out public/tileflow
69
+ ```
70
+
71
+ Build is local and credential-free. Hosted compatibility is a separate
72
+ `tileflow validate --target hosted` preflight; both that command and `deploy` reject non-Tileflow
73
+ World data through the same compatibility check.
74
+
75
+ ## Deterministic agent diagnostics
76
+
77
+ Use JSON mode when an agent or CI job needs to validate or reason about the resolved config:
78
+
79
+ ```sh
80
+ npx tileflow validate --json
81
+ npx tileflow inspect --json
82
+ npx tileflow inspect --map madrid --json
83
+ ```
84
+
85
+ Success writes exactly one schema-version-1 document to stdout. Failure leaves stdout empty and
86
+ writes one document to stderr. Both summaries and every failure diagnostic always contain
87
+ `phase`, `code`, `path`, `severity`, `message`, and a bounded safe `suggestion`; diagnostics are
88
+ sorted and deduplicated deterministically.
89
+
90
+ Config inspection returns the resolved map, root-to-leaf lineage, declared paths, leaf-level merge
91
+ provenance, and an explicit `themeContract`: default/system selection, shared token schema,
92
+ concrete values, typography, lighting, differences from the default, and stable audit diagnostics.
93
+ It omits the executable input-file graph and sanitizes credentials, URL queries,
94
+ secret-shaped values, data URLs, and absolute filesystem paths. The generated
95
+ [`modules-api-reference.json`](../../docs/modules-api-reference.json) is the machine-readable
96
+ map/module schema; its default `authoring` entrypoint tells an agent how to write the singular
97
+ config with `extends` and `scenes`, while its named `resolved` entrypoint matches inspection and
98
+ compiler input. Both are produced from executable core schemas rather than a handwritten parallel
99
+ API list.
100
+
101
+ ## Command families
102
+
103
+ | Family | Commands |
104
+ | --------------- | ---------------------------------------------------------------------------------------------- |
105
+ | Map authoring | `init`, `validate`, `inspect`, `build`, `preview` (`dev` alias) |
106
+ | Local evidence | `setup capture`, `capture`, `visual compare`, `visual analyze`, `visual diff`, `visual update` |
107
+ | Data and assets | `inspect features`, `icons list --json`, `icons diff` |
108
+ | Account | `login`, `logout`, `whoami` |
109
+ | Hosted delivery | `deploy`, `status` |
110
+
111
+ Run `tileflow <command> --help` (or the family help, such as `tileflow icons --help`) for the exact
112
+ arguments and bounded JSON modes.
113
+
114
+ The CLI retains the hidden `projects` command family as a compatibility and support surface for the
115
+ internal application boundary. It is not part of the ordinary workflow or the product catalog.
116
+
117
+ ## Local visual feedback
118
+
119
+ Explore with capture itself. The first run may install Playwright's exact pinned Chromium headless
120
+ shell into its versioned per-user cache:
121
+
122
+ ```sh
123
+ npx tileflow capture \
124
+ --map madrid \
125
+ --theme dark \
126
+ --center=-3.69201,40.40871 \
127
+ --zoom=16.15 \
128
+ --width=1200 \
129
+ --height=1200 \
130
+ --json
131
+ ```
132
+
133
+ The successful entry includes a normalized `definition` with its concrete `theme`, `camera`,
134
+ `viewport`, and default map `target`. Copy it under a chosen `scenes.<name>` key; the CLI never rewrites
135
+ executable TypeScript config. Then commit the bounded scene so agents and CI render the same map,
136
+ camera, and viewport:
137
+
138
+ ```ts
139
+ import {defineMap} from '@tileflow/core';
140
+ import {streets} from '@tileflow/maps';
141
+
142
+ export default defineMap({
143
+ id: 'madrid',
144
+ name: 'Madrid',
145
+ version: 1,
146
+ extends: streets,
147
+ scenes: {
148
+ 'madrid-desktop': {
149
+ theme: 'dark',
150
+ camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
151
+ viewport: {width: 1280, height: 800, dpr: 1},
152
+ },
153
+ 'madrid-product': {
154
+ theme: 'dark',
155
+ camera: {type: 'center', center: [-3.7038, 40.4168], zoom: 12},
156
+ viewport: {width: 390, height: 844, dpr: 2},
157
+ target: {
158
+ kind: 'application',
159
+ path: '/maps/madrid',
160
+ captureId: 'product-map',
161
+ },
162
+ },
163
+ },
164
+ });
165
+ ```
166
+
167
+ `scenes` is tooling metadata owned by this map. It is not inherited and does not enter the
168
+ cartographic compiler; tooling supplies the exported map ID when it normalizes a scene internally.
169
+ Every scene stores a concrete theme; omission and browser-only `system` are invalid.
170
+
171
+ ### Live comparison workbench
172
+
173
+ Use the ordinary preview server with a right-hand selection to compare two maps or themes while
174
+ authoring:
175
+
176
+ ```sh
177
+ # Two maps or themes from one config.
178
+ npx tileflow preview \
179
+ --map harad --theme light \
180
+ --against-map ferraris --against-theme light
181
+
182
+ # Or compare against another config.
183
+ npx tileflow preview \
184
+ --config ./candidate.config.ts --map candidate \
185
+ --against-config ./reference.config.ts --against-map reference
186
+ ```
187
+
188
+ Any `--against-*` option opens the comparison workbench. Both previews remain same-origin and share
189
+ one synchronized camera. The toolbar switches between side-by-side, split, opacity overlay, and
190
+ blink modes; the selected mode, camera, split, opacity, and active interaction side survive in the
191
+ URL. Each config has its own watched, last-known-good session, so an invalid edit shows bounded
192
+ diagnostics without discarding the last valid map and recovers automatically after a fix.
193
+
194
+ Enable **Inspect** and click a rendered feature to see its bounded feature data, paint/layout values,
195
+ zoom-curve samples, and compiler contribution sidecar. The sprite gallery shows the compiled atlas,
196
+ not source directories. **Copy scene** produces a concrete-theme scene for the current camera and
197
+ viewport; **Copy command** produces the corresponding capture command. The inspector sidecar exists
198
+ only in memory in this local workbench and is not emitted by `tileflow build`.
199
+
200
+ ### Reproducible two-style review
201
+
202
+ For shareable evidence with exact pixels, use headless capture instead of the live preview:
203
+
204
+ ```sh
205
+ npx tileflow visual compare \
206
+ --config ./candidate.config.ts --map candidate --theme light \
207
+ --against-config ./reference.config.ts --against-map reference --against-theme light \
208
+ --center=-3.7038,40.4168 --zooms=12,14,16 \
209
+ --width=1200 --height=800 --dpr=1 --region=0,0,1200,760 \
210
+ --diff --open --json
211
+ ```
212
+
213
+ `--against-config` is explicit and may name the same config. Both sides use one camera, viewport,
214
+ DPR, ordered zoom matrix, pinned browser session, and concrete theme. At most 16 zooms and 64 MiB
215
+ of aggregate embedded PNG data are accepted; an earlier preflight also caps the matrix at
216
+ 67,108,864 aggregate physical pixels. The default transaction writes
217
+ `.tileflow/comparisons/compare.html`, a machine-readable sibling JSON document, and PNG/receipt-v4
218
+ pairs below `compare.assets`; `--diff` adds a highlighted diff when identities are comparable. The
219
+ HTML embeds every image, uses a restrictive CSP, performs no network request, and offers the same
220
+ side-by-side, wipe, overlay, and blink review modes.
221
+
222
+ `--region=x,y,width,height` selects one shared physical-pixel rectangle for compact appearance
223
+ profiles. Review JSON uses the explicit signed direction `rightMinusLeft`; the region does not crop
224
+ captures, changed-pixel metrics, or diffs.
225
+
226
+ Stdout and the sibling JSON use the same schema-version-1 `visual.compare` document: concrete
227
+ left/right selections, shared camera and viewport, zoom-sorted rows with Review v1 documents and
228
+ relative artifact paths, report/document paths, and sorted warnings. This document and the watch
229
+ NDJSON described below are versioned compatibility boundaries.
230
+
231
+ The asset directory also contains a bounded schema-version-1
232
+ `.tileflow-visual-compare.json` inventory. It assigns ownership only to the listed sibling assets
233
+ and records each exact SHA-256. A later successful transaction removes stale generated rows or
234
+ diffs only when that valid prior inventory names them, preserves unrelated files, and refuses a
235
+ managed asset that has been modified, replaced by a symlink/non-file, or grown beyond its bound.
236
+ `--force` does not weaken those ownership checks. The inventory is capped at 64 KiB and the complete
237
+ report, JSON, inventory, PNGs, receipts, stale cleanup, and optional diffs commit or roll back as one
238
+ file transaction.
239
+
240
+ Different pixels are ordinary review evidence and exit 0. A frame, dimension, runtime, or exact
241
+ data mismatch exits 1 and suppresses pixel metrics because the images are not comparable.
242
+ `--allow-data-mismatch` keeps a data-mismatch report inspectable with exit 0, but does not invent
243
+ pixel metrics. `--watch` observes both configs, cancels stale renders, preserves the last complete
244
+ report through invalid edits, and emits deterministic NDJSON with `--json`. An explicit report path
245
+ requires `--force` in watch mode; the managed default path is safely replaceable.
246
+
247
+ Watch events are `watching`, `building`, `invalid`, `recovered`, `generation-complete`, `failed`,
248
+ and `stopped`, with monotonic comparison generations and source-side generations where applicable.
249
+ Without `--allow-data-mismatch`, an incompatible generation emits `failed`, does not replace the
250
+ last complete report, and remains an unresolved failure; an allowed data mismatch emits
251
+ `generation-complete` but still has no pixel metrics. An orderly stop exits 0 only after at least
252
+ one complete generation and no unresolved invalid or failed state; otherwise it exits 1.
253
+
254
+ Render one checkpoint, or recapture after every edit:
255
+
256
+ ```sh
257
+ npx tileflow capture madrid-desktop --json
258
+ npx tileflow capture --all --json
259
+ npx tileflow capture madrid-desktop --watch --json
260
+ ```
261
+
262
+ Use the first command when you want a single image and the last command while refining a map. Watch
263
+ mode stays open, follows changes to the config, its local imports, and its effective icons and fonts,
264
+ and writes a new PNG after each valid edit. Keeping the browser ready makes repeated renders faster.
265
+ If an edit is invalid, the last good image remains in place and capture resumes automatically after
266
+ the fix. Stop watch mode with `Ctrl+C`. It accepts named committed scenes or `--all`, not exploratory
267
+ camera options.
268
+
269
+ Standalone scenes need no app server, account, API key, MCP service, system Chrome, or visible
270
+ window. The first command—or the first capture—may provision Playwright's pinned Chromium headless
271
+ shell in its versioned per-user cache. `--no-browser-install` makes a prepared/offline environment
272
+ strict. Tileflow never falls back to a system browser or packages the browser in npm.
273
+
274
+ For prepared or offline CI, install ahead of time with
275
+ `npx tileflow setup capture --json`. Use `setup capture --no-browser-install` or
276
+ `capture --no-browser-install` as an enforcement check that the exact shell is already cached.
277
+
278
+ For an application scene, its route and component props must render the committed camera. Tileflow
279
+ uses the camera in scene identity but does not mutate arbitrary application map state.
280
+
281
+ One-shot success JSON is one deterministic schema-version-1 document plus one newline. It contains
282
+ sorted scene/map/theme/target entries, relative output and receipt paths, image hash/dimensions/DPR,
283
+ renderer identity, remote-dependency state, and warnings; it contains no image bytes, credential,
284
+ origin, absolute path, user, or timestamp. Managed output is an atomic pair under
285
+ `.tileflow/captures/<scene>.png` and `<scene>.receipt.json`. `tileflow init` ignores managed captures
286
+ and `.tileflow/diffs` when creating a new `.gitignore`. Explicit output replacement requires
287
+ `--force`; symlinks and path escapes fail closed.
288
+
289
+ Each written receipt uses capture schema v4 and records the concrete theme. A Tileflow World
290
+ capture resolves its TileJSON once for
291
+ the whole capture session and records the exact `world-v1` release plus descriptor, archive, data
292
+ contract, and product-contract hashes. All selected scenes and retries therefore use one immutable
293
+ World release even if `current` moves while the command is running.
294
+
295
+ One-shot `--json` failures leave stdout empty and write exactly one deterministic failure document
296
+ to stderr with `code`, `phase`, bounded diagnostics, and optional origin-only resource metadata.
297
+
298
+ Watch JSON is NDJSON with monotonic `building`, `invalid`, `recovered`, `captured`, `failed`, and
299
+ `stopped` generation events. It watches config, transitive local imports, and effective icon/font
300
+ files, preserves the last good capture through invalid edits, cancels stale work, and reuses one
301
+ headless Browser. An orderly stop exits 0 only after a successful capture with no unresolved
302
+ terminal failure.
303
+
304
+ ### Capture through the real application
305
+
306
+ Application scenes use the app's existing Vite, Next.js, Webpack, or custom loopback server. Start
307
+ only that normal server; Tileflow does not run a package script, scan ports, or start
308
+ `tileflow preview`:
309
+
310
+ ```sh
311
+ npm run dev
312
+ TILEFLOW_APP_ORIGIN=http://127.0.0.1:3000 \
313
+ npx tileflow capture madrid-product --json
314
+ ```
315
+
316
+ The React, Vue, and Svelte wrappers expose `data-tileflow-map`, the resolved concrete
317
+ `data-tileflow-theme`, optional `data-tileflow-capture-id`, and
318
+ `data-tileflow-state="loading|idle|error"`. The headless client selects exactly one target, verifies
319
+ the theme, and waits for idle. Use `--url` for one explicit full loopback URL, `--selector` to
320
+ override target selection, and `--frame map|viewport` to override framing. Origins and URLs must be
321
+ credential-free loopback HTTP(S) without fragments. Each navigation uses a fresh context without a
322
+ reused profile, cookies, local storage, or service workers.
323
+
324
+ ### Match a reference and protect the result
325
+
326
+ While refining a style, compare the current scene with any reference screenshot:
327
+
328
+ ```sh
329
+ npx tileflow visual analyze madrid-desktop \
330
+ --reference ./design-reference.png --region=0,0,1200,760 --json
331
+ ```
332
+
333
+ `visual analyze` renders exactly one committed scene and writes the current image and a JSON report
334
+ under `.tileflow/analysis` by default. The report includes both images' dimensions and dominant
335
+ colors. When their physical dimensions match, it also includes exact and perceptual pixel metrics,
336
+ mean channel difference, a high-contrast `.diff.png`, and signed
337
+ `appearance.actualMinusReference` luminance, OKLab, edge-density, and local-contrast evidence. The
338
+ optional region affects only those appearance metrics. The reference PNG is never modified or
339
+ treated as a baseline.
340
+
341
+ Once the map is approved, save it as the expected baseline and use that image to catch later
342
+ regressions:
343
+
344
+ ```sh
345
+ # Save the reviewed current render as the expected baseline.
346
+ npx tileflow visual update madrid-desktop \
347
+ --baseline-dir test/visual-baselines --json
348
+
349
+ # Compare a fresh render without changing the baseline.
350
+ npx tileflow visual diff madrid-desktop \
351
+ --baseline-dir test/visual-baselines --json
352
+
353
+ # Or apply the operation to every committed scene.
354
+ npx tileflow visual diff --all \
355
+ --baseline-dir test/visual-baselines --json
356
+
357
+ # In CI, return exit 2 when the pixels have changed.
358
+ npx tileflow visual diff madrid-desktop \
359
+ --baseline-dir test/visual-baselines --fail-on changed --json
360
+ ```
361
+
362
+ `visual diff` and `visual update` require either one or more positional scene names or `--all`;
363
+ those forms are mutually exclusive. `visual analyze` always accepts exactly one scene.
364
+
365
+ The baseline directory holds the reviewed expected PNG and its receipt. The files generated by a
366
+ comparison are working evidence: the current `.actual.png`, a transparent high-contrast
367
+ `.diff.png` when comparable, and a `.visual.json` report under `.tileflow/diffs` by default.
368
+ `visual diff` may replace those working files on each run, but it never changes the baseline. This
369
+ separation prevents a check from silently accepting the change it was meant to reveal.
370
+
371
+ Before comparing, `visual diff` validates the baseline PNG and receipt, including their hashes,
372
+ dimensions, runtime, and scene identity, without executing or fetching baseline metadata.
373
+ Statuses are `unchanged`, `changed`, `missing-baseline`, `scene-mismatch`, and `runtime-mismatch`.
374
+ Same-size images report exact RGBA changed pixels and an informational fixed-threshold perceptual
375
+ metric; exact inequality determines changed.
376
+
377
+ Changed pixels exit 0 by default so a person or agent can inspect the working files.
378
+ `--fail-on changed` exits 2 for CI; missing or incompatible baselines and operational failures exit
379
+
380
+ 1. Run `visual update` again only after reviewing and accepting the new render. It is the only
381
+ command that creates or changes baseline pairs, and it never stages, commits, or pushes. Remote
382
+ tiles, glyphs, and sprites are reported because their pixels can change independently of the style.
383
+
384
+ ## Inspect vector features
385
+
386
+ See which bounded source features the exported map sees near a camera before tuning taxonomy,
387
+ density, or labels:
388
+
389
+ ```sh
390
+ npx tileflow inspect features \
391
+ --center=-3.6927512,40.4086555 --zoom=16 \
392
+ --layers=poi --properties=name,category,type,icon,filter_rank,size_rank --json
393
+ ```
394
+
395
+ The command supports HTTP(S) TileJSON/vector sources and returns only requested properties plus
396
+ geometry summaries in stable order. Viewport, tiles, bytes, scanned/returned features, names, and
397
+ timeouts are bounded. Safe provenance includes origins, a query-free TileJSON path, and configured
398
+ source revision or `null`; credentials, URL queries, hidden properties, response bodies, and
399
+ absolute paths are omitted. It is read-only apart from executable config's own possible side
400
+ effects and removes `TILEFLOW_API_KEY` before loading config.
401
+
402
+ ## Hosted authentication and deploy
403
+
404
+ Hosted writes have two independent authorization paths:
405
+
406
+ - Personal developer session: run `npx tileflow login` once for a
407
+ Tileflow API origin. Login authenticates the account and selects no managed destination.
408
+ The CLI exchanges that account session for a brief, narrow capability only
409
+ after resolving the application internally.
410
+ - CI key: create a dashboard `CI deploy` key, store it as
411
+ `TILEFLOW_API_KEY`, and let the repository workflow deploy without a local
412
+ login. The server binds that key to exactly one internal application boundary.
413
+
414
+ Hosted control-plane requests stay pinned to the configured HTTP(S) origin, apply a hard bounded
415
+ timeout, and stream at most 1 MiB of response data. CLI diagnostics never echo an untrusted remote
416
+ response body or bearer credential.
417
+
418
+ Inspect the account and use the automatically resolved destination:
419
+
420
+ ```sh
421
+ npx tileflow whoami --json
422
+ npx tileflow deploy
423
+ npx tileflow status
424
+ npx tileflow logout
425
+ ```
426
+
427
+ When Tileflow gives an agent a continuation command for a verified World conversion, keep its
428
+ opaque technical destination and promotion reference in that exact deploy action:
429
+
430
+ ```sh
431
+ npx tileflow deploy \
432
+ --project @acme/web \
433
+ --world-promotion wpr_example1234
434
+ ```
435
+
436
+ The promotion reference is neither a map ID nor a credential. The CLI uses the map exported by the
437
+ selected config. A successful server-confirmed continuation keeps unrelated manifest entries and
438
+ records only that map's stable `mapId`, hosted theme URLs, World `v1` generation, and fixed session
439
+ usage mode. Do not add an API key or payment authority to a copied prompt.
440
+
441
+ If the account has exactly one accessible managed destination, a hosted command resolves it
442
+ automatically. With more than one, writes fail before config execution or network mutation, print
443
+ deterministically sorted application choices with their technical `@organization/project`
444
+ references, and give an exact retry containing `--project`. There is no persistent selection,
445
+ per-directory profile, or `login --project`. The hidden `projects` compatibility family remains
446
+ callable when support or explicit multi-application administration requires it.
447
+
448
+ An explicit key or `TILEFLOW_API_KEY` never uses the saved account session. If
449
+ it is combined with `--project`, the selector is an assertion: the CLI checks
450
+ `/v1/me` and rejects any mismatch before loading executable config or writing.
451
+
452
+ The CI key grants only `styles:write` and `status:read`. It cannot upload
453
+ datasets or render images. Give it an expiration, rotate the repository secret
454
+ before it expires, and avoid non-expiring CI keys.
455
+
456
+ Detected CI never falls back to the credential saved by `tileflow login`: if
457
+ `TILEFLOW_API_KEY` is absent, deploy stops before loading config or making a
458
+ request. Once captured, the key is hidden from `process.env` and `--api-key` is removed from the
459
+ temporary `process.argv` view before the CLI imports executable `tileflow.config.ts`; both process
460
+ surfaces are restored after the protected operation. Every config-aware command uses the same
461
+ scope, including validate, build, dev/watch, capture, visual, inspect, icon inspection, and deploy.
462
+ This is defense in depth, not a sandbox; deploy only reviewed repository code on a trusted runner,
463
+ and do not pass keys through the `--api-key` command line in CI.
464
+
465
+ The [deploy documentation](https://tileflow.dev/docs/deploy) contains copyable
466
+ GitHub Actions and GitLab CI workflows. Both use `npm ci`, the committed
467
+ lockfile, and the project-local CLI. GitHub exposes the key only to its deploy
468
+ step. GitLab variables are job-scoped, so its example validates in a keyless job
469
+ and deploys in a protected `production` job; the locked install and runner are
470
+ part of that trusted job boundary. Both serialize publications and intentionally
471
+ omit path filters because `tileflow.config.ts` can import arbitrary repository
472
+ files.
473
+
474
+ The CLI records bounded private provenance for GitHub Actions, GitLab CI,
475
+ generic CI, and local runs. These provider fields are descriptive client
476
+ reports, not OIDC attestations. Provider values can be overridden with:
477
+
478
+ ```txt
479
+ TILEFLOW_DEPLOY_REPOSITORY
480
+ TILEFLOW_DEPLOY_REVISION
481
+ TILEFLOW_DEPLOY_REF
482
+ TILEFLOW_DEPLOY_RUN_ID
483
+ TILEFLOW_DEPLOY_RUN_URL
484
+ ```
485
+
486
+ Invalid explicit values fail before a network write. Provider metadata that is
487
+ missing, malformed, or too long is omitted. Secrets and the complete
488
+ environment are never logged.
489
+
490
+ Application build is absent from the minimal workflows. Self-hosted build and deploy emit the same
491
+ runtime manifest version 1 shape; Hosted fields are optional identity metadata on its map/theme
492
+ entries. Filesystem build writers refuse to replace a manifest carrying Hosted metadata by default,
493
+ and deploy refuses to replace a purely local manifest. Prefer `emitBuildArtifacts: false` or
494
+ separate output paths when the app deliberately packages a deploy manifest. The explicit
495
+ `overwriteHostedManifest: true` build option and
496
+ `tileflow deploy --overwrite-self-hosted-manifest` flag authorize that destination replacement. A
497
+ retry of the same compiled theme family and delivery policy reuses its deployment version and prints
498
+ `Unchanged`; changed desired state prints `Published` with a new version. The
499
+ fingerprint binds resource references, not the changing bytes behind referenced
500
+ glyph or sprite URLs. Every theme of one logical map publishes atomically; separate maps can still
501
+ partially succeed when no remote batch release is used.
502
+
503
+ All config, theme-style, icon, and compatibility preflight happens before the first style write. After
504
+ that boundary the current Hosted API has no batch commit across repository-internal orchestration:
505
+ one singular config can publish before a later config fails. The orchestrator then leaves the
506
+ previous local manifest untouched, reports which publications succeeded, and tells the operator to
507
+ retry; idempotent publication converges on that retry. The local manifest
508
+ writer uses a durable same-directory temporary followed by direct rename and directory sync, so
509
+ readers see either the previous or next complete manifest. Multi-file artifact writers remain
510
+ rollback-capable sets. Neither mechanism is a remote transaction or can make several API writes
511
+ all-or-none.
512
+
513
+ Deploy treats the locally compiled MapLibre Style family as one logical-map artifact. The request
514
+ contains an exact `styles[theme]` collection, per-theme canonical SHA-256 identities in the build
515
+ manifest, only bounded hosting policy that does not belong in MapLibre itself, and the exact managed
516
+ icon-package identity when the family has one. Delivery
517
+ policy is not inherited with cartographic fields. Deploy does not serialize the module
518
+ configuration, send a public `tileset` selector, or ask the hosted API to compile the map again.
519
+ Hosted publication currently requires Tileflow World; `vectorTiles()` remains available for local
520
+ builds and previews but fails deploy before any remote write. The API binds recognized
521
+ Tileflow-owned world and terrain URLs to its generated map ID. A successful response must return
522
+ exactly the submitted theme names and a hosted Style URL for each. Deploy writes strict runtime
523
+ manifest version 1 with stable theme URLs, `colorScheme`, content revision, optional font faces and
524
+ style IDs, rather than duplicating dataset configuration. No older manifest is normalized.
525
+
526
+ For a map with `icons: ['./icons']`, hosted validation compiles the local SVG/PNG/JPEG/WebP files
527
+ without a key. Ordered directories apply left to right and later exact IDs replace earlier files.
528
+ Deploy uploads the four generated sprite files before its first style write and reuses
529
+ byte-identical packages by content hash. Source icons and absolute paths remain local; generated
530
+ atlas bytes and sorted icon names are public. Each hosted package is limited to 256 icons and 8 MiB
531
+ generated bytes as a processing envelope. There is no package-count quota; physically retained
532
+ generated packages share the organization's 5/10-GB hosted-storage pool with retained PMTiles,
533
+ remain protected by deployment/library references, and otherwise enter the technical orphan grace
534
+ before deletion. Upload success must confirm the submitted `contentHash`, exact icon count, exact
535
+ generated byte total, one opaque `icp_<id>`, and the corresponding query-free
536
+ `/sprites/<id>/sprite` URL. The CLI rejects any missing or divergent field before using that URL.
537
+
538
+ For a map with local or package-owned `fonts`, deploy selects only faces used by the final Style and
539
+ requires the contributing `LICENSE.txt` bytes. It uploads one bounded canonical font bundle to
540
+ `PUT /v1/font-bundles/:contentHash` before the dependent Style. Hosted confirms the exact file set,
541
+ hashes, MIME/signature agreement, licenses, and immutable public base URL; the CLI then binds
542
+ `tileflow:fontFaces` to the returned `.../font-bundles/fnb_<id>/fonts/<content-addressed-file>` and
543
+ sends only `fontBundle: {contentHash}` with the Style. Upload success must carry that exact opaque ID
544
+ and base URL; clients never derive a storage URL from the hash. The build manifest records the effective font-byte
545
+ digests, so a claimed hash or a different public URL cannot substitute for the uploaded bytes.
546
+ Hosted's matching rollout candidate persists project ownership, organization quota, deployment
547
+ references, grace-based cleanup, and deletion receipts. This repository contract does not by itself
548
+ establish that the matching Hosted migration and API are already deployed in production.
549
+
550
+ For both managed resource kinds, `assetSetSha256` remains a separate per-map output identity. It
551
+ hashes each generated file's exact portable name, media type, byte count, and SHA-256 using Core's
552
+ asset-set v1 contract; it does not hash only the icon-package or font-bundle manifest. Hosted must
553
+ confirm the immutable resource bytes, reconstruct those same per-file identities (including the
554
+ `icons/<mapId>/` prefix), and reject the Style when the submitted build-manifest hash diverges.
555
+
556
+ ## Agent icon composition
557
+
558
+ List the effective local catalog used by the exported map:
559
+
560
+ ```sh
561
+ npx tileflow icons list --json
562
+ ```
563
+
564
+ The successful stdout contract is one deterministic JSON document followed by exactly one newline.
565
+ Its top-level fields, in order, are `schemaVersion: 2`, `pathBase: "cwd"`, and `maps`.
566
+ Schema version 2 is a compatibility boundary: removing or renaming a field or changing an enum's
567
+ meaning requires a new schema version. Additive optional fields require corresponding documentation
568
+ and tests.
569
+
570
+ Each sorted map entry has one `id` and an `icons` value. `icons.kind` is `none` for `icons: []`, or
571
+ `directories` for a prepared ordered composition. A directory entry contains:
572
+
573
+ - `directories`, in the exact left-to-right authoring order, using config-relative paths or safe
574
+ `npm:<package>/<path>` descriptors;
575
+ - `finalIds`, the sorted canonical lower-kebab IDs in the resulting sprite;
576
+ - `replacements`, with the replaced path and winning path for every later exact-ID replacement;
577
+ - `sources`, with the winning ID, cwd-relative path, format, byte length, and intrinsic dimensions;
578
+ - `insideWorkingTree`; and
579
+ - the deterministic generated `packageHash`.
580
+
581
+ There is deliberately no parallel catalog registry, provider kind, semantic mapping, external
582
+ sprite branch, or arbitrary source-to-runtime renaming. `<id>.<ext>` publishes `<id>` and the
583
+ reserved pattern form `<id>.pattern.<ext>` also publishes `<id>`. This lets an
584
+ agent answer “which directory wins this icon?” directly from one map record without reconstructing
585
+ hidden provider state. Use `--map <id>` to select one exact map when internal orchestration supplies
586
+ more than one.
587
+
588
+ All reported paths are relative to the invocation working directory. A source outside that tree
589
+ uses `../` and `insideWorkingTree: false`; no absolute path is emitted. Listing follows local
590
+ authoring semantics. Run `npx tileflow validate --target hosted` separately before a
591
+ hosted deploy to check containment, portable IDs, SVG safety, and hosted limits.
592
+
593
+ The Tileflow command performs no authentication, network request, browser/server launch, or file
594
+ write. It removes an ambient `TILEFLOW_API_KEY` before importing config. As with every config-aware
595
+ command, loading `tileflow.config.ts` executes repository code and is not a sandbox; side effects in
596
+ that code remain the repository's responsibility. Success emits no ANSI or progress prose. Usage,
597
+ config, source, decode, and render failures exit 1, write diagnostics to stderr, and leave stdout
598
+ empty.
599
+
600
+ The output contains no source contents or source hash, rendered cells, atlas coordinates,
601
+ image/base64 payload, credential, timestamp, random value, or absolute path. Open a reported local
602
+ source path with normal repository tools only when the pixels themselves are needed.
603
+
604
+ ## Read-only icon comparison
605
+
606
+ Compare the exported map with its active hosted revision before deciding whether to deploy:
607
+
608
+ ```sh
609
+ npx tileflow icons diff --against production
610
+ npx tileflow icons diff --against production --json
611
+ npx tileflow icons diff --against production --report ./icon-diff.html
612
+ ```
613
+
614
+ The command compiles only the exported map, performs one authenticated baseline `GET`, and never
615
+ uploads a package, creates a deployment, or writes the frontend manifest. Plain and JSON modes
616
+ create no files. A requested HTML report adds reads of the baseline's four public sprite files and
617
+ embeds verified old/new images in one self-contained file. The report groups exact-cell icon
618
+ previews under `Added`, `Modified`, and `Removed`. Added and removed cards show only the affected
619
+ icon; modified cards compare `Before` with `Next`. A script-free 1x/2x selector defaults to 2x,
620
+ switches the embedded density, and renders 1x at half the 2x preview size inside a stable frame so
621
+ the selected state is immediately visible. Package hashes, generated sizes, and icon counts stay in
622
+ collapsed `Details`. A map badge is shown only for a map-scoped report. It exposes no atlas
623
+ implementation UI and contains no API key, remote runtime dependency, original icon source, or
624
+ local source path. Use `--force` to replace different existing report bytes and `--open` only
625
+ together with `--report`.
626
+
627
+ Visual modification means that normalized rendered RGBA pixels changed at 1x or 2x. Fully
628
+ transparent RGB is ignored. The manifest also hashes the four encoded artifacts, so an artifact can
629
+ change while its per-icon pixel list remains unchanged. The report and digest analysis operate on
630
+ generated sprite data; SVG, raster source files, and absolute paths stay on the local machine.
631
+
632
+ `--json` writes exactly one deterministic schema-version-1 document plus a trailing newline. Its
633
+ stable top-level fields, in order, are:
634
+
635
+ ```json
636
+ {
637
+ "schemaVersion": 1,
638
+ "environment": "production",
639
+ "baseline": {"deploymentId": "dep_...", "package": null, "version": 7},
640
+ "proposed": {"package": null},
641
+ "icons": {"added": [], "removed": [], "modified": [], "unchangedCount": 0},
642
+ "generatedBytes": {"before": 0, "after": 0, "delta": 0},
643
+ "artifacts": {"report": null},
644
+ "hasChanges": false
645
+ }
646
+ ```
647
+
648
+ Package summaries, when present, contain `contentHash`, `iconCount`, `label`, and `totalBytes`.
649
+ A missing hosted baseline is an initial comparison against an empty package. The command compares
650
+ only delivered icon pixels, canonical package manifests, and generated bytes; config-directory
651
+ provenance and style-reference analysis belong to `icons list` and final style validation.
652
+
653
+ Normal differences exit 0. Config, compilation, credential, network, response, and report errors
654
+ exit 1.
655
+
656
+ This workflow does not provide OIDC, automatic secret installation, an official
657
+ GitHub Action, an atomic multi-map deploy set, or a Tileflow-hosted remote
658
+ builder.
659
+
660
+ Docs: https://tileflow.dev/docs/deploy
package/TRADEMARKS.md ADDED
@@ -0,0 +1,10 @@
1
+ # Tileflow trademarks
2
+
3
+ The Apache License, Version 2.0, and the Tileflow generated-output grant do not grant permission to
4
+ use the Tileflow name, `tileflow.dev`, logos, service marks, product names, or other Tileflow brand
5
+ features except for reasonable and customary use that truthfully describes the origin of the SDK or
6
+ compatibility with Tileflow, and for reproducing required notices.
7
+
8
+ You may not use Tileflow brand features in a way that suggests sponsorship, endorsement, or an
9
+ official Tileflow product when none exists. Third-party names and marks remain the property of their
10
+ respective owners.