Style spec parity
MapLibre GL JS and MapLibre Native implement the style spec at different paces.
Properties on shared layer types stay in the common API, with unsupported writes
filtered by StyleBinding. A whole layer type that only one engine accepts has
its composable in that engine's source set.
mise run style-spec:parity reads v8.json at the pinned
maplibre-style-spec release
and compares it with every Main source set that writes a layer or source.
mise run style-spec:parity
mise run style-spec:parity -- --check
mise run style-spec:parity -- --spec /path/to/v8.json
--check fails when a layer type, source type, or paint or layout property that
the pinned engines implement is missing on an engine that implements it, written
with the other kind, or wrapped in a setter that nothing calls, or when the
native unsupported table disagrees with pinned support.
A sdk-support version counts only when it is at most the pin. maplibre-js in
gradle/libs.versions.toml is the GL JS pin, and maplibre-styleSpec pins the
spec release the catalog reads: the one the pinned maplibre-gl release depends
on, so bump both together. The FFI pin is a date stamp, so a recorded Android or
iOS release counts as native support, and the pinned spec release keeps that
from running ahead of the shipped engines. Properties that exist only in a newer
GL JS than the pin stay out of scope until that pin moves.
GL JS parses every property in the pinned spec, because that release is the one
it bundles. A property only native renders is therefore still written in
commonMain; the catalog lists it as stored but not rendered on js.
Types the spec does not list, spec types this API does not construct, and spec
properties this API writes under another name belong in the extra, omitted, and
alias sets in ci/style_spec_parity.py. Read those sets and the catalog for the
current list.
Read the catalog
Each spec property's sdk-support.basic functionality field is a version string
on engines that implement it, and an issue URL on engines that do not.
- js and native both implement it at the pins. Write the property in
commonMainwith the matchingsetPaintPropertyorsetLayoutProperty. - js implements it, native does not. Write it in
commonMainand list it on the native binding so a write cannot refuse the whole layer. - Native renders a property that JS only stores. Keep the property in
commonMainand document the rendering difference. A whole layer type that JS cannot accept follows the separate layer-type guidance below.
Style-root objects
The light, sky, and projection objects at the style root are typed classes
in lib/maplibre-compose/src/commonMain/kotlin/org/maplibre/compose/style/, one
file per object, each writing its properties with putExpression. The catalog
checks every spec property of those objects against the writes in that file. An
engine that lacks a whole object reports it through the supportsSky-style flag
on StyleBinding rather than the native table. terrain is in the omitted set
until the API exposes it.
Add a property both engines implement
- Add the composable parameter and a setter that calls
setLayoutPropertyorsetPaintPropertywith the spec name. Follow the surrounding layer. A new enum belongs next to the others inlib/maplibre-compose/src/commonMain/kotlin/org/maplibre/compose/expressions/value/. - Default to the spec default when writing it on native is safe. Use
nil()when the property is optional and an unset value should stay absent. - Add a
liveMapTestround-trip case inLayerPropertyRoundTripTest. Native and GL JS sometimes report the same value in different JSON shapes; theCasehelper takes a GL JS form for that.
Add a property native lacks
Keep the setter in commonMain. The binding decides what reaches the engine.
- Default the composable parameter to
nil(). A spec default that is always written would log an unsupported warning on every layer of that type. - Note on the parameter that it is not yet supported on native, with the issue
link from
sdk-support. - Add a row to
MlnFfiStyleBinding.UNSUPPORTED_LAYER_PROPERTIES. The reason string is what the layer logs once. - Add the round-trip case to the
glJsOnly*list inLayerPropertyRoundTripTest, so desktop does not assert a write native will skip.
UnsupportedLayerPropertyTest covers the drop-versus-refuse mechanism. Do not
add a case per table row.
A value one engine rejects, on a property it otherwise implements, stays out of
that table. skipUnsupportedProperty or the live StyleMutationException
handler covers a rejected value.
When a later native release implements the property, delete the table row and
move the test from the glJsOnly* list into the shared cases.
Add a layer type one engine lacks
A whole type has no binding filter like a property does. Put the public composable in the source set that has the engine:
- Native only:
maplibreNativeMain, asLocationIndicatorLayerdoes. The internalLayerclass can stay incommonMainso style reconstruction and native tests share it. - GL JS only:
jsMain.
The other platform's demo or helper uses expect/actual when it needs a
stand-in, the way NativeLocationIndicator falls back to nothing on the
browser.
Verify
mise run style-spec:parity -- --check
mise run test:desktop
mise run test:js
mise run check
Run mise run ci:test-scripts when changing the catalog checker. For catalog or
documentation-only changes, select the relevant checks without running map
suites whose behavior is unchanged. Browser setup and test constraints are in
AGENTS.md.