From 08e225bb75e147bdca2b7247568d3cde25907628 Mon Sep 17 00:00:00 2001 From: byrongamatos Date: Sun, 12 Jul 2026 12:03:59 +0200 Subject: [PATCH] refactor(highway): flip highway.js to an ES module (R3c) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two lines. index.html: defer -> type="module". highway.js: one explicit assignment. highway.js can now `import`, which is the whole point — the carve can begin. ━━━ THE ONE THING THE FLIP ACTUALLY BREAKS: window.createHighway ━━━ A top-level `function createHighway()` in a CLASSIC script IMPLICITLY becomes window.createHighway. In a module it does not — module declarations are module-scoped, and the name vanishes from the global object the instant the tag grows type="module". The constitution names window.createHighway as PUBLIC EXTENSION CONTRACT (alongside window.playSong / showScreen / feedBack). NOTHING IN-TREE CALLS IT. That is exactly why this would have shipped: the only consumers are third-party plugins rendering their own highway panel, and I cannot grep those. Green CI, green tests, and a broken plugin API. Verified by removing the assignment and reloading: flip WITHOUT an explicit assignment: window.createHighway === undefined <-- gone flip WITH it: window.createHighway === function So it is assigned explicitly now — same object, same behaviour, no longer an accident of how the file happens to be loaded. ━━━ AND A CORRECTION TO #912 ━━━ #912 (merged) rewrote 73 bare `highway.x` -> `window.highway.x` on the stated grounds that the flip would turn every one of them into a ReferenceError. HAVING NOW ACTUALLY FLIPPED IT, THAT WAS WRONG. highway.js already did `window.highway = highway`, which puts the name on the GLOBAL OBJECT — and bare-identifier resolution falls back to the global object whether or not a lexical global binding exists. Measured on both builds: bare `highway` resolves either way. #912 is defensible as hygiene and it does not hurt, but it was not a precondition and it fixed no latent bug. A correction is posted on the PR so its commit message does not mislead. The real hazard was the factory, not the instance — same class of breakage, wrong name. ORDERING is unchanged: classic-defer and non-async type="module" share ONE post-parse execution queue, in document order, so highway.js keeps its position at index.html:1244. VERIFIED. A/B against origin/main: 15 probes IDENTICAL, zero page errors — window.highway, window.createHighway, the full API surface, a real song playing, the chart clock advancing, and the seek->setTime sync. THE PERF GATE PASSES at 1.85ms against its 12ms budget (module evaluation costs nothing at render time), which is exactly what #910 was built to tell me. node 1045, pytest 2416, ESLint 0, Codex 0. Co-Authored-By: Claude Opus 4.8 (1M context) --- static/highway.js | 15 +++++++++++++++ static/v3/index.html | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/static/highway.js b/static/highway.js index 0ad47564..7188aeae 100644 --- a/static/highway.js +++ b/static/highway.js @@ -4240,6 +4240,21 @@ function createHighway() { } const highway = createHighway(); window.highway = highway; // expose for plugins + +// THE FACTORY IS PART OF THE PUBLIC CONTRACT, and a classic script gave it to us for free. +// +// A top-level `function createHighway()` in a CLASSIC script implicitly becomes +// window.createHighway. In a MODULE it does not — module declarations are module-scoped, and +// the name would silently vanish from the global object the moment this file grew a +// type="module" attribute. The constitution names window.createHighway as public extension +// contract (alongside window.playSong / showScreen / feedBack), and plugins that render their +// own highway panel construct a second instance with it. Nothing in-tree calls it, which is +// exactly why this would have shipped: the only consumers are third-party, and I cannot grep +// those. +// +// So it is assigned explicitly now. Same object, same behaviour, no longer an accident of how +// the file happens to be loaded. +window.createHighway = createHighway; highway.setOnLyricsChange(function(visible) { const btn = document.getElementById('btn-lyrics'); if (btn) { diff --git a/static/v3/index.html b/static/v3/index.html index 6ad6e655..73a67714 100644 --- a/static/v3/index.html +++ b/static/v3/index.html @@ -1241,7 +1241,7 @@

Gameplay Settings

- +