release: v0.1.0 — one-click install, keyless boot, in-app Provider Settings (#142)

The first formal release.

- One-click install via Pinokio; keyless boot lands on a live Esri World
  Imagery satellite globe with keyless terrain, with automatic OSM fallback
  and graceful degradation when terrain is unavailable.
- Provider Settings (the POWER UP panel): add, replace, or remove API keys
  inside the app; credential files made owner-only before any secret is
  written; external keys shown read-only; the panel refuses shared or proxied
  servers.
- Keyless capability responses for the optional HUD summary and place search.
- Aircraft-identity voice answers cover operator, type, and route, and say so
  plainly when enrichment is unavailable.
- README rewritten keyless-first; concise 0.1.0 changelog section; CI
  workflow included.

Gates: 2,672 unit tests pass / 0 fail, build clean, tracking regression
108/108, map-source tray QA pass, setup doctor ready.
This commit is contained in:
Bilawal Sidhu authored and GitHub committed 2026-08-31 17:49:45 -05:00
1 parent 6d83bb6008
commit b6da93b8ac
170 files changed
+5800 -853

No files matched your search

+8 -4
View File
@@ -91,7 +91,7 @@ const NESTED_CONTEXT_RESULT_FIELDS = Object.freeze(['context', 'contextRollback'
* `set_context_mode` accepts 'contacts' while the mode's internal id is
* 'flights'. Reporting the internal id back made the model read
* `mode:'flights'` as "Contacts is off" and refuse to answer from the Contacts
* window counts sitting in the very same payload (field session
* window counts sitting in the very same payload (owner field session
* 2026-08-21). Secondary fields and nested transition/rollback results are
* translated too — one leaked internal id is enough to recreate the confusion,
* and a rollback result is exactly what the model reads when something went
@@ -208,6 +208,10 @@ const STACK_ALIASES = new Map([
['bing labels', 'bing-labels'],
['labels', 'bing-labels'],
['aerial with labels', 'bing-labels'],
['esri-imagery', 'esri-imagery'],
['esri', 'esri-imagery'],
['esri imagery', 'esri-imagery'],
['esri satellite', 'esri-imagery'],
['osm', 'osm'],
['openstreetmap', 'osm'],
['open street map', 'osm'],
@@ -1038,7 +1042,7 @@ function clearAnnotations(annotations) {
return { ok: true, action: 'clear_annotations' };
}
function normalizeStackId(value) {
export function normalizeStackId(value) {
const raw = String(value || '').trim().toLowerCase();
if (!raw) return null;
return STACK_ALIASES.get(raw) || null;
@@ -3371,7 +3375,7 @@ async function runAnalystQuery(viewer, dataManager, args = {}) {
// hand this result straight to track_entity, and `id` is a DISPLAY label
// (callsign, else registration, else hex). A callsign-less contact therefore
// handed track_entity a tail number the lookup could not resolve, and the
// model burned the turn on retries (field session 2026-08-21, 23:48).
// model burned the turn on retries (owner field session 2026-08-21, 23:48).
const items = result.items.map((r) => {
const compact = { layerKey: r.layerKey, id: r.id };
for (const k of ['icao24', 'mmsi', 'registration', 'label', 'callsign', 'name', 'altitudeM', 'speedMps', 'speedKts', 'frp', 'magnitude', 'shipType', 'destination', 'operator', 'routeOrigin', 'routeDestination', 'aircraftClass', 'military', 'onGround', 'distanceKm', 'confidence', 'place']) {
@@ -3413,7 +3417,7 @@ async function runAnalystQuery(viewer, dataManager, args = {}) {
const aircraftQueried = (result.coverage?.layersQueried || [])
.some((l) => l.layerKey === 'flights' || l.layerKey === 'military');
// Both numbers, and which one answers the question. The window counts have
// ridden along in `contactsWindow` for a while, and the live trial showed
// ridden along in `contactsWindow` for a while, and the owner's trial showed
// that is not enough on its own: with Contacts active and a DATACENTER in
// the selection slot, the model centred a radius on the datacenter, answered
// 15, and then explained away the 111 sitting in the same payload ("that
+31 -1
View File
@@ -14,7 +14,37 @@ import {
cctvVoiceFocusOutcome,
formatTrackedEntityLabel,
knownRadioLocation,
normalizeStackId,
} from './gevActions.js';
import { MAP_STACKS } from '../mapStackController.js';
import { readFileSync } from 'node:fs';
test('every live basemap is reachable by its own id — no enum value without a voice alias', () => {
// B1 regression: a stack added to MAP_STACKS (and the set_map_stack enum)
// without a matching STACK_ALIASES entry resolves to null and throws
// "Unknown map stack" at the controller — a broken voice command for a
// shipped basemap. Every live id must self-resolve.
for (const stack of MAP_STACKS) {
assert.equal(
normalizeStackId(stack.id),
stack.id,
`set_map_stack '${stack.id}' has no self-mapping alias — voice selection would throw`,
);
}
// The Esri phrasings the voice prompt promises must also resolve.
assert.equal(normalizeStackId('Esri'), 'esri-imagery');
assert.equal(normalizeStackId('esri imagery'), 'esri-imagery');
// And the voice tool's enum must equal the set of live ids — no drift either way.
const config = readFileSync(new URL('../../vite.config.js', import.meta.url), 'utf8');
const enumMatch = config.match(/enum: \[('photoreal'[^\]]*)\],\s*\n\s*description: 'photoreal = Google 3D/);
assert.ok(enumMatch, 'set_map_stack enum literal must still be findable');
const enumIds = enumMatch[1].split(',').map((s) => s.trim().replace(/^'|'$/g, ''));
assert.deepEqual(
[...enumIds].sort(),
MAP_STACKS.map((s) => s.id).sort(),
'the set_map_stack voice enum and MAP_STACKS must name exactly the same basemaps',
);
});
test('track_entity narration names aircraft callsign → registration → icao24', () => {
const found = { callsign: 'SWA696', registration: 'N123AB', icao24: 'ae1fa4' };
@@ -2774,7 +2804,7 @@ test('a nested Cockpit rollback result is translated too', async () => {
/**
* Front 5 (the live trial, 2026-08-22 01:42-01:44). Contacts was active
* Front 5 (owner's live trial, 2026-08-22 01:42-01:44). Contacts was active
* with contact N546PC as its subject, a DATACENTER sat in the recency slot,
* and "how many nearby" produced 15 from a radius centred on the datacenter
* while the panel showed 111. Two causes: the wrong centre, and two different