mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 03:09:57 +00:00
29 constants, 190 lines. highway.js 4,267 -> 4,158. The first real slice, and the one that
every later one imports.
━━━ WHY ONLY THE CONSTANTS MAY LIVE AT MODULE SCOPE ━━━
createHighway() is a FACTORY, not a singleton. The constitution publishes
window.createHighway precisely so a plugin can build a SECOND highway for its own panel, and
highway.js already says so at the top of the closure:
// R3c: per-instance mutable state in one object, so extracted renderer/ws
// modules can close over it as a factory arg without cross-panel sharing.
So hwState — all 79 mutable properties — must NEVER become a module-level singleton: two
highways would silently share it, and one panel would drive the other's clock, scale and
colour tables. Extracted functions will take it as an ARGUMENT.
That is the OPPOSITE of the app.js carve, where a single state container (player-state.js,
library-state.js) was exactly right, because there is exactly one app. Same epic, same
language, opposite answer — because one is a singleton and the other is a factory.
These 29 are pure literals: numbers, strings and colour tables, never reassigned, never
mutated. Sharing them across instances is not merely safe, it is what you want — one copy of
the shimmer LUT bounds and the string palettes rather than one per panel. Anything with a
runtime dependency (document, window, performance, localStorage) stays in the factory;
checked, and none of these has one.
ESLint now knows static/highway.js is a module. It could not have known before this commit:
the flip (#913) changed the SCRIPT TAG, but the file had no import/export yet, so it still
parsed as a script and lint stayed green. The first `import` is what makes the config wrong.
TESTS. Four source-shape harnesses asserted `const _AUTO_SCALE_MIN = …` etc. lived in
highway.js. They now read highway.js AND every static/js/highway-*.js — deliberately, rather
than being re-pinned at whichever file currently holds a constant. Re-pinning just breaks
again on the next carve, and a source-shape assertion that silently stops finding its target
is indistinguishable from one that passes. Bite-tested: renaming two constants away fails
them.
VERIFIED. A/B against origin/main: 15 probes IDENTICAL, zero page errors. AND THE PERF GATE
PASSES AT 1.97ms against its 12ms budget — which is the point of having built it (#910)
first: these constants moved from closure scope to module scope, and V8 does not treat those
identically. It does here. Now I know rather than hope.
node 1045, pytest 2416, ESLint 0, Codex 0.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
80 lines
3.9 KiB
JavaScript
80 lines
3.9 KiB
JavaScript
// Source-level guards for the playback-aware paused-render throttle
|
|
// (feedBack#654). The createHighway closure owns the rAF loop + WebGL
|
|
// context lifecycle that's too heavy to reproduce in a vm sandbox, so —
|
|
// like highway_visibility.test.js — these checks lock in the wiring.
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const highwayJs = path.join(__dirname, '..', '..', 'static', 'highway.js');
|
|
|
|
// Brace-balanced extraction (shared shape with highway_visibility.test.js)
|
|
// so a future edit that grows the loop body doesn't get truncated.
|
|
function extractBlock(src, signature) {
|
|
const start = src.indexOf(signature);
|
|
assert.ok(start !== -1, `signature '${signature}' not found`);
|
|
const openBrace = src.indexOf('{', start);
|
|
assert.ok(openBrace !== -1, `opening brace after '${signature}' not found`);
|
|
let depth = 1;
|
|
let i = openBrace + 1;
|
|
while (i < src.length && depth > 0) {
|
|
const ch = src[i];
|
|
if (ch === '{') depth++;
|
|
else if (ch === '}') depth--;
|
|
i++;
|
|
}
|
|
assert.ok(depth === 0, `unbalanced braces after '${signature}'`);
|
|
return src.slice(start, i);
|
|
}
|
|
|
|
|
|
// R3c: highway.js is being carved into modules, so its source is no longer ONE file. Read the
|
|
// whole set. Re-pinning these assertions at whichever file currently holds a constant just
|
|
// means they break again on the next carve — and worse, a source-shape assertion that silently
|
|
// stops finding its target is indistinguishable from one that passes.
|
|
function highwaySources() {
|
|
const root = path.join(__dirname, '..', '..');
|
|
const jsDir = path.join(root, 'static', 'js');
|
|
const parts = [fs.readFileSync(path.join(root, 'static', 'highway.js'), 'utf8')];
|
|
for (const f of fs.readdirSync(jsDir).sort()) {
|
|
if (f.startsWith('highway-') && f.endsWith('.js')) {
|
|
parts.push(fs.readFileSync(path.join(jsDir, f), 'utf8'));
|
|
}
|
|
}
|
|
return parts.join('\n');
|
|
}
|
|
|
|
test('highway declares the paused-render throttle state', () => {
|
|
const src = highwaySources();
|
|
assert.match(src, /(?:export\s+)?const\s+_PAUSED_FRAME_INTERVAL_MS\s*=\s*\d+/, 'missing _PAUSED_FRAME_INTERVAL_MS cap');
|
|
assert.match(src, /hwState\._lastPausedDrawAt\s*=\s*0/, 'missing _lastPausedDrawAt accumulator');
|
|
});
|
|
|
|
test('draw() throttles full renders while the audio clock is stalled', () => {
|
|
const src = highwaySources();
|
|
const fn = extractBlock(src, 'function draw()');
|
|
// Reuse getTime()'s pause signal rather than inventing a parallel one.
|
|
assert.match(fn, /_chartLastAdvanceAt/, 'throttle must key off _chartLastAdvanceAt (the advance timestamp)');
|
|
assert.match(fn, /_CHART_MAX_INTERP_MS/, 'throttle must reuse the _CHART_MAX_INTERP_MS pause threshold');
|
|
assert.match(fn, /_PAUSED_FRAME_INTERVAL_MS/, 'throttle must cap paused draws to _PAUSED_FRAME_INTERVAL_MS');
|
|
assert.match(fn, /hwState\._lastPausedDrawAt\s*=\s*_nowP/, 'throttle must record the last paused draw time');
|
|
});
|
|
|
|
test('throttle runs after the ready gate, before bundle/draw', () => {
|
|
const src = highwaySources();
|
|
const fn = extractBlock(src, 'function draw()');
|
|
// Regex landmarks (not exact-string indexOf) so harmless spacing /
|
|
// semicolon changes don't break the ordering guard — matches the
|
|
// search-based style of the other highway source-guard tests.
|
|
const readyIdx = fn.search(/if\s*\(\s*!hwState\.ready\s*\)\s*return;/);
|
|
const throttleIdx = fn.search(/_PAUSED_FRAME_INTERVAL_MS/);
|
|
const drawIdx = fn.search(/_renderer\.draw\s*\(/);
|
|
assert.ok(readyIdx !== -1, 'ready gate not found');
|
|
assert.ok(throttleIdx !== -1, 'throttle not found');
|
|
assert.ok(drawIdx !== -1, '_renderer.draw call not found');
|
|
assert.ok(readyIdx < throttleIdx, 'throttle must come after the ready gate');
|
|
assert.ok(throttleIdx < drawIdx, 'throttle must come before the renderer draw');
|
|
});
|