Roadmap

What is next, in the order we intend to do it, with the reason for the order. Items near the top are ones the architecture is already shaped for; items near the bottom are ones that will need an ADR before a line is written.

Nothing here is a promise of a date. It is a statement of intent and of sequence, which is the more useful thing to publish.

Before anything else: close the edges we already know about

Run the browser suite on a GPU runner. The node suite runs against FakeRenderer, which proves the renderer seam is real; npm run test:browser (vitest.browser.config.ts) now runs MapLibreRenderer against real MapLibre and a real WebGL2 context, and covers the two places the seam could leak — our translation of LayerStyle into MapLibre paint/layout, judged by MapLibre's own validator, and our normalisation of its pointer and touch events. What it cannot cover on the runners we have is hit testing: queryRenderedFeatures needs a completed render pass, and neither ubuntu-latest nor headless SwiftShader ever delivers one, so four tests are gated on a probe and skip with a warning. That leaves the pointer-to-feature path — including the leading-zero-id case — verified by reading rather than by running. A GPU runner closes it, and nothing else will.

Raise the mutation ratchet. The first full Stryker run scored 65.5% killed against 88.7% line coverage, and the distribution is the finding rather than the average: SpatialIndex.ts at 34.6% and FeatureStore.ts at 54.5% are the two files a wrong answer in is least visible — an R-tree that returns the wrong candidates degrades silently into a map that "feels laggy". The break threshold in stryker.config.json sits at the measured floor on purpose, so the number can only go up; strengthening the store's assertions and raising it is the cheapest remaining way to find defects the suite is currently green through.

(Both of the edges this section used to list have shipped: a commit path that owns its own pipeline run is commands.commit() — see ADR 0009 — and browser-mode tests for MapLibreRenderer are npm run test:browser, gated in CI on every pull request and nightly.)

v2

3D and terrain — a Three.js renderer through the existing seam

Renderer is already the seam, Camera already carries pitch, and FakeRenderer already proves a second implementation is possible. A Three.js renderer is therefore a new package rather than a fork — which is the payoff of ADR 0008 being cashed.

The honest scope: terrain and extruded geometry (a zoning plan with height caps is a 3D object and planners already think of it that way) come first; true 3D editing — dragging a vertex in three dimensions with a snap engine that understands planes — is a much larger problem and is not in this bullet.

Real-time collaboration — the command bus is already the CRDT seam

This is the item that most looks like luck and is not.

A collaboration layer needs a stream of operations that are (a) typed, (b) reversible, (c) serialisable, and (d) semantically meaningful rather than positional diffs. Command is all four, and it is all four because undo needed it to be — a command that can restore deep equality has, by construction, captured enough to be replayed on another machine, and a command bus that already broadcasts every mutation to a subscriber (history) can broadcast it to a transport instead.

So collaboration is not a new mechanism. It is a second subscriber to onDidExecute, plus a history plugin that respects remote authorship (you may not undo my edit), plus a conflict policy. The kernel does not change. That is what good seams buy you, and it is why ADR 0002 argued for history-as-subscriber rather than history-in-the-core.

The genuinely hard parts, which no amount of good design removes: concurrent edits to a shared vertex (topological editing means one corner belongs to two parcels and possibly two editors), and the fact that a cadastral system has an authority — a merge that is mathematically clean can still be legally wrong. Expect this to ship first as last-writer-wins-with-locking on a collection, and only later as a real CRDT.

Touch-first mobile editing

Field surveyors use tablets. Today the interaction model already helps more than it looks: tools receive a normalised InteractionContext, never a DOM event, so a tool written for a mouse already runs under touch, and the default handle size is 10 px — a fingertip — for exactly this reason.

What is missing is the part that cannot be abstracted away: a long-press vertex grab, a two-finger rotate that does not fight the map's own gesture handling, a snap indicator sized for a finger that is covering the corner it is snapping to, and a UI that assumes one hand. That is design work more than architecture work, and it is high on the list because it is the difference between a tool a surveyor uses in the office and one they use in the field.

A React binding package (@blaeu/react)

@blaeu/plugin-ui is framework-free DOM on purpose, and it should stay that way — but the host app is usually React, and the current story ("useEffect, create the map, remember to destroy it") is boilerplate we should own.

The shape falls out of the existing API rather than fighting it: every subscription returns a Disposable, which is precisely an effect cleanup; ListenerOptions.signal already takes an AbortSignal; map.plugin('draw') is already typed, so a usePlugin('draw') hook is typed with no extra machinery. <BlaeuMap preset={cadastrePreset()}> plus useBlaeuMap(), usePlugin(), useSelection(), useHistory(). Nothing in the kernel changes.

Offline / PWA tile caching

A field surveyor in a village outside Konya has no connectivity, and a cadastral tool that requires a network is a cadastral tool that stays in the office. Tile caching in a service worker, plus a store that can be seeded from and flushed to IndexedDB, plus a command log that survives a reload and replays on reconnect — the last of which is, again, the command bus being the right seam.

deck.gl layer types

LayerTypeDef is the extension point and it already works: preset-game registers a tile-grid layer type in one file, and the core has never heard of it. A deck.gl plugin registering type: 'deckgl' — scatterplot, hexbin, arc, trip — is the same move, and gives Blaeu large-scale analytical visualisation without a line of it entering the kernel.

WASM GEOS for heavy topology

JSTS is correct (ADR 0007) and fast enough for interactive work on a few hundred neighbours. It is not fast enough for validating a 100 000-parcel batch import, and it does not need to be — that is a different workload. WASM GEOS is the same C++ engine PostGIS uses, with the same precision model, and it belongs behind the same plugin API.

This is deliberately optional: the WASM blob is a large thing to ship to a browser that may only ever draw one polygon. Because topology is a plugin and the engine is an implementation detail of that plugin, swapping it is a plugin-internal change and not a contract change. ADR 0007 is what makes this cheap.

AI-assisted digitisation

The largest single win available to a cadastre product, and the one worth being most careful about.

Tracing a parcel boundary from an orthophoto is the bulk of a digitiser's day, and it is the kind of edge detection that a segmentation model is genuinely good at. The shape of the feature is clear: the model proposes a ring; it enters as a normal Command, so it is undoable like anything else; it goes through the commit pipeline, so the same topology rules that judge a hand-drawn parcel judge this one; and it snaps to the neighbours' existing corners through the snap providers, because a traced boundary that does not share the neighbour's corner has manufactured a sliver.

Every one of those three is an existing seam. The AI does not get a privileged path into the store, and that is not a technicality — it is the whole safety argument. A model that could write geometry directly would be a model that could write geometry no rule had judged.

The product constraint, which is not negotiable: the software proposes, the surveyor accepts. Auto-tracing that commits without review is the same mistake as autoFix: true, and it loses the trust of the people whose job it is to be exactly right — permanently, because once a surveyor has found one boundary they did not draw, they must re-check every boundary they did not draw.

Headless / SSR export

A Renderer with no GPU — enough to produce a PNG or a PDF of a parcel plan on a server, from the same store, the same theme and the same layer definitions the browser uses. Falls out of ADR 0008. Mostly a matter of drawing LayerStyle onto a canvas.

A plugin registry

A published index of community plugins, with the version and capability metadata the PluginManager already understands (dependencies, range, provides). Worth stating why this is last and not first: it is the item that most depends on everything above it being stable, and the deferred-install "parking" mechanism (ARCHITECTURE §4) was designed with it in mind — a marketplace installs plugins at runtime, in an order nobody chose, which is exactly the case a topological sort cannot handle and parking can.

Explicitly not planned

Edit this page on GitHub — this site is generated from ROADMAP.md, which is the source of truth.