@tileflow/dev 0.1.0-alpha.23 → 0.1.0-alpha.24

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,153 @@
1
+ # Artifact lifecycle
2
+
3
+ Start with the [@tileflow/dev guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/README.md) for installation and a complete first example.
4
+
5
+ Node utilities used by the Tileflow CLI and build integrations.
6
+
7
+ Most apps should use `tileflow`, `@tileflow/vite`, or `@tileflow/next` directly. Use this
8
+ package when you are building a custom dev server, bundler plugin, or deployment adapter that needs
9
+ to load and compile the single map exported by `tileflow.config.ts`.
10
+
11
+ ```ts
12
+ import {createTileflowBuildArtifacts} from '@tileflow/dev/artifacts';
13
+
14
+ const artifacts = await createTileflowBuildArtifacts({
15
+ config: 'tileflow.config.ts',
16
+ });
17
+
18
+ try {
19
+ // Serve, capture, or write this exact generation.
20
+ } finally {
21
+ await artifacts.dispose?.();
22
+ }
23
+ ```
24
+
25
+ The integration surfaces are deliberately split: use `@tileflow/dev/artifacts` for source
26
+ preparation and production outputs, `@tileflow/dev/server` for HTTP development handling,
27
+ `@tileflow/dev/icons` for icon compilation, `@tileflow/dev/inspect` for safe resolved-config and
28
+ bounded feature inspection, `@tileflow/dev/validation` for structured diagnostics, and
29
+ `@tileflow/dev/preview` only for the local preview contract. The root entry keeps compatibility
30
+ re-exports.
31
+
32
+ Artifact construction is an explicit `source map → resolved map → prepared map → ArtifactPlan`
33
+ pipeline. Map inheritance resolves before its ordered local/package icon and font directories are
34
+ prepared, and before the result reaches the core style compiler. The Node layer may normalize
35
+ the map into an internal build catalog, but that catalog is orchestration state rather than another
36
+ public authoring model. The plan exposes its complete files and observed input graph, so build
37
+ adapters emit and watch the same generation instead of reconstructing either list themselves. Production manifests
38
+ point to content-addressed `generations/<sha256>/...` theme styles and sprites; those immutable files are
39
+ installed before the manifest pointer changes. Stable style paths are
40
+ `styles/<map>/<theme>.json`; every manifest lookup resolves a concrete theme. The disk writer also stages the complete plan,
41
+ records a managed-file inventory, rolls back caught filesystem failures, and removes only stale
42
+ files named by its previous valid inventory. Stable `styles/` and `icons/` paths remain available,
43
+ but generation-consistent consumers resolve maps through `manifest.json`.
44
+
45
+ A local `hostedTileset()` path remains user-owned input. Dev performs bounded header, root-directory,
46
+ and metadata checks, then creates an opaque immutable snapshot under
47
+ `.tileflow/cache/pmtiles-snapshots/v1/`, using copy-on-write cloning when available and a safe copy
48
+ otherwise. It does not hash the complete archive or traverse every leaf directory. Dev and Capture
49
+ read only that snapshot. The current generation and already-started acquisitions retain references;
50
+ a replaced generation accepts no new acquisitions and is collected after its last acquisition.
51
+ The Style-facing path remains `tilesets/<logical-id>.pmtiles` across generations; physical snapshot
52
+ identity stays out of Style JSON. Standalone Capture retains one generation for the complete render.
53
+ Application Capture delegates PMTiles reads to the application's development server, where each
54
+ range request retains its generation until that response is prepared; it does not lease one
55
+ generation for the complete capture. Strong generation ETags make a cached PMTiles read restart if
56
+ current changes between its ranges. Invalid edits keep the prior valid generation.
57
+
58
+ Production writers reject unresolved local PMTiles instead of copying, content-addressing,
59
+ deduplicating, retaining, or publishing them. Production data therefore requires an explicit
60
+ `tileset publish` operation or an application-owned PMTiles location. Managed publication owns its
61
+ separate exhaustive validation and version lifecycle. Startup removes snapshot generations owned
62
+ by dead processes, and symlinked snapshot-store boundaries are rejected.
63
+
64
+ Every plan also emits canonical `build-manifest.json` (schema version 1). For each map it records
65
+ the leaf `mapVersion`, resolved lineage, effective icon/font source identities, semantic compiler ABI, and
66
+ one entry per concrete theme with `colorScheme`, identity, inferred legacy `dataRequirements`,
67
+ multi-source `sourceRequirements`, and `styleSha256`. The multi-source contract records each
68
+ referenced vector source independently and records raster DEM source ID, encoding, and tile size;
69
+ it therefore distinguishes Bathymetry vector bands from optional relief without inventing vector
70
+ fields. `mapRevisionSha256` identifies the effective cartographic definition after `extends`;
71
+ `assetSetSha256` for that map's generated runtime resources. Data requirements are derived from the
72
+ final Style layers and fields rather than copied from a manually maintained allowlist, so disabled
73
+ or overridden modules do not claim data they no longer use. Maps use the single implicit semantic
74
+ compiler; its ABI remains a separate `semanticCompiler: {name, version}` build-manifest axis.
75
+
76
+ The map revision is versioned and domain-separated. It contains the resolved cartographic design,
77
+ semantic language version, effective public module/render-stack design, and effective icon/font
78
+ identities. Map `id`, `name`, editorial `mapVersion`, default `view`, capture `scenes`, package
79
+ SemVer, compiler ABI, local paths, timestamps, generated sprite/font output, and a
80
+ concrete resolution of a floating World selector are deliberately outside it. Changing a shadowed
81
+ ancestor does not change the revision; changing an effective cartographic override or source asset
82
+ does. Lineage and the leaf `mapVersion` remain beside the hash for traceability, and Style JSON owns
83
+ its compiled-output identity. Hosted browser policy is managed outside repository artifacts. The
84
+ top-level `provenance` block records the exact participating Tileflow package versions and the
85
+ nearest package-manager lockfile's format and content hash, without embedding its local path;
86
+ provenance remains outside every map revision. The same portable block is available to Hosted
87
+ adapters through
88
+ `createTileflowBuildProvenance(cwd)`. The dev server exposes the complete document at
89
+ `/build-manifest.json`.
90
+
91
+ `createTileflowStyle`, `createTileflowStyles`, and artifact construction all enforce the same
92
+ recursive JSON-value invariant and MapLibre style-spec semantics without fetching remote resources.
93
+ The family API returns `styles[map][theme]`; it never flattens a default theme into a map alias.
94
+ Invalid output throws `TileflowStyleValidationError` with at most 32 deterministic
95
+ `map`/`path`/`message` issues; layer indexes are projected to stable layer IDs.
96
+
97
+ For a watched, last-known-good integration, use one artifact session rather than recompiling each
98
+ request independently:
99
+
100
+ ```ts
101
+ import {createTileflowArtifactSession} from '@tileflow/dev/artifacts';
102
+
103
+ const session = await createTileflowArtifactSession({
104
+ config: 'tileflow.config.ts',
105
+ watch: true,
106
+ });
107
+
108
+ const unsubscribe = session.subscribe((state) => {
109
+ // building -> ready, or invalid while getLastGoodArtifacts() remains available
110
+ console.log(state.status, state.generation);
111
+ });
112
+
113
+ unsubscribe();
114
+ await session.close();
115
+ ```
116
+
117
+ `getState()` and `getLastGoodArtifacts()` are borrowed synchronous views. Before starting async
118
+ work, call `session.acquireArtifacts(generation?)` and release the returned acquisition in `finally`.
119
+ A replacement stops new acquisitions of the old generation and deletes its snapshot after the last
120
+ release. `close()` waits for outstanding acquisitions.
121
+
122
+ The bounded watcher follows the config, transitive local TypeScript/JavaScript/JSON imports, and
123
+ effective local icon/font directory inputs. Generations are monotonic, overlapping refreshes are latest-wins, invalid
124
+ edits retain the last good snapshot, and caller-supplied output directories can be ignored to avoid
125
+ feedback loops. `tileflow preview` (`tileflow dev` compatibility alias), `capture --watch`, and
126
+ framework adapters use this shared status
127
+ vocabulary. The built-in preview reloads after its live event stream reconnects following a server
128
+ restart, even when the replacement process begins again at generation 1.
129
+
130
+ Custom preview servers use the exported map or one of its standalone scenes:
131
+
132
+ ```ts
133
+ import {createTileflowDevRequestHandler} from '@tileflow/dev/server';
134
+
135
+ const fetch = createTileflowDevRequestHandler({
136
+ config: 'tileflow.config.ts',
137
+ scene: 'madrid-mobile',
138
+ });
139
+
140
+ // On server shutdown:
141
+ await fetch.close();
142
+ ```
143
+
144
+ The handler does not close a caller-owned session.
145
+
146
+ Map preview uses the exported map's `view` and accepts one concrete `theme`; scene preview uses its
147
+ committed concrete theme, camera, and CSS viewport metadata from that same map. A scene does not
148
+ repeat the map ID because its owner is implicit.
149
+ `resolveTileflowPreview()` exposes the validated selection for custom integrations.
150
+ Application-target scenes remain the responsibility of the application's development server. The
151
+ built-in preview records longitude, latitude, zoom, bearing, and pitch in the current URL, so browser
152
+ refreshes and config-triggered reloads return to the same view. Removing those query parameters
153
+ restores the configured camera.
@@ -0,0 +1,21 @@
1
+ # Bounded PMTiles resource inspection
2
+
3
+ Start with the [@tileflow/dev guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/README.md) for installation and a complete first example.
4
+
5
+ Node tooling can inspect one local PMTiles archive without reading it completely:
6
+
7
+ ```ts
8
+ import {inspectTileflowPmtiles} from '@tileflow/dev/tilesets';
9
+
10
+ const inspection = await inspectTileflowPmtiles('./data/stores.pmtiles', {
11
+ includeValues: ['category', 'status'],
12
+ });
13
+ ```
14
+
15
+ Inspection schema 1 separates the authoritative PMTiles header and TileJSON `vector_layers` metadata
16
+ from deterministic bounded MVT observations. Each sampled field reports present/missing feature
17
+ counts, observed primitive types, capped distinct-value cardinality, numeric min/max, and explicit
18
+ truncation. Values are returned only for requested portable field names, with at most 32 requested
19
+ fields, 16 values per field, and 256 characters per string. Sampling reads at most eight tiles,
20
+ 20,000 features, or 24 MB of tile data; `sample: false` returns only authoritative metadata and does
21
+ not accept `includeValues`.
@@ -0,0 +1,101 @@
1
+ # Bounded vector-feature inspection
2
+
3
+ Start with the [@tileflow/dev guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/README.md) for installation and a complete first example.
4
+
5
+ Custom Node tooling can inspect only the features visible near a configured camera without opening
6
+ a browser:
7
+
8
+ ```ts
9
+ import {inspectTileflowFeatures} from '@tileflow/dev/inspect';
10
+
11
+ const internalCatalog = artifacts.project;
12
+ const inspection = await inspectTileflowFeatures(internalCatalog, 'madrid', {
13
+ center: [-3.6927512, 40.4086555],
14
+ zoom: 16,
15
+ sourceLayers: ['poi'],
16
+ properties: ['name', 'category', 'type', 'icon', 'filter_rank', 'size_rank'],
17
+ width: 512,
18
+ height: 512,
19
+ limit: 100,
20
+ });
21
+ ```
22
+
23
+ The inspector supports ordinary HTTP(S) vector sources and TileJSON. It bounds viewport, tile
24
+ count, individual/total bytes, scanned and returned features, property count/string lengths, and
25
+ request time; results are deduplicated and sorted deterministically. Output contains projected
26
+ properties, geometry summaries, safe origin/path metadata, and the configured source revision or
27
+ `null`. It never emits URL queries, credentials, hidden properties, response bodies, or absolute
28
+ paths. PMTiles and authenticated provider adapters are not currently supported by this primitive.
29
+
30
+ Deployment adapters can compile the same sources without writing them to disk:
31
+
32
+ ```ts
33
+ import {compileTileflowIconPackages} from '@tileflow/dev/icons';
34
+
35
+ const internalCatalog = artifacts.project;
36
+ const result = await compileTileflowIconPackages(internalCatalog, {
37
+ cwd: process.cwd(),
38
+ target: 'hosted',
39
+ });
40
+ ```
41
+
42
+ Streets declares `[streetsIcons]` and keeps light/dark image-token targets in that one closure;
43
+ Cyberpunk and Matrix independently declare `[cyberpunkIcons]` and `[matrixIcons]`. Baedeker,
44
+ Ferraris, Härad, Siegfried, Soundings, Verdant, and San Francisto declare only `[baedekerIcons]`,
45
+ `[ferrarisIcons]`, `[haradIcons]`, `[siegfriedIcons]`, `[soundingsIcons]`, `[verdantIcons]`, and
46
+ `[sanFrancistoIcons]`, respectively. No official root composes another map's assets even though all
47
+ use the semantic compiler ABI. Baedeker's directory contains eight original travel-atlas patterns;
48
+ its browser-derived Mapterhorn contours use runtime terrain tiles and are not part of the icon
49
+ package. Härad's directory contains nine original Tileflow patterns inspired by Lantmäteriet's CC0
50
+ Häradsekonomiska kartan series from 1859–1934. Soundings owns ten original nautical symbols and
51
+ patterns. San Francisto owns four technical blueprint patterns and one schematic POI node; its
52
+ unbundled Mapterhorn contours remain runtime terrain, and its text uses the canonical Noto Sans
53
+ glyph provider. An application map may inherit a root's exact array, replace it, clear it with `[]`,
54
+ or compose it explicitly with a spread.
55
+ `modules.poi.icons: false` or a disabled POI module suppresses POI icon layers without moving asset
56
+ ownership into dev. POI density is the numeric producer threshold 1–5; it is not an asset-provider
57
+ preset.
58
+
59
+ The hosted target enforces repository containment, portable icon IDs, safe SVG
60
+ references, deterministic ordering, and the public package limits. Package-owned directories are
61
+ trusted only inside their declared owning package and are exempt from config working-tree
62
+ containment. It returns deduplicated generated files plus a canonical manifest/content hash. Upload
63
+ only those generated files: source icons and absolute paths remain local.
64
+
65
+ The version-1 manifest includes `renderedIcons` in the exact sorted `iconNames` order. Each entry
66
+ contains SHA-256 digests for the normalized 1x and 2x RGBA cell. The digest framing includes its
67
+ density and dimensions, zeroes RGB only where alpha is zero, and excludes filenames/source bytes.
68
+ The package content hash still covers the complete canonical manifest, including all four encoded
69
+ file hashes. Consumers can therefore distinguish a visible per-icon change from an atlas-layout or
70
+ encoding-only package change without receiving the original SVG/raster file.
71
+
72
+ `compileTileflowIconPackages` retains the exact RGBA cells used to assemble each atlas, so its
73
+ pixel digests describe delivered pixels rather than a second render. Hosted upload services must
74
+ decode the submitted PNGs and reconstruct those digests independently; client-authored manifest
75
+ analysis is not authoritative.
76
+
77
+ Custom Node tooling can inspect the same local pipeline without walking or decoding source files a
78
+ second time:
79
+
80
+ ```ts
81
+ import {inspectTileflowIconCatalogs} from '@tileflow/dev/icons';
82
+
83
+ const internalCatalog = artifacts.project;
84
+ const inspection = await inspectTileflowIconCatalogs(internalCatalog, {
85
+ cwd: process.cwd(),
86
+ mapNames: ['madrid'],
87
+ });
88
+ ```
89
+
90
+ The result keeps one catalog per distinct ordered directory composition, the exact winning source
91
+ for every final ID, explicit same-ID replacements, config-relative source metadata, exact atlas rectangles,
92
+ both manifest pixel digests, and the shared compiled package. `icons: []` is represented as an
93
+ absent catalog. This is a local-authoring integration surface: use the hosted compilation target
94
+ separately for deployment compatibility, and project only the metadata needed by an external
95
+ protocol rather than serializing generated byte arrays accidentally.
96
+
97
+ This package is public, but it is an integration layer. Styling primitives live in
98
+ `@tileflow/core`, official maps and their source assets live in `@tileflow/maps`, and React
99
+ rendering lives in `@tileflow/react`.
100
+
101
+ Docs: https://tileflow.dev/docs
@@ -0,0 +1,250 @@
1
+ # Local comparison workbench and compiler inspection
2
+
3
+ Start with the [@tileflow/dev guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/README.md) for installation and a complete first example.
4
+
5
+ The CLI exposes the complete authoring surface through `tileflow preview --against-map ...` or
6
+ `--against-config ...`. Custom local servers can compose the same shell around two existing dev
7
+ handlers:
8
+
9
+ ```ts
10
+ import {createTileflowComparisonRequestHandler} from '@tileflow/dev/server';
11
+
12
+ const fetch = createTileflowComparisonRequestHandler({
13
+ left: {
14
+ basePath: '/left',
15
+ handler: leftHandler,
16
+ label: 'candidate / light',
17
+ previewUrl: '/left/',
18
+ sidecarUrl: '/left/__inspection/candidate/light.json',
19
+ },
20
+ right: {
21
+ basePath: '/right',
22
+ handler: rightHandler,
23
+ label: 'reference / light',
24
+ previewUrl: '/right/',
25
+ sidecarUrl: '/right/__inspection/reference/light.json',
26
+ },
27
+ });
28
+ ```
29
+
30
+ Each side handler retains ownership of its styles, fonts, sprites, events, diagnostics, and
31
+ last-known-good artifact generation. The comparison router only owns the root HTML and strict
32
+ dispatch to two disjoint same-origin route prefixes. Its synchronized camera, URL state,
33
+ side-by-side/split/overlay/blink modes, rendered-feature inspector, zoom-curve sampling, sprite
34
+ atlas, and scene/capture-command copying require no hosted service.
35
+
36
+ Set `inspection: true` on `createTileflowBuildArtifacts` or
37
+ `createTileflowArtifactSession` to compute `artifacts.styleInspections[map][theme]`. A dev handler
38
+ serves an available sidecar at `/__inspection/<map>/<theme>.json`; otherwise that route is 404. The
39
+ option is deliberately off by default. Sidecars remain in memory, do not alter Style bytes or
40
+ manifest hashes, and are excluded from `getTileflowArtifactFiles()` and every stable build output.
41
+
42
+ When a resolved map declares `fonts`, preparation reads its ordered directories and uses OpenType
43
+ full names as the canonical IDs referenced by `text-font`. TTF, OTF, and WOFF2 inputs are supported;
44
+ every contributing directory must contain `LICENSE.txt`. Later directories replace earlier faces
45
+ with the same exact ID, case-only collisions fail, and every primary font in the final style must be
46
+ present. `font` is an exact face ID; local `fallbacks` entries are exact face names or explicit CSS
47
+ generic families such as `sans-serif`. Preparation never synthesizes a face name from a family and
48
+ weight. Only selected primary faces and licenses are emitted as
49
+ content-addressed managed artifacts.
50
+
51
+ `fonts` and `glyphs` are mutually exclusive map fields. Omission inherits the parent's current text
52
+ provider; declaring either one replaces an inherited provider of either kind atomically. The font
53
+ pipeline is driven only by directory metadata and final style usage—there are no hard-coded map or
54
+ family names, and a URL provider comes only from the resolved map instead of being invented by the
55
+ pipeline. After inheritance resolves, a map with any text layer must have exactly one provider. A
56
+ `glyphs: {kind: 'url', ...}` provider enumerates the exact comma-joined MapLibre request keys
57
+ produced by the style's `text-font` arrays.
58
+
59
+ The resulting style records strict `tileflow:fontFaces` metadata. Preview, capture, and browser
60
+ framework adapters load those definitions before constructing MapLibre. Hosted deploy rejects a
61
+ prepared local-font bundle before authentication until managed font storage is available. A derived
62
+ map that declares `glyphs` replaces the inherited local provider atomically; the URL-backed map is
63
+ complete as declared and independent of its World selection.
64
+
65
+ For Streets styles whose vegetation layer declares `tileflow:vegetation-mode = 3d`, the built-in
66
+ preview uses the same portable circle fallback as capture and the published framework adapters by
67
+ default. This makes an ordinary `tileflow preview` session WYSIWYG with production and visual
68
+ baselines. Append `?treeRenderer=simple` or `?treeRenderer=complex` to opt into the experimental,
69
+ preview-only instanced renderer described below; that explicit mode is not capture evidence.
70
+
71
+ The experimental renderer uses source height and crown diameter when present, distinguishes palms,
72
+ columnar cypresses, conifers, and three broadleaf silhouettes from the bound botanical fields, and
73
+ falls back to deterministic broadleaf variety and dimensions when tags are missing. Bark color,
74
+ crown palettes, height scale, and crown scale come from `vegetation.threeDimensional`; the preview
75
+ does not maintain a second hard-coded appearance contract. During camera
76
+ gestures the existing 3D batch remains visible while native circles cover newly arriving tiles; the
77
+ batch refreshes when movement ends without waiting for the whole map to become idle. Source-tile
78
+ events are rate-limited and unchanged instance selections do not upload new GPU buffers. The
79
+ preview caps one view at 3,000 trees and uses zoom-dependent density. Both detailed modes use thin,
80
+ tapered trunks, branches that enter the crown, softly shaded overlapping ellipsoids, and stable
81
+ per-tree scale and rotation. `simple` reduces lobe and segment counts; `complex` keeps the complete
82
+ round, open, avenue, pine, cypress, and palm silhouettes. Each archetype remains fused into one
83
+ reusable geometry and every complete tree remains
84
+ one GPU instance, so the additional detail introduces no extra draw calls. The experimental
85
+ renderer defaults to its WebGL2 backend, which
86
+ draws those instances directly without a Three.js scene traversal; append `&treeBackend=three` for
87
+ the compatibility backend. Use `?treeRenderer=circle`, `?treeRenderer=simple`, or
88
+ `?treeRenderer=complex&treeBenchmark=1` to compare identical scenes; the benchmark reports frame
89
+ p95, query, selection, build and render time, draw calls, and triangle count in the tree status badge.
90
+ Visible candidates come from MapLibre's worker-built tile index instead of a second copy of the
91
+ vector source. Terrain height is sampled on a fixed 5-by-5 viewport grid and interpolated per tree,
92
+ avoiding hundreds of synchronous terrain queries when a new batch first appears.
93
+ The separate `TREES ON/OFF` control sits below the building `3D ON/OFF` control and pauses tree
94
+ refresh work while disabled. Both choices survive reloads and shared links through the
95
+ `buildings3d=on|off` and `trees3d=on|off` URL parameters.
96
+
97
+ Detailed trees retain the compiler's scene order: they render above road and pedestrian surfaces,
98
+ ordinary building geometry, and detailed landmark models. Map labels and other annotations remain
99
+ above the physical scene, while road names and route references stay attached to the carriageway
100
+ below buildings and trees.
101
+
102
+ The same control toggles any compiled layer whose metadata contains
103
+ `tileflow:3d-toggle = building`. It switches the building volumes independently from the camera,
104
+ so enabling 3D never changes the pitch and users remain free to tilt the map with pointer controls.
105
+
106
+ ### Detailed landmark models
107
+
108
+ A hidden style layer can opt the Streets preview into detailed GLB landmarks by setting
109
+ `tileflow:landmark-manifest-url` in its metadata. The bounded version-2 manifest indexes GLB entries
110
+ inside one or more PMTiles archives and is fetched only when the 3D building control is enabled near
111
+ the landmark runtime zoom. Archive URLs may be absolute or relative to the manifest URL.
112
+
113
+ ```json
114
+ {
115
+ "schemaVersion": 2,
116
+ "id": "madrid-landmarks-2026-08-19",
117
+ "minzoom": 16,
118
+ "maximumVisibleModels": 8,
119
+ "maximumCachedModels": 16,
120
+ "archives": [
121
+ {
122
+ "id": "madrid-v1",
123
+ "url": "./madrid-landmarks-v1.pmtiles",
124
+ "bytes": 14832912,
125
+ "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
126
+ }
127
+ ],
128
+ "landmarks": [
129
+ {
130
+ "id": "palacio-cibeles",
131
+ "center": [-3.6921, 40.4193],
132
+ "bounds": [-3.693, 40.4186, -3.6912, 40.42],
133
+ "priority": 100,
134
+ "models": [
135
+ {
136
+ "minzoom": 16,
137
+ "archiveId": "madrid-v1",
138
+ "z": 0,
139
+ "x": 0,
140
+ "y": 0,
141
+ "bytes": 481024,
142
+ "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
143
+ "axisConvention": "EUN_Y_UP"
144
+ },
145
+ {
146
+ "minzoom": 18,
147
+ "archiveId": "madrid-v1",
148
+ "z": 1,
149
+ "x": 0,
150
+ "y": 0,
151
+ "bytes": 1276416,
152
+ "sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
153
+ "axisConvention": "EUN_Y_UP"
154
+ }
155
+ ]
156
+ }
157
+ ]
158
+ }
159
+ ```
160
+
161
+ `maximumVisibleModels` is bounded to 1–64. `maximumCachedModels` defaults to twice that value,
162
+ must be at least the visible limit, and is bounded to 128. Higher `priority` values win when more
163
+ landmarks intersect the viewport than can be shown. `models[0]` is the base LOD and must start at the
164
+ manifest `minzoom`; later entries replace it at their distinct `minzoom`. The parser bounds manifest,
165
+ archive, landmark, model and GLB-entry sizes, rejects credential-bearing or non-HTTP archive URLs,
166
+ and verifies each extracted entry's declared byte length and SHA-256 before parsing it.
167
+
168
+ The preview starts the Three.js runtime and manifest request together one zoom before the landmark
169
+ layer becomes visible. A landmark entering directly at a close zoom always displays its base LOD
170
+ first and upgrades in place. Nearby base LODs are warmed in the parsed-model cache. Style layers
171
+ marked with `tileflow:landmark-fallback = true` remain visible until the corresponding active GLB is
172
+ ready, so a cold request never leaves an empty building volume.
173
+
174
+ Embedded Streets previews also accept a same-origin parent command without reloading the document:
175
+
176
+ ```js
177
+ iframe.contentWindow.postMessage(
178
+ {
179
+ type: 'tileflow:set-map-state',
180
+ schemaVersion: 1,
181
+ state: {
182
+ center: [-3.688344, 40.453053],
183
+ zoom: 17.75,
184
+ bearing: -24,
185
+ pitch: 58,
186
+ buildings3d: true,
187
+ trees3d: false,
188
+ visibleLayerGroups: ['labels', 'roads', 'buildings', 'landuse', 'water'],
189
+ },
190
+ },
191
+ location.origin,
192
+ );
193
+ ```
194
+
195
+ The command is applied atomically only for the embedding parent, with exact same-origin and numeric
196
+ range checks. `visibleLayerGroups` is optional and accepts only `labels`, `pois`, `roads`, `transit`,
197
+ `buildings`, `landuse`, and `water`; omitted groups are hidden while base background and attribution
198
+ remain. Camera and toggle state continue to be written to the preview URL after application.
199
+
200
+ GLB base colors, textures, emissive values, metalness, and roughness are authoritative and are not
201
+ rewritten by the preview. Models outside the active selection remain hidden in a least-recently-used
202
+ cache; leaving the cache disposes their geometry, materials, and textures.
203
+
204
+ The vector building represented by a detailed model must carry `hide_3d = true`. The ordinary
205
+ building extrusion and both screen-space shadow passes filter on that field, preventing the coarse
206
+ volume from intersecting the GLB while retaining the flat footprint below it. This flag belongs in
207
+ the vector-tile dataset; the landmark endpoint cannot substitute for it. Version model and GLB URLs
208
+ so they can be served with immutable caching. Cross-origin endpoints must allow credentialed GETs
209
+ because the development renderer requests the manifest and models with credentials.
210
+
211
+ If a map uses `icons: ['./icons/brand']`, build artifacts include the generated MapLibre
212
+ `sprite.json`, `sprite.png`, `sprite@2x.json`, and `sprite@2x.png` beside its style. Ordinary sources
213
+ are normalized into 24 px icon cells. A source named `<id>.pattern.svg` (or another supported raster
214
+ format with the same marker) instead keeps its intrinsic dimensions and is published as `<id>`.
215
+ This provides calibrated, non-square textures for line and fill patterns while the runtime still
216
+ consumes only the generated PNG sprite. Pattern widths must be a power of two from 2 through 512
217
+ pixels, matching MapLibre's seamless line-pattern contract.
218
+
219
+ `icons` is one ordered array. Omitting it inherits the parent's exact array, declaring it replaces
220
+ the array atomically, and `[]` means no icons. Use a spread to preserve imported directories while
221
+ adding or replacing files:
222
+
223
+ ```ts
224
+ import {defineMap} from '@tileflow/core';
225
+ import {streets} from '@tileflow/maps';
226
+
227
+ export default defineMap({
228
+ id: 'brand-map',
229
+ version: 1,
230
+ extends: streets,
231
+ icons: [...streets.icons, './icons/brand'],
232
+ });
233
+ ```
234
+
235
+ Directories apply left to right. `<id>.<ext>` publishes an icon as `<id>`;
236
+ `<id>.pattern.<ext>` publishes the intrinsic-size pattern as `<id>`. The published ID must already
237
+ be canonical lower-kebab-case. A later file replaces an earlier file only for the same exact ID;
238
+ case-only collisions fail. Package maps export `streetsIcons`, `baedekerIcons`, `ferrarisIcons`,
239
+ `haradIcons`, `siegfriedIcons`, `soundingsIcons`, `cyberpunkIcons`, `matrixIcons`, and
240
+ `verdantIcons`, and `sanFrancistoIcons` directory descriptors from `@tileflow/maps`. Cyberpunk,
241
+ Matrix, Baedeker, and Siegfried also export `cyberpunkFonts`, `matrixFonts`, `baedekerFonts`, and
242
+ `siegfriedFonts`. Preparation
243
+ resolves package descriptors inside their owning package, checks real-path containment, and
244
+ compiles them through the same pipeline as a config-relative directory. There are no
245
+ built-in/source/sprite selectors, mappings, icon-specific inheritance, or compatibility aliases.
246
+
247
+ Local directories resolve from the directory containing the selected `tileflow.config.ts`, while
248
+ the workspace `cwd` remains their containment boundary. Canonical local syntax starts with `./` or
249
+ one or more leading `../` segments and then uses only non-empty segments other than `.` or `..`;
250
+ trailing or repeated slashes, backslashes, controls, and paths longer than 512 characters fail.
@@ -0,0 +1,39 @@
1
+ # Structured config inspection and diagnostics
2
+
3
+ Start with the [@tileflow/dev guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/README.md) for installation and a complete first example.
4
+
5
+ Custom Node tooling can use the same deterministic documents as `tileflow validate --json` and
6
+ `tileflow inspect --json`:
7
+
8
+ ```ts
9
+ import {inspectTileflowConfig} from '@tileflow/dev/inspect';
10
+ import {
11
+ createTileflowCommandFailureDocument,
12
+ serializeTileflowCommandDocument,
13
+ } from '@tileflow/dev/validation';
14
+
15
+ try {
16
+ const inspection = await inspectTileflowConfig({config: 'tileflow.config.ts'});
17
+ process.stdout.write(serializeTileflowCommandDocument(inspection));
18
+ } catch (error) {
19
+ const failure = createTileflowCommandFailureDocument('inspect', error, process.cwd(), {
20
+ code: 'INSPECTION_FAILED',
21
+ phase: 'config-inspection',
22
+ });
23
+ process.stderr.write(serializeTileflowCommandDocument(failure));
24
+ }
25
+ ```
26
+
27
+ The inspection resolves one config load into sorted maps, root-to-leaf lineage, declared paths, and
28
+ leaf-level merge provenance. Its `themeContract` exposes the default/system mapping, shared token
29
+ schema, concrete resolved token values, typography and lighting, differences from the default, and
30
+ stable `THEME_IMPLICIT_FIXED` diagnostics across modules, render stacks, and terrain. Each
31
+ diagnostic carries a semantic scope and, for module values, its owner, plus machine-readable
32
+ severity and remediation. Colors, fonts, and images are followed through
33
+ expression outputs; numeric diagnostics intentionally cover only direct visual scalars, excluding
34
+ expression operands, zoom stops, tuples, placement, priority, and other structural numbers.
35
+ Agents can therefore edit semantic roles without
36
+ reverse-engineering compiler layers or parsing prose. It never returns `inputFiles`, absolute filesystem paths, credentials,
37
+ URL queries, data URLs, or recognized secret formats. Structured summaries and diagnostics use the
38
+ same required schema-version-1 fields and bounded safe suggestions so consumers do not need to
39
+ parse human prose.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tileflow/dev",
3
- "version": "0.1.0-alpha.23",
3
+ "version": "0.1.0-alpha.24",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Node development utilities for Tileflow configs and builds.",
6
6
  "type": "module",
@@ -28,6 +28,7 @@
28
28
  "types": "./dist/index.d.ts",
29
29
  "files": [
30
30
  "dist",
31
+ "docs",
31
32
  "THIRD_PARTY_NOTICES.md",
32
33
  "LICENSE"
33
34
  ],
@@ -89,7 +90,7 @@
89
90
  "dependencies": {
90
91
  "@mapbox/vector-tile": "3.0.0",
91
92
  "@maplibre/maplibre-gl-style-spec": "24.8.5",
92
- "@tileflow/core": ">=0.1.0-alpha.24 <0.1.0-beta.0",
93
+ "@tileflow/core": ">=0.1.0-alpha.25 <0.1.0-beta.0",
93
94
  "chokidar": "4.0.3",
94
95
  "fflate": "0.8.3",
95
96
  "fontkit": "2.0.4",
@@ -101,7 +102,7 @@
101
102
  "three": "0.185.1"
102
103
  },
103
104
  "devDependencies": {
104
- "@tileflow/maps": "0.1.0-alpha.3"
105
+ "@tileflow/maps": "0.1.0-alpha.4"
105
106
  },
106
107
  "optionalDependencies": {
107
108
  "sharp": "0.35.4"