Skip to content

feat(web): show which thread's browser tab is playing audio in the sidebar - #27988

Open
Rhinomcd wants to merge 3 commits into
pingdotgg:mainfrom
Rhinomcd:feat/sidebar-preview-audio
Open

Rhinomcd wants to merge 3 commits into
pingdotgg:mainfrom
Rhinomcd:feat/sidebar-preview-audio

Conversation

@Rhinomcd

@Rhinomcd Rhinomcd commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Note

opus-5.5 responding on behalf of Ryan.

Problem

When a thread's browser tab plays audio and you switch to another thread, nothing in the sidebar shows which thread is making the sound. The speaker glyph only appears on the browser tab inside the panel, so you can't see it until you're already on that thread. With a few threads open you have to click through them one by one. Ideas discussion: #20205.

Change

Thread rows now show a speaker button (lucide Volume2, the icon the browser tab already uses) while any of the thread's browser tabs plays unmuted audio. It sits next to the existing "terminal process running" button and works the same way: clicking it opens the thread with the playing tab active. It disappears when the audio stops, the tab is muted, or the tab closes.

The audible state already reaches previewStateStore for every thread's live browser tab, from Electron's audio-state-changed through usePreviewBridge. ElectronBrowserHost mounts every session, not only the active thread's. Each row subscribes to its own thread's preview atom through a selector that returns the tab id or null, so unrelated preview updates don't re-render rows.

usePreviewBridge now clears a tab's desktop state when its webview host unmounts. Before, the last audible state stayed in the store after the guest was gone, for example when a server tab went headless or the environment lost preview permission, so the speaker could stay on for a tab that no longer played.

Scope and approval

This is a new indicator, so under prior approval it needs a maintainer to approve the direction in #20205. That approval isn't there yet. I'm opening this so that, if you approve #20205, this PR can be reviewed and merged without another round-trip.

It replaces #19116 by @Nyxalium, which was closed only because approval was missing. That PR's problem statement and the #20205 discussion are theirs. This version is rebuilt on current main:

  • The legacy sidebar has been removed since fix(web): identify noisy preview threads in the sidebar #19116, so there's one row component to change, sidebar/SidebarThreadRow.tsx.
  • The indicator is a button that opens the playing tab, instead of a static image. It reuses the terminal-status button's pattern: stopped propagation, the reveal-then-activate call, and the meta-line placement.
  • The test is one store-level case, the lowest layer that fails on main, rather than a new component test file.

Six files: the selector and its test, the button, the row wiring, and the bridge teardown and its test. Playback, muting, defaults, and settings are unchanged.

Other surfaces: browser tabs with audio state exist only in the desktop app. Web without Electron has no tab audio state. Mobile shows streamed browser previews but gets no Electron audio state, so neither shows the indicator. Rows from other environments work because each thread is looked up by its environment-scoped thread ref.

Verification

Current head ed8856facd, real desktop app. Rebased onto main at 4b87a45770 and rebuilt with vp run build:desktop. I ran the same drive.mjs on Linux in a headless Xvfb box against the scripts/fixtures/desktop-launch database:

guest after play   audible: true
indicator rows while playing 1
guest after pause  audible: false
indicator rows after pause 0
indicator rows after replay 1
after click, aria-current rows [ 'Release launch check …' ]

Playing, with "Second thread" open:

ed8856: speaker on the playing thread's row

After clicking the speaker:

ed8856: click opens the playing thread with its browser tab

The runs below were on the earlier patch, before the teardown change.

Real desktop app, before and after. I built the desktop app (vp run build:desktop) for main at 6d46336 and for this branch. On Linux, each build was launched with Playwright _electron under Xvfb against the repo's scripts/fixtures/desktop-launch database, which has one project and two threads. The script (drive.mjs):

  1. opens "Release launch check", opens a browser tab, and loads a local page with a looping 440 Hz <audio>
  2. starts playback in the guest with webContents.executeJavaScript(..., true) and confirms isCurrentlyAudible() === true
  3. switches to "Second thread" and counts sidebar audio buttons
  4. pauses, waits for Chromium's audible hold-off, and counts again
  5. plays again and clicks the button
                          guest audible   sidebar audio buttons
main    playing           true            0
branch  playing           true            1   (on "Release launch check")
branch  paused            false           0
branch  click             -               route + aria-current move to "Release launch check", browser tab shown

Before (main, audio playing in "Release launch check" while "Second thread" is open):

main: no sidebar indicator while audio plays

After, playing:

branch: speaker on the playing thread's row

After, hover:

tooltip: Browser tab playing audio. Open tab

After, paused:

branch: indicator gone after pause

After clicking the speaker:

click opens the playing thread with its browser tab

Crowded row. The speaker shares the meta line with the branch, the terminal-running button, and the PR badge, so I checked the busiest case. crowded.mjs seeds the thread with a long branch (docs/app-vs-prototype-comparison) and a linked PR, then runs sleep 900 in its terminal and plays audio. The icons fit on one line at the default sidebar width. The branch name middle-truncates about four more characters ("docs…comparison" becomes "d…comparison"), and nothing wraps or overlaps.

Playing, with terminal and PR:

crowded row while playing: branch, terminal, speaker, PR, provider

Paused:

crowded row after pause

Windows. I ran the same two scripts on Windows 11 Pro 25H2 (build 26200), building with Node 26.12.1 and pnpm 12.14 through vp, and got the same results as on Linux. While playing, 1 row has the speaker, on the playing thread. After pause, no row has it. After playing again, the speaker returns, and clicking it moves aria-current to the playing thread and shows its browser tab. In the crowded row, the meta line stays on one line with the speaker between the branch and the PR number.

Windows, playing, with "Second thread" open:

Windows: speaker on the playing thread's row

Windows, paused:

Windows: indicator gone after pause

Windows, after clicking the speaker:

Windows: click opens the playing thread with its browser tab

Windows, crowded row:

Windows: crowded row stays on one line

Focused tests. apps/web/src/previewStateStore.test.ts gains "finds the background tab playing unmuted audio". It covers a silent active tab next to a playing background tab, muting, and closing the tab. The new usePreviewBridge.browser.test.tsx mounts the real hook against a stub bridge, reports an audible state, unmounts, and checks the state is gone. It fails on the earlier head.

Checks. At ed8856facd, vp run check:changed passed: format, lint, knip 1/2 and 2/2, typecheck (2 packages), tests beside the changes, and the React compiler check (4 files). The preview, browser, right panel, and sidebar tests passed 309/309.

Unverified

  • Headless and permission-loss teardown in the real app. The browser test covers the hook's teardown. I didn't drive a headless fallback or a permission change in Electron.

  • macOS. I ran Linux and Windows only. The audible signal is Electron's cross-platform audio-state-changed, which the existing tab glyph already relies on.

  • A real video site. Playback came from a local <audio> tone. Chromium raises the same audible flag for <video>, and the tab glyph already shows it on sites like the lesson video that prompted this.

  • Performance at scale. I didn't profile a large sidebar. Each row adds one atom subscription with a primitive selector, the same pattern fix(web): identify noisy preview threads in the sidebar #19116 measured: 1,000 rows mount 4.15 → 6.62 ms, with zero redundant commits.

Decisions

  • Icon: lucide Volume2, muted foreground, size-3.5. Runner-up: a pulsing icon like the terminal button. Volume2 is already the browser tab's audible glyph, and a constant pulse next to playing media felt noisy.
  • Click opens the thread on the playing tab rather than toggling mute. Runner-up: mute from the sidebar. Finding the thread was the reported need, the terminal button sets the precedent, and muting is one click away on the tab once you're there.
  • Placement follows the terminal button, so the indicator adds a meta line to rows that had none. Runner-up: put it on the title line to keep row height fixed. Following the terminal button keeps one rule for activity icons, and the height change happens only when playback starts or stops.

Reviewed with sol-loop: 1 round with gpt-6.1-sol. Sol verdict: approved. It confirmed that openBrowser gets the server tab id and that rows don't re-render on unrelated preview updates.

Council after the rebase: gpt-6.1-sol and gpt-6-astra each found the stale-overlay P2 independently, first for headless tabs at 3c7ad11434, then for permission loss at 2d9f84f5c1. ed8856facd fixes it at teardown. Both returned PASS at ed8856facd with no findings.

Created with Claude Opus 5.5 in Claude Code (via T3 Code), reviewed by GPT-6.1 Sol (Codex).

🤖 Generated with Claude Code

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Oct 31, 2026
@Rhinomcd
Rhinomcd marked this pull request as ready for review October 31, 2026 05:31
@coderabbitai

coderabbitai Bot commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Explain Change →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

The preview state now identifies audible, unmuted browser tabs. Sidebar thread rows display an audio control for a qualifying tab. Clicking the control opens the tab in the right panel and activates its thread.

Changes: Audible Preview Indicators

Layer / File(s) Summary
Select audible preview tabs
apps/web/src/previewStateStore.ts, apps/web/src/previewStateStore.test.ts
A selector returns the first tab with web contents that is audible and not muted. A scoped hook exposes the selection. Tests cover background tabs, muted audio, headless sessions, optimistic closure, and restoring the desktop snapshot.
Render and activate the audio control
apps/web/src/components/ThreadStatusIndicators.tsx, apps/web/src/components/sidebar/SidebarThreadRow.tsx
Sidebar rows render the audio control alongside terminal status indicators. Clicking it opens the associated browser tab and activates the thread. The metadata line remains visible when an audible preview tab exists.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant SidebarThreadRow
  participant ThreadPreviewAudioButton
  participant RightPanelStore
  User->>ThreadPreviewAudioButton: Click audio indicator
  SidebarThreadRow->>ThreadPreviewAudioButton: Provide tabId and activation callback
  ThreadPreviewAudioButton->>RightPanelStore: Open associated browser tab
  ThreadPreviewAudioButton->>SidebarThreadRow: Invoke thread activation callback
Loading

Possibly related issues

Suggested reviewers: juliusmarminge, t3dotgg, macroscopeapp

Merge Risk: Minimal · up to 2d9f8

No concrete current-head defect was established; the feature is mergeable with normal checks.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Description check Warning The description includes the required Problem, Change, Scope and approval, and Verification sections, with detailed behavior, test results, screenshots, limitations, and agent attribution. However, th… Obtain explicit maintainer approval for the direction and scope in discussion #20205, then link the approval comment in the Scope and approval section. If the change qualifies for an exemption, document the specific exemption and why it app…
Passed Passed checks: 3 passed

Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: showing which thread has a browser tab playing audio in the sidebar.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description includes the required Problem, Change, Scope and approval, and Verification sections, with detailed behavior, test results, screenshots, limitations, and agent attribution. However, the required maintainer approval for the new indicator is explicitly not present.

Resolution

Obtain explicit maintainer approval for the direction and scope in discussion #20205, then link the approval comment in the Scope and approval section. If the change qualifies for an exemption, document the specific exemption and why it applies instead of approval.

Finishing Touches

Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Recent review info

Run configuration

Commits

Reviewing files that changed from the base of the PR and between 3c7ad11 and 2d9f84f.

Files selected for processing: 2

  • apps/web/src/previewStateStore.test.ts
  • apps/web/src/previewStateStore.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@Rhinomcd
Rhinomcd force-pushed the feat/sidebar-preview-audio branch from 6357cd8 to 91a7516 Compare October 31, 2026 06:10

maria-rcks commented Oct 31, 2026 •

Copy link
Copy Markdown
Collaborator

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified as-is at 91a7516325771ebf29c944aa82ba8a32834305fc on linux. no source changes or pushes were needed.

reproduced on github/main at 54c5f25249961a1aa0b61932329092eadbd9d6fc in a separate worktree, then checked out the exact pr head. ran vp i and the background dev server with isolated state. drove chrome from the repository's playwright dependency using /usr/bin/google-chrome. created a real git project and two threads through the app, using real codex gpt-6.1-sol turns. codex built the local audio player; another turn completed on the pr head.

because the changed audio signal is desktop-only, also ran the real linux electron client and backend against the dev renderer. used actual looping wav playback in its browser guest, with no injected preview-store state. only one development backend ran at a time. electron used --password-store=basic with an isolated profile to avoid the vm's keyring prompt.

after waiting for the browser surface to initialize, clicked play and pause in the rendered panel through playwright input. an initial automation attempt submitted the address before the new tab finished mounting; navigating the settled tab resolved that setup issue.

observed through the running client and electron's native audio state:

main: background tab audible=true, muted=false -> 0 sidebar speakers
pr:   background tab audible=true, muted=false -> 1 sidebar speaker
pr:   mute   -> 0 speakers; unmute -> 1
pr:   pause  -> 0 speakers; resume -> 1
pr:   click or keyboard Enter -> correct thread and playing tab
pr:   silent tab active -> speaker still finds the playing background tab
pr:   close playing tab -> 0 speakers; terminal indicator remains
pr:   terminal indicator -> correct thread and running terminal

checked light at 1440x900 and dark at 1024x768. the captures use the same app-created project, threads, and messages; elapsed labels differ. the recording shows click, mute/unmute, pause/resume, and keyboard activation. inspected the captures and played and sought the uploaded recording.

blacksmith passed vp run build:desktop and vp test run apps/web/src/previewStateStore.test.ts (16 tests). the exact head currently has 29 successful ci checks, none failing or pending.

main: no speaker while the background browser tab plays audio

pr: speaker on the playing thread with the same project and messages

pr: opening the playing tab, mute, pause, resume, and keyboard activation

Not verified

macos, windows, native ios/android, remote or multi-environment audio, external streaming sites, and performance at scale were not exercised on this linux vm. plain web has no electron audio signal, and mobile has no browser panel; their absence of this indicator is intentional. chrome verification covered the real client/provider flow, not desktop audio detection.

@Rhinomcd
Rhinomcd force-pushed the feat/sidebar-preview-audio branch from 91a7516 to 05500a8 Compare November 3, 2026 08:03
…debar

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Rhinomcd
Rhinomcd force-pushed the feat/sidebar-preview-audio branch from 05500a8 to 3c7ad11 Compare November 5, 2026 00:15
Rhinomcd and others added 2 commits November 4, 2026 16:22
A tab that goes headless unmounts its guest, but its last desktop overlay
stays in the store, so the speaker stayed on after playback was gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The headless filter missed other ways a guest unmounts, such as losing
preview permission. Clearing the overlay when the bridge tears down covers
every teardown, so the selector no longer needs its own check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Rhinomcd

Rhinomcd commented Nov 5, 2026

Copy link
Copy Markdown
Contributor Author

Note

🤖 opus-5.5 responding on behalf of Ryan.

Review summary at ed8856facd (rebased onto main at 4b87a45770)

[P2] Finding: The sidebar speaker could stay on for a browser tab whose guest was gone.
Cause: usePreviewBridge's cleanup unsubscribed without clearing the tab's desktop overlay, and the lease's deferred closeTab sent its final audible: false to no listener. selectAudiblePreviewTabId trusted the stale overlay.
Impact: A playing tab that went headless, or whose environment lost preview:operate, unmounted its webview, but the row kept showing the speaker.
Model: gpt-6.1-sol and gpt-6-astra (independent, both rounds) · confirmed by a browser test that mounts the real hook, which failed at 2d9f84f5c1, plus sol's extracted-cleanup probe.
Action: pushed ed8856f. The bridge cleanup clears the overlay. This replaces the headless-only selector filter from 2d9f84f, which missed the permission path.

  • Open P1/P2s: none. gpt-6.1-sol: PASS at ed8856facd. gpt-6-astra: PASS at ed8856facd.
  • Body correction (gpt-6-astra): mobile does show streamed browser previews. It gets no Electron audio state, so it shows no speaker. The PR body now says that.
  • Decision: I cleared the overlay at teardown instead of filtering in the selector, because both stale paths are the same teardown gap. Both reviewers traced the other overlay readers (zoom and PiP in ElectronBrowserHost, PreviewView, the tab glyph and mute control, the mini player) and found no regression. In dev, Fast Refresh can clear the overlay until the next native event.
  • Proof: I rebuilt the desktop app at ed8856facd and re-ran drive.mjs. It showed one speaker while playing and none after pause, and the click opened the playing thread. The output and screenshots are in the PR body.
  • Hold: this still needs a maintainer to approve the direction in Identify background preview audio in sidebar thread rows #20205. No maintainer has replied there.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants