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
- A basemap or tile service. Bring your own MapLibre style.
- A full GIS. No raster reprojection, no geoprocessing suite, no cross-dataset attribute joins. Blaeu is an editing kernel, and the boundary is deliberate.
- A visual plugin builder. Plugins are forty lines of TypeScript. That is already the simplification.
ROADMAP.md, which is the source of truth.