refactor: compose UI shell with explicit application operations and component owners

This commit is contained in:
Sameh Khamis committed 2026-09-13 04:26:09 -07:00
1 parent 8a226a2fe3
commit f5ef9091f0
67 files changed
+15799 -15059

No files matched your search

+15 -14
View File
@@ -1,3 +1,4 @@
import { readStylesheet } from './testSupport/readStylesheet.mjs';
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
@@ -89,15 +90,15 @@ test('deferred terminal notices lose ownership to newer acquisition epochs and d
});
test('share-follow failures use the universal top-center status instead of the bottom toast', () => {
const ui = readFileSync(new URL('./ui.js', import.meta.url), 'utf8');
const ui = readFileSync(new URL('./ui/applicationShell.js', import.meta.url), 'utf8');
const start = ui.indexOf(' _handleShareTrackingRestoreStatus(result) {');
const end = ui.indexOf('\n _initGlobalContextPanel() {', start);
const handler = ui.slice(start, end);
assert.match(handler, /this\._showGlobalStatusNotice\(message\)/);
assert.match(handler, /this\.initialRestorePromise\.then\(showAfterStartupCover\)/);
assert.match(handler, /requestAnimationFrame\(\(\) => \{/);
assert.match(handler, /startupCover\.addEventListener\('transitionend', showOnce, \{ once: true \}\)/);
assert.match(handler, /fallbackTimer = setTimeout\(showOnce, 1000\)/);
assert.match(handler, /this\._lifetime\.frame\(\(\) => \{/);
assert.match(handler, /this\._lifetime\.listen\(startupCover, 'transitionend', showOnce, \{ once: true \}\)/);
assert.match(handler, /fallbackTimer = this\._lifetime\.timeout\(showOnce, 1000\)/);
assert.doesNotMatch(handler, /this\._showToast\(message\)/);
assert.doesNotMatch(handler, /pushCockpitSignal/);
assert.match(handler, /result\.classification === 'pending'/);
@@ -195,13 +196,13 @@ test('replacement, repetition, and hidden-tab elapsed time use the newest fixed
});
test('universal notice lifecycle clears on dispose and uses the one top-center live region', () => {
const ui = readFileSync(new URL('./ui.js', import.meta.url), 'utf8');
const ui = readFileSync(new URL('./ui/applicationShell.js', import.meta.url), 'utf8');
const html = readFileSync(new URL('../index.html', import.meta.url), 'utf8');
const disposeStart = ui.indexOf(' async dispose() {');
const disposeEnd = ui.indexOf('\n }\n', disposeStart);
const dispose = ui.slice(disposeStart, disposeEnd);
assert.match(dispose, /this\._globalStatusNotice = null;/);
assert.match(dispose, /this\._feedback\._globalStatusNotice = null;/);
assert.match(dispose, /this\._shareTrackingNoticeGeneration \+= 1;/);
assert.match(html, /<div id="global-loading-status" role="status" aria-live="polite" aria-atomic="true" hidden>/);
});
@@ -235,7 +236,7 @@ test('reveals sustained loading and then a bounded completion state', () => {
});
test('terminal loading feedback centers its label without an empty detail slot', () => {
const css = readFileSync(new URL('../style.css', import.meta.url), 'utf8');
const css = readStylesheet(new URL('../style.css', import.meta.url));
assert.match(
css,
/#global-loading-status:is\(\[data-state='complete'\], \[data-state='cancelled'\], \[data-state='error'\]\)\s*\{[\s\S]*?justify-content:\s*center;[\s\S]*?text-align:\s*center;/,
@@ -531,8 +532,8 @@ test('the settled traffic chip shows exactly one percentage — the coverage it
});
test('the chip renderer clears the progress slot instead of stranding the last value', () => {
const ui = readFileSync(new URL('./ui.js', import.meta.url), 'utf8');
const css = readFileSync(new URL('../style.css', import.meta.url), 'utf8');
const ui = readFileSync(new URL('./ui/shellFeedback.js', import.meta.url), 'utf8');
const css = readStylesheet(new URL('../style.css', import.meta.url));
const start = ui.indexOf(' _updateTrafficSyncChip(');
assert.ok(start > 0, '_updateTrafficSyncChip is missing');
const body = ui.slice(start, ui.indexOf('\n }', start));
@@ -620,7 +621,7 @@ test('aggregates Mapped Installations refresh beside CCTV without changing eithe
// stack layout contract — the ticker's lifecycle is pinned against ui.js
// source. (perf rebase 2026-08-17)
test('the loading ticker never runs hidden and stops after loading and notices settle', () => {
const ui = readFileSync(new URL('./ui.js', import.meta.url), 'utf8');
const ui = readFileSync(new URL('./ui/shellFeedback.js', import.meta.url), 'utf8');
// Scope every assertion to _armLoadingFeedbackTicker's own body. The
// neighbouring _startTrafficChipTicker is a deliberately PERMANENT 500ms
// safety-net poll, so its `if (document.hidden) return;` is correct there
@@ -667,17 +668,17 @@ test('the loading ticker never runs hidden and stops after loading and notices s
// 4. Returning to visible resamples, so a batch that finished while hidden
// is reconciled and a still-running one re-arms its ticker. The handler
// must live on the class that OWNS _updateGlobalLoadingFeedback
// (StyleManager) — wiring it into a neighbouring controller instead
// (ShellFeedback) — wiring it into a neighbouring controller instead
// throws on every visibilitychange — and must be torn down with the rest.
assert.match(
ui,
/this\._loadingVisibilityHandler = \(\) => \{\s*if \(!document\.hidden\) this\._updateGlobalLoadingFeedback\(\);\s*\};\s*document\.addEventListener\('visibilitychange', this\._loadingVisibilityHandler\);/,
/this\._loadingVisibilityHandler = \(\) => \{\s*if \(!document\.hidden\) this\._updateGlobalLoadingFeedback\(\);\s*else this\._stopLoadingFeedbackTicker\(\);\s*\};\s*document\.addEventListener\('visibilitychange', this\._loadingVisibilityHandler\);/,
'visibilitychange must resample the chip on return',
);
const styleManager = ui.slice(ui.indexOf('export class StyleManager'));
const styleManager = ui.slice(ui.indexOf('export class ShellFeedback'));
assert.ok(
styleManager.includes('this._loadingVisibilityHandler'),
'the resample handler must be owned by StyleManager, which defines _updateGlobalLoadingFeedback',
'the resample handler must be owned by ShellFeedback, which defines _updateGlobalLoadingFeedback',
);
assert.match(
ui,