@chrtco/sdk 0.9.2 → 0.10.1

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/CHANGELOG.md CHANGED
@@ -3,11 +3,136 @@
3
3
  All notable changes to `@chrtco/sdk` will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
+
7
+ ### Added
8
+ - **Served styles now render in MapLibre Native as shipped.** Every generated
9
+ style begins with a `chrt-background` layer (S-52 NODTA backdrop per
10
+ palette) — previously native renderers drew a black void and
11
+ `didFinishLoading` never fired, because unlike a web app compositing over a
12
+ basemap, native draws exactly and only what the style declares.
13
+ - **`sprite` is declared in every served s52/chrt/raster style** —
14
+ `{tileBaseUrl}/v1/sprites/{family}-{theme}`, a standard GL sprite pair
15
+ (public, unauthenticated) generated from the same atlas registries the
16
+ style generator reads. GL JS behaviour is unchanged (runtime-registered
17
+ images win); native renderers get buoys, beacons, wrecks and patterns with
18
+ no client work. s101 styles deliberately declare **no** sprite until the
19
+ vendored S-100 artwork clears licence review.
20
+ - **Offline styles carry everything a chart needs**: `sprite` and `glyphs`
21
+ now point at `{protocol}://assets/…` inside the chart-assets pack served by
22
+ `GET /v1/offline/assets` (sprite sheets + Open Sans glyph PBFs). An offline
23
+ chart previously rendered mute geometry — no labels, no soundings figures,
24
+ no symbols.
25
+ - Style `metadata` carries `chrt:style-rev` beside `chrt:style-id` when a
26
+ custom style is bound — provenance that survives every cache and proxy.
27
+
28
+ ### Known limits
29
+ - MapLibre Native iOS silently collapses its entire render pass above ~482
30
+ symbol layers (measured externally; exact threshold). Served styles
31
+ currently ship 575 symbol layers, pinned by a ratchet test; reduction below
32
+ the cliff is in progress. Native consumers should avoid stacking many
33
+ additional symbol layers on top of the served style.
34
+
35
+ - **SOUNDG depth-number labels use a single-font stack (`["Open Sans
36
+ Semibold"]`), and every text-font in every served style is now pinned to
37
+ one vendored font.** The previous semibold stack carried a Regular
38
+ "fallback" — but MapLibre joins a multi-font stack into ONE glyph URL
39
+ (`Open Sans Semibold,Open Sans Regular/0-255.pbf`), which no static glyph
40
+ host serves. fonts.openmaptiles.org answers it with an HTML error page as
41
+ HTTP 200: GL JS swallowed the garbage and simply never drew those glyphs,
42
+ MapLibre **Native fatally parses it as protobuf** — the sounding depth
43
+ numbers were the casualty, on the one surface where they matter most. The
44
+ fallback therefore broke the primary; it is gone, and a served-style test
45
+ now rejects any multi-font stack. Caught by the new MapLibre Native
46
+ golden-image harness (`scripts/native-render/`) on its first run.
47
+ - **The offline chart-assets pack now vendors Open Sans Semibold** (0-255 /
48
+ 256-511, OFL, same origin as Regular/Italic). Offline charts previously
49
+ rendered SOUNDG depth numbers and all 60 nav-aid labels (beacons, fog
50
+ signals…) textless — the zip only carried fonts the style half-used.
51
+ - **`applyChrtSymbolSet` is passed the active palette at both serving
52
+ routes** (offline styles, and online custom-style composition). Omitting
53
+ it silently resolved the house cable/pipeline dash colours against DAY —
54
+ NIGHT/DUSK offline charts shipped day-bright dashes. Caught by the
55
+ harness's online≡offline twin gate.
56
+
57
+ - **`/v1/cells` now sends flat `bbox_north` / `bbox_south` / `bbox_east` / `bbox_west`, matching `CellDetail`.** This is a **wire change to `/v1/cells` and `/v1/cells/{id}`**, not just a type change — read it before upgrading a REST client.
58
+ - The bug: the SDK declared flat `bbox_*`, the endpoint returned a nested `bbox` object. `cell.bbox_north` typechecked and was `undefined` on **every** response — TypeScript vouching for a field that never arrived, which is worse than no type at all.
59
+ - **The endpoint was the outlier, so the endpoint moved.** `enc_cells`' own columns, `/v1/coverage/route`'s `CoverageRouteCell`, and every published `CellDetail` are flat; `formatCell()` alone nested. One shape across the API is the point.
60
+ - **REST clients reading `cell.bbox.north` must move to `cell.bbox_north`.** SDK callers need no change: this is the shape `@chrtco/sdk` has always declared, so **0.9.2 callers are fixed the moment the endpoint deploys** — the phantom field starts carrying real values without a release.
61
+ - Bounds are now passed through **independently** rather than gated on `bbox_west != null`, which would have discarded three real bounds had that one column ever been null alone.
62
+ - The shape is now pinned from both sides: `serve-cells/format-cell.test.ts` (Deno) asserts what the server emits, `endpoints/cells.test.ts` (vitest) asserts what the SDK expects. Neither existed, which is why the two drifted for months. They cannot share a fixture across runtimes — **change one, change the other.**
63
+ - New: **`cell.last_checked_at`** (ISO 8601 | null) — when CHRT last checked the issuing office's catalogue for the cell. It is the freshness of the *check*, not of the chart: `edition` / `update_number` / `issue_date` remain the data-currency fields.
64
+ - **CHRToffline is Pro-and-above, and `chrt.offline.*` now says so.** New typed error code `offline_requires_pro_plan` (403 from `POST /v1/mint/offline-token`). This refusal previously fell through to the generic 403 mapping and surfaced as **`coverage_denied`** — reporting a plan gate as a coverage problem, and pointing the user at the one thing that could not fix it. The per-key `offline:download` scope (`offline_not_enabled`) is a **separate, narrower gate that is checked first**: a key without the scope is refused before the plan is ever read.
65
+ - **`chrt.cells.list()` gains the `scaleBand` and `bbox` filters** that `/v1/cells` has always supported and the SDK could not reach:
66
+ - `scaleBand?: ScaleBand` — new exported union `'Overview' | 'General' | 'Coastal' | 'Approach' | 'Harbor'`, matched **case-sensitively** (`'harbor'` matches nothing). These are the S-57 usage bands CHRT derives from compilation scale; they are **words, not band numbers** — `scale_band: '4'` matches no cell.
67
+ - `bbox?: [west, south, east, north]` — cells whose bounds *intersect* the box (same tuple encoding as `chrt.features()`).
68
+ - **`chrt.query()` returns the new `QueryResponse`** instead of a bare `FeatureCollection` — a widening, so no caller breaks:
69
+ - `res.query` (`{ point, radius_m }`) is now declared. The server echoes back what it **actually applied after clamping**, which is the only way to learn that `radius: 90000` was cut to `50000`. It was previously invisible to typed callers.
70
+ - `res.features[].properties` is typed `QueryFeatureProperties` (`layer`, `cell_id`, `distance_m`, plus an open index signature) — `distance_m` no longer needs a cast, which is the whole point of a proximity search.
71
+ - Documented that `/v1/query` is **not paginated**: it returns no `offset`. Widen `radius` instead of paging.
72
+ - **Corrected docs that sent callers to empty results** (no code change, but they were wrong in the same way the types were):
73
+ - `source` takes the chart-source **id** (`'NOAA_ENC'`), not the agency name (`'NOAA'`). Passing the agency **returns an empty list rather than an error** — a silent zero. Affects `CellsOptions`, `CoverageOptions`, `CoverageSource.source`, and the README quick-start, which shipped the broken `{ source: 'NOAA' }`.
74
+ - `chrt.query()`'s `limit` default (50) and cap (500) are now documented on `QueryOptions`, alongside the `radius` default corrected in the README rewrite below.
75
+ - New exported types: `ScaleBand`, `QueryResponse`, `QueryFeatureProperties`.
76
+ - **`vessel` on `ChrtConfig` — one safety depth, read by every depth-aware call (ADR 0020 F2).** CHRT had grown four disagreeing notions of "how deep is safe"; this is the single place a client states it once.
77
+ ```ts
78
+ const chrt = new Chrt({ apiKey, vessel: { safetyDepthM: 3 } });
79
+ await chrt.ahead({ point, bearing }); // filters at 3 m
80
+ chrt.style(); // labels + danger rings at 3 m
81
+ ```
82
+ - Defaults `ahead({ safetyDepth })`, `alongRoute({ safetyDepth })` and `style({ safetyContour })`. An explicit per-call value always wins. One `safetyDepthM` drives **30 sites** in the generated style (SOUNDG two-shade + all four danger-ring classes) at a single value.
83
+ - **Client-side and per-instance, deliberately — never bound to your API key.** A consumer app has one key and many skippers with different boats; a key-scoped draft would give them all the same number, which is worse than none — it's a confidently wrong one. Construct a `Chrt` per user, or pass `safetyDepth` per call.
84
+ - **With no profile, nothing is sent.** CHRT never guesses a draft: filtering on an invented one silently drops real hazards.
85
+ - **Known gap:** `styleUrl()` (the *served* style — the only style path for non-JS clients) still takes `theme` + `symbols` only, so a **native app cannot yet give each user their own depth shading**. And `depareFillExpression` bins depth into fixed 0/2/5/10 tiers, so the profile does not reshade depth *areas* — only soundings and danger rings. Both are tracked in ADR 0020.
86
+ - New type: `VesselProfile`.
87
+ - **`chrt.alongRoute()` + `chrt.coverage.forRoute()` — the along-route pair (CHRTquery; ADR 0020 D1).** Completes the family the staged `routeQuery` / `routeFeatures` stubs never shipped, and **splits them by the question they answer**:
88
+ - **`chrt.alongRoute({ route, buffer | bufferNm, safetyDepth, layer, limit, offset })`** → `GET /v1/query/route`. What's charted in a corridor either side of a passage — the planning sibling of {@link ahead}. Each feature carries **`along_m`** (metres along the route to closest approach) alongside `distance_m`, `least_depth_m` and `depth_clearance_m`, and results are **ordered by `along_m`** — the order you actually meet them, not lateral offset. `chrt.alongRouteIter()` auto-paginates.
89
+ - **`chrt.coverage.forRoute({ route, buffer | bufferNm, scaleBand, limit })`** → `GET /v1/coverage/route`. Which ENC cells a passage needs, largest-scale first (S-57 says believe the finer chart), each with `edition` / `update_number` / `issue_date` so you can tell whether the copy you hold is stale. **It lives on `coverage`, not `query`, deliberately** — it returns *cells*, so it answers "which charts must I hold?", a download-planning question and CHRToffline's companion. Filing it with the hazard queries is what made the family look like two overlapping things.
90
+ - **`alongRoute` does not replace `ahead`, and `ahead` does not replace it.** A boat with a route still drifts, and COG rarely equals the route bearing — a passage app wants both: this once per route edit, `ahead` continuously against the real track.
91
+ - Same invariants as `ahead`: **facts that sort, never a verdict** (no severity, no `safe` flag), `safetyDepth` omits only what is *known* to be deeper, and **an empty result is not a clear passage** — every response carries `coverage` plus `attribution` / `disclaimer` / `notice`.
92
+ - Both validate locally before the round trip (≥2 waypoints; `buffer` and `bufferNm` are mutually exclusive).
93
+ - New types: `AlongRouteOptions`, `AlongRouteResponse`, `AlongRouteFeatureProperties`, `CoverageRouteOptions`, `CoverageRouteResponse`, `CoverageRouteCell`. **Removed** the never-shipped staged types `RouteQueryOptions`, `RouteQueryResponse`, `RouteFeatureQueryOptions`, `RouteFeatureQueryResponse` — they were exported but unreachable (their methods were commented out), so nothing could have depended on them.
94
+ - **`chrt.ahead()` — the fan-ahead query (CHRTquery; ADR 0020).** *"What's charted ahead of me?"* — the significant features in a sector from a point along a bearing. Built for the position-plus-COG case: a craft that has a heading but not a planned route.
95
+ - `chrt.ahead({ point, bearing, arc, range | rangeNm, safetyDepth, layer, limit })` → `GET /v1/query/ahead`. `bearing` is degrees true (a COG/heading or a fixed input; out-of-range values normalise, so a heading that wraps past north is fine). `arc` is the **full** sector width centred on the bearing — `arc: 60` sweeps ±30°. `arc: 360` degenerates to a full circle, making `ahead` a strict generalisation of `query()`.
96
+ - **Facts that sort, never a verdict.** Each feature carries `distance_m`, `bearing_deg`, `least_depth_m` and `depth_clearance_m` (negative = shallower than your declared `safetyDepth`). There is **no severity score and no `safe` flag, deliberately**: a risk model is a navigation-grade claim, and CHRT is situational-awareness only. You sort; CHRT ranks nothing.
97
+ - **`safetyDepth` filters out only what is *known* to be deeper.** Features with no charted depth are always kept — a wreck with no sounded value is still a wreck. Fail-safe by construction.
98
+ - **An empty result is not an all-clear**, so every response carries a `coverage` block (`covered`, `covered_fraction`, and `cells` with real `edition` / `update_number` / `issue_date` per source cell) alongside inline `attribution`, `disclaimer` and `notice`. Zero features in covered water and zero features because CHRT has no cells there are different answers; `coverage` is what tells them apart. **Check `coverage.covered` before telling a user anything.**
99
+ - **No `speed` parameter, by design** — deriving range from SOG means deciding how many minutes of lookahead a vessel needs, which is a navigational judgment that belongs to the client. Recipe: `rangeNm = SOG_knots × lookahead_hours`, narrowing `arc` as speed rises; re-fan on a meaningful course change rather than a timer.
100
+ - Defaults to CHRT's significant-feature set (S-52 viewing group 24020 — wrecks, obstructions, rocks, plus the depth-carrying classes). Pass `layer` for a different set, or use `query()` for an unfiltered point-radius search.
101
+ - New types: `AheadOptions`, `AheadResponse`, `AheadFeatureProperties`, `AheadCoverageCell`.
102
+ - **README rewritten against the real surface — three artifacts in the published tarball disagreed.**
103
+ The API Reference had not been updated past 0.8.0, so `README.md`, `CHANGELOG.md` and
104
+ `dist/index.d.ts` shipped inside `@chrtco/sdk@0.9.2` contradicting each other:
105
+ - **`styleUrl()` is no longer documented as deprecated.** The README still said *"CHRT serves no
106
+ remote `style.json` — use `chrt.style()`"*, three minor versions after the served Style API
107
+ shipped (`GET tiles.chrt.co/v1/styles/{region}/style.json`, live and auth-gated in production).
108
+ This was the severity-1 case: `chrt.style()` is a **JavaScript function**, so native iOS/Android,
109
+ Swift, Kotlin and Python clients cannot call it — the served style URL is the only style path
110
+ they have, and the README told them it did not exist.
111
+ - **Every method is now marked JS-only or HTTP-backed**, with the HTTP method and path for each, so
112
+ non-JS clients can see what they can actually reach. New section: *Using CHRT without
113
+ JavaScript* — the two-call mint → served-style recipe in `curl`, documenting the **wire** shape
114
+ (`expires_at`, `build_tag` — snake_case; the SDK's `TileTokenResponse` camelCases them).
115
+ - **`interact`, `fetchStyle()`, `coverage.list()` and `ahead()` are documented for the first time.**
116
+ `interact`, `fetchStyle` and `coverage` were all present in `0.9.2`'s `.d.ts` but absent from its
117
+ README — shipped and invisible.
118
+ - **`tiles.groups()` stays deprecated** — unlike `styleUrl()` it is genuinely gone (`/v1/tiles/*`
119
+ is absent from the gateway route table; it fails with `ChrtError` 404). The old heading's error
120
+ was bundling a live method and a dead one under one "deprecated" label.
121
+ - **Quick Start no longer hard-codes a secret**, and no longer uses `chrt_live_…` — a key format
122
+ CHRT has never generated. Real keys are `chrt_{pk|sk}_{live|test}_{team}_{random}`. New *API
123
+ keys* section documents the real `pk`/`sk` distinction: they differ in whether the plaintext can
124
+ be re-revealed, **not in privilege**. `pk` is not a capability boundary, so the README says to
125
+ treat any key embedded in a client as public rather than implying the prefix protects it.
126
+ - **The non-SOLAS disclaimer is now in the README.** The SDK enforces the notice on every
127
+ `InspectResult`, but the most-read CHRT artifact — the npm landing page — carried no disclaimer
128
+ at all.
129
+ - `QueryOptions.radius` JSDoc corrected: the server default is **500 m**, not 1000. `ChrtConfig.apiKey`
130
+ JSDoc corrected to the real key format. (JSDoc only — no behaviour change; both ship in `.d.ts`.)
6
131
  - **Symbol families (`symbolSet`)** — `chrt.style({ symbolSet })`, `styleUrl({ symbols })` and `fetchStyle({ symbols })` select the symbol artwork:
7
132
  - **`s52`** (default) — now **spec-exact for ATONs and LIGHTS**: buoy/beacon icon selection is compiled from the S-52 Presentation Library lookup tables (COLOUR-list keyed — preferred-channel junction marks render correctly; no IALA region hardcoding), and light flares follow the LIGHTS05/06 conditional-symbology procedure (LIGHTS11/12/13 colour-specific flares, LIGHTS81/82 strip/floodlight, LITDEF11 default).
8
133
  - **`s101`** — the official IHO S-101 Portrayal Catalogue ATON/LIGHTS artwork (vendored at a pinned commit, themed through the official S-101 day/dusk/night profiles). Load images via `loadS101SymbolImages` (`@chrtco/sdk/atlas/browser`).
9
134
  - **`chrt`** — the CHRT house set: simplified colour-true silhouettes (tinted at runtime from the feature's COLOUR — green marks render green, red marks red), fixed-palette identity marks (cardinals, isolated danger, safe water), and capability badges (sound for fog signals, radar for racons). Load via `loadChrtSymbolImages`.
10
- - Served Style API: `GET /v1/styles/{region}/style.json?symbols=s52|s101|chrt` (own cache-key slot, `X-Style-Symbols` response header); composes with Dev+ custom-style claims.
135
+ - Served Style API: `GET /v1/styles/{region}/style.json?symbols=s52|s101|chrt|raster` (own cache-key slot, `X-Style-Symbols` response header). All four families are **free on every plan** — the style route reads no `plan_id`, and the served document is byte-identical across tiers. Composes with a bound **custom style**, which is the one part of CHRTstyle that is paid (Dev and above).
11
136
  - **Behaviour change (S-52 correctness):** LIGHTS icons are no longer SDF-tinted by COLOUR; the procedure-selected flare carries its own colour. Blue/orange lights now render the spec's LITDEF11 default flare (use the `chrt` family for exact-colour flares).
12
137
 
13
138
  ## [0.9.1] — 2026-07-14