Skip to content

fix(studio): audition from the playhead while paused, and let an author out of a menu - #3190

Merged
vanceingalls merged 925 commits into
mainfrom
wa-20d5b-audition-fixes
Aug 13, 2026
Merged

fix(studio): audition from the playhead while paused, and let an author out of a menu#3190
vanceingalls merged 925 commits into
mainfrom
wa-20d5b-audition-fixes

Conversation

@vanceingalls

Copy link
Copy Markdown
Collaborator

Auditioning while paused. Hovering a preset writes it to the running graph, which is silent while the transport is paused — so the whole affordance only worked mid-playback. Hovering now plays from the playhead and leaving stops and returns it exactly where it was. A transport the author started is left alone in both directions.

Getting out of a menu. Opening the preset shelf or add menu hid both buttons, and the only thing that set them back was picking something. The buttons now stay and close what they opened; Escape closes whichever is open.

Also two audition bugs found by review rather than by tests: a teardown keyed on an inline callback reverted the audition ~30 times a second during playback, and sliding from one shelf entry to its neighbour left a measurement in flight.

🤖 Generated with Claude Code

@vanceingalls
vanceingalls force-pushed the wa-20d5b-audition-fixes branch from 83f09a3 to 7eb2dcb Compare August 11, 2026 08:35

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: LGTM_PENDING_CI — the two features and the transport-request plumbing are correct and well-tested; the only red CI is unrelated .md drift under plans/, skills/hyperframes-audio/, and README.md (9 files), which cascades into preview-regression via the skipped Preflight.

Verified claims (@ 7eb2dcb)

  • "Hovering writes to the running graph, silent while paused": propertyPanelFxSection.tsx:234-244audition() writes via onChainPreview (preview channel).
  • "Hovering now plays from the playhead": propertyPanelAudioFxGroup.tsx:598-610 — captures store.currentTime on (true), requests requestPlayback(true); on (false) requests requestPlayback(false, returnTo). useTimelinePlayer.ts:386-396 subscribes and calls pause() then seek(returnTo).
  • "Transport the author started is left alone, in both directions": propertyPanelAudioFxGroup.tsx:600-609 — guard store.isPlaying || auditionReturn.current !== null on enter; on leave, no-ops if auditionReturn.current === null. Correct in both directions.
  • "Order matters": propertyPanelFxSection.tsx:239-244onChainPreview(...) before onAuditionTransport?.(true); onAuditionTransport?.(false) before the revert onChainPreview(auditionBase.current). Justified in comment.
  • "Buttons stay and close what they opened, one closes the other": propertyPanelFxSection.tsx:843-885 — buttons rendered unconditionally with toggling onClick and aria-expanded. Opening add closes preset (setPicking(false)) and vice versa.
  • "Escape closes whichever is open": propertyPanelFxSection.tsx:533-543closeMenus gated on event.key !== "Escape" || (!adding && !picking); stopPropagation() only fires when there's a menu to close.
  • Nonce mechanism: playerStore.ts:355-359nonce: (prev.nonce ?? 0) + 1 per request; consumed via nonce !== prev.playbackRequest?.nonce at useTimelinePlayer.ts:390.

OLD-assumption counterfactuals (each new test)

  • plays from the playhead, then puts it back — OLD: audition() never called onAuditionTransport, so playbackRequest stays null. expect(...playing).toBe(true) fails.
  • leaves a transport the author started alone — OLD (no isPlaying guard): nonce bumps on enter/leave despite already-playing. toBe(before) fails.
  • starts playback for paused author / stops it on the way out / stops on apply / stops on unmount — each fails without the corresponding onAuditionTransport?.(false) in applyPreset/addEffect/addJob/addEq and the transportRef.current?.(false) in the unmount effect (propertyPanelFxSection.tsx:274).
  • Menu tests (closes with same button, add menu same way, one closes the other, closes on Escape, puts the chain back on close-while-auditioning) — all fail under the pre-diff {adding || picking ? null : (…buttons)} conditional and the missing onKeyDown={closeMenus}.
  • leaves Escape alone when no menu is open — guards against a naive fix that would stopPropagation() unconditionally.
  • wave only when hovering can be heard — OLD: no hf-fx-preset-wave DOM at all.

Cross-PR coupling

  • auditionBase semantics unchanged; new transportRef mirrors the existing previewRef pattern (propertyPanelFxSection.tsx:270-282) — same defense against inline-callback teardown storms.
  • Every exit path from an audition (applyPreset, addJob, addEffect, addEq, unmount teardown, menu-toggle-close, Escape) now calls onAuditionTransport?.(false) — full enumeration verified.
  • No new HfAudioFxNode field. audioFx.ts untouched; parse/serialize allow-list not affected.
  • Leveller decoded cache and per-shelf auditionEnter counter are on separate paths in propertyPanelAudioFxGroup.tsx; the transport hook is orthogonal.

Nits (non-blocking)

  • Accessibility: after Escape closes a menu, focus lands nowhere — the section div is not focusable. Consider returning focus to the just-closed trigger ("Presets"/"Add effect") for keyboard-only authors.
  • aria-expanded is set on the buttons; aria-haspopup="menu" would sharpen the screen-reader read.
  • auditionTransport is a fresh arrow every AudioFxGroup render → FxSection's audition callback also re-creates every render. The group already re-renders on playhead ticks so this is not a new cost, but a stable ref (useCallback with empty deps + reading store on invocation) would be cheap defense.

CI

  • Preflight fails on oxfmt --check for 9 unrelated .md files (README + plans/… + skills/hyperframes-audio/…) — stack-wide drift, none touched by this PR.
  • preview-regression FAILURE is a cascade: PREVIEW_PARITY_RESULT=skipped because Preflight failed. Not a real signal from this PR.
  • Real signals: player-perf, regression, WIP all green. Preview parity step itself was SKIPPED, not run.

— Review by tai (pr-review)

@miga-heygen miga-heygen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: fix(studio): audition from the playhead while paused, and let an author out of a menu — #3190

Verdict: LGTM

Playhead-while-paused fix is correct. Reads store.currentTime at audition start, stores in auditionReturn.current, passes back as returnTo when stopping. The consumer processes via pause() then seek(returnTo). The nonce-based request detection handles consecutive same-direction requests and rapid hover/leave sequences. The "already playing" guard correctly leaves the author's transport alone (tested).

Audition ordering is deliberate: start writes the chain FIRST then starts transport (first audible frame is the preset); stop halts transport FIRST then reverts chain (last audible frame is the preset, not the snap-back).

Menu dismiss — three gestures: toggle button click (Presets→Close), Escape key (with stopPropagation to prevent panel's own Escape from deselecting the clip), mutual exclusivity (opening one menu closes the other). No click-outside, consistent with the toggle-button pattern.

No regressions to hover-audition from #3192. The FxPresetMenu enter/leave wiring is unchanged. The unmount cleanup is strengthened to also stop transport.

Tests cover: start-from-paused + return, already-playing no-op, close button (both menus), Escape (both menus), mutual exclusivity, cleanup on close with active audition. Wave indicator gets conditional rendering test and prefers-reduced-motion: reduce support.

Nit: playbackRequest: null is missing from createTimelineResetState() — the other request-type state (requestedSeekTime, clipRevealRequest) is reset there. Low practical risk but inconsistent.


Review by Miga

🤖 Generated with Claude Code

@vanceingalls
vanceingalls force-pushed the wa-20d5b-audition-fixes branch from 7eb2dcb to 3972215 Compare August 12, 2026 02:57
@vanceingalls
vanceingalls force-pushed the wa-20d5b-audition-fixes branch from 3972215 to 0a6bf88 Compare August 12, 2026 04:49
@vanceingalls
vanceingalls force-pushed the wa-20d5b-audition-fixes branch from 0a6bf88 to 788bf68 Compare August 12, 2026 07:27
@vanceingalls
vanceingalls force-pushed the wa-20d5b-audition-fixes branch from 788bf68 to 890c0fd Compare August 12, 2026 07:50
# Conflicts:
#	packages/studio/src/components/editor/propertyPanelAudioFxGroup.test.tsx
#	packages/studio/src/components/editor/propertyPanelAudioFxGroup.tsx
#	packages/studio/src/components/editor/propertyPanelFxSection.tsx
#	skills-manifest.json
#	skills/hyperframes-audio/SKILL.md
#	skills/hyperframes-audio/references/attributes.md
#	skills/hyperframes-audio/scripts/carve.mjs
Base automatically changed from wa-20d5a-preset-ramp to main August 13, 2026 19:35
# Conflicts:
#	packages/core/src/audio/audioFxGraph.ts
#	packages/core/src/audioFx.ts
#	packages/studio/src/components/editor/propertyPanelAudioFxGroup.test.tsx
#	packages/studio/src/components/editor/propertyPanelAudioFxGroup.tsx
#	packages/studio/src/components/editor/propertyPanelFxPresetMenu.tsx
#	packages/studio/src/components/editor/propertyPanelFxSection.test.tsx
#	packages/studio/src/components/editor/propertyPanelFxSection.tsx
#	plans/audio-automation-lanes/SPEC.md
#	plans/audio-fx-presets.md
#	plans/audio-fx-ux/README.md
#	plans/automation-lanes-shared-rows.md
#	plans/automation-time-selection-design.md
@vanceingalls
vanceingalls merged commit 6bcf739 into main Aug 13, 2026
56 of 57 checks passed
@vanceingalls
vanceingalls deleted the wa-20d5b-audition-fixes branch August 13, 2026 20:07
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.

3 participants