Skip to content

fix(preview): improve browser recording quality - #8839

Merged
maria-rcks merged 11 commits into
pingdotgg:mainfrom
maria-rcks:t3code/improve-browser-recording-quality
Aug 31, 2026
Merged

maria-rcks merged 11 commits into
pingdotgg:mainfrom
maria-rcks:t3code/improve-browser-recording-quality

fix(preview): default browser recordings to 30 fps

385b431
Select commit
Loading
Failed to load commit list.
MacroscopeApp / Macroscope - UI Consistency succeeded Aug 31, 2026 in 2m 47s

UI Consistency: no issues found

All clear

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.

Reviewed in-scope web files: apps/web/src/browser/browserRecording.ts, apps/web/src/components/settings/IntegrationsSettings.tsx, SettingsPanels.tsx, SettingsPanels.logic.ts, settingsSearch.ts, and the related tests.

  • The new BrowserRecordingFrameRateSetting row composes the shared SettingsRow, Select/SelectTrigger/SelectPopup/SelectItem, and SettingResetButton primitives with the same props, widths (w-full sm:w-40), aria-label, hideIndicator, and popup alignment as the sibling zoom and appearance rows; no raw control reconstruction and no call-site overrides of primitive geometry, focus ring, or state colors.
  • Setting registration is complete and consistent: catalog entry in SETTINGS_SEARCH_ITEMS (title/anchor via searchableSetting), dirty label in getChangedBrowserSettingLabels, restore-defaults entry and memo dependency in useSettingsRestore, and the desktop-only grouping still wraps the row through previewDefaults.
  • No CSS, Tailwind, theme-variant, scroll/virtualization, or environment-scoped renderer changes are introduced, so the ownership, cascade, and environment-routing rules are not engaged.
  • browserRecording.ts changes are capture-pipeline logic (native tab stream instead of canvas frames) and keep the existing surface-activity lease and active-recording atom contracts used by HostedBrowserWebview; no visual/layout regression is evident from the diff.

🔍 Agent activity — 16 tool calls

💭 I'm looking at the in-scope files, particularly the new frame rate setting component in IntegrationsSettings.tsx, checking whether it follows the existing select-based settings row patterns used elsewhere in that file, and also considering how browserSurfaceStore's acquisition logic ties in.

1. git_tools — SettingsRow|Select|searchableSetting — rejected: repo.url: required parameter is missing or empty;repo: required parameter is missing or empty

2. browse_code — apps/web/src/components/settings/IntegrationsSettings.tsx

💭 Let me look at nearby settings components like BrowserAppearanceSetting and the zoom setting to check consistency with SettingsRow props, labels, and titles.

3. browse_code — apps/web/src/components/settings/IntegrationsSettings.tsx

💭 The new setting component matches the existing pattern well. I want to verify that searchableSetting's returned title actually populates the row label, so I'm checking settingsSearch.ts and settingsLayout to confirm how the search item connects to the rendered row. I'm also noting the test expecting "recording frame rate" to route correctly to browser-recording-frame-rate as the top result, though it could collide with similarly worded items. I should check whether the new SETTINGS_SEARCH_ITEMS entry needs keywords to disambiguate it.

4. browse_code — apps/web/src/components/settings/settingsSearch.ts

5. browse_code — apps/web/src/components/settings/settingsSearch.ts

💭 Other browser settings don't set desktopOnly since they render disabled on web, so this new one stays consistent with that pattern. I'm also noting the label mismatch between "Recording frame rate" and "Browser recording frame rate" is fine since other labels like "Browser viewport" or "Floating preview" already differ from their search titles. Now I'm checking SettingsPanels.tsx around lines 556 and 692 where browserRecordingFrameRate needs to be added to whatever dependency or "changed settings" list exists there, to make sure the restore logic properly accounts for it.

6. browse_code — apps/web/src/components/settings/SettingsPanels.tsx

💭 I'm checking the browserRecording.ts changes for consistency—though these are logic changes rather than UI, I want to verify that the imported function acquireBrowserSurfaceActivity actually exists in browserSurfaceStore, since the diff doesn't show that file being modified.

7. git_tools — acquireBrowserSurfaceActivity|activityByTabId

💭 These pieces are pre-existing infrastructure, not part of this PR's diff. I'm checking whether HostedBrowserWebview keeps the webview mounted while activity is ongoing, since that hidden-but-mounted surface is what the tab capture stream relies on. I'm also noting a potential mismatch in browserRecording.ts where it calls window.requestAnimationFrame but tests stub the global version instead, though in jsdom/happy-dom globalThis equals window so it likely still works. Tracing through the paint-timeout test logic and confirming the animation frame IDs and surface activity acquire/release calls line up correctly, and considering whether getUserMedia with tab-based chromeMediaSource is Electron-specific and thus fine for a desktop-only recording feature. Checking that the settings search matches "recording frame rate" against the "Browser recording frame rate" title correctly, then moving to review the full browserRecording.ts file for real correctness issues in the stop path, error handling, and how the recording state atom publishes updates.

8. browse_code — apps/web/src/browser/browserRecording.ts

💭 I'm examining whether throwIfStartupCancelled properly cleans up after itself — it stops the screencast and throws a cancellation error, but doesn't stop the actual recording stream or release the surface activity lease. I need to check whether cancellation already implies the recording was removed from the active map elsewhere, which would mean cleanup happened via a different path.

9. browse_code — apps/web/src/browser/browserRecording.ts

💭 I'm spotting a potential resource leak: after the stream is assigned in startBrowserRecording, a later cancellation check can throw without stopping that stream. Unlike before, when there was no stream at that stage, this leaves a live tab-capture MediaStream track running, which keeps Chromium capturing and prevents proper throttling. Tracing further, though — cancellation only triggers if the recording entry was replaced or removed from the map. I'm checking who does that: clearActiveRecording paths and discardBrowserRecording both remove entries, but discard only runs after startupSettled resolves and explicitly stops the stream, so that path seems safe. I need to check whether any other code removes the recording from the map while startup is still pending, which would trigger the cancellation throw before the stream gets cleaned up. Since finalize always awaits startupSettled before deleting, the cancellation window seems practically unreachable in normal flow, making this more of a theoretical stream leak than a concrete bug. I'm also second-guessing whether this even belongs in scope, since the review focus is UI consistency (shared controls, CSS ownership, themes, layout preservation) rather than backend resource leaks—so I should be cautious about flagging this as a finding. Turning to the settings UI itself, I'm checking the new frame rate setting against sibling patterns: it uses the shared Select primitives correctly, has proper aria labels, a reset button, and consistent formatting like the fps suffix. The row's search catalog entry, restore-defaults behavior, and logic labels all appear updated to match. I'm also verifying the ID anchor is unique, the row correctly renders (disabled) on web rather than being desktop-only, and that it pulls its title from the searchable setting and uses useClientSettings consistently with sibling rows. Now I want to confirm browserRecordingFrameRate is properly included in the ClientSettings type used by useClientSettings, and I should double check that useSettings.ts actually exports getClientSettings and ensureClientSettingsHydrated, since browserRecording.ts imports them—if those don't exist, that's a build-breaking issue worth flagging since it's within scope.

10. git_tools — export (const|function|async function) (getClientSettings|ensureClientSettingsHydrated)

11. git_tools — browserRecordingFrameRate

💭 Everything seems properly wired up. I'm checking for gaps like desktop settings persistence, but that seems fine since it derives from the schema generically. I don't see any layout or CSS changes in scope, so now I'm verifying the recording UI indicator hooks work correctly. I want to make sure the activity lease that keeps the webview mounted doesn't accidentally force it to become visible on screen during hidden-tab recording, which would be a visual regression compared to the previous offscreen capturePage approach. I'm going to look at the HostedBrowserWebview code around where activity is handled to confirm this.

12. browse_code — apps/web/src/browser/HostedBrowserWebview.tsx

💭 I'm checking how backgroundActivity and recordingActive influence the styling and offscreen positioning logic.

13. git_tools — backgroundActivity|recordingActive

💭 I'm checking that the hidden webview wrapper style is pre-existing behavior for automation, positioning offscreen with hidden opacity rather than actually stopping render. I'm also tracing the recording flow to confirm the activity lease is acquired synchronously in startBrowserRecording before publishing active recording tab IDs, now that two animation frames are awaited before starting the screencast so the surface has time to paint. Now I'm wondering whether React actually commits the state update before those two rAFs fire, since acquiring the browser surface activity updates the store synchronously but React needs to schedule and flush the re-render first — that's more of a runtime timing concern than a strict correctness rule, and the fallback timeout handles cases where the window is hidden and rAF never fires. I also want to check whether the removed BrowserRecordingCanvasUnavailableError class was still referenced elsewhere for error mapping or toast messages, so I should grep the repo for that class name and related frame-waiting identifiers to make sure no consumers were left broken.

14. git_tools — BrowserRecordingCanvasUnavailableError|wait-first-frame|subscribe-frames|BROWSER_RECORDING_FIRST_FRAME_SIZE_TIMEOUT_MS|onFrame

15. git_tools — BrowserRecordingCanvasUnavailableError|wait-first-frame|BROWSER_RECORDING_FIRST_FRAME_SIZE_TIMEOUT_MS

16. git_tools — recording.onFrame|onRecordingFrame|subscribeRecordingFrames

💭 I'm realizing the recording.onFrame bridge is now essentially dead since the desktop side no longer delivers frames after removing that code from Manager.ts, but since there's no web consumer of it besides browserRecording.ts, this is out of scope for now — I should check previewBridge in web to confirm there's no onFrame definition still relying on it. Given this, I'm concluding there are no UI-consistency violations, and the settings UI addition matches the sibling rows using shared primitives. Let me do one last check on how SelectValue is used with children, since siblings pass a function-based label like zoomLabel while my code passes an array combining frameRate and "fps" — I want to confirm Base UI's Select.Value accepts array children just as it does string or function children.


Agent Credits: 141 credits

Token usage: 26 uncached input · 963,344 cache-read input · 100,163 cache-write input · 9,180 output
Agent Credits may also include non-token charges from external tools such as web research.