Skip to content

feat(medium): Refactor: Implement Optimistic UI for Spotify Controls - #9550

Closed
arii wants to merge 17 commits into
leaderfrom
refactor/spotify-optimistic-ui-11189282566171114552
Closed

feat(medium): Refactor: Implement Optimistic UI for Spotify Controls#9550
arii wants to merge 17 commits into
leaderfrom
refactor/spotify-optimistic-ui-11189282566171114552

Conversation

@arii

@arii arii commented Mar 16, 2026

Copy link
Copy Markdown
Owner

Description

This PR implements a comprehensive Optimistic UI pattern for Spotify playback controls to resolve state contention issues and improve overall responsiveness.

Fixes #9295

Changes Made

  • Frontend Sync Lock: Replaced the previous grace period logic with a robust 2000ms "lock" triggered by user interactions. This prevents asynchronous server updates from overwriting local state during transitions.
  • Throttled Volume Control: Volume adjustments are now throttled to 200ms on the client, providing a "live" feel while preventing network congestion.
  • Backend Optimism: Modified SpotifyPolling and SpotifyPlayerManager to broadcast intended state changes immediately upon receiving a command, significantly reducing perceived latency for Play, Pause, and Volume adjustments.
  • UI/UX & Accessibility: Increased touch targets for the VolumeSlider thumb and icons to better support gym/active environments. Added getAriaValueText for improved WCAG compliance.
  • Stability: Added automatic state reversion in the backend if a Spotify API command fails, ensuring the UI eventually reflects the authoritative server state.

Testing

Verified via unit tests, Playwright visual regression tests, and manual screenshot inspection.

Change Type: 🏗️ Refactoring (code change that neither fixes bug nor adds feature)

Related Issues

Closes #9295

Original PR Body

This PR implements a comprehensive Optimistic UI pattern for Spotify playback controls to resolve state contention issues and improve overall responsiveness.

Key changes include:

  • Frontend Sync Lock: Replaced the previous grace period logic with a robust 2000ms "lock" triggered by user interactions. This prevents asynchronous server updates from overwriting local state during transitions.
  • Throttled Volume Control: Volume adjustments are now throttled to 200ms on the client, providing a "live" feel while preventing network congestion.
  • Backend Optimism: Modified SpotifyPolling and SpotifyPlayerManager to broadcast intended state changes immediately upon receiving a command, significantly reducing perceived latency for Play, Pause, and Volume adjustments.
  • UI/UX & Accessibility: Increased touch targets for the VolumeSlider thumb and icons to better support gym/active environments. Added getAriaValueText for improved WCAG compliance.
  • Stability: Added automatic state reversion in the backend if a Spotify API command fails, ensuring the UI eventually reflects the authoritative server state.

Verified via unit tests, Playwright visual regression tests, and manual screenshot inspection.

Fixes #9295


PR created automatically by Jules for task 11189282566171114552 started by @arii

- Implement interaction lock (2s) in SpotifyControls to prevent volume "snap-back".
- Add throttled (200ms) volume command emission during slider adjustment.
- Enhance VolumeSlider accessibility with aria-valuetext.
- Increase VolumeSlider touch targets (thumb, track, icons) for active environments.
- Implement backend optimistic state broadcast for PLAY/PAUSE/SET_VOLUME commands.
- Reduce backend authoritative poll delay to 300ms for faster consistency.
- Add backend state reversion on command failure.

Co-authored-by: arii <342438+arii@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Welcome to HRM!

Thanks for your contribution. This repository uses Gemini AI for automated triage, code review, and generation.

🤖 Gemini Manual Trigger Quick Reference

Command Action
@gemini-bot Run AI Code Review (PR only)
@gemini-triage Run Issue Triage
@gemini-coder <task> Generate Code
@create-review-issues Create issues from review (PR only)
@gemini-help Show this help message
@pr-squash Squash PR commits (PR only)
@conflict-resolve Resolve merge conflicts (PR only)

For more details and GitHub CLI examples, see the Manual Trigger Guide.

@github-actions

Copy link
Copy Markdown
Contributor

ℹ️ Gemini Review Disabled

Automatic review is currently disabled via GEMINI_ENABLE_PR_REVIEW. To run review manually:

Via Comment:

@gemini-bot

Via GitHub Actions:

  1. Go to Actions → Reusable Gemini Review
  2. Click "Run workflow"
  3. Enter PR number: 9550

Via GitHub CLI:

gh workflow run reusable-gemini-review.yml -f pr_number=9550

🤖 Manual Trigger Quick Reference

Command Action
@gemini-bot Run AI Code Review (PR only)
@gemini-triage Run Issue Triage
@gemini-coder <task> Generate Code
@create-review-issues Create issues from review (PR only)
@gemini-help Show this help message

Full reference: Manual Trigger Guide

To re-enable: Set repository variable GEMINI_ENABLE_PR_REVIEW=true

- Implement interaction lock (2s) in SpotifyControls to prevent volume "snap-back".
- Add throttled (200ms) volume command emission during slider adjustment.
- Enhance VolumeSlider accessibility with aria-valuetext.
- Increase VolumeSlider touch targets (thumb, track, icons) for active environments.
- Implement backend optimistic state broadcast for PLAY/PAUSE/SET_VOLUME commands.
- Reduce backend authoritative poll delay to 300ms for faster consistency.
- Add backend state reversion on command failure.
- Fix CI failures by removing redundant 'ref' in actions/checkout across workflows.

Co-authored-by: arii <342438+arii@users.noreply.github.com>
@github-actions github-actions Bot changed the title Refactor: Implement Optimistic UI for Spotify Controls feat(medium): Refactor: Implement Optimistic UI for Spotify Controls Mar 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Manual Trigger Quick Reference

Command Action
@gemini-bot Run AI Code Review (PR only)
@gemini-triage Run Issue Triage
@gemini-coder <task> Generate Code
@create-review-issues Create issues from review (PR only)
@gemini-help Show this help message

Manual Trigger Guide

@arii arii added the not reviewed The review could not be performed or completed successfully. label Mar 16, 2026
@arii

arii commented Mar 16, 2026

Copy link
Copy Markdown
Owner Author

🤖 AI Technical Audit

Code Review: PR #9550 - Optimistic UI for Spotify Controls

Overall, the architectural shift towards an Optimistic UI for playback controls is a highly requested and necessary UX improvement. By shifting the perceived state authority to the client temporarily, we mask network latency. However, there are significant flaws in how the throttling is managed in React and how the backend optimistic state is partitioned.

🚨 ANTI-AI-SLOP DIRECTIVES

1. OVERLY VERBOSE COMMENTS:

  • Flagged: // Immediate Optimistic Update in spotifyPlayerManager.ts and // 1. Immediate Optimistic Broadcast for Play/Pause in spotifyPolling.ts.
  • Action: Remove these. The code this.setState(...) immediately followed by this.broadcastUpdate(...) is self-documenting.

2. OVER-ENGINEERING:

  • Flagged: Splitting the backend optimistic logic across two different classes. You handle Play/Pause optimism in SpotifyPolling but Volume optimism in SpotifyPlayerManager. This creates a split-brain architecture where tracking down state contention requires hopping between multiple service boundaries.

3. DUPLICATE HOOKS/TYPES:

  • Flagged: You imported lodash.throttle directly into the component. In a codebase of this size, there should be a centralized useThrottle hook. Directly using lodash.throttle inside a useMemo without a teardown phase is an anti-pattern in React.

4. CODE RATIO (Prioritize Deletion):

  • Flagged for Deletion (>10 lines):
    1. You removed VOLUME_SYNC_GRACE_PERIOD_MS from the import list in SpotifyControls.tsx, but you didn't delete it from @/constants/spotify.ts. Remove the dead constant.
    2. The handleVolumeChange logic checks if (connectionStatus !== 'Connected') and throttles a warning. This logic is duplicating connection state checks that should be handled at the command execution layer, not the slider change layer. Remove the localized warning throttle and handle offline states globally.

5. STALE FEATURES:

  • Flagged: The PR correctly removed lastVolumeSyncTimeRef and hasPendingSendRef, but left behind the dead VOLUME_SYNC_GRACE_PERIOD_MS constant in the constants file.

📁 File-by-File Analysis

app/client/control/components/SpotifyControls.tsx

Problem: Memory leak and stale closures with lodash.throttle. useMemo does not automatically invoke .cancel() on unmount. If the user navigates away while a throttled volume change is queued, it will execute on an unmounted component, potentially throwing an error or sending unintended network requests.
Implementation Sample:

  const throttledSendVolume = useMemo(
    () =>
      throttle((val: number) => {
        sendVolumeCommand(val)
      }, 200),
    [sendVolumeCommand]
  )

  // MUST ADD CLEANUP:
  useEffect(() => {
    return () => {
      throttledSendVolume.cancel()
    }
  }, [throttledSendVolume])

services/spotifyPlayerManager.ts & services/spotifyPolling.ts

Problem: Flawed failure recovery. In spotifyPlayerManager.ts, if setPlaybackVolume fails, the optimistic state is never reverted locally within the manager. spotifyPolling.ts catches it and calls getCurrentlyPlaying(), but since you reduced the timeout to 300ms, the Spotify API might still return the old state, causing the UI to flicker before eventually correcting itself on the next natural polling cycle.
Implementation Sample:

// Inside SpotifyPlayerManager.ts
try {
  await this.executeSdkCommand(...)
} catch (error) {
  // Revert localized optimistic state immediately before throwing
  this.setState(previousValidState);
  this.broadcastUpdate({ type: 'SPOTIFY_UPDATE', payload: this.getState() });
  throw error;
}

components/shared/VolumeSlider.tsx

Praise: Good implementation of getAriaValueText and responsive touch targets for accessibility. No structural issues here.

🏗️ Architectural Impact

The shift to Optimistic UI introduces the "Eventually Consistent UI" paradigm. By lowering the polling delay from 500ms to 300ms (setTimeout(() => this.getCurrentlyPlaying(), 300)), you are risking race conditions. Spotify's API is notoriously sluggish in reflecting changes via their currently-playing endpoint. I strongly advise reverting the delay to 500ms or implementing a smart back-off poll after a command mutation.

🛡️ GitHub Checks Status

All quality gates and automated tests passed. However, Knip (stale code detector) may not be configured to catch unused exports in your constants/spotify.ts file, which is why VOLUME_SYNC_GRACE_PERIOD_MS slipped through. Double-check Knip's configuration.

Review automatically published via RepoAuditor.

- Implement interaction lock (2s) in SpotifyControls/Display to prevent volume "snap-back".
- Add throttled (200ms) volume command emission during slider adjustment.
- Enhance VolumeSlider accessibility with aria-valuetext.
- Increase VolumeSlider touch targets (thumb, track, icons) for active environments.
- Implement backend optimistic state broadcast for PLAY/PAUSE/SET_VOLUME commands.
- Implement backend state reversion in SpotifyPlayerManager on volume command failure.
- Fix CI failures by removing redundant 'ref' in actions/checkout across workflows.
- Clean up overly verbose comments and stale constants.
- Revert authoritative poll delay to 500ms for stability.

Co-authored-by: arii <342438+arii@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Manual Trigger Quick Reference

Command Action
@gemini-bot Run AI Code Review (PR only)
@gemini-triage Run Issue Triage
@gemini-coder <task> Generate Code
@create-review-issues Create issues from review (PR only)
@gemini-help Show this help message

Manual Trigger Guide

@arii arii added not reviewed The review could not be performed or completed successfully. and removed not reviewed The review could not be performed or completed successfully. labels Mar 16, 2026
- Implement 2s interaction lock in SpotifyControls and SpotifyDisplay.
- Add throttled (200ms) volume updates with proper cleanup.
- Consolidate optimistic state logic and failure recovery into SpotifyPlayerManager.
- Revert authoritative poll delay to 500ms to avoid race conditions.
- Increase VolumeSlider touch targets and add aria-valuetext.
- Fix CI failures by removing redundant 'ref' in actions/checkout.
- Clean up unused variables, imports, and dead constants.

Co-authored-by: arii <342438+arii@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Manual Trigger Quick Reference

Command Action
@gemini-bot Run AI Code Review (PR only)
@gemini-triage Run Issue Triage
@gemini-coder <task> Generate Code
@create-review-issues Create issues from review (PR only)
@gemini-help Show this help message

Manual Trigger Guide

@arii arii added not reviewed The review could not be performed or completed successfully. and removed not reviewed The review could not be performed or completed successfully. labels Mar 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Manual Trigger Quick Reference

Command Action
@gemini-bot Run AI Code Review (PR only)
@gemini-triage Run Issue Triage
@gemini-coder <task> Generate Code
@create-review-issues Create issues from review (PR only)
@gemini-help Show this help message

Manual Trigger Guide

@arii arii added not reviewed The review could not be performed or completed successfully. and removed not reviewed The review could not be performed or completed successfully. labels Mar 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Gemini Manual Trigger Guide

@arii arii added not reviewed The review could not be performed or completed successfully. and removed not reviewed The review could not be performed or completed successfully. labels Mar 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Gemini Manual Trigger Guide

@arii arii added not reviewed The review could not be performed or completed successfully. and removed not reviewed The review could not be performed or completed successfully. labels Mar 26, 2026
@arii
arii marked this pull request as ready for review March 26, 2026 03:22
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Gemini Manual Trigger Guide

@arii arii added not reviewed The review could not be performed or completed successfully. and removed not reviewed The review could not be performed or completed successfully. labels Mar 26, 2026
@arii

arii commented Mar 26, 2026

Copy link
Copy Markdown
Owner Author

@Gemini-bot

@github-actions

github-actions Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

🤖 Command Operation Status

Review: Success


Triggered by: @arii via comment

@arii

arii commented Mar 26, 2026

Copy link
Copy Markdown
Owner Author

Reviewed commit: 51be57a1b0a039192dd64cd334879dafd0c86799

This PR introduces a significant and well-executed refactoring of the Spotify controls, moving complex volume and playback synchronization logic into a dedicated useSpotifyVolume hook. This greatly simplifies SpotifyControls.tsx and SpotifyDisplay.tsx, adhering to the 'Don't Repeat Yourself (DRY)' and 'Code Conciseness & Redundancy Prevention' guidelines.

The implementation of optimistic UI for Spotify commands, including a robust rollback mechanism via a new SPOTIFY_OPTIMISTIC_FAILURE WebSocket message and window events, is a strong improvement for user experience. The VolumeSlider component also received minor accessibility and styling enhancements, aligning with the design system.

Key improvements:

  • Centralized Logic: useSpotifyVolume encapsulates all Spotify-specific volume and mute state management, including optimistic updates and server synchronization.
  • Reduced Complexity: SpotifyControls.tsx and SpotifyDisplay.tsx are much cleaner and easier to read/maintain.
  • Optimistic UI: Improved responsiveness for playback and volume changes.
  • Code Hygiene: clampVolume is now centralized in utils/audioManager.ts, and useVolumePreference was appropriately renamed to useAudioPreference to reflect its broader scope.

Areas for attention:

  • Missing Tests: The new useSpotifyVolume hook is critical and complex. It requires comprehensive unit tests to ensure its optimistic update logic, grace periods, and rollback mechanisms function correctly under various network conditions and server responses. This is a blocking issue.
  • SpotifyControls.tsx (Line 112): The // eslint-disable-next-line react-hooks/exhaustive-deps comment is still present. Please re-evaluate if the dependency array for the useEffect can be made truly exhaustive or if the comment can be removed.
  • SpotifyDisplay.tsx (Line 180): The // eslint-disable-next-line react-hooks/set-state-in-effect comment is still present. While sometimes necessary for initial sync, it's good practice to minimize setState calls directly within effects. Consider if this can be refactored to avoid the lint disable.

Reviewed at commit: 51be57a1b0a039192dd64cd334879dafd0c86799


🤖 Gemini Manual Trigger Guide

@arii arii added enhancement New feature or request changes-requested not approved The review identified significant issues or deviations from standards. and removed not reviewed The review could not be performed or completed successfully. labels Mar 26, 2026
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: arii <342438+arii@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Gemini Manual Trigger Guide

@arii arii added not reviewed The review could not be performed or completed successfully. and removed changes-requested not approved The review identified significant issues or deviations from standards. labels Mar 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Gemini Manual Trigger Guide

@arii arii added not reviewed The review could not be performed or completed successfully. and removed not reviewed The review could not be performed or completed successfully. labels Mar 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Gemini Review Skipped

Review was skipped for the following reason: Gemini review is globally disabled

🤖 Gemini Manual Trigger Guide

@arii arii added not reviewed The review could not be performed or completed successfully. and removed not reviewed The review could not be performed or completed successfully. labels Mar 27, 2026
@arii arii closed this May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor: Implement Optimistic UI for Spotify Controls to Resolve State Contention and Enhance Responsiveness

1 participant