mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-21 20:31:21 +00:00
* refactor(app): carve resume-session out of app.js (R3a)
static/js/resume-session.js (157) — the snapshot taken when you leave a song and the
pill that offers it back. Bodies VERBATIM. app.js 7,727 → 7,601.
Fifth slice out of the strongly-connected core. ONE hook (playSong) + a
currentFilename getter.
S.pendingResume JOINS THE CONTAINER — on demand, exactly as intended. app.js WRITES
it (playSong({ resume }) arms it; the song:ready listener consumes it) while this
module reads it, so it cannot be a plain export: an imported binding is read-only.
Same reason isPlaying is there. The container grows one field per carve that needs
it, never speculatively.
THE CONTRACT TEST CAUGHT THE MISSING HOOK, again on a path nothing executes:
"playSong is read by a module but never wired by app.js — it would throw at runtime".
Second time it has caught a real wiring gap the moment it appeared.
A REAL TRAP, worth remembering: I first did the S.pendingResume rewrite by feeding
acorn's identifier RANGES from node into python, and it corrupted the file
(`_pS.pendingResume null;`). **Acorn's offsets are UTF-16 code units; Python's string
indices are code points.** static/app.js contains emoji, so every offset past one
drifts. Do an AST-driven rewrite in the SAME language that produced the offsets.
`node --check` caught it; a silent version of that bug is very easy to imagine.
VERIFIED. A/B against origin/main in two browsers, real song: the window API
(resumeLastSession / _snapshotResumeSession / _readResumeSession /
_clearResumeSession), snapshot, read-back, and clear — IDENTICAL, zero page errors.
HONEST LIMIT: my probe never got the snapshot to actually PERSIST (there is a guard
beyond the 3s minimum position that a scripted playSong does not satisfy), so that
path is verified only as identical-to-main, not as observed-working. The real
coverage is tests/browser/resume-session.spec.ts, which drives the flow properly.
Zero harnesses broke. pytest 2396, node 1040/1040, ESLint 0 (no-cycle clean),
tailwind clean, Codex 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(app): carve the JUCE/desktop audio shims out of app.js (R3a)
static/js/juce-audio.js (994) — bodies VERBATIM. app.js 7,603 → 6,643.
THE LARGEST SINGLE SLICE of the whole carve phase: 960 lines, ~13% of what was left.
Three self-installing IIFEs:
_installJuceEngineRoutingWatcher (444) routes a song to the JUCE engine or HTML5 as
the desktop output enters/leaves exclusive/ASIO
_installRendererBusFeeder (337) feeds the highway renderer bus from whichever
transport is actually running
_installJuceAudioElementShim (156) patches audio.play/pause so the rest of the app
keeps talking to the <audio> element while JUCE
owns the transport
They EXPORT NOTHING — all three publish through `window.*` (_juceMode,
_reevaluateJuceRouting, _reevaluateRendererBus, …). So app.js needs only a
side-effect import plus the one binding it actually uses
(_resetJuceAudioShimChain, which the shim IIFE assigns).
THE ORDERING QUESTION, CHECKED RATHER THAN ASSUMED. Importing this module runs the
IIFEs EARLIER than before: imports evaluate ahead of app.js's body, and therefore
ahead of configureHost(). A hook read at IIFE-execution time would THROW. So I walked
the AST at IIFE-body depth to see what they actually touch when they run: nothing but
listener registration, and `audio.play`/`audio.pause` patching — and `audio` is itself
an imported module now. Verified in the browser: both are patched on the carved build
exactly as on main, which proves the shim installs correctly at its new, earlier point.
(Had I got this wrong, host.js throws loudly rather than silently misbehaving — which
is the whole reason it has no no-op defaults.)
VERIFIED. A/B against origin/main in two browsers: the entire window.* surface the
IIFEs publish (_juceMode, _juceOutputIsExclusive, _reevaluateJuceRouting,
_reevaluateRendererBus, _clearJuceRerouteMemo), audio.play/pause patched, a real song
loading and togglePlay driving the public mirror — IDENTICAL, zero page errors.
Harnesses: juce_engine_reroute (19 tests) + renderer_bus_feeder (13) slice the IIFEs by
signature — retargeted, and each sandbox gains a `host` object routed at its EXISTING
stubs so every assertion holds unchanged. test_plugin_runtime_idempotence is SPLIT: 3 of
its 4 source-asserts stayed in app.js, the sm.emit('song:resume') one moved.
pytest 2396, node 1040/1040, ESLint 0 (no-cycle clean), tailwind clean, Codex 0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
43 lines
2.1 KiB
JavaScript
43 lines
2.1 KiB
JavaScript
// Shared, MUTABLE player state.
|
|
//
|
|
// WHY A CONTAINER AND NOT PLAIN EXPORTS. An imported binding is read-only. Every
|
|
// slice carved out of app.js so far has only ever READ the state it shares
|
|
// (loopA/loopB, _audioSeekGen, currentFilename), so a getter hook was enough and no
|
|
// container was needed. That runs out here: count-in genuinely WRITES `isPlaying`
|
|
// (it starts and stops playback) and `lastAudioTime`. `import { isPlaying }` then
|
|
// `isPlaying = true` throws — the binding cannot be assigned to.
|
|
//
|
|
// So the state moves onto an object. `S.isPlaying = true` is a property write, which
|
|
// works from any module holding the same `S`. This is the same shape the stems,
|
|
// studio, and editor migrations converged on.
|
|
//
|
|
// It is deliberately SMALL. app.js has ~104 top-level `let` scalars; lifting all of
|
|
// them would be a ~977-site rewrite for no benefit, since most are private to one
|
|
// cluster and travel with it. Only the ones a carved module must WRITE belong here.
|
|
// Add to it when a carve actually needs it, not before.
|
|
//
|
|
// NB app.js's own 71 reference sites were rewritten mechanically — but from the AST,
|
|
// not by text substitution. Of 100 textual occurrences of these two names, only 71
|
|
// resolve to the module binding: 22 are member accesses (`someObj.isPlaying`), 4 are
|
|
// the local parameter of setPlayButtonState(isPlaying), one is an object key, and two
|
|
// are shorthand properties (`{ isPlaying }`) that must become `{ isPlaying: S.isPlaying }`.
|
|
// A blind find-and-replace corrupts all 29.
|
|
export const S = {
|
|
/** Is the transport running? Written by playback, count-in, and the JUCE shims. */
|
|
isPlaying: false,
|
|
|
|
/**
|
|
* The last audio position we saw, in seconds. Used to detect a seek that did not
|
|
* land where it was asked to (JUCE can clamp; HTML5 can round).
|
|
*/
|
|
lastAudioTime: 0,
|
|
|
|
/**
|
|
* A resume request armed by playSong({ resume }) and consumed on song:ready.
|
|
* Written by app.js (playSong, and the song:ready listener that consumes it) and
|
|
* read by the resume-session module — so, like the two above, it cannot be a plain
|
|
* export.
|
|
*/
|
|
pendingResume: null,
|
|
};
|