Quality refactor#73
Merged
Merged
Conversation
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
…solo, background) Claude-Session: https://claude.ai/code/session_01FGkTWdCL2B5miGHdTQ9qT1
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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.