Files
gods-eye-view/scripts/qa-firstrun-mutations.mjs
T
Sameh Khamis 6802c9c8b0 feat(ui): render every application template except the loading screen as a Vue view
The scene chrome, cockpit, Display controls, command dock, layer panels,
Context rail, welcome card and Provider Settings move verbatim into
src/ui/views and mount into their hosts before the page is translated and
wired. Each view renders the same elements, attributes and text as its
template; Vue keeps whitespace between elements as one space, and no view
markup sits under a white-space: pre rule.

Template asset URLs stay literal, view files are left out of Prettier so
the markup is not reflowed, and the Node component loader moves to build/
and is exported as build/register-vue for host test runners. Hosts that do
not initialize Core's locale pass localePicker: false instead of editing
the markup.
2026-10-30 17:47:52 -07:00

564 lines
22 KiB
JavaScript

#!/usr/bin/env node
/**
* Are the first-run launcher's pins actually load-bearing?
*
* A pin that only goes red when you delete the whole feature proves very little.
* This reverts each decision INDIVIDUALLY — the smallest edit that reintroduces
* the original defect or contradicts the owner's ruling — and requires
* src/firstRunExperience.test.mjs to go red for it. Every entry names what it
* restores, so the count is reproducible rather than asserted in a commit
* message.
*
* node scripts/qa-firstrun-mutations.mjs
*
* Every touched file is restored on exit, including on failure.
*
* NOTE: two mutations edit the server voice tools/instructions, and a running dev server watches those
* files and restarts on every write. Writes are therefore content-guarded below
* so the file is touched exactly twice per mutation instead of on every
* iteration — enough that a dev server survives, but expect it to restart. If
* you are mid-QA on a live server, run this before or after, not during.
*/
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const TESTS = [
'src/firstRunExperience.test.mjs',
'src/standalone/startupChrome.test.mjs',
];
const FILES = {
keyboard: path.join(ROOT, 'src', 'ui', 'surfaceKeyboard.js'),
module: path.join(ROOT, 'src', 'firstRunExperience.js'),
html: path.join(ROOT, 'src', 'ui', 'views', 'WelcomeView.vue'),
css: path.join(ROOT, 'src', 'ui', 'styles', 'first-run.css'),
voiceInstructions: path.join(ROOT, 'server/providers/openai/instructions.js'),
voiceDescriptions: path.join(
ROOT,
'server/providers/openai/toolDescriptions.js',
),
main: path.join(ROOT, 'src', 'app', 'startupChrome.js'),
ui: path.join(ROOT, 'src', 'ui', 'panelPositionControls.js'),
radio: path.join(ROOT, 'src', 'ui', 'radioBindings.js'),
docs: path.join(ROOT, 'docs', 'CURRENT-STATE.md'),
};
/** @type {Array<{defect: string, file: keyof FILES, from: string, to: string}>} */
const MUTATIONS = [
// ── Show policy (owner ruling: session-scoped dismiss vs durable checkbox) ──
{
defect: 'dismissing writes the DURABLE key, so the launcher never returns',
file: 'module',
from: "writeStored('session', sessionStorageRef, FIRST_RUN_SESSION_KEY, 'dismissed');",
to: "writeStored('local', sessionStorageRef, FIRST_RUN_STORAGE_KEY, 'suppressed');",
},
{
defect:
'the checkbox is ignored, so nothing can ever suppress the launcher',
file: 'module',
from: " ? writeStored('local', storage, FIRST_RUN_STORAGE_KEY, 'suppressed')",
to: ' ? false',
},
{
defect: 'unticking the box cannot take the suppression back',
file: 'module',
from: " : removeStored('local', storage, FIRST_RUN_STORAGE_KEY);",
to: ' : true;',
},
{
defect: 'the session flag is never read, so it re-nags on every reload',
file: 'module',
from: " if (\n readStored('session', sessionStorageRef, FIRST_RUN_SESSION_KEY) ===\n 'dismissed'\n )\n return false;",
to: ' // session check removed',
},
{
defect: 'the durable flag is never read, so the checkbox does nothing',
file: 'module',
from: " if (readStored('local', storage, FIRST_RUN_STORAGE_KEY) === 'suppressed')\n return false;",
to: ' // durable check removed',
},
{
defect:
'?welcome=1 no longer outranks suppression, so demos cannot replay it',
file: 'module',
from: "if (params.get('welcome') === '1') return true;",
to: '// replay hatch removed',
},
{
defect: '?welcome=0 stops suppressing',
file: 'module',
from: "if (params.get('welcome') === '0') return false;",
to: '// suppression param removed',
},
{
defect: 'a share link gets the launcher over the view its author chose',
file: 'module',
from: 'if (hasShareState) return false;',
to: '// share bypass removed',
},
{
defect: 'storage is read from a DEFAULT PARAMETER, outside every guard',
file: 'module',
from: 'export function rememberFirstRunSessionDismissed(sessionStorageRef) {',
to: 'export function rememberFirstRunSessionDismissed(sessionStorageRef = globalThis.sessionStorage) {',
},
{
defect: 'shouldShowFirstRun resolves storage in its parameter list again',
file: 'module',
from: 'export function shouldShowFirstRun({\n hasShareState = false,\n storage,\n sessionStorageRef,',
to: 'export function shouldShowFirstRun({\n hasShareState = false,\n storage = globalThis.localStorage,\n sessionStorageRef = globalThis.sessionStorage,',
},
{
defect: 'the guarded resolver stops catching, so a hostile getter escapes',
file: 'module',
from: " try {\n return kind === 'session'\n ? globalThis.sessionStorage\n : globalThis.localStorage;\n } catch {\n // Privacy-restricted storage should not make first launch silent.\n return null;\n }",
to: " return kind === 'session' ? globalThis.sessionStorage : globalThis.localStorage;",
},
{
defect: 'a blocked getItem throws instead of reading as "nothing stored"',
file: 'module',
from: ' return resolveStore(kind, injected)?.getItem?.(key) ?? null;\n } catch {\n return null;\n }',
to: ' return resolveStore(kind, injected)?.getItem?.(key) ?? null;\n } finally {\n // no catch\n }',
},
// ── ESC arbitration ───────────────────────────────────────────────────────
{
defect: 'the key handler trusts the CLASS, so an invisible card eats ESC',
file: 'module',
from: 'isActive: () => !closing && isTopmost(),',
to: 'isActive: () => !closing,',
},
{
defect: 'the launcher stops yielding and fights Cockpit/Scenes for ESC',
file: 'module',
from: 'if (revealed && blocked) yieldToExclusiveSurface();\n else if (!revealed && !blocked) reveal();',
to: 'if (!revealed) reveal();',
},
{
defect: 'nothing watches for another surface taking the screen',
file: 'module',
from: " surfaceObserver?.observe(documentRef.body, {\n attributes: true,\n attributeFilter: ['class'],\n });",
to: ' /* observer never attached */',
},
{
defect:
'cockpit drops off the exclusive list and can stack with the launcher',
file: 'module',
from: "export const EXCLUSIVE_SURFACE_CLASSES = Object.freeze([\n 'cockpit-mode',",
to: 'export const EXCLUSIVE_SURFACE_CLASSES = Object.freeze([',
},
{
defect: 'a scene hides the launcher in CSS but the JS never learns of it',
file: 'css',
from: 'body.scene-playback-mode #first-run-launcher {',
to: 'body.scene-playback-mode-typo #first-run-launcher {',
},
{
defect: 'yielding steals focus back from the surface that just took over',
file: 'module',
from: 'dismiss({ restoreFocus: false });',
to: 'dismiss();',
},
{
defect:
'isTopmost stops hit-testing, so an unclassed overlay buries the card again',
file: 'module',
from: ' root.getClientRects().length > 0 &&\n !coveredByOverlay();',
to: ' root.getClientRects().length > 0;',
},
{
defect:
'an inconclusive hit test reads as COVERED, so ESC quietly stops working',
file: 'module',
from: ' return Boolean(hit) && !root.contains(hit);',
to: ' return !root.contains(hit);',
},
{
defect:
'the launcher stops honouring a key another surface already claimed',
file: 'keyboard',
from: ' || event.defaultPrevented',
to: ' /* belt removed */',
},
{
// Anchored past the call so it lands on the Cockpit utility disclosure's
// Escape claim and nowhere else.
defect:
'a Cockpit utility disclosure returns to stopPropagation, so later document listeners also act on its ESC',
file: 'radio',
from: " event.stopImmediatePropagation();\n const kind = displayOpen ? 'display' : 'radio';",
to: " event.stopPropagation();\n const kind = displayOpen ? 'display' : 'radio';",
},
{
defect:
'the "no timer" decision is deleted, so the next editor re-litigates it blind',
file: 'module',
from: ' * ACCEPTED, DELIBERATELY NOT TIMED OUT: a surface class that never clears',
to: ' * (note removed)',
},
{
defect:
'a bounded reveal timer punches the card through a recording in progress',
file: 'module',
from: ' const syncToExclusiveSurfaces = () => {\n if (closing) return;',
to: ' const syncToExclusiveSurfaces = () => {\n if (closing) return;\n globalThis.setTimeout?.(reveal, 45000);',
},
{
defect:
'the accepted no-show vanishes from CURRENT-STATE and reads as a bug',
file: 'docs',
from: 'a surface class that never clears means no launcher for that page',
to: 'the launcher always turns up eventually for that page',
},
// ── The checkbox may not promise what storage refused ─────────────────────
{
defect:
'a refused write leaves the box ticked, promising a suppression nobody stored',
file: 'module',
from: ' if (setFirstRunSuppressed(wanted, storage)) return;',
to: ' setFirstRunSuppressed(wanted, storage);\n return;',
},
{
defect: 'a missing storage area reports the write as saved',
file: 'module',
from: " if (typeof store?.setItem !== 'function') return false;\n store.setItem(key, value);\n return true;",
to: ' store?.setItem?.(key, value);\n return true;',
},
{
defect:
'the scroll fade promises more list on a card where everything fits',
file: 'module',
from: 'choiceList.dataset.scrollable = String(overflows);',
to: "choiceList.dataset.scrollable = 'true'; void overflows;",
},
{
defect:
'the DISPLAY rail opens on first run again, stealing the impression',
file: 'ui',
from: "if (panelId === 'pp-toggles' && stored === null) collapsed = true;",
to: "if (panelId === 'pp-toggles' && stored === null) collapsed = false;",
},
{
defect: 'the first-run rail default overrides a stored user choice',
file: 'ui',
from: "if (panelId === 'pp-toggles' && stored === null) collapsed = true;",
to: "if (panelId === 'pp-toggles') collapsed = true;",
},
// ── Mission definitions ────────────────────────────────────────────────────
{
defect:
'the removed INFRASTRUCTURE tile comes back as a one-click globe dump',
file: 'module',
from: ' environmental: Object.freeze({',
to: " infrastructure: Object.freeze({\n kind: 'globe',\n layerIds: Object.freeze(['local-datacenters', 'local-dams', 'telegeography-submarine-cables']),\n busyText: 'Mapping global infrastructure…',\n }),\n environmental: Object.freeze({",
},
{
defect:
'ENVIRONMENTAL drops the keyless earthquakes that carry it without a key',
file: 'module',
from: "layerIds: Object.freeze(['earthquakes', 'local-firms']),",
to: "layerIds: Object.freeze(['local-firms']),",
},
{
defect: 'FIRMS is dropped again from the tile whose subcopy promises it',
file: 'module',
from: "layerIds: Object.freeze(['earthquakes', 'local-firms']),",
to: "layerIds: Object.freeze(['earthquakes']),",
},
{
defect: 'the tile stops promising the fires it actually turns on',
file: 'html',
from: '<small>Live earthquakes and active fires, from USGS and NASA</small>',
to: '<small>Live earthquakes worldwide, straight from USGS</small>',
},
{
defect: 'the owner-authored first-run line is quietly rewritten',
file: 'html',
from: 'It feels like a forbidden cockpit—then you realize the sources are public and the data is real.',
to: "It feels like a forbidden cockpit. It isn't — every feed is public, and every contact is live.",
},
{
defect: 'the ledgered global-chip defect loses its pointer to the real fix',
file: 'module',
from: ' // means a KEY REQUIRED terminal state in src/loadingFeedback.js, a state',
to: ' // (note removed)',
},
{
defect: 'the globe missions never pull the camera out',
file: 'module',
from: ' .then(() => flyToGlobe())',
to: ' .then(() => null)',
},
{
defect: 'a refused layer is reported as a successful mission',
file: 'module',
from: 'return { ok: failedLayerIds.length === 0, choice, failedLayerIds };',
to: 'return { ok: true, choice, failedLayerIds };',
},
{
defect: 'a cancelled globe flight fails the whole mission',
file: 'module',
from: ' .catch(() => null);',
to: ' .catch(() => { throw new Error("flight failed"); });',
},
{
defect: 'Explore manually quietly runs a mission instead of nothing',
file: 'module',
from: "if (mission.kind === 'none') return { ok: true, choice };",
to: "if (mission.kind === 'none') return { ok: true, choice, ran: await setContextMode('contacts') };",
},
{
defect: 'an unknown choice is treated as a success',
file: 'module',
from: 'if (!mission) return { ok: false, choice };',
to: 'if (!mission) return { ok: true, choice };',
},
{
defect:
'the fires/quakes tile name stops being switchable from one constant',
file: 'module',
from: 'return ENVIRONMENTAL_LABELS[choice] || ENVIRONMENTAL_LABELS.ENVIRONMENTAL;',
to: 'return ENVIRONMENTAL_LABELS.ENVIRONMENTAL;',
},
// ── Defaults interplay (the ruling this whole integration turns on) ────────
{
defect:
'mission layer enables stop persisting, unlike the clicks they stand for',
file: 'module',
from: "dataManager.setEnabled(layerId, true, { origin: 'user' })",
to: 'dataManager.setEnabled(layerId, true)',
},
{
defect:
'a mission hand-edits detection and kills the style auto-preset contract',
file: 'module',
from: ' flyToGlobe: () => styleManager.resetToGlobeView(),',
to: ' flyToGlobe: () => { styleManager._detectionUserOverridden = true; return styleManager.resetToGlobeView(); },',
},
{
defect: 'a mission persists a 3D-models choice nobody made',
file: 'module',
from: ' const result = await styleManager.setContextMode(mode);',
to: ' styleManager._setModels3dEnabled?.(true);\n const result = await styleManager.setContextMode(mode);',
},
{
defect:
'the Context panel is left collapsed, hiding the mode that just started',
file: 'module',
from: " styleManager.setPanelCollapsed?.('global-context-panel', false, {\n explicit: true,\n });",
to: ' // panel reveal removed',
},
{
defect:
'the decision table is deleted, so the next editor re-litigates it blind',
file: 'module',
from: ' * MISSION → APP STATE, AND WHAT IT IS ALLOWED TO PERSIST',
to: ' * (notes removed)',
},
// ── Accessibility and the transplant defects ──────────────────────────────
{
defect:
'disabling the focused tile drops the keyboard to <body> mid-flight',
file: 'module',
from: " for (const button of buttons)\n button.setAttribute('aria-disabled', String(next));",
to: ' for (const button of buttons) button.disabled = next;',
},
{
defect: 'Tab escapes into an app the visitor has not seen yet',
file: 'keyboard',
from: "if (event.key !== 'Tab') return;",
to: 'return;',
},
{
defect: 'ESC no longer dismisses the launcher',
file: 'keyboard',
from: "if (event.key === 'Escape') {",
to: 'if (false) {',
},
{
defect: 'focus is never returned to where the visitor left it',
file: 'keyboard',
from: "if (typeof target?.focus === 'function' && target.isConnected) {",
to: 'if (false) {',
},
{
defect: 'app hotkeys eat the launcher keys (bubble instead of capture)',
file: 'keyboard',
from: "documentRef.addEventListener('keydown', onKeyDown, true);",
to: "documentRef.addEventListener('keydown', onKeyDown);",
},
{
defect: 'the card stays clickable through its fade-out (double-launch)',
file: 'css',
from: ' transition:\n opacity 220ms ease,\n transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);\n /* Inert until revealed AND again while fading out, so a click during the\n dismiss animation cannot launch a second mission behind the fade. */\n pointer-events: none;',
to: ' transition:\n opacity 220ms ease,\n transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);',
},
{
defect: 'the launcher survives Clean-UI and recording mode',
file: 'css',
from: 'body.ui-clean-view #first-run-launcher,',
to: 'body.never-matches-anything #first-run-launcher,',
},
{
defect:
'the flex card defeats [hidden] and sits in the a11y tree pre-reveal',
file: 'css',
from: '#first-run-launcher[hidden] {\n display: none;\n}',
to: '/* [hidden] rule removed */',
},
{
defect:
'the card loses its viewport cap and clips on a short landscape phone',
file: 'css',
from: ' max-height: calc(100dvh - 1.5rem);',
to: ' /* cap removed */',
},
{
defect:
'the mission list stops scrolling, so tiles overflow the capped card',
file: 'css',
from: ' min-height: 0;\n overflow-y: auto;',
to: ' /* scroll removed */',
},
{
// Anchored to the launcher's OWN selectors: style.css has four other
// reduced-motion blocks, and a bare `@media` anchor would mutate one of
// those instead and prove nothing about this feature.
defect: "the launcher's reduced-motion opt-out is dropped",
file: 'css',
from: ' #first-run-launcher,\n .first-run-choices button,\n .first-run-arrow {\n transition: none;\n }\n /* Scrolling a tile into view must not animate either. */\n .first-run-choices {\n scroll-behavior: auto;\n }',
to: ' .first-run-nothing {\n transition: none;\n }',
},
// ── Markup and startup ordering ───────────────────────────────────────────
{
defect: 'a mission tile is dropped from the menu',
file: 'html',
from: ' <button type="button" data-first-run-choice="space-missions">',
to: ' <button type="button" data-first-run-choice-disabled="space-missions">',
},
{
defect: "the menu order stops matching the owner's",
file: 'html',
from: '<button type="button" data-first-run-choice="environmental">',
to: '<button type="button" data-first-run-choice="zzz-environmental">',
},
{
defect: 'the "don\'t show again" checkbox is removed',
file: 'html',
from: '<input type="checkbox" data-first-run-suppress />',
to: '<span data-first-run-suppress-removed></span>',
},
{
defect: 'the status line stops being a polite live region',
file: 'html',
from: 'data-first-run-status role="status" aria-live="polite"',
to: 'data-first-run-status',
},
{
defect: 'the launcher is no longer announced as a dialog',
file: 'html',
from: '<aside id="first-run-launcher" role="dialog"',
to: '<aside id="first-run-launcher"',
},
{
defect: 'the launcher is revealed before the loading cover yields',
file: 'main',
from: " loadingScreen.classList.add('hidden');",
to: " revealFirstRun();\n loadingScreen.classList.add('hidden');",
},
{
defect:
'the globe missions lose their DataManager and cannot enable layers',
file: 'main',
from: 'firstRun = initializeWelcome?.({ styleManager, dataManager });',
to: 'firstRun = initializeWelcome?.({ styleManager });',
},
// ── Voice: schema must not drift ──────────────────────────────────────────
{
defect: 'the voice TOOL SCHEMA is edited (a Realtime prompt-cache bust)',
file: 'voiceDescriptions',
from: "'Counts, lists, superlatives and attribute filters over records loaded by ENABLED layers",
to: "'Counts, lists, superlatives, and attribute filters over records loaded by ENABLED layers",
},
{
defect:
'the instruction mapping is dropped, so voice cannot reach the modes',
file: 'voiceInstructions',
from: " 'NAMED VIEWS are shorthand",
to: " // 'NAMED VIEWS are shorthand",
},
];
const originals = new Map();
for (const [key, file] of Object.entries(FILES))
originals.set(key, fs.readFileSync(file, 'utf8'));
/** Write only on a real change: a no-op write still wakes every file watcher. */
const write = (file, next) => {
if (fs.readFileSync(file, 'utf8') !== next) fs.writeFileSync(file, next);
};
const restoreAll = () => {
for (const [key, file] of Object.entries(FILES))
write(file, originals.get(key));
};
process.on('exit', restoreAll);
let caught = 0;
const missed = [];
console.log(
`\nFirst-run launcher pin strength — ${MUTATIONS.length} individual reverts\n`,
);
for (const { defect, file, from, to } of MUTATIONS) {
const original = originals.get(file);
if (!original.includes(from)) {
missed.push(`${defect} (ANCHOR MISSING — the mutation no longer applies)`);
console.log(` \x1b[31mSTALE\x1b[0m ${defect}`);
continue;
}
write(FILES[file], original.replace(from, to));
let red = false;
let by = '';
try {
execFileSync(
'node',
['--import=./build/registerVue.js', '--test', ...TESTS],
{ cwd: ROOT, encoding: 'utf8', stdio: 'pipe' },
);
} catch (error) {
red = true;
const failed = String(error.stdout || '')
.split('\n')
.filter((line) => line.trim().startsWith('✖') && line.includes('('))
.map((line) => line.trim().slice(2).split(' (')[0]);
by = [...new Set(failed)].slice(0, 2).join('; ');
}
write(FILES[file], original);
if (red) {
caught += 1;
console.log(` \x1b[32mRED \x1b[0m ${defect}\n caught by: ${by}`);
} else {
missed.push(defect);
console.log(` \x1b[31mGREEN\x1b[0m ${defect} <-- no pin covers this`);
}
}
restoreAll();
console.log(`\n ${caught}/${MUTATIONS.length} defects caught by the pins`);
if (missed.length) {
console.log('\n uncovered:');
for (const item of missed) console.log(` - ${item}`);
}
console.log('');
process.exitCode = missed.length ? 1 : 0;