mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-21 20:31:21 +00:00
Puts every external `<script>` in the v3 shell into the deferred queue, and
keeps each script's boot() firing at DOMContentLoaded exactly as it does today.
Behaviourally a no-op; it is what makes the ES-module flips safe.
WHY. `type="module"` defers execution to after HTML parse. Classic-`defer` and
module scripts share ONE "execute after parsing" list and run in DOCUMENT ORDER,
but a plain classic script runs DURING parse — ahead of all of them. So the
moment capabilities.js becomes a module while app.js is still plain, app.js runs
FIRST, and its 11 top-level `window.feedBack.on(...)` calls (app.js:6245-6722)
hit a bare `{}` — `_ensureFeedBackEventBus()` (capabilities.js:33), which
attaches .on/.emit/.off, would not have run yet. TypeError, app.js dies
mid-parse. Deferring everything now keeps document order == execution order
through the rest of the migration.
THE CATCH (Codex preflight caught this — a real ordering change). 22 scripts
guard their boot with `if (document.readyState === 'loading')`. A deferred
script runs at readyState 'interactive', so that test is FALSE and the else-branch
fires boot() immediately, at the script's position in document order — instead of
at DOMContentLoaded, after every script has evaluated.
That matters far more than one call site: a scan of the shell's scripts found
**43 forward references** where a script's boot() reads a global that a LATER
script defines (shell.js -> profile.js's window.v3Onboarding, songs.js ->
settings.js's window._confirmDialog, badges.js -> songs.js's
window.displayTuningName, ...). Every one of them resolves today only because
all boots happen at DOMContentLoaded. So the guards now treat 'interactive' as
not-ready (`!== 'complete'`), restoring that exactly.
Codex's specific finding (first-run onboarding silently skipped) did NOT
reproduce — shell.js's boot() awaits /api/profile, and that yield lets the
remaining deferred scripts run first. But the race it described is real, the
guard is silent when it fails (`&& window.v3Onboarding`), and the other 42
forward refs have no such await protecting them. Fixed at the root rather than
at the one site.
VERIFIED. A/B against origin/main on a fresh profile, 13 probes (onboarding
overlay, v3Onboarding/v3Songs/v3Profile/fbNotify/v3Badges/uiPrompt/showScreen,
bus, capabilities.version, createHighway, plugin scripts, mounted screens):
IDENTICAL, zero console/page errors on both. pytest 2396, node 1028/1028,
ESLint 0 errors, Codex 0.
New guard: test_every_external_script_defers_so_document_order_is_execution_order
fails if any external tag is plain classic — verified to fail on a single
reverted tag, so it actually bites.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
58 lines
2.6 KiB
JavaScript
58 lines
2.6 KiB
JavaScript
// v3 Settings → Accessibility: keeps the "Interface size" control in sync with
|
|
// the host `feedBack.scale` capability. The buttons/slider WRITE via inline
|
|
// `feedBack.scale.set(...)`; this module only REFLECTS current state (active
|
|
// preset, slider position, % readout) so the control mirrors the live value on
|
|
// load, on every change, and each time Settings is opened.
|
|
//
|
|
// Plain non-module script, matching the rest of static/v3/*. Null-guarded so it
|
|
// no-ops on the classic v2 page (which has no Accessibility panel).
|
|
(function () {
|
|
'use strict';
|
|
|
|
function sync(state) {
|
|
if (!state) {
|
|
var cap = window.feedBack && window.feedBack.scale;
|
|
state = cap && typeof cap.get === 'function' ? cap.get() : null;
|
|
}
|
|
if (!state) return;
|
|
|
|
var seg = document.getElementById('setting-interface-size');
|
|
if (seg) {
|
|
seg.querySelectorAll('.fb-seg-btn').forEach(function (b) {
|
|
var on = Math.abs(parseFloat(b.dataset.scale) - state.value) < 0.001;
|
|
b.classList.toggle('active', on);
|
|
b.setAttribute('aria-pressed', on ? 'true' : 'false');
|
|
});
|
|
}
|
|
var slider = document.getElementById('setting-interface-size-slider');
|
|
if (slider && document.activeElement !== slider) {
|
|
slider.value = String(Math.round(state.value * 100));
|
|
}
|
|
var val = document.getElementById('setting-interface-size-val');
|
|
if (val) val.textContent = String(Math.round(state.value * 100));
|
|
}
|
|
|
|
if (window.feedBack && typeof window.feedBack.on === 'function') {
|
|
// Fires on every set() and once on load. The bus delivers a CustomEvent,
|
|
// so we ignore the arg and read the authoritative value via sync() → get().
|
|
window.feedBack.on('scale:changed', function () { sync(); });
|
|
// Re-sync when the user opens Settings (the panel may have re-rendered).
|
|
window.feedBack.on('screen:changed', function (e) {
|
|
var id = e && (e.detail ? e.detail.id : e.id);
|
|
if (id === 'settings') sync();
|
|
});
|
|
}
|
|
// Settings markup is static, but re-sync when settings.js signals it wired.
|
|
document.addEventListener('v3:settings-rendered', function () { sync(); });
|
|
|
|
// `defer` runs this at readyState 'interactive' — later scripts have not
|
|
// evaluated yet, so wait for DOMContentLoaded (see static/v3/index.html).
|
|
if (document.readyState !== 'complete') {
|
|
document.addEventListener('DOMContentLoaded', function () { sync(); }, { once: true });
|
|
} else {
|
|
sync();
|
|
}
|
|
|
|
window.feedBackInterfaceSize = { sync: sync };
|
|
})();
|