From a2dcfbf6c4f8ee67c6e6683aa601266fac20801b Mon Sep 17 00:00:00 2001 From: maria-rcks <254055478+maria-rcks@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:32:32 +0000 Subject: [PATCH 1/3] feat(web): show preview recording beside the thread title --- apps/web/src/browser/browserRecording.ts | 5 ++-- apps/web/src/components/chat/ChatHeader.tsx | 6 ++++- .../preview/BrowserRecordingIndicator.tsx | 23 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/components/preview/BrowserRecordingIndicator.tsx diff --git a/apps/web/src/browser/browserRecording.ts b/apps/web/src/browser/browserRecording.ts index 5ea10c2b05d5..1166555dd3e8 100644 --- a/apps/web/src/browser/browserRecording.ts +++ b/apps/web/src/browser/browserRecording.ts @@ -141,8 +141,9 @@ const activeBrowserRecordingTabIdsAtom = Atom.make( tabIds: new Set(), }).pipe(Atom.keepAlive, Atom.withLabel("preview:active-browser-recording-tabs")); -export function useActiveBrowserRecordingTabIds(): ReadonlySet { - return useAtomValue(activeBrowserRecordingTabIdsAtom).tabIds; +export function useActiveBrowserRecordingTabIds(threadRef?: ScopedThreadRef): ReadonlySet { + const tabIds = useAtomValue(activeBrowserRecordingTabIdsAtom).tabIds; + return threadRef ? readActiveBrowserRecordingTabIds(threadRef) : tabIds; } const activeRecordings = new Map(); diff --git a/apps/web/src/components/chat/ChatHeader.tsx b/apps/web/src/components/chat/ChatHeader.tsx index fbebc323a950..c3e4802a13da 100644 --- a/apps/web/src/components/chat/ChatHeader.tsx +++ b/apps/web/src/components/chat/ChatHeader.tsx @@ -47,6 +47,7 @@ import { WorkspaceBreadcrumbSeparator, } from "../WorkspaceBreadcrumb"; import { cn } from "~/lib/utils"; +import { BrowserRecordingIndicator } from "../preview/BrowserRecordingIndicator"; interface ChatHeaderProps { activeThreadEnvironmentId: EnvironmentId; @@ -347,7 +348,7 @@ export const ChatHeader = memo(function ChatHeader({ ) : null} - + {renamingTitle !== null ? ( {activeThreadTitle} )} +
+ + Recording + + ); +} From c6c1e18d385f05294a4132d85f08b1a218665104 Mon Sep 17 00:00:00 2001 From: maria-rcks <254055478+maria-rcks@users.noreply.github.com> Date: Fri, 11 Sep 2026 21:52:05 +0000 Subject: [PATCH 2/3] fix(web): keep recording dot visible in narrow headers --- apps/web/src/components/preview/BrowserRecordingIndicator.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/preview/BrowserRecordingIndicator.tsx b/apps/web/src/components/preview/BrowserRecordingIndicator.tsx index 9a25ac507323..38e9916b12fc 100644 --- a/apps/web/src/components/preview/BrowserRecordingIndicator.tsx +++ b/apps/web/src/components/preview/BrowserRecordingIndicator.tsx @@ -11,13 +11,13 @@ export function BrowserRecordingIndicator({ threadRef }: { threadRef: ScopedThre - Recording + Recording ); } From 609fa3322cfa9eb3e439a6d32628e2f8ecd23419 Mon Sep 17 00:00:00 2001 From: maria-rcks <254055478+maria-rcks@users.noreply.github.com> Date: Fri, 11 Sep 2026 22:11:57 +0000 Subject: [PATCH 3/3] feat(web): move recording indicator into floating preview --- apps/web/src/browser/browserRecording.ts | 5 ++- apps/web/src/components/chat/ChatHeader.tsx | 6 +--- .../preview/BrowserRecordingIndicator.tsx | 23 -------------- .../preview/ThreadPreviewMiniPlayer.tsx | 31 +++++++++++++++++-- 4 files changed, 31 insertions(+), 34 deletions(-) delete mode 100644 apps/web/src/components/preview/BrowserRecordingIndicator.tsx diff --git a/apps/web/src/browser/browserRecording.ts b/apps/web/src/browser/browserRecording.ts index 1166555dd3e8..5ea10c2b05d5 100644 --- a/apps/web/src/browser/browserRecording.ts +++ b/apps/web/src/browser/browserRecording.ts @@ -141,9 +141,8 @@ const activeBrowserRecordingTabIdsAtom = Atom.make( tabIds: new Set(), }).pipe(Atom.keepAlive, Atom.withLabel("preview:active-browser-recording-tabs")); -export function useActiveBrowserRecordingTabIds(threadRef?: ScopedThreadRef): ReadonlySet { - const tabIds = useAtomValue(activeBrowserRecordingTabIdsAtom).tabIds; - return threadRef ? readActiveBrowserRecordingTabIds(threadRef) : tabIds; +export function useActiveBrowserRecordingTabIds(): ReadonlySet { + return useAtomValue(activeBrowserRecordingTabIdsAtom).tabIds; } const activeRecordings = new Map(); diff --git a/apps/web/src/components/chat/ChatHeader.tsx b/apps/web/src/components/chat/ChatHeader.tsx index c3e4802a13da..fbebc323a950 100644 --- a/apps/web/src/components/chat/ChatHeader.tsx +++ b/apps/web/src/components/chat/ChatHeader.tsx @@ -47,7 +47,6 @@ import { WorkspaceBreadcrumbSeparator, } from "../WorkspaceBreadcrumb"; import { cn } from "~/lib/utils"; -import { BrowserRecordingIndicator } from "../preview/BrowserRecordingIndicator"; interface ChatHeaderProps { activeThreadEnvironmentId: EnvironmentId; @@ -348,7 +347,7 @@ export const ChatHeader = memo(function ChatHeader({ ) : null} - + {renamingTitle !== null ? ( {activeThreadTitle} )} -
- - Recording - - ); -} diff --git a/apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx b/apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx index 76dba69eceee..5249fe37401a 100644 --- a/apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx +++ b/apps/web/src/components/preview/ThreadPreviewMiniPlayer.tsx @@ -12,6 +12,10 @@ import { } from "react"; import { BrowserSurfaceSlot } from "~/browser/BrowserSurfaceSlot"; +import { + findActiveBrowserRecordingRuntimeTabId, + useActiveBrowserRecordingTabIds, +} from "~/browser/browserRecording"; import { useBrowserSurfaceStore } from "~/browser/browserSurfaceStore"; import type { BrowserViewportResizeDirection } from "~/browser/browserViewportLayout"; import { previewRuntimeTabId } from "~/browser/previewRuntimeTabId"; @@ -151,6 +155,10 @@ function BrowserMiniPlayer({ const previewState = useThreadPreviewState(threadRef); const snapshot = previewState.sessions[tabId] ?? null; const runtimeTabId = previewRuntimeTabId(threadRef, previewState.serverEpoch, tabId); + const recordingTabIds = useActiveBrowserRecordingTabIds(); + const recording = + recordingTabIds.has(runtimeTabId) || + findActiveBrowserRecordingRuntimeTabId(threadRef, tabId) !== null; const desktopOverlay = previewState.desktopByTabId[tabId] ?? null; const fittedSourceContent = useBrowserSurfaceStore( (state) => state.byTabId[runtimeTabId]?.fittedSourceContent ?? null, @@ -189,6 +197,7 @@ function BrowserMiniPlayer({ sourceSize={sourceSize} composerOverlayElement={composerOverlayElement} label="Floating browser preview" + recording={recording} onOpenInPanel={openInPanel} pillActions={ @@ -314,6 +323,7 @@ function MiniPlayerShell({ label, onOpenInPanel, pillActions, + recording = false, cornerRadius = frameCornerRadius, children, }: { @@ -324,6 +334,7 @@ function MiniPlayerShell({ readonly label: string; readonly onOpenInPanel: () => void; readonly pillActions?: ReactNode; + readonly recording?: boolean; /** The clip radius for a given frame; the pill stays inside the curve. */ readonly cornerRadius?: (frame: PreviewMiniPlayerSize) => number; readonly children: (frame: PreviewMiniPlayerFrame) => ReactNode; @@ -443,9 +454,18 @@ function MiniPlayerShell({ style={{ right: pillInset, top: pillInset }} >
beginGesture(event, null)} @@ -453,6 +473,11 @@ function MiniPlayerShell({ onPointerUp={endGesture} onPointerCancel={endGesture} > + {recording ? ( + + + + ) : null}