| ... | ... | @@ -12,6 +12,10 @@ import { |
| 12 | 12 | } from "react"; |
| 13 | 13 | |
| 14 | 14 | import { BrowserSurfaceSlot } from "~/browser/BrowserSurfaceSlot"; |
| 15 | import { |
| 16 | findActiveBrowserRecordingRuntimeTabId, |
| 17 | useActiveBrowserRecordingTabIds, |
| 18 | } from "~/browser/browserRecording"; |
| 15 | 19 | import { useBrowserSurfaceStore } from "~/browser/browserSurfaceStore"; |
| 16 | 20 | import type { BrowserViewportResizeDirection } from "~/browser/browserViewportLayout"; |
| 17 | 21 | import { previewRuntimeTabId } from "~/browser/previewRuntimeTabId"; |
| ... | ... | @@ -151,6 +155,10 @@ function BrowserMiniPlayer({ |
| 151 | 155 | const previewState = useThreadPreviewState(threadRef); |
| 152 | 156 | const snapshot = previewState.sessions[tabId] ?? null; |
| 153 | 157 | const runtimeTabId = previewRuntimeTabId(threadRef, previewState.serverEpoch, tabId); |
| 158 | const recordingTabIds = useActiveBrowserRecordingTabIds(); |
| 159 | const recording = |
| 160 | recordingTabIds.has(runtimeTabId) || |
| 161 | findActiveBrowserRecordingRuntimeTabId(threadRef, tabId) !== null; |
| 154 | 162 | const desktopOverlay = previewState.desktopByTabId[tabId] ?? null; |
| 155 | 163 | const fittedSourceContent = useBrowserSurfaceStore( |
| 156 | 164 | (state) => state.byTabId[runtimeTabId]?.fittedSourceContent ?? null, |
| ... | ... | @@ -189,6 +197,7 @@ function BrowserMiniPlayer({ |
| 189 | 197 | sourceSize={sourceSize} |
| 190 | 198 | composerOverlayElement={composerOverlayElement} |
| 191 | 199 | label="Floating browser preview" |
| 200 | recording={recording} |
| 192 | 201 | onOpenInPanel={openInPanel} |
| 193 | 202 | pillActions={ |
| 194 | 203 | <Tooltip> |
| ... | ... | @@ -314,6 +323,7 @@ function MiniPlayerShell({ |
| 314 | 323 | label, |
| 315 | 324 | onOpenInPanel, |
| 316 | 325 | pillActions, |
| 326 | recording = false, |
| 317 | 327 | cornerRadius = frameCornerRadius, |
| 318 | 328 | children, |
| 319 | 329 | }: { |
| ... | ... | @@ -324,6 +334,7 @@ function MiniPlayerShell({ |
| 324 | 334 | readonly label: string; |
| 325 | 335 | readonly onOpenInPanel: () => void; |
| 326 | 336 | readonly pillActions?: ReactNode; |
| 337 | readonly recording?: boolean; |
| 327 | 338 | /** The clip radius for a given frame; the pill stays inside the curve. */ |
| 328 | 339 | readonly cornerRadius?: (frame: PreviewMiniPlayerSize) => number; |
| 329 | 340 | readonly children: (frame: PreviewMiniPlayerFrame) => ReactNode; |
| ... | ... | @@ -443,9 +454,18 @@ function MiniPlayerShell({ |
| 443 | 454 | style={{ right: pillInset, top: pillInset }} |
| 444 | 455 | > |
| 445 | 456 | <div |
| 446 | | aria-hidden="true" |
| 447 | | className="absolute right-0 top-0 size-2 rounded-full bg-foreground/25 shadow-sm ring-1 ring-background/70 transition-opacity group-hover:opacity-0 group-focus-within:opacity-0" |
| 448 | | /> |
| 457 | role={recording ? "status" : undefined} |
| 458 | aria-label={recording ? "Recording preview" : undefined} |
| 459 | aria-hidden={!recording} |
| 460 | className="absolute right-0 top-0 size-2 transition-opacity group-hover:opacity-0 group-focus-within:opacity-0" |
| 461 | > |
| 462 | <span |
| 463 | className={cn( |
| 464 | "block size-2 rounded-full shadow-sm ring-1 ring-background/70", |
| 465 | recording ? "bg-red-500 motion-safe:animate-status-pulse" : "bg-foreground/25", |
| 466 | )} |
| 467 | /> |
| 468 | </div> |
| 449 | 469 | <div |
| 450 | 470 | className="pointer-events-none absolute right-0 top-0 flex h-8 cursor-grab items-center gap-0.5 rounded-lg border border-border/80 bg-popover/92 p-0.5 opacity-0 shadow-lg/20 backdrop-blur-xl transition-opacity group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100 active:cursor-grabbing" |
| 451 | 471 | onPointerDown={(event) => beginGesture(event, null)} |
| ... | ... | @@ -453,6 +473,11 @@ function MiniPlayerShell({ |
| 453 | 473 | onPointerUp={endGesture} |
| 454 | 474 | onPointerCancel={endGesture} |
| 455 | 475 | > |
| 476 | {recording ? ( |
| 477 | <span aria-hidden className="flex size-6 shrink-0 items-center justify-center"> |
| 478 | <span className="size-2 rounded-full bg-red-500 motion-safe:animate-status-pulse" /> |
| 479 | </span> |
| 480 | ) : null} |
| 456 | 481 | <Tooltip> |
| 457 | 482 | <TooltipTrigger |
| 458 | 483 | render={ |