@tileflow/dev 0.1.0-alpha.22 → 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.
- package/README.md +130 -515
- package/dist/artifacts.d.ts +1 -1
- package/dist/artifacts.js +2 -2
- package/dist/{chunk-MIGBHCXX.js → chunk-6OJICHB3.js} +3 -1
- package/dist/{chunk-CENQR5Q2.js → chunk-NUHSYLLS.js} +312 -170
- package/dist/{chunk-KH7CMHT3.js → chunk-XS5Q4IS2.js} +2 -2
- package/dist/{chunk-XTRHREXO.js → chunk-XXVI42UZ.js} +1 -1
- package/dist/fonts.d.ts +1 -1
- package/dist/icons-CkLVtaP-.d.ts +164 -0
- package/dist/icons.d.ts +3 -136
- package/dist/icons.js +5 -3
- package/dist/index.d.ts +52 -3
- package/dist/index.js +588 -5
- package/dist/{preview-assets-6LYL7GP7.js → preview-assets-UTUP3OML.js} +24 -15
- package/dist/preview.js +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +3 -3
- package/docs/artifact-lifecycle.md +153 -0
- package/docs/bounded-pmtiles-resource-inspection.md +21 -0
- package/docs/bounded-vector-feature-inspection.md +101 -0
- package/docs/local-comparison-workbench-and-compiler-inspection.md +250 -0
- package/docs/structured-config-inspection-and-diagnostics.md +39 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -1,558 +1,173 @@
|
|
|
1
1
|
# @tileflow/dev
|
|
2
2
|
|
|
3
|
-
Node utilities
|
|
3
|
+
Node.js utilities for custom Tileflow build tools and development servers. Load a trusted
|
|
4
|
+
`tileflow.config.ts`, prepare its assets, and produce a validated artifact generation.
|
|
4
5
|
|
|
5
|
-
Most
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
Most applications should use the [CLI](https://github.com/tileflow/tileflow-sdk/blob/main/packages/cli/README.md)
|
|
7
|
+
or the [Vite](https://github.com/tileflow/tileflow-sdk/blob/main/packages/vite/README.md),
|
|
8
|
+
[Next.js](https://github.com/tileflow/tileflow-sdk/blob/main/packages/next/README.md), or
|
|
9
|
+
[Webpack](https://github.com/tileflow/tileflow-sdk/blob/main/packages/webpack/README.md) integration.
|
|
10
|
+
This package is for implementing an integration, not for browser rendering.
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
import {createTileflowBuildArtifacts} from '@tileflow/dev/artifacts';
|
|
11
|
-
|
|
12
|
-
const artifacts = await createTileflowBuildArtifacts({
|
|
13
|
-
config: 'tileflow.config.ts',
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
// Serve, capture, or write this exact generation.
|
|
18
|
-
} finally {
|
|
19
|
-
await artifacts.dispose?.();
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
The integration surfaces are deliberately split: use `@tileflow/dev/artifacts` for source
|
|
24
|
-
preparation and production outputs, `@tileflow/dev/server` for HTTP development handling,
|
|
25
|
-
`@tileflow/dev/icons` for icon compilation, `@tileflow/dev/inspect` for safe resolved-config and
|
|
26
|
-
bounded feature inspection, `@tileflow/dev/validation` for structured diagnostics, and
|
|
27
|
-
`@tileflow/dev/preview` only for the local preview contract. The root entry keeps compatibility
|
|
28
|
-
re-exports.
|
|
29
|
-
|
|
30
|
-
Artifact construction is an explicit `source map → resolved map → prepared map → ArtifactPlan`
|
|
31
|
-
pipeline. Map inheritance resolves before its ordered local/package icon and font directories are
|
|
32
|
-
prepared, and before the result reaches the core style compiler. The Node layer may normalize
|
|
33
|
-
the map into an internal build catalog, but that catalog is orchestration state rather than another
|
|
34
|
-
public authoring model. The plan exposes its complete files and observed input graph, so build
|
|
35
|
-
adapters emit and watch the same generation instead of reconstructing either list themselves. Production manifests
|
|
36
|
-
point to content-addressed `generations/<sha256>/...` theme styles and sprites; those immutable files are
|
|
37
|
-
installed before the manifest pointer changes. Stable style paths are
|
|
38
|
-
`styles/<map>/<theme>.json`; every manifest lookup resolves a concrete theme. The disk writer also stages the complete plan,
|
|
39
|
-
records a managed-file inventory, rolls back caught filesystem failures, and removes only stale
|
|
40
|
-
files named by its previous valid inventory. Stable `styles/` and `icons/` paths remain available,
|
|
41
|
-
but generation-consistent consumers resolve maps through `manifest.json`.
|
|
42
|
-
|
|
43
|
-
A local `hostedTileset()` path remains user-owned input. Dev performs bounded header, root-directory,
|
|
44
|
-
and metadata checks, then creates an opaque immutable snapshot under
|
|
45
|
-
`.tileflow/cache/pmtiles-snapshots/v1/`, using copy-on-write cloning when available and a safe copy
|
|
46
|
-
otherwise. It does not hash the complete archive or traverse every leaf directory. Dev and Capture
|
|
47
|
-
read only that snapshot. The current generation and already-started acquisitions retain references;
|
|
48
|
-
a replaced generation accepts no new acquisitions and is collected after its last acquisition.
|
|
49
|
-
The Style-facing path remains `tilesets/<logical-id>.pmtiles` across generations; physical snapshot
|
|
50
|
-
identity stays out of Style JSON. Standalone Capture retains one generation for the complete render.
|
|
51
|
-
Application Capture delegates PMTiles reads to the application's development server, where each
|
|
52
|
-
range request retains its generation until that response is prepared; it does not lease one
|
|
53
|
-
generation for the complete capture. Strong generation ETags make a cached PMTiles read restart if
|
|
54
|
-
current changes between its ranges. Invalid edits keep the prior valid generation.
|
|
55
|
-
|
|
56
|
-
Production writers reject unresolved local PMTiles instead of copying, content-addressing,
|
|
57
|
-
deduplicating, retaining, or publishing them. Production data therefore requires an explicit
|
|
58
|
-
`tileset publish` operation or an application-owned PMTiles location. Managed publication owns its
|
|
59
|
-
separate exhaustive validation and version lifecycle. Startup removes snapshot generations owned
|
|
60
|
-
by dead processes, and symlinked snapshot-store boundaries are rejected.
|
|
61
|
-
|
|
62
|
-
Every plan also emits canonical `build-manifest.json` (schema version 1). For each map it records
|
|
63
|
-
the leaf `mapVersion`, resolved lineage, effective icon/font source identities, semantic compiler ABI, and
|
|
64
|
-
one entry per concrete theme with `colorScheme`, identity, inferred legacy `dataRequirements`,
|
|
65
|
-
multi-source `sourceRequirements`, and `styleSha256`. The multi-source contract records each
|
|
66
|
-
referenced vector source independently and records raster DEM source ID, encoding, and tile size;
|
|
67
|
-
it therefore distinguishes Bathymetry vector bands from optional relief without inventing vector
|
|
68
|
-
fields. `mapRevisionSha256` identifies the effective cartographic definition after `extends`;
|
|
69
|
-
`assetSetSha256` for that map's generated runtime resources. Data requirements are derived from the
|
|
70
|
-
final Style layers and fields rather than copied from a manually maintained allowlist, so disabled
|
|
71
|
-
or overridden modules do not claim data they no longer use. Maps use the single implicit semantic
|
|
72
|
-
compiler; its ABI remains a separate `semanticCompiler: {name, version}` build-manifest axis.
|
|
73
|
-
|
|
74
|
-
The map revision is versioned and domain-separated. It contains the resolved cartographic design,
|
|
75
|
-
semantic language version, effective public module/render-stack design, and effective icon/font
|
|
76
|
-
identities. Map `id`, `name`, editorial `mapVersion`, default `view`, capture `scenes`, package
|
|
77
|
-
SemVer, compiler ABI, local paths, timestamps, generated sprite/font output, and a
|
|
78
|
-
concrete resolution of a floating World selector are deliberately outside it. Changing a shadowed
|
|
79
|
-
ancestor does not change the revision; changing an effective cartographic override or source asset
|
|
80
|
-
does. Lineage and the leaf `mapVersion` remain beside the hash for traceability, and Style JSON owns
|
|
81
|
-
its compiled-output identity. Hosted browser policy is managed outside repository artifacts. The
|
|
82
|
-
top-level `provenance` block records the exact participating Tileflow package versions and the
|
|
83
|
-
nearest package-manager lockfile's format and content hash, without embedding its local path;
|
|
84
|
-
provenance remains outside every map revision. The same portable block is available to Hosted
|
|
85
|
-
adapters through
|
|
86
|
-
`createTileflowBuildProvenance(cwd)`. The dev server exposes the complete document at
|
|
87
|
-
`/build-manifest.json`.
|
|
88
|
-
|
|
89
|
-
`createTileflowStyle`, `createTileflowStyles`, and artifact construction all enforce the same
|
|
90
|
-
recursive JSON-value invariant and MapLibre style-spec semantics without fetching remote resources.
|
|
91
|
-
The family API returns `styles[map][theme]`; it never flattens a default theme into a map alias.
|
|
92
|
-
Invalid output throws `TileflowStyleValidationError` with at most 32 deterministic
|
|
93
|
-
`map`/`path`/`message` issues; layer indexes are projected to stable layer IDs.
|
|
94
|
-
|
|
95
|
-
For a watched, last-known-good integration, use one artifact session rather than recompiling each
|
|
96
|
-
request independently:
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
import {createTileflowArtifactSession} from '@tileflow/dev/artifacts';
|
|
100
|
-
|
|
101
|
-
const session = await createTileflowArtifactSession({
|
|
102
|
-
config: 'tileflow.config.ts',
|
|
103
|
-
watch: true,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
const unsubscribe = session.subscribe((state) => {
|
|
107
|
-
// building -> ready, or invalid while getLastGoodArtifacts() remains available
|
|
108
|
-
console.log(state.status, state.generation);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
unsubscribe();
|
|
112
|
-
await session.close();
|
|
113
|
-
```
|
|
12
|
+
> Related packages and guides: [documentation index](https://raw.githubusercontent.com/tileflow/tileflow-sdk/main/llms.txt).
|
|
114
13
|
|
|
115
|
-
|
|
116
|
-
work, call `session.acquireArtifacts(generation?)` and release the returned acquisition in `finally`.
|
|
117
|
-
A replacement stops new acquisitions of the old generation and deletes its snapshot after the last
|
|
118
|
-
release. `close()` waits for outstanding acquisitions.
|
|
14
|
+
## Install
|
|
119
15
|
|
|
120
|
-
|
|
121
|
-
effective local icon/font directory inputs. Generations are monotonic, overlapping refreshes are latest-wins, invalid
|
|
122
|
-
edits retain the last good snapshot, and caller-supplied output directories can be ignored to avoid
|
|
123
|
-
feedback loops. `tileflow preview` (`tileflow dev` compatibility alias), `capture --watch`, and
|
|
124
|
-
framework adapters use this shared status
|
|
125
|
-
vocabulary. The built-in preview reloads after its live event stream reconnects following a server
|
|
126
|
-
restart, even when the replacement process begins again at generation 1.
|
|
127
|
-
|
|
128
|
-
Custom preview servers use the exported map or one of its standalone scenes:
|
|
129
|
-
|
|
130
|
-
```ts
|
|
131
|
-
import {createTileflowDevRequestHandler} from '@tileflow/dev/server';
|
|
16
|
+
Use Node.js 22 or newer:
|
|
132
17
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
scene: 'madrid-mobile',
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
// On server shutdown:
|
|
139
|
-
await Promise.all([fetch.close(), session.close()]);
|
|
18
|
+
```sh
|
|
19
|
+
npm install --save-dev @tileflow/dev@alpha @tileflow/core@alpha @tileflow/maps@alpha
|
|
140
20
|
```
|
|
141
21
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
Map preview uses the exported map's `view` and accepts one concrete `theme`; scene preview uses its
|
|
145
|
-
committed concrete theme, camera, and CSS viewport metadata from that same map. A scene does not
|
|
146
|
-
repeat the map ID because its owner is implicit.
|
|
147
|
-
`resolveTileflowPreview()` exposes the validated selection for custom integrations.
|
|
148
|
-
Application-target scenes remain the responsibility of the application's development server. The
|
|
149
|
-
built-in preview records longitude, latitude, zoom, bearing, and pitch in the current URL, so browser
|
|
150
|
-
refreshes and config-triggered reloads return to the same view. Removing those query parameters
|
|
151
|
-
restores the configured camera.
|
|
152
|
-
|
|
153
|
-
## Local comparison workbench and compiler inspection
|
|
22
|
+
Create a trusted `tileflow.config.ts` in the working directory:
|
|
154
23
|
|
|
155
|
-
|
|
156
|
-
`--against-config ...`. Custom local servers can compose the same shell around two existing dev
|
|
157
|
-
handlers:
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
import {createTileflowComparisonRequestHandler} from '@tileflow/dev/server';
|
|
161
|
-
|
|
162
|
-
const fetch = createTileflowComparisonRequestHandler({
|
|
163
|
-
left: {
|
|
164
|
-
basePath: '/left',
|
|
165
|
-
handler: leftHandler,
|
|
166
|
-
label: 'candidate / light',
|
|
167
|
-
previewUrl: '/left/',
|
|
168
|
-
sidecarUrl: '/left/__inspection/candidate/light.json',
|
|
169
|
-
},
|
|
170
|
-
right: {
|
|
171
|
-
basePath: '/right',
|
|
172
|
-
handler: rightHandler,
|
|
173
|
-
label: 'reference / light',
|
|
174
|
-
previewUrl: '/right/',
|
|
175
|
-
sidecarUrl: '/right/__inspection/reference/light.json',
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
Each side handler retains ownership of its styles, fonts, sprites, events, diagnostics, and
|
|
181
|
-
last-known-good artifact generation. The comparison router only owns the root HTML and strict
|
|
182
|
-
dispatch to two disjoint same-origin route prefixes. Its synchronized camera, URL state,
|
|
183
|
-
side-by-side/split/overlay/blink modes, rendered-feature inspector, zoom-curve sampling, sprite
|
|
184
|
-
atlas, and scene/capture-command copying require no hosted service.
|
|
185
|
-
|
|
186
|
-
Set `inspection: true` on `createTileflowBuildArtifacts` or
|
|
187
|
-
`createTileflowArtifactSession` to compute `artifacts.styleInspections[map][theme]`. A dev handler
|
|
188
|
-
serves an available sidecar at `/__inspection/<map>/<theme>.json`; otherwise that route is 404. The
|
|
189
|
-
option is deliberately off by default. Sidecars remain in memory, do not alter Style bytes or
|
|
190
|
-
manifest hashes, and are excluded from `getTileflowArtifactFiles()` and every stable build output.
|
|
191
|
-
|
|
192
|
-
When a resolved map declares `fonts`, preparation reads its ordered directories and uses OpenType
|
|
193
|
-
full names as the canonical IDs referenced by `text-font`. TTF, OTF, and WOFF2 inputs are supported;
|
|
194
|
-
every contributing directory must contain `LICENSE.txt`. Later directories replace earlier faces
|
|
195
|
-
with the same exact ID, case-only collisions fail, and every primary font in the final style must be
|
|
196
|
-
present. `font` is an exact face ID; local `fallbacks` entries are exact face names or explicit CSS
|
|
197
|
-
generic families such as `sans-serif`. Preparation never synthesizes a face name from a family and
|
|
198
|
-
weight. Only selected primary faces and licenses are emitted as
|
|
199
|
-
content-addressed managed artifacts.
|
|
200
|
-
|
|
201
|
-
`fonts` and `glyphs` are mutually exclusive map fields. Omission inherits the parent's current text
|
|
202
|
-
provider; declaring either one replaces an inherited provider of either kind atomically. The font
|
|
203
|
-
pipeline is driven only by directory metadata and final style usage—there are no hard-coded map or
|
|
204
|
-
family names, and a URL provider comes only from the resolved map instead of being invented by the
|
|
205
|
-
pipeline. After inheritance resolves, a map with any text layer must have exactly one provider. A
|
|
206
|
-
`glyphs: {kind: 'url', ...}` provider enumerates the exact comma-joined MapLibre request keys
|
|
207
|
-
produced by the style's `text-font` arrays.
|
|
208
|
-
|
|
209
|
-
The resulting style records strict `tileflow:fontFaces` metadata. Preview, capture, and browser
|
|
210
|
-
framework adapters load those definitions before constructing MapLibre. Hosted deploy rejects a
|
|
211
|
-
prepared local-font bundle before authentication until managed font storage is available. A derived
|
|
212
|
-
map that declares `glyphs` replaces the inherited local provider atomically; the URL-backed map is
|
|
213
|
-
complete as declared and independent of its World selection.
|
|
214
|
-
|
|
215
|
-
For Streets styles whose vegetation layer declares `tileflow:vegetation-mode = 3d`, the built-in
|
|
216
|
-
preview uses the same portable circle fallback as capture and the published framework adapters by
|
|
217
|
-
default. This makes an ordinary `tileflow preview` session WYSIWYG with production and visual
|
|
218
|
-
baselines. Append `?treeRenderer=simple` or `?treeRenderer=complex` to opt into the experimental,
|
|
219
|
-
preview-only instanced renderer described below; that explicit mode is not capture evidence.
|
|
220
|
-
|
|
221
|
-
The experimental renderer uses source height and crown diameter when present, distinguishes palms,
|
|
222
|
-
columnar cypresses, conifers, and three broadleaf silhouettes from the bound botanical fields, and
|
|
223
|
-
falls back to deterministic broadleaf variety and dimensions when tags are missing. Bark color,
|
|
224
|
-
crown palettes, height scale, and crown scale come from `vegetation.threeDimensional`; the preview
|
|
225
|
-
does not maintain a second hard-coded appearance contract. During camera
|
|
226
|
-
gestures the existing 3D batch remains visible while native circles cover newly arriving tiles; the
|
|
227
|
-
batch refreshes when movement ends without waiting for the whole map to become idle. Source-tile
|
|
228
|
-
events are rate-limited and unchanged instance selections do not upload new GPU buffers. The
|
|
229
|
-
preview caps one view at 3,000 trees and uses zoom-dependent density. Both detailed modes use thin,
|
|
230
|
-
tapered trunks, branches that enter the crown, softly shaded overlapping ellipsoids, and stable
|
|
231
|
-
per-tree scale and rotation. `simple` reduces lobe and segment counts; `complex` keeps the complete
|
|
232
|
-
round, open, avenue, pine, cypress, and palm silhouettes. Each archetype remains fused into one
|
|
233
|
-
reusable geometry and every complete tree remains
|
|
234
|
-
one GPU instance, so the additional detail introduces no extra draw calls. The experimental
|
|
235
|
-
renderer defaults to its WebGL2 backend, which
|
|
236
|
-
draws those instances directly without a Three.js scene traversal; append `&treeBackend=three` for
|
|
237
|
-
the compatibility backend. Use `?treeRenderer=circle`, `?treeRenderer=simple`, or
|
|
238
|
-
`?treeRenderer=complex&treeBenchmark=1` to compare identical scenes; the benchmark reports frame
|
|
239
|
-
p95, query, selection, build and render time, draw calls, and triangle count in the tree status badge.
|
|
240
|
-
Visible candidates come from MapLibre's worker-built tile index instead of a second copy of the
|
|
241
|
-
vector source. Terrain height is sampled on a fixed 5-by-5 viewport grid and interpolated per tree,
|
|
242
|
-
avoiding hundreds of synchronous terrain queries when a new batch first appears.
|
|
243
|
-
The separate `TREES ON/OFF` control sits below the building `3D ON/OFF` control and pauses tree
|
|
244
|
-
refresh work while disabled. Both choices survive reloads and shared links through the
|
|
245
|
-
`buildings3d=on|off` and `trees3d=on|off` URL parameters.
|
|
246
|
-
|
|
247
|
-
Detailed trees retain the compiler's scene order: they render above road and pedestrian surfaces,
|
|
248
|
-
ordinary building geometry, and detailed landmark models. Map labels and other annotations remain
|
|
249
|
-
above the physical scene, while road names and route references stay attached to the carriageway
|
|
250
|
-
below buildings and trees.
|
|
251
|
-
|
|
252
|
-
The same control toggles any compiled layer whose metadata contains
|
|
253
|
-
`tileflow:3d-toggle = building`. It switches the building volumes independently from the camera,
|
|
254
|
-
so enabling 3D never changes the pitch and users remain free to tilt the map with pointer controls.
|
|
255
|
-
|
|
256
|
-
### Detailed landmark models
|
|
257
|
-
|
|
258
|
-
A hidden style layer can opt the Streets preview into detailed GLB landmarks by setting
|
|
259
|
-
`tileflow:landmark-manifest-url` in its metadata. The bounded version-2 manifest indexes GLB entries
|
|
260
|
-
inside one or more PMTiles archives and is fetched only when the 3D building control is enabled near
|
|
261
|
-
the landmark runtime zoom. Archive URLs may be absolute or relative to the manifest URL.
|
|
262
|
-
|
|
263
|
-
```json
|
|
264
|
-
{
|
|
265
|
-
"schemaVersion": 2,
|
|
266
|
-
"id": "madrid-landmarks-2026-08-19",
|
|
267
|
-
"minzoom": 16,
|
|
268
|
-
"maximumVisibleModels": 8,
|
|
269
|
-
"maximumCachedModels": 16,
|
|
270
|
-
"archives": [
|
|
271
|
-
{
|
|
272
|
-
"id": "madrid-v1",
|
|
273
|
-
"url": "./madrid-landmarks-v1.pmtiles",
|
|
274
|
-
"bytes": 14832912,
|
|
275
|
-
"sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
|
|
276
|
-
}
|
|
277
|
-
],
|
|
278
|
-
"landmarks": [
|
|
279
|
-
{
|
|
280
|
-
"id": "palacio-cibeles",
|
|
281
|
-
"center": [-3.6921, 40.4193],
|
|
282
|
-
"bounds": [-3.693, 40.4186, -3.6912, 40.42],
|
|
283
|
-
"priority": 100,
|
|
284
|
-
"models": [
|
|
285
|
-
{
|
|
286
|
-
"minzoom": 16,
|
|
287
|
-
"archiveId": "madrid-v1",
|
|
288
|
-
"z": 0,
|
|
289
|
-
"x": 0,
|
|
290
|
-
"y": 0,
|
|
291
|
-
"bytes": 481024,
|
|
292
|
-
"sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
|
293
|
-
"axisConvention": "EUN_Y_UP"
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
"minzoom": 18,
|
|
297
|
-
"archiveId": "madrid-v1",
|
|
298
|
-
"z": 1,
|
|
299
|
-
"x": 0,
|
|
300
|
-
"y": 0,
|
|
301
|
-
"bytes": 1276416,
|
|
302
|
-
"sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
|
303
|
-
"axisConvention": "EUN_Y_UP"
|
|
304
|
-
}
|
|
305
|
-
]
|
|
306
|
-
}
|
|
307
|
-
]
|
|
308
|
-
}
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
`maximumVisibleModels` is bounded to 1–64. `maximumCachedModels` defaults to twice that value,
|
|
312
|
-
must be at least the visible limit, and is bounded to 128. Higher `priority` values win when more
|
|
313
|
-
landmarks intersect the viewport than can be shown. `models[0]` is the base LOD and must start at the
|
|
314
|
-
manifest `minzoom`; later entries replace it at their distinct `minzoom`. The parser bounds manifest,
|
|
315
|
-
archive, landmark, model and GLB-entry sizes, rejects credential-bearing or non-HTTP archive URLs,
|
|
316
|
-
and verifies each extracted entry's declared byte length and SHA-256 before parsing it.
|
|
317
|
-
|
|
318
|
-
The preview starts the Three.js runtime and manifest request together one zoom before the landmark
|
|
319
|
-
layer becomes visible. A landmark entering directly at a close zoom always displays its base LOD
|
|
320
|
-
first and upgrades in place. Nearby base LODs are warmed in the parsed-model cache. Style layers
|
|
321
|
-
marked with `tileflow:landmark-fallback = true` remain visible until the corresponding active GLB is
|
|
322
|
-
ready, so a cold request never leaves an empty building volume.
|
|
323
|
-
|
|
324
|
-
Embedded Streets previews also accept a same-origin parent command without reloading the document:
|
|
325
|
-
|
|
326
|
-
```js
|
|
327
|
-
iframe.contentWindow.postMessage(
|
|
328
|
-
{
|
|
329
|
-
type: 'tileflow:set-map-state',
|
|
330
|
-
schemaVersion: 1,
|
|
331
|
-
state: {
|
|
332
|
-
center: [-3.688344, 40.453053],
|
|
333
|
-
zoom: 17.75,
|
|
334
|
-
bearing: -24,
|
|
335
|
-
pitch: 58,
|
|
336
|
-
buildings3d: true,
|
|
337
|
-
trees3d: false,
|
|
338
|
-
visibleLayerGroups: ['labels', 'roads', 'buildings', 'landuse', 'water'],
|
|
339
|
-
},
|
|
340
|
-
},
|
|
341
|
-
location.origin,
|
|
342
|
-
);
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
The command is applied atomically only for the embedding parent, with exact same-origin and numeric
|
|
346
|
-
range checks. `visibleLayerGroups` is optional and accepts only `labels`, `pois`, `roads`, `transit`,
|
|
347
|
-
`buildings`, `landuse`, and `water`; omitted groups are hidden while base background and attribution
|
|
348
|
-
remain. Camera and toggle state continue to be written to the preview URL after application.
|
|
349
|
-
|
|
350
|
-
GLB base colors, textures, emissive values, metalness, and roughness are authoritative and are not
|
|
351
|
-
rewritten by the preview. Models outside the active selection remain hidden in a least-recently-used
|
|
352
|
-
cache; leaving the cache disposes their geometry, materials, and textures.
|
|
353
|
-
|
|
354
|
-
The vector building represented by a detailed model must carry `hide_3d = true`. The ordinary
|
|
355
|
-
building extrusion and both screen-space shadow passes filter on that field, preventing the coarse
|
|
356
|
-
volume from intersecting the GLB while retaining the flat footprint below it. This flag belongs in
|
|
357
|
-
the vector-tile dataset; the landmark endpoint cannot substitute for it. Version model and GLB URLs
|
|
358
|
-
so they can be served with immutable caching. Cross-origin endpoints must allow credentialed GETs
|
|
359
|
-
because the development renderer requests the manifest and models with credentials.
|
|
360
|
-
|
|
361
|
-
If a map uses `icons: ['./icons/brand']`, build artifacts include the generated MapLibre
|
|
362
|
-
`sprite.json`, `sprite.png`, `sprite@2x.json`, and `sprite@2x.png` beside its style. Ordinary sources
|
|
363
|
-
are normalized into 24 px icon cells. A source named `<id>.pattern.svg` (or another supported raster
|
|
364
|
-
format with the same marker) instead keeps its intrinsic dimensions and is published as `<id>`.
|
|
365
|
-
This provides calibrated, non-square textures for line and fill patterns while the runtime still
|
|
366
|
-
consumes only the generated PNG sprite. Pattern widths must be a power of two from 2 through 512
|
|
367
|
-
pixels, matching MapLibre's seamless line-pattern contract.
|
|
368
|
-
|
|
369
|
-
`icons` is one ordered array. Omitting it inherits the parent's exact array, declaring it replaces
|
|
370
|
-
the array atomically, and `[]` means no icons. Use a spread to preserve imported directories while
|
|
371
|
-
adding or replacing files:
|
|
24
|
+
<!-- docs:check -->
|
|
372
25
|
|
|
373
26
|
```ts
|
|
374
27
|
import {defineMap} from '@tileflow/core';
|
|
375
28
|
import {streets} from '@tileflow/maps';
|
|
376
29
|
|
|
377
|
-
export default defineMap({
|
|
378
|
-
id: 'brand-map',
|
|
379
|
-
version: 1,
|
|
380
|
-
extends: streets,
|
|
381
|
-
icons: [...streets.icons, './icons/brand'],
|
|
382
|
-
});
|
|
30
|
+
export default defineMap({id: 'madrid', version: 1, extends: streets});
|
|
383
31
|
```
|
|
384
32
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
be canonical lower-kebab-case. A later file replaces an earlier file only for the same exact ID;
|
|
388
|
-
case-only collisions fail. Package maps export `streetsIcons`, `baedekerIcons`, `ferrarisIcons`,
|
|
389
|
-
`haradIcons`, `siegfriedIcons`, `soundingsIcons`, `cyberpunkIcons`, `matrixIcons`, and
|
|
390
|
-
`verdantIcons`, and `sanFrancistoIcons` directory descriptors from `@tileflow/maps`. Cyberpunk,
|
|
391
|
-
Matrix, Baedeker, and Siegfried also export `cyberpunkFonts`, `matrixFonts`, `baedekerFonts`, and
|
|
392
|
-
`siegfriedFonts`. Preparation
|
|
393
|
-
resolves package descriptors inside their owning package, checks real-path containment, and
|
|
394
|
-
compiles them through the same pipeline as a config-relative directory. There are no
|
|
395
|
-
built-in/source/sprite selectors, mappings, icon-specific inheritance, or compatibility aliases.
|
|
396
|
-
|
|
397
|
-
Local directories resolve from the directory containing the selected `tileflow.config.ts`, while
|
|
398
|
-
the workspace `cwd` remains their containment boundary. Canonical local syntax starts with `./` or
|
|
399
|
-
one or more leading `../` segments and then uses only non-empty segments other than `.` or `..`;
|
|
400
|
-
trailing or repeated slashes, backslashes, controls, and paths longer than 512 characters fail.
|
|
33
|
+
Loading the config executes its imports. It is not a sandbox for untrusted code. Local preparation
|
|
34
|
+
needs no API key, but serving a map can still cause requests to its remote data or font providers.
|
|
401
35
|
|
|
402
|
-
##
|
|
36
|
+
## Prepare a generation
|
|
403
37
|
|
|
404
|
-
|
|
405
|
-
`tileflow inspect --json`:
|
|
38
|
+
<!-- docs:check -->
|
|
406
39
|
|
|
407
40
|
```ts
|
|
408
|
-
import {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
serializeTileflowCommandDocument,
|
|
412
|
-
} from '@tileflow/dev/validation';
|
|
41
|
+
import {createTileflowBuildArtifacts} from '@tileflow/dev/artifacts';
|
|
42
|
+
|
|
43
|
+
const artifacts = await createTileflowBuildArtifacts({config: 'tileflow.config.ts'});
|
|
413
44
|
|
|
414
45
|
try {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
code: 'INSPECTION_FAILED',
|
|
420
|
-
phase: 'config-inspection',
|
|
421
|
-
});
|
|
422
|
-
process.stderr.write(serializeTileflowCommandDocument(failure));
|
|
46
|
+
console.log(artifacts.manifest);
|
|
47
|
+
console.log(Object.keys(artifacts.styles.madrid));
|
|
48
|
+
} finally {
|
|
49
|
+
await artifacts.dispose?.();
|
|
423
50
|
}
|
|
424
51
|
```
|
|
425
52
|
|
|
426
|
-
The
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
stable `THEME_IMPLICIT_FIXED` diagnostics across modules, render stacks, and terrain. Each
|
|
430
|
-
diagnostic carries a semantic scope and, for module values, its owner, plus machine-readable
|
|
431
|
-
severity and remediation. Colors, fonts, and images are followed through
|
|
432
|
-
expression outputs; numeric diagnostics intentionally cover only direct visual scalars, excluding
|
|
433
|
-
expression operands, zoom stops, tuples, placement, priority, and other structural numbers.
|
|
434
|
-
Agents can therefore edit semantic roles without
|
|
435
|
-
reverse-engineering compiler layers or parsing prose. It never returns `inputFiles`, absolute filesystem paths, credentials,
|
|
436
|
-
URL queries, data URLs, or recognized secret formats. Structured summaries and diagnostics use the
|
|
437
|
-
same required schema-version-1 fields and bounded safe suggestions so consumers do not need to
|
|
438
|
-
parse human prose.
|
|
53
|
+
The artifact plan exposes files and observed inputs from the same generation. Styles are keyed as
|
|
54
|
+
`styles[map][theme]`, never as an implicit default-theme alias. Keep that generation alive for the
|
|
55
|
+
whole operation, then release it. Do not reconstruct its asset list from directory guesses.
|
|
439
56
|
|
|
440
|
-
|
|
57
|
+
To write production output rather than manage it in memory:
|
|
441
58
|
|
|
442
|
-
|
|
59
|
+
<!-- docs:check -->
|
|
443
60
|
|
|
444
61
|
```ts
|
|
445
|
-
import {
|
|
62
|
+
import {writeTileflowBuildArtifacts} from '@tileflow/dev/artifacts';
|
|
446
63
|
|
|
447
|
-
|
|
448
|
-
|
|
64
|
+
await writeTileflowBuildArtifacts({
|
|
65
|
+
config: 'tileflow.config.ts',
|
|
66
|
+
outDir: 'public/tileflow',
|
|
449
67
|
});
|
|
450
68
|
```
|
|
451
69
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
20,000 features, or 24 MB of tile data; `sample: false` returns only authoritative metadata and does
|
|
458
|
-
not accept `includeValues`.
|
|
70
|
+
Serve the complete output. The writer installs immutable generation assets before changing the
|
|
71
|
+
manifest pointer, tracks its own files, and refuses to replace a hosted manifest by default.
|
|
72
|
+
`overwriteHostedManifest: true` is an explicit migration option, not a normal build setting.
|
|
73
|
+
Production rejects unresolved local PMTiles; publish data explicitly or provide an application-owned
|
|
74
|
+
production source. Local development snapshots do not publish or redistribute the original dataset.
|
|
459
75
|
|
|
460
|
-
##
|
|
76
|
+
## Keep a watched request handler
|
|
461
77
|
|
|
462
|
-
|
|
463
|
-
a browser:
|
|
78
|
+
Use one session for repeated work and close it when the owning server stops:
|
|
464
79
|
|
|
465
|
-
|
|
466
|
-
import {inspectTileflowFeatures} from '@tileflow/dev/inspect';
|
|
467
|
-
|
|
468
|
-
const internalCatalog = artifacts.project;
|
|
469
|
-
const inspection = await inspectTileflowFeatures(internalCatalog, 'madrid', {
|
|
470
|
-
center: [-3.6927512, 40.4086555],
|
|
471
|
-
zoom: 16,
|
|
472
|
-
sourceLayers: ['poi'],
|
|
473
|
-
properties: ['name', 'category', 'type', 'icon', 'filter_rank', 'size_rank'],
|
|
474
|
-
width: 512,
|
|
475
|
-
height: 512,
|
|
476
|
-
limit: 100,
|
|
477
|
-
});
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
The inspector supports ordinary HTTP(S) vector sources and TileJSON. It bounds viewport, tile
|
|
481
|
-
count, individual/total bytes, scanned and returned features, property count/string lengths, and
|
|
482
|
-
request time; results are deduplicated and sorted deterministically. Output contains projected
|
|
483
|
-
properties, geometry summaries, safe origin/path metadata, and the configured source revision or
|
|
484
|
-
`null`. It never emits URL queries, credentials, hidden properties, response bodies, or absolute
|
|
485
|
-
paths. PMTiles and authenticated provider adapters are not currently supported by this primitive.
|
|
486
|
-
|
|
487
|
-
Deployment adapters can compile the same sources without writing them to disk:
|
|
80
|
+
<!-- docs:check -->
|
|
488
81
|
|
|
489
82
|
```ts
|
|
490
|
-
import {
|
|
491
|
-
|
|
492
|
-
const internalCatalog = artifacts.project;
|
|
493
|
-
const result = await compileTileflowIconPackages(internalCatalog, {
|
|
494
|
-
cwd: process.cwd(),
|
|
495
|
-
target: 'hosted',
|
|
496
|
-
});
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
Streets declares `[streetsIcons]` and keeps light/dark image-token targets in that one closure;
|
|
500
|
-
Cyberpunk and Matrix independently declare `[cyberpunkIcons]` and `[matrixIcons]`. Baedeker,
|
|
501
|
-
Ferraris, Härad, Siegfried, Soundings, Verdant, and San Francisto declare only `[baedekerIcons]`,
|
|
502
|
-
`[ferrarisIcons]`, `[haradIcons]`, `[siegfriedIcons]`, `[soundingsIcons]`, `[verdantIcons]`, and
|
|
503
|
-
`[sanFrancistoIcons]`, respectively. No official root composes another map's assets even though all
|
|
504
|
-
use the semantic compiler ABI. Baedeker's directory contains eight original travel-atlas patterns;
|
|
505
|
-
its browser-derived Mapterhorn contours use runtime terrain tiles and are not part of the icon
|
|
506
|
-
package. Härad's directory contains nine original Tileflow patterns inspired by Lantmäteriet's CC0
|
|
507
|
-
Häradsekonomiska kartan series from 1859–1934. Soundings owns ten original nautical symbols and
|
|
508
|
-
patterns. San Francisto owns four technical blueprint patterns and one schematic POI node; its
|
|
509
|
-
unbundled Mapterhorn contours remain runtime terrain, and its text uses the canonical Noto Sans
|
|
510
|
-
glyph provider. An application map may inherit a root's exact array, replace it, clear it with `[]`,
|
|
511
|
-
or compose it explicitly with a spread.
|
|
512
|
-
`modules.poi.icons: false` or a disabled POI module suppresses POI icon layers without moving asset
|
|
513
|
-
ownership into dev. POI density is the numeric producer threshold 1–5; it is not an asset-provider
|
|
514
|
-
preset.
|
|
515
|
-
|
|
516
|
-
The hosted target enforces repository containment, portable icon IDs, safe SVG
|
|
517
|
-
references, deterministic ordering, and the public package limits. Package-owned directories are
|
|
518
|
-
trusted only inside their declared owning package and are exempt from config working-tree
|
|
519
|
-
containment. It returns deduplicated generated files plus a canonical manifest/content hash. Upload
|
|
520
|
-
only those generated files: source icons and absolute paths remain local.
|
|
521
|
-
|
|
522
|
-
The version-1 manifest includes `renderedIcons` in the exact sorted `iconNames` order. Each entry
|
|
523
|
-
contains SHA-256 digests for the normalized 1x and 2x RGBA cell. The digest framing includes its
|
|
524
|
-
density and dimensions, zeroes RGB only where alpha is zero, and excludes filenames/source bytes.
|
|
525
|
-
The package content hash still covers the complete canonical manifest, including all four encoded
|
|
526
|
-
file hashes. Consumers can therefore distinguish a visible per-icon change from an atlas-layout or
|
|
527
|
-
encoding-only package change without receiving the original SVG/raster file.
|
|
528
|
-
|
|
529
|
-
`compileTileflowIconPackages` retains the exact RGBA cells used to assemble each atlas, so its
|
|
530
|
-
pixel digests describe delivered pixels rather than a second render. Hosted upload services must
|
|
531
|
-
decode the submitted PNGs and reconstruct those digests independently; client-authored manifest
|
|
532
|
-
analysis is not authoritative.
|
|
533
|
-
|
|
534
|
-
Custom Node tooling can inspect the same local pipeline without walking or decoding source files a
|
|
535
|
-
second time:
|
|
83
|
+
import {createTileflowArtifactSession} from '@tileflow/dev/artifacts';
|
|
84
|
+
import {createTileflowDevRequestHandler} from '@tileflow/dev/server';
|
|
536
85
|
|
|
537
|
-
|
|
538
|
-
|
|
86
|
+
export async function createMapHandler() {
|
|
87
|
+
const session = await createTileflowArtifactSession({
|
|
88
|
+
config: 'tileflow.config.ts',
|
|
89
|
+
watch: true,
|
|
90
|
+
});
|
|
91
|
+
const handler = createTileflowDevRequestHandler({session});
|
|
539
92
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
93
|
+
return {
|
|
94
|
+
fetch: handler,
|
|
95
|
+
async close() {
|
|
96
|
+
await handler.close();
|
|
97
|
+
await session.close();
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
545
101
|
```
|
|
546
102
|
|
|
547
|
-
The
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
103
|
+
The caller mounts `fetch` in its server and calls `close()` on shutdown. This factory does not open
|
|
104
|
+
a listener. Sessions follow config imports and local asset inputs, retain the last good generation
|
|
105
|
+
after invalid edits, and publish structured state changes through `subscribe()`.
|
|
106
|
+
|
|
107
|
+
`getState()` and `getLastGoodArtifacts()` return borrowed synchronous views. Before asynchronous
|
|
108
|
+
work, call `acquireArtifacts(generation?)` and release the acquisition in `finally`. A replaced
|
|
109
|
+
generation accepts no new acquisitions; closing a session waits for existing acquisitions.
|
|
110
|
+
|
|
111
|
+
## Choose an entry point
|
|
112
|
+
|
|
113
|
+
Use `/artifacts` for preparation and output, `/server` for HTTP handling, `/config` for trusted config
|
|
114
|
+
loading, `/icons` and `/fonts` for asset preparation, `/inspect` for bounded inspection, `/tilesets`
|
|
115
|
+
for local PMTiles inspection, and `/validation` for diagnostics. `/preview` is the local preview
|
|
116
|
+
contract, including experimental preview-only rendering; it does not promise hosted support.
|
|
117
|
+
The root retains compatibility re-exports.
|
|
118
|
+
|
|
119
|
+
Invalid compiled styles throw `TileflowStyleValidationError` with bounded issues. Keep diagnostics
|
|
120
|
+
structured; do not return executable config, local paths, credentials, or complete generated byte
|
|
121
|
+
arrays from a public endpoint.
|
|
122
|
+
|
|
123
|
+
## Locked Icon Set composition foundations
|
|
124
|
+
|
|
125
|
+
`composeTileflowIconSources(sources, {cwd, lock, cacheRoot, offline: true})` combines local/package
|
|
126
|
+
directories and `iconSet('@team/set')` contributors into one effective sprite. Without an explicit
|
|
127
|
+
lock object, it reads `tileflow.icons.lock.json` beside `baseDirectory` (default: `cwd`). It never
|
|
128
|
+
resolves latest or writes the lock. The result contains the generated `package`, ordered
|
|
129
|
+
`composition` receipt, effective `sourceIdentities`, replacement ordinals, and local watch paths.
|
|
130
|
+
Pass both the source identities and receipt to Core map-revision hashing.
|
|
131
|
+
|
|
132
|
+
The reader verifies each file length/checksum, PNG/index geometry, names, density pairing, and both
|
|
133
|
+
per-icon pixel hashes. It copies decoded 1x and 2x cells independently, without resampling either.
|
|
134
|
+
The existing directory compiler and this port share the same sprite packer. A sole shared set reuses
|
|
135
|
+
its exact artifact; mixed inputs are packed deterministically. Later contributors win and all
|
|
136
|
+
dependencies remain in the receipt, even when entirely shadowed. Shared inputs never pretend to
|
|
137
|
+
contain original SVG/source identities. Existing local-only compilation remains unchanged.
|
|
138
|
+
|
|
139
|
+
`storeTileflowIconSetArtifact` seeds an exact verified cache entry. `loadTileflowIconSetArtifact`
|
|
140
|
+
uses the cache or hydrates only the selected artifact's four public files. Hydration defaults to the
|
|
141
|
+
trusted `https://api.tileflow.dev` origin, sends no credentials, and follows no redirects.
|
|
142
|
+
Applications may explicitly configure trusted `deliveryOrigins`; locks cannot add them. `offline`
|
|
143
|
+
forbids all fetches and fails on missing or corrupt entries. Complete-map offline rendering still
|
|
144
|
+
depends on unrelated tiles, glyphs, and other external resources.
|
|
145
|
+
|
|
146
|
+
The cache lives under the OS user cache with `tileflow/icons/v1/<contentHash>`. `cacheRoot` or
|
|
147
|
+
`TILEFLOW_ICON_CACHE_DIR` overrides its parent. Atomic directory installation and exact-entry
|
|
148
|
+
retirement support concurrent writers and corruption repair. Symlink entries are rejected. This is
|
|
149
|
+
a disposable cache, not a security sandbox against another process controlling the same user
|
|
150
|
+
account. Incomplete temporary entries are never accepted as published cache entries.
|
|
151
|
+
|
|
152
|
+
`writeTileflowIconsLockfile(directory, lock, expectedContents)` uses a whole-file compare-and-swap
|
|
153
|
+
and atomic rename. A conflicting writer fails without overwriting pins. An interrupted writer leaves
|
|
154
|
+
the previous complete lock intact; remove a stale `.writing` guard only after confirming that its
|
|
155
|
+
writer has exited. Ordinary reads and Core validation never acquire write guards.
|
|
156
|
+
|
|
157
|
+
The existing 256-effective-icon, 2048-atlas-dimension, 4 MiB/file, and 8 MiB/package bounds still
|
|
158
|
+
apply. Several individually valid sets can exceed the effective map limit; composition fails
|
|
159
|
+
explicitly instead of clipping. Cache and composition do not create Team resources or publish
|
|
160
|
+
anything. Normal CLI/build/framework and Hosted command integration follows separately; those
|
|
161
|
+
existing entry points currently reject shared descriptors rather than silently dropping them.
|
|
162
|
+
|
|
163
|
+
## Detailed guides
|
|
164
|
+
|
|
165
|
+
These references are also included under `docs/` in the installed package:
|
|
166
|
+
|
|
167
|
+
- [Artifact lifecycle](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/artifact-lifecycle.md): generations, immutable snapshots, provenance, and cleanup.
|
|
168
|
+
- [Local comparison and inspection](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/local-comparison-workbench-and-compiler-inspection.md): workbench controls and preview-only models.
|
|
169
|
+
- [Config diagnostics](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/structured-config-inspection-and-diagnostics.md): bounded, machine-readable inspection.
|
|
170
|
+
- [PMTiles inspection](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/bounded-pmtiles-resource-inspection.md): metadata, sampling limits, and requested properties.
|
|
171
|
+
- [Feature and icon inspection](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/bounded-vector-feature-inspection.md): supported sources, icon compilation, and delivered-pixel hashes.
|
|
172
|
+
|
|
173
|
+
For an installed release, prefer its declarations and packaged guides over newer source on `main`.
|