The stack — and the three rules that picked it

Someone asked what the stack is. Fair question. But the list of tools is the boring half of the answer. The interesting half is why these and not the obvious alternatives — because for a ground control station, the constraints do the choosing.

Pick the tool first and you'll fight the constraints forever. So: three rules. Every dependency answers to all three.

Rule 1

License-clean, enforced by CI

This is a defense-and-commercial project. "We'll sort the licenses later" is how you end up unable to ship. So there's an allowlist (licenses.allowed.json) and a check that fails the build if any dependency falls outside it. GPL, AGPL, BSL, SSPL, Elastic-2.0 — out. Permissive only: MIT, Apache-2.0, BSD, ISC.

This rule has teeth, and it's vetoed real tools:

And the clean-room rule rides alongside it: every protocol gets re-authored from the public spec. No lifting code from QGroundControl, Mission Planner, or ArduPilot (all GPL). Patterns are ideas; ideas aren't copyrightable. Implementations are not ideas.

Rule 2

It has to run air-gapped

A tactical GCS can't assume the internet exists. No CDN fonts, no map tiles phoned home from someone's cloud, no telemetry beacon. Everything ships in the bundle.

That single rule is why the map is MapLibre GL JS + PMTiles + Protomaps, not Google Maps. Google Maps is a great product and completely disqualified here: it needs a billed API key, it phones home, and its ToS won't survive an air-gapped deployment. MapLibre renders vector tiles locally; PMTiles is a single-file tile archive you can drop in the repo; Protomaps is an OSM-derived basemap you self-host. The whole map works on a laptop in a field with no signal.

Same rule killed CDN icon fonts. The asset markers (drone, tank, ship, submarine, satellite) are Tabler Icons (MIT) inlined as SVG — no font fetch, themeable via currentColor, attributed in NOTICE. Even the cockpit background — scanlines, glow — is pure CSS, no images.

Rule 3

Make the dangerous parts impossible by type

This is the one I care about most, and it's why the whole thing is TypeScript, strict, no any — plus noUncheckedIndexedAccess, exactOptionalPropertyTypes, verbatimModuleSyntax.

The point isn't "types are nice." The point is that in this domain there are operations you must not be able to do by accident — and the strongest place to stop them is the compiler, not a runtime check you hope runs. The engagement command can't be constructed without an authorization token: it's in the type. A ground vehicle can't show a "take off" button: controls render from declared capabilities, and a UGV doesn't declare flight. Add a new command and the exhaustive switch won't compile until every site handles it.

Discipline forgets. Types don't.

Screenshots and deeper technical detail

The live console screenshots, the rest of the stack, and the deferred-tools list are behind the wall. Same password as the other GCS posts — or request access below.

The takeaway, if there is one: don't ask "what's the best tool." Ask "what can't this system be allowed to do," write that down as a rule with teeth, and let the rules pick the tools. The stack falls out almost for free.

Build log entry. Apache-2.0. The full table — versions, licenses, and the rejected list — lives in docs/architecture/tech-stack.md.