Skip to content

Quality refactor#73

Merged
mracette merged 76 commits into
2026-overhaulfrom
quality
Jun 26, 2026
Merged

Quality refactor#73
mracette merged 76 commits into
2026-overhaulfrom
quality

Conversation

@mracette

Copy link
Copy Markdown
Owner

No description provided.

mracette and others added 30 commits June 24, 2026 10:48
Removed dead store fields + useTraceUpdate hook + CRA browserslist; fixed
StarQuandrants typo; stripped import-banner and restating comments.
Behavior-identical (no runtime code path changed).

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
Store callback types; config/context types + 2 dead contexts removed;
audio-engine accessor overloads. App/engine/state as-any casts eliminated
(except the deferred background-mode bug). Behavior-identical (types only).

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
Moving initAppState into an effect (parent) changed effect ordering so the
mute effect (child) ran before app effects existed, crashing on
getEffects().premaster. Gate it on wawLoadStatus; premaster.gain defaults
to 1 and mute starts false, so the pre-load run was a redundant no-op.
Behavior-identical.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
AppWrap side effects -> mount effects; MusicPlayer IIFE-in-JSX + redundant
JSX memos removed; mute effect gated on wawLoadStatus (effect-ordering fix).
Behavior-identical.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
Invisible fixes (stop() arg, dead getFrequencyBins, cleanup null-guard);
order-preserving voice/player updates; background-mode random-voice fix
(bug #1 — the one sanctioned behavior change, user-confirmed logic).

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
JSDoc on the core engine (Scheduler, AudioPlayerWrapper, WebAudioWrapper,
Analyser, musicPlayerStore, shared hooks, SceneManager). Comments-only,
behavior-identical; all factual claims verified against the code.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
ToggleButton derives its state from the store; per-group polyphony/override
moved into the store; ToggleButtonGroupReducer deleted. The triple-tracked
voice state now lives once. Behavior-identical (override/polyphony probed).

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
Q3 removed useCallback/useMemo from MusicPlayer assuming the React Compiler
would keep them stable, but the component bails compilation (its render calls
store.reset()), so the callbacks were recreated on every render. handleSetCanvasLoadStatus
is a dep of CanvasViz scene-init effect, so the scene re-initialized on every
store change (each toggle) and once on load -- stacking WebGLRenderers on the
canvas and causing flashing, wrong shading, and B&W/distorted visuals.

Restore the manual memos (keeping Q3 IIFE removal). Verified: scene-init runs
once on load, zero on toggle.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
mracette and others added 15 commits June 24, 2026 23:31
autoClear is off, so the freshly-sized buffer's uninitialized GPU memory
survived the first (transparent) paint until a resize reallocated it.
Clear once in onWindowResize, which is also the first-paint path.
Never imported; the live scene is Swamp.ts (used by CanvasViz).
…ays)

Frame-skip throttle in the base render loop: on 120Hz+ panels RAF would render
2x as often for no visible benefit (visuals are time-based via clock delta, not
per-frame) — pure heat/battery. Audio is unaffected (AudioContext clock, not RAF).

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
The threshold was exactly one 60Hz frame, so jittered-short frames got skipped
(every-other -> 30fps); the elapsed%interval re-alignment also made skips
erratic. Render within a 4ms margin of the interval and drop the alignment:
60Hz renders every frame, 120Hz every other = 60.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
Toggles voices over a long spaced session and watches the real leak signals —
live GSAP tweens, pending scheduler events, store voices, JS heap — settling to
idle before the final reading. Result on current code: no leak (tweens/queue
drain to 0, heap plateaus). Run from time to time, not in CI.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
Add SceneManager.dispose() (stop + disposeAll + renderer.dispose) and call it from
CanvasViz cleanup instead of stop()+disposeAll alone. Frees the renderer GPU
resources explicitly rather than relying on GC of the detached canvas.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
Brings in quality 3362cef (drawing-buffer clear / black-rectangle fix).
Clean auto-merge; only SceneManager.ts overlapped, in disjoint regions.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
on: [push, pull_request] fired both events for any branch with an open PR,
running the suite twice. Restrict push to master/2026-overhaul/quality (the
merge targets, which have no PRs); pull_request covers feature branches.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
Audited iconList.svg against real app usage: only 8 of 25 symbols are
referenced (envelope, equalizer, github, home, info, music, plus, twitter).
Removed the other 17 and pruned the now-dead references — the commented-out
instagram entry in SocialIcons and the studio gallery/menu lists.
Audio/model assets are not in git, so a fresh CI checkout had nothing to
serve and every scene-load test timed out. Point the e2e dev server at the
CDN via the existing cloudfront asset mode. Requires permissive CORS on the
CloudFront distribution to allow the cross-origin fetch from the CI server.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
Perf & diagnostics tooling: telemetry, harnesses, 60fps cap, dispose hardening
@mracette mracette changed the base branch from modernization to 2026-overhaul June 26, 2026 02:08
mracette added 13 commits June 25, 2026 21:24
Audit: the store's isLoading flag is initialized true and never set false
anywhere, so LoadingIcon only ever renders the loading animation. The
!isLoading branch (the inert 'Enter' button) and animateReady were
unreachable — the loading screen is dismissed by MusicPlayer unmounting it
once the canvas/audio/song load flags complete, not by isLoading flipping.

Removes: the Enter button + animateReady + isLoading prop in LoadingIcon,
the #loading-button styles, the store's vestigial isLoading field, and the
studio loading story's now-pointless ready/loading toggle.
Replaces the raw CloudFront domain (dwhp4p8tx0lwn.cloudfront.net) with the
custom alias in the production env and the CI e2e asset config.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
In CI all audio/model assets load from the CDN over the network; the
scene-invariants randomize/reset test exceeded the 60s local-calibrated
budget. Doubled only under CI, matching the existing retries pattern.

Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
…mode

- Break each story out of the monolithic stories.tsx into its own file
  under src/studio/stories/ (registry kept in stories/index.tsx).
- Colocate styles with the component they style: each chrome component
  (Studio/StudioSidebar/StudioStage) and each story that needs styling
  (MenuStory/IconGalleryStory) owns a sibling .css.ts. Shared tokens stay
  in src/styles/settings. Removes the monolithic studio.css.ts.
- Remove the dark/light background toggle: the app has no light mode, so it
  was speculative and misrepresented the app. Studio now uses the app's
  actual dark background only.
…radually

The stop branch used gsap.to(strokeDashoffset: 0), which reads its start value
lazily. The active->stopped re-render sets the resting offset to 0 first, so
gsap animated 0->0 (a no-op) and the ring appeared instantly. Use gsap.fromTo
from the full offset, symmetric with the start sweep, so it always fills in.
Driving the svg strokeDashoffset off the live `active` prop made React reset
it on the active->stopped re-render, racing gsap: with gsap.to it snapped full
(React set 0 before gsap read it); with gsap.fromTo it flashed full for a frame
(React painted 0 before gsap's first tick). Seed the resting offset from the
mount-time active state only, so React never re-applies it and gsap owns the
property — gsap.to now reads the real current offset and fills smoothly.
Integrate ~25 commits of quality work (perf-review merge, CDN/CI changes, and the vanilla-extract + cx styling refactor) into the studio branch.

Conflict resolutions keep the studio cleanups on top of quality's refactor:
- ToggleButton: kept the ToggleButtonView extraction; carried quality's scoped toggleButton/svgCircle classes (via cx) into ToggleButtonView.
- LoadingIcon/LoadingScreen/CustomSongIcons.css: kept the dead loading-state removal; dropped quality's now-unused loadingButton style/import.
- SocialIcons: kept the instagram removal on quality's cx-refactored rows.
Studio updated for quality's newly-scoped classes: SongIconsStory uses songSelectionPanel/songLink, MenuStory uses flexPanel; menu e2e selector .menu-button-parent -> .menu-button-child.
- StudioSidebar: compose classes with cx and dedup groups via Set
- Add root CLAUDE.md noting the cx className convention
- Studio: redirect unknown/bare storyId to the default story's URL
- Document /studio dev-server coupling in the e2e spec
- Rename ToggleButtonView active prop to initialActive (write-once contract)

Claude-Session: https://claude.ai/code/session_011utsEBGPQyyYstVH2HdAxR
Add dev-only /studio component playground
@mracette mracette merged commit 059c827 into 2026-overhaul Jun 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant