This commit is contained in:
Memel 2026-09-09 21:38:57 +02:00 committed by GitHub
commit 58159a1bb9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 103 additions and 2 deletions

View File

@ -5,6 +5,16 @@ of current runtime behavior, see [`docs/CURRENT-STATE.md`](docs/CURRENT-STATE.md
## [Unreleased]
### Changed
- The Material Symbols icon font is now requested as a subset of the glyphs the
app actually renders, and the unused Material Icons Round stylesheet is gone.
Loading the app pulled 442 kB from Google Fonts over six requests, 330 kB of
it the complete icon font served for the 28 glyphs on screen; it now pulls
115 kB over five. `src/materialSymbolsSubset.test.mjs` fails when a source
names a glyph the subset is missing, because an absent glyph renders as its
own name rather than as a missing-glyph box.
### Fixed
- Mapped-site outages show their scheduled retry countdown and distinguish

View File

@ -9,8 +9,11 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet" />
<!-- Icon font subset to the glyphs this app renders. Keep `icon_names` in sync
with the sources: src/materialSymbolsSubset.test.mjs fails when a source
names a glyph this list is missing, because a missing glyph does not draw
a box — the ligature never forms and the element renders the literal word. -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,chevron_left,chevron_right,close,close_fullscreen,dark_mode,east,flare,flight,layers_clear,light_mode,local_fire_department,my_location,navigation,normal,on,open_in_full,public,radar,radio,right_panel_close,right_panel_open,rocket_launch,skip_next,skip_previous" rel="stylesheet" />
</head>
<body>
<div id="cesiumContainer"></div>

View File

@ -0,0 +1,88 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const SRC_ROOT = fileURLToPath(new URL('.', import.meta.url));
const REPO_ROOT = path.resolve(SRC_ROOT, '..');
const INDEX_HTML = path.join(REPO_ROOT, 'index.html');
/** The glyph written as element text: `<span class="material-symbols-outlined">radar</span>`. */
const SPAN_TEXT = /class="[^"]*material-symbols-outlined[^"]*"[^>]*>\s*([a-z0-9_]+)\s*</g;
/** A whole `textContent =` statement, across lines, so a multi-line ternary is read once. */
const TEXT_ASSIGNMENT = /(?:textContent|innerText)\s*=\s*([^;]{0,400})/gs;
const STRING_LITERAL = /['"`]([a-z0-9_]{2,})['"`]/g;
/** Files that SHIP markup. Tests assert on markup, they do not render it. */
function sourceFiles(directory = SRC_ROOT) {
const files = [];
for (const entry of readdirSync(directory, { withFileTypes: true })) {
const absolute = path.join(directory, entry.name);
if (entry.isDirectory()) files.push(...sourceFiles(absolute));
else if (entry.isFile() && /\.(js|mjs)$/.test(entry.name) && !entry.name.endsWith('.test.mjs')) {
files.push(absolute);
}
}
return [...files.sort(), INDEX_HTML];
}
/**
* Glyph names the sources ask the icon font for.
*
* Errs WIDE on purpose. A name the font does not carry costs nothing Google
* ignores an unknown `icon_names` entry and still returns 200 while a glyph
* the subset is missing breaks the interface SILENTLY: the ligature never
* forms, so the element renders the literal word `right_panel_open` instead of
* falling back to a visible box.
*
* A literal to the LEFT of a `?` is the condition being tested, not the text
* being shown, so it is dropped: `status === 'loading' ? …` must not enrol
* `loading`. Optional chaining is neutralised first `payload?.newsStatus`
* is not a ternary, and splitting on its `?` would keep the condition.
* @returns {Map<string, string>} glyph -> the first file that names it.
*/
function referencedGlyphs() {
const found = new Map();
for (const file of sourceFiles()) {
const source = readFileSync(file, 'utf8');
const relative = path.relative(REPO_ROOT, file).split(path.sep).join('/');
const add = (glyph) => { if (!found.has(glyph)) found.set(glyph, relative); };
for (const match of source.matchAll(SPAN_TEXT)) add(match[1]);
for (const match of source.matchAll(TEXT_ASSIGNMENT)) {
const statement = match[1].replaceAll('?.', '.');
const assigned = statement.includes('?') ? statement.slice(statement.indexOf('?') + 1) : statement;
for (const literal of assigned.matchAll(STRING_LITERAL)) add(literal[1]);
}
}
return found;
}
/** The `icon_names` list index.html asks Google for. */
function subsettedGlyphs(html = readFileSync(INDEX_HTML, 'utf8')) {
const match = /Material\+Symbols\+Outlined[^"]*[?&]icon_names=([a-z0-9_,]+)/.exec(html);
assert.ok(match, 'index.html must request Material Symbols with an icon_names subset');
return new Set(match[1].split(','));
}
test('every glyph the sources render is in the icon_names subset', () => {
const subset = subsettedGlyphs();
const missing = [...referencedGlyphs()]
.filter(([glyph]) => !subset.has(glyph))
.map(([glyph, file]) => `${glyph} (${file})`);
assert.deepEqual(
missing,
[],
'Glyphs named by the sources but absent from the index.html icon_names list. '
+ 'Add them there — an unlisted glyph renders as its own name on screen: '
+ missing.join(', '),
);
});
test('the unused Material Icons Round family is not loaded', () => {
// A second icon font, 173 kB, for a family no source ever uses — and
// src/cockpitMarkup.test.mjs already asserts the markup must not use it.
const html = readFileSync(INDEX_HTML, 'utf8');
assert.doesNotMatch(html, /Material\+Icons\+Round/, 'index.html loads an icon font nothing renders');
});