ADR 0014 — The maplibre-gl peer range is bounded, declared once, and checked by CI

Status: accepted · Amends: — · Amended by: —

Extends ADR 0008, which decided that MapLibre is the engine but said nothing about which versions of it.

Context

@blaeu/core does not depend on maplibre-gl; it peer-depends on it, because the host owns the map engine and two copies of it in one page is a bug nobody enjoys diagnosing. A peer range is therefore a promise made to the host: install anything inside this range and the renderer works.

We broke that promise once, and the shape of the failure is the whole reason this ADR exists. The manifest said >=4.7.0 <6. MapLibreRenderer cleaned up its listeners through maplibre 5's Subscription.unsubscribe() — the return value of map.on(...). On maplibre 4, on() returns the map, so destroy() threw; the throw escaped whenLoaded's load handler before it could resolve; and mount() therefore hung forever on every v4 host. Not a rendering artefact, not a degraded mode — the map never appeared.

The suite stayed green throughout, because the fake maplibre map the renderer's own tests use returned a v5-shaped subscription. Every test asserted the code did what we meant it to do against a double built from the same misunderstanding.

Two things were wrong, and only one of them was the listener teardown. The other was that the range lived in four places — the manifest, the scaffold that mints new packages, the loader's "install a compatible version" error message, and the teardown note in the renderer explaining which majors it reasons about — and nothing checked that the four agreed.

Decision

The peer range is bounded, it is declared in one place, and both halves of that are mechanically enforced.

// packages/core/package.json
"peerDependencies": { "maplibre-gl": ">=4.7.0 <7" }

The test is the cheap half and the matrix is the honest half. The test proves the four declarations agree; only the matrix proves the code compiles against what they declare.

Alternatives rejected

An open range, >=4.7.0. Maximally permissive, and it never needs touching. Rejected because it is a claim about majors that have not been written — including the one that will remove the API we depend on. A peer range that cannot be false is not a promise, it is a disclaimer, and the host finds out at runtime.

Pin a single major, ^6. Honest, checkable, and it would have made the v4 incident impossible by construction. Rejected because MapLibre 4 and 5 are still widely deployed and a host does not get to choose its map engine's version independently of the rest of its application. Supporting three majors is the cost of being a library rather than an application.

Drop v4 and set the floor at 5. Tempting immediately after the incident, since v4 is what broke. Rejected because it mistakes the symptom for the cause: the defect was a version-specific API used without a version-specific guard, and it would have happened at the v5/v6 boundary just as readily. The fix is the guard and the matrix, not a narrower range.

Check the four declarations by review. That is what we were doing. It survives exactly as long as the person who bumps the manifest remembers all four places, and the last person did not.

Consequences

Edit this page on GitHub — this site is generated from docs/adr/0014-maplibre-peer-range-policy.md, which is the source of truth.