feat(ui): render application templates as Vue views mounted before wiring

Templates listed in build/applicationViews.js expand to mount hosts, and
mountApplicationViews() renders them before translation and wiring. Markup
tests read the page through a server-side render helper, the i18n scanner
reads view templates, and Vue preserves whitespace so views keep the exact
DOM. No template is converted yet.
This commit is contained in:
Sameh Khamis committed 2026-10-30 17:23:45 -07:00
1 parent b7e7058a5c
commit 9d24945143
32 files changed
+243 -63

No files matched your search

+2 -2
View File
@@ -52,7 +52,7 @@ import {
} from '../i18n/index.js';
import { applyLocaleChoice, mountLocalePicker } from './localePicker.js';
import { PanelNavigation } from './panelNavigation.js';
import { createUiRuntime } from './vueRuntime.js';
import { applicationPinia, createUiRuntime } from './vueRuntime.js';
/**
* Central UI orchestrator for the God's Eye View application.
@@ -136,7 +136,7 @@ export class StyleManager extends ShellFacade {
this._mobileShell?.sharedCollapsed(panelId),
});
// Vue views share this runtime and its stores; dispose unmounts them.
this._ui = createUiRuntime();
this._ui = createUiRuntime({ pinia: applicationPinia() });
this._feedback = new ShellFeedback({
readLayers: () => this._dataManager?.getAll?.() || [],
});
+2 -2
View File
@@ -2,11 +2,11 @@ import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { expandApplicationHtml } from '../../build/application-html.js';
import { renderApplicationHtml } from '../testSupport/applicationHtml.mjs';
import { createBrowserViteConfig } from '../../build/vite.js';
import { R820T_GAIN_STEPS_DB } from '../sdr/gain.js';
const html = expandApplicationHtml(
const html = await renderApplicationHtml(
readFileSync(new URL('../../index.html', import.meta.url), 'utf8'),
);
const css = readFileSync(
+6
View File
@@ -98,3 +98,9 @@
font: 600 9px/14px var(--font-mono);
text-align: center;
}
/* Application view hosts (src/ui/views): the view's own elements lay out as
children of the page, as the static markup they replace did. */
.gev-view {
display: contents;
}
+27
View File
@@ -0,0 +1,27 @@
import { createApp } from 'vue';
import { applicationPinia } from '../vueRuntime.js';
/**
* Application views by template name (build/applicationViews.js lists the
* same names). Each renders the markup its static template held.
*/
export const APPLICATION_VIEWS = Object.freeze({});
const mounted = new WeakSet();
/**
* Render every application view into its host. Views belong to the page, as
* their static markup did, so they are never unmounted. Call before anything
* reads or translates the page; repeated calls are harmless.
* @param {Document} [doc] Page document.
*/
export function mountApplicationViews(doc = globalThis.document) {
for (const host of doc?.querySelectorAll?.('[data-gev-view]') ?? []) {
if (mounted.has(host)) continue;
const name = host.getAttribute('data-gev-view');
const view = APPLICATION_VIEWS[name];
if (!view) throw new Error(`Unknown application view: ${name}`);
createApp(view).use(applicationPinia()).mount(host);
mounted.add(host);
}
}
+16 -4
View File
@@ -1,13 +1,25 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
let pagePinia = null;
/**
* Create the application's Vue runtime: one Pinia instance shared by every
* view the shell mounts, and the mounts themselves for teardown.
* The page's Pinia instance, shared by the application views and the shell,
* so a view and a shell control read the same stores.
* @returns {import('pinia').Pinia}
*/
export function applicationPinia() {
pagePinia ??= createPinia();
return pagePinia;
}
/**
* Create a Vue runtime: one Pinia instance shared by every view it mounts,
* and the mounts themselves for teardown.
* @param {{pinia?: import('pinia').Pinia}} [options]
* @returns {{pinia: import('pinia').Pinia, mount: Function, destroy: Function}}
*/
export function createUiRuntime() {
const pinia = createPinia();
export function createUiRuntime({ pinia = createPinia() } = {}) {
const apps = new Set();
return {
pinia,