mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
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.
This commit is contained in:
1 parent
9d24945143
commit
6802c9c8b0
46 files changed
+258
-97
No files matched your search
@@ -1121,8 +1121,8 @@
|
||||
"src/share/shareLinkPath.test.mjs",
|
||||
"src/ui/kit/UiRail.test.mjs",
|
||||
"src/testSupport/vueDom.mjs",
|
||||
"src/testSupport/vueLoader.mjs",
|
||||
"src/testSupport/registerVue.mjs",
|
||||
"build/vueLoader.js",
|
||||
"build/registerVue.js",
|
||||
"src/ui/panelNavigation.test.mjs",
|
||||
"src/ui/stores/panels.test.mjs"
|
||||
]
|
||||
+2
-1
@@ -8,7 +8,8 @@
|
||||
// lists it.
|
||||
//
|
||||
// Messages are the English literals passed to t(), tn() and msg() in src/,
|
||||
// plus the markup opted in with data-i18n* in src/ui/templates and index.html.
|
||||
// plus the markup opted in with data-i18n* in index.html, src/ui/templates and
|
||||
// the Vue views.
|
||||
// It also lists UI text that is not marked at all (see findUnmarked).
|
||||
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
|
||||
@@ -20,7 +20,15 @@
|
||||
"modules": [
|
||||
"src/app/application.js",
|
||||
"src/ui/vueRuntime.js",
|
||||
"src/ui/views/index.js"
|
||||
"src/ui/views/index.js",
|
||||
"src/ui/views/CockpitView.vue",
|
||||
"src/ui/views/CommandDockView.vue",
|
||||
"src/ui/views/ContextView.vue",
|
||||
"src/ui/views/DisplayControlsView.vue",
|
||||
"src/ui/views/LayerPanelsView.vue",
|
||||
"src/ui/views/ProviderSettingsView.vue",
|
||||
"src/ui/views/SceneChromeView.vue",
|
||||
"src/ui/views/WelcomeView.vue"
|
||||
],
|
||||
"external": ["vue", "pinia"]
|
||||
},
|
||||
@@ -38,7 +46,7 @@
|
||||
},
|
||||
"vite-build": {
|
||||
"runtime": "node",
|
||||
"exports": ["./build/vite", "./build/html"],
|
||||
"exports": ["./build/vite", "./build/html", "./build/register-vue"],
|
||||
"modules": [
|
||||
"build/vite.js",
|
||||
"build/application-html.js",
|
||||
@@ -46,9 +54,11 @@
|
||||
"build/embed-framing.js",
|
||||
"build/panel.js",
|
||||
"build/applicationViews.js",
|
||||
"build/vueOptions.js"
|
||||
"build/vueOptions.js",
|
||||
"build/registerVue.js",
|
||||
"build/vueLoader.js"
|
||||
],
|
||||
"external": ["vite-plugin-cesium", "@vitejs/plugin-vue"]
|
||||
"external": ["vite-plugin-cesium", "@vitejs/plugin-vue", "vue"]
|
||||
},
|
||||
"live-providers": {
|
||||
"runtime": "node",
|
||||
|
||||
@@ -25,12 +25,15 @@ 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 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', 'templates', 'welcome.html'),
|
||||
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(
|
||||
@@ -53,7 +56,8 @@ const MUTATIONS = [
|
||||
to: "writeStored('local', sessionStorageRef, FIRST_RUN_STORAGE_KEY, 'suppressed');",
|
||||
},
|
||||
{
|
||||
defect: 'the checkbox is ignored, so nothing can ever suppress the launcher',
|
||||
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',
|
||||
@@ -77,7 +81,8 @@ const MUTATIONS = [
|
||||
to: ' // durable check removed',
|
||||
},
|
||||
{
|
||||
defect: '?welcome=1 no longer outranks suppression, so demos cannot replay it',
|
||||
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',
|
||||
@@ -139,7 +144,8 @@ const MUTATIONS = [
|
||||
to: ' /* observer never attached */',
|
||||
},
|
||||
{
|
||||
defect: 'cockpit drops off the exclusive list and can stack with the launcher',
|
||||
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([',
|
||||
@@ -157,19 +163,22 @@ const MUTATIONS = [
|
||||
to: 'dismiss();',
|
||||
},
|
||||
{
|
||||
defect: 'isTopmost stops hit-testing, so an unclassed overlay buries the card again',
|
||||
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',
|
||||
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',
|
||||
defect:
|
||||
'the launcher stops honouring a key another surface already claimed',
|
||||
file: 'keyboard',
|
||||
from: ' || event.defaultPrevented',
|
||||
to: ' /* belt removed */',
|
||||
@@ -177,25 +186,29 @@ const MUTATIONS = [
|
||||
{
|
||||
// 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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
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',
|
||||
@@ -203,7 +216,8 @@ const MUTATIONS = [
|
||||
|
||||
// ── The checkbox may not promise what storage refused ─────────────────────
|
||||
{
|
||||
defect: 'a refused write leaves the box ticked, promising a suppression nobody stored',
|
||||
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;',
|
||||
@@ -215,14 +229,16 @@ const MUTATIONS = [
|
||||
to: ' store?.setItem?.(key, value);\n return true;',
|
||||
},
|
||||
{
|
||||
defect: 'the scroll fade promises more list on a card where everything fits',
|
||||
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',
|
||||
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;",
|
||||
@@ -236,13 +252,15 @@ const MUTATIONS = [
|
||||
|
||||
// ── Mission definitions ────────────────────────────────────────────────────
|
||||
{
|
||||
defect: 'the removed INFRASTRUCTURE tile comes back as a one-click globe dump',
|
||||
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',
|
||||
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']),",
|
||||
@@ -260,7 +278,7 @@ const MUTATIONS = [
|
||||
to: '<small>Live earthquakes worldwide, straight from USGS</small>',
|
||||
},
|
||||
{
|
||||
defect: "the owner-authored first-run line is quietly rewritten",
|
||||
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.",
|
||||
@@ -302,7 +320,8 @@ const MUTATIONS = [
|
||||
to: 'if (!mission) return { ok: true, choice };',
|
||||
},
|
||||
{
|
||||
defect: 'the fires/quakes tile name stops being switchable from one constant',
|
||||
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;',
|
||||
@@ -310,13 +329,15 @@ const MUTATIONS = [
|
||||
|
||||
// ── Defaults interplay (the ruling this whole integration turns on) ────────
|
||||
{
|
||||
defect: "mission layer enables stop persisting, unlike the clicks they stand for",
|
||||
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',
|
||||
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(); },',
|
||||
@@ -328,13 +349,15 @@ const MUTATIONS = [
|
||||
to: ' styleManager._setModels3dEnabled?.(true);\n const result = await styleManager.setContextMode(mode);',
|
||||
},
|
||||
{
|
||||
defect: 'the Context panel is left collapsed, hiding the mode that just started',
|
||||
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',
|
||||
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)',
|
||||
@@ -342,7 +365,8 @@ const MUTATIONS = [
|
||||
|
||||
// ── Accessibility and the transplant defects ──────────────────────────────
|
||||
{
|
||||
defect: 'disabling the focused tile drops the keyboard to <body> mid-flight',
|
||||
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;',
|
||||
@@ -384,19 +408,22 @@ const MUTATIONS = [
|
||||
to: 'body.never-matches-anything #first-run-launcher,',
|
||||
},
|
||||
{
|
||||
defect: 'the flex card defeats [hidden] and sits in the a11y tree pre-reveal',
|
||||
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',
|
||||
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',
|
||||
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 */',
|
||||
@@ -419,7 +446,7 @@ const MUTATIONS = [
|
||||
to: ' <button type="button" data-first-run-choice-disabled="space-missions">',
|
||||
},
|
||||
{
|
||||
defect: 'the menu order stops matching the owner\'s',
|
||||
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">',
|
||||
@@ -449,7 +476,8 @@ const MUTATIONS = [
|
||||
to: " revealFirstRun();\n loadingScreen.classList.add('hidden');",
|
||||
},
|
||||
{
|
||||
defect: 'the globe missions lose their DataManager and cannot enable layers',
|
||||
defect:
|
||||
'the globe missions lose their DataManager and cannot enable layers',
|
||||
file: 'main',
|
||||
from: 'firstRun = initializeWelcome?.({ styleManager, dataManager });',
|
||||
to: 'firstRun = initializeWelcome?.({ styleManager });',
|
||||
@@ -463,7 +491,8 @@ const MUTATIONS = [
|
||||
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',
|
||||
defect:
|
||||
'the instruction mapping is dropped, so voice cannot reach the modes',
|
||||
file: 'voiceInstructions',
|
||||
from: " 'NAMED VIEWS are shorthand",
|
||||
to: " // 'NAMED VIEWS are shorthand",
|
||||
@@ -471,21 +500,25 @@ const MUTATIONS = [
|
||||
];
|
||||
|
||||
const originals = new Map();
|
||||
for (const [key, file] of Object.entries(FILES)) originals.set(key, fs.readFileSync(file, 'utf8'));
|
||||
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));
|
||||
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`);
|
||||
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)) {
|
||||
@@ -497,10 +530,15 @@ for (const { defect, file, from, to } of MUTATIONS) {
|
||||
let red = false;
|
||||
let by = '';
|
||||
try {
|
||||
execFileSync('node', ['--test', ...TESTS], { cwd: ROOT, encoding: 'utf8', stdio: 'pipe' });
|
||||
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')
|
||||
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('; ');
|
||||
|
||||
@@ -12,10 +12,14 @@ import { after, before, test } from 'node:test';
|
||||
import puppeteer from 'puppeteer';
|
||||
|
||||
const root = new URL('../', import.meta.url);
|
||||
const template = await readFile(
|
||||
new URL('src/ui/templates/provider-settings.html', root),
|
||||
const view = await readFile(
|
||||
new URL('src/ui/views/ProviderSettingsView.vue', root),
|
||||
'utf8',
|
||||
);
|
||||
const template = view.slice(
|
||||
view.indexOf('<template>') + '<template>'.length,
|
||||
view.lastIndexOf('</template>'),
|
||||
);
|
||||
const assets = new Set([
|
||||
'/src/keySetup.js',
|
||||
'/src/voice/cloudVoiceAuth.js',
|
||||
|
||||
@@ -74,7 +74,7 @@ export function allocationTestArgs(file) {
|
||||
/** Node flags every unit-test process receives. */
|
||||
export const UNIT_TEST_NODE_FLAGS = Object.freeze([
|
||||
// Single-file components compile on import.
|
||||
'--import=./src/testSupport/registerVue.mjs',
|
||||
'--import=./build/registerVue.js',
|
||||
]);
|
||||
|
||||
function runTests(args) {
|
||||
|
||||
Reference in new issue
Block a user