import { useCallback, useEffect, useState } from "react"; import { useNavigate } from "react-router-dom"; import { enumerateDisplays, enumerateWindows, getCaptureReadiness, openAccessibilitySettings, startTerminalRecording, stopTerminalRecording, getTerminalWsUrl, type CaptureReadiness, type DisplayDevice, type WindowInfo, } from "../lib/api"; import LiveTerminal from "../components/LiveTerminal"; import { useShortcutContext } from "../contexts/ShortcutContext"; import { useRecording } from "../hooks/useRecording"; type RecordingMode = "screen" | "terminal" | "both"; type SourceType = "display" | "window"; // Styles using design tokens const styles = { container: { display: "flex", minHeight: "100vh", backgroundColor: "var(--bg-base)", color: "var(--text-primary)", }, sidebar: { width: "380px", padding: "var(--space-6)", borderRight: "1px solid var(--border-default)", overflowY: "auto" as const, display: "flex", flexDirection: "column" as const, gap: "var(--space-6)", backgroundColor: "var(--bg-surface)", }, header: { display: "flex", alignItems: "center", gap: "var(--space-3)", }, logo: { width: "32px", height: "32px", borderRadius: "var(--radius-lg)", background: "linear-gradient(135deg, var(--accent-record) 0%, #b91c1c 100%)", display: "flex", alignItems: "center", justifyContent: "center", }, headerText: { flex: 1, }, title: { fontSize: "var(--text-lg)", fontWeight: "var(--weight-bold)" as unknown as number, letterSpacing: "var(--tracking-tight)", marginBottom: "var(--space-1)", }, subtitle: { color: "var(--text-tertiary)", fontSize: "var(--text-sm)", }, backLink: { display: "flex", alignItems: "center", gap: "var(--space-1)", color: "var(--text-tertiary)", fontSize: "var(--text-sm)", cursor: "pointer", transition: "var(--transition-colors)", background: "none", border: "none", padding: "var(--space-2)", borderRadius: "var(--radius-md)", marginLeft: "auto", }, errorBanner: { padding: "var(--space-3) var(--space-4)", backgroundColor: "var(--accent-record-muted)", border: "1px solid var(--accent-record)", borderRadius: "var(--radius-lg)", color: "var(--accent-record-hover)", fontSize: "var(--text-sm)", display: "flex", alignItems: "center", gap: "var(--space-3)", }, section: { display: "flex", flexDirection: "column" as const, gap: "var(--space-3)", }, sectionTitle: { fontSize: "var(--text-xs)", fontWeight: "var(--weight-semibold)" as unknown as number, color: "var(--text-tertiary)", textTransform: "uppercase" as const, letterSpacing: "var(--tracking-wide)", }, readinessCard: { border: "1px solid var(--border-default)", borderRadius: "var(--radius-lg)", backgroundColor: "var(--bg-elevated)", padding: "var(--space-4)", display: "flex", flexDirection: "column" as const, gap: "var(--space-3)", }, readinessHeader: { display: "flex", flexDirection: "column" as const, gap: "2px", }, readinessHeaderTop: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "var(--space-3)", }, readinessTitle: { fontSize: "var(--text-sm)", fontWeight: "var(--weight-semibold)" as unknown as number, color: "var(--text-primary)", }, readinessHint: { fontSize: "var(--text-xs)", color: "var(--text-tertiary)", lineHeight: 1.4, }, readinessList: { display: "flex", flexDirection: "column" as const, gap: "var(--space-2)", }, readinessItem: { border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", padding: "var(--space-3)", display: "flex", flexDirection: "column" as const, gap: "var(--space-2)", backgroundColor: "var(--bg-surface)", }, readinessItemTop: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "var(--space-3)", }, readinessItemLeft: { display: "flex", alignItems: "center", gap: "var(--space-2)", minWidth: 0, }, readinessCheckbox: { margin: 0, accentColor: "var(--accent-primary)", }, readinessIndicator: { width: "10px", height: "10px", borderRadius: "50%", backgroundColor: "var(--text-muted)", border: "1px solid var(--border-default)", flexShrink: 0, }, readinessLabel: { fontSize: "var(--text-sm)", color: "var(--text-primary)", fontWeight: "var(--weight-medium)" as unknown as number, whiteSpace: "nowrap" as const, overflow: "hidden", textOverflow: "ellipsis", }, readinessBadge: { padding: "2px 8px", borderRadius: "999px", fontSize: "11px", fontWeight: "var(--weight-medium)" as unknown as number, border: "1px solid var(--border-default)", color: "var(--text-secondary)", backgroundColor: "var(--bg-base)", flexShrink: 0, }, readinessMeta: { fontSize: "var(--text-xs)", color: "var(--text-tertiary)", lineHeight: 1.4, }, readinessPath: { marginTop: "4px", fontFamily: "var(--font-mono)", fontSize: "11px", color: "var(--text-muted)", wordBreak: "break-all" as const, }, readinessTimestamp: { marginTop: "2px", fontSize: "11px", color: "var(--text-muted)", fontFamily: "var(--font-mono)", }, readinessActionBtn: { marginTop: "8px", alignSelf: "flex-start" as const, }, modeButtons: { display: "flex", gap: "var(--space-2)", }, modeBtn: { flex: 1, height: "var(--btn-height-md)", padding: "0 var(--space-3)", fontSize: "var(--text-sm)", fontWeight: "var(--weight-medium)" as unknown as number, backgroundColor: "var(--bg-elevated)", color: "var(--text-tertiary)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", cursor: "pointer", transition: "var(--transition-all)", display: "flex", alignItems: "center", justifyContent: "center", gap: "var(--space-2)", }, modeBtnActive: { backgroundColor: "var(--accent-primary)", color: "white", borderColor: "var(--accent-primary)", }, sourceButtons: { display: "flex", gap: "var(--space-2)", }, sourceBtn: { flex: 1, height: "var(--btn-height-md)", fontSize: "var(--text-sm)", fontWeight: "var(--weight-medium)" as unknown as number, backgroundColor: "var(--bg-elevated)", color: "var(--text-tertiary)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", cursor: "pointer", transition: "var(--transition-all)", }, sourceBtnActive: { backgroundColor: "var(--accent-success)", color: "white", borderColor: "var(--accent-success)", }, displayList: { display: "flex", flexDirection: "column" as const, gap: "var(--space-2)", }, displayItem: { display: "flex", alignItems: "center", padding: "var(--space-3) var(--space-4)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-lg)", cursor: "pointer", backgroundColor: "var(--bg-elevated)", transition: "var(--transition-all)", }, displayItemActive: { borderColor: "var(--accent-primary)", backgroundColor: "var(--accent-primary-muted)", }, displayRadio: { marginRight: "var(--space-3)", accentColor: "var(--accent-primary)", }, displayInfo: { flex: 1, }, displayName: { fontWeight: "var(--weight-medium)" as unknown as number, fontSize: "var(--text-sm)", display: "flex", alignItems: "center", gap: "var(--space-2)", }, primaryBadge: { fontSize: "var(--text-xs)", color: "var(--accent-success)", fontWeight: "var(--weight-medium)" as unknown as number, }, displayMeta: { fontSize: "var(--text-xs)", color: "var(--text-tertiary)", fontFamily: "var(--font-mono)", marginTop: "2px", }, windowList: { display: "flex", flexDirection: "column" as const, gap: "var(--space-2)", maxHeight: "280px", overflowY: "auto" as const, }, windowGroup: { marginBottom: "var(--space-2)", }, windowGroupTitle: { fontSize: "var(--text-xs)", color: "var(--text-muted)", marginBottom: "var(--space-2)", fontWeight: "var(--weight-semibold)" as unknown as number, display: "flex", alignItems: "center", gap: "var(--space-2)", }, windowItem: { display: "flex", alignItems: "center", padding: "var(--space-2) var(--space-3)", marginBottom: "var(--space-1)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", cursor: "pointer", backgroundColor: "var(--bg-elevated)", transition: "var(--transition-all)", }, windowItemActive: { borderColor: "var(--accent-primary)", backgroundColor: "var(--accent-primary-muted)", }, windowInfo: { overflow: "hidden", flex: 1, }, windowTitle: { fontWeight: "var(--weight-medium)" as unknown as number, fontSize: "var(--text-sm)", whiteSpace: "nowrap" as const, overflow: "hidden", textOverflow: "ellipsis", }, windowSize: { fontSize: "var(--text-xs)", color: "var(--text-tertiary)", fontFamily: "var(--font-mono)", }, refreshBtn: { padding: "var(--space-1) var(--space-2)", fontSize: "var(--text-xs)", backgroundColor: "transparent", color: "var(--text-tertiary)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-sm)", cursor: "pointer", transition: "var(--transition-colors)", }, recordingStatus: { padding: "var(--space-4)", backgroundColor: "var(--accent-record-muted)", border: "1px solid var(--accent-record)", borderRadius: "var(--radius-lg)", display: "flex", alignItems: "center", justifyContent: "space-between", }, recordingInfo: { display: "flex", alignItems: "center", gap: "var(--space-3)", }, recordingDot: { width: "12px", height: "12px", borderRadius: "50%", backgroundColor: "var(--accent-record)", }, recordingLabel: { fontWeight: "var(--weight-semibold)" as unknown as number, color: "var(--accent-record-hover)", }, recordingTime: { fontFamily: "var(--font-mono)", color: "var(--text-tertiary)", fontSize: "var(--text-sm)", }, recordBtn: { width: "100%", height: "var(--btn-height-lg)", fontSize: "var(--text-base)", fontWeight: "var(--weight-semibold)" as unknown as number, background: "linear-gradient(135deg, var(--accent-record) 0%, #dc2626 100%)", color: "white", border: "none", borderRadius: "var(--radius-lg)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: "var(--space-2)", boxShadow: "0 4px 12px var(--accent-record-glow)", transition: "var(--transition-all)", }, stopBtn: { width: "100%", height: "var(--btn-height-lg)", fontSize: "var(--text-base)", fontWeight: "var(--weight-semibold)" as unknown as number, backgroundColor: "var(--bg-muted)", color: "var(--text-primary)", border: "none", borderRadius: "var(--radius-lg)", cursor: "pointer", transition: "var(--transition-colors)", }, mainContent: { flex: 1, display: "flex", flexDirection: "column" as const, }, terminalContainer: { flex: 1, padding: "var(--space-4)", }, terminalWrapper: { height: "100%", borderRadius: "var(--radius-xl)", overflow: "hidden", border: "1px solid var(--border-default)", backgroundColor: "var(--bg-void)", }, emptyState: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", color: "var(--text-muted)", }, emptyStateContent: { textAlign: "center" as const, }, emptyStateIcon: { fontSize: "3rem", marginBottom: "var(--space-4)", opacity: 0.3, }, emptyStateText: { color: "var(--text-tertiary)", fontSize: "var(--text-sm)", }, shortcutHint: { display: "flex", alignItems: "center", justifyContent: "center", gap: "var(--space-2)", marginTop: "var(--space-4)", color: "var(--text-muted)", fontSize: "var(--text-xs)", }, startBlockedHint: { marginTop: "var(--space-3)", fontSize: "var(--text-xs)", lineHeight: 1.4, color: "var(--status-warning)", textAlign: "center" as const, }, }; // Icons const icons = { screen: ( ), terminal: ( ), both: ( ), back: ( ), record: ( ), stop: ( ), refresh: ( ), }; function RecordingView() { const navigate = useNavigate(); const [displays, setDisplays] = useState([]); const [windows, setWindows] = useState([]); const [sourceType, setSourceType] = useState("display"); const [selectedDisplay, setSelectedDisplay] = useState(null); const [selectedWindow, setSelectedWindow] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [screenReadyConfirmed, setScreenReadyConfirmed] = useState(false); const [domExtensionConfirmed, setDomExtensionConfirmed] = useState(false); const [captureReadiness, setCaptureReadiness] = useState(null); const [captureReadinessLoading, setCaptureReadinessLoading] = useState(false); const [captureReadinessError, setCaptureReadinessError] = useState(null); const { recording, stopping, elapsedTime, start: startScreenRecording, stop: stopScreenRecording, } = useRecording({ onError: (err) => setError(err), }); const [recordingMode, setRecordingMode] = useState("screen"); const [terminalWsUrl, setTerminalWsUrl] = useState(null); const { registerCategory, unregisterCategory, setShowHelp } = useShortcutContext(); const refreshCaptureReadiness = useCallback(async () => { setCaptureReadinessLoading(true); setCaptureReadinessError(null); try { const readiness = await getCaptureReadiness(); setCaptureReadiness(readiness); } catch (err) { const message = err instanceof Error ? err.message : String(err); setCaptureReadiness(null); setCaptureReadinessError(message); } finally { setCaptureReadinessLoading(false); } }, []); useEffect(() => { registerCategory({ name: "Recording", shortcuts: { "toggle-recording": { key: "R", meta: true, shift: true, handler: () => { if (stopping) return; if (recording) { handleStopRecording(); } else { handleStartRecording(); } }, description: "Toggle recording (start/stop)", }, stop: { key: "Escape", handler: () => { if (recording && !stopping) { handleStopRecording(); } }, description: "Stop recording", enabled: recording, }, }, }); registerCategory({ name: "Navigation", shortcuts: { help: { key: "?", meta: true, shift: true, handler: () => setShowHelp(true), description: "Show keyboard shortcuts", }, library: { key: "L", meta: true, handler: () => navigate("/library"), description: "Back to library", }, }, }); return () => { unregisterCategory("Recording"); unregisterCategory("Navigation"); }; }, [registerCategory, unregisterCategory, setShowHelp, recording, stopping, navigate]); useEffect(() => { loadSources(); }, []); useEffect(() => { if (sourceType === "window") { loadWindows(); } }, [sourceType]); useEffect(() => { void refreshCaptureReadiness(); }, [refreshCaptureReadiness]); useEffect(() => { if (recording) return; let refreshTimeout: ReturnType | null = null; const scheduleRefresh = () => { if (refreshTimeout) { clearTimeout(refreshTimeout); } // Small delay allows macOS privacy state to settle after returning from System Settings. refreshTimeout = setTimeout(() => { void refreshCaptureReadiness(); }, 300); }; const handleWindowFocus = () => { scheduleRefresh(); }; const handleVisibilityChange = () => { if (document.visibilityState === "visible") { scheduleRefresh(); } }; window.addEventListener("focus", handleWindowFocus); document.addEventListener("visibilitychange", handleVisibilityChange); return () => { window.removeEventListener("focus", handleWindowFocus); document.removeEventListener("visibilitychange", handleVisibilityChange); if (refreshTimeout) { clearTimeout(refreshTimeout); } }; }, [recording, refreshCaptureReadiness]); useEffect(() => { const needsScreenSource = recordingMode === "screen" || recordingMode === "both"; if (!needsScreenSource) return; const hasSource = sourceType === "display" ? Boolean(selectedDisplay) : Boolean(selectedWindow); if (!hasSource) { setScreenReadyConfirmed(false); } }, [recordingMode, sourceType, selectedDisplay, selectedWindow]); async function handleOpenAccessibilitySettings() { try { setCaptureReadinessError(null); await openAccessibilitySettings(); } catch (err) { const message = err instanceof Error ? err.message : String(err); setCaptureReadinessError(message); } } async function loadSources() { try { const [devicesList, windowsList] = await Promise.all([ enumerateDisplays(), enumerateWindows(), ]); setDisplays(devicesList); setWindows(windowsList); const primary = devicesList.find((d) => d.is_primary); if (primary) { setSelectedDisplay(primary.id); } if (windowsList.length > 0) { setSelectedWindow(windowsList[0].id); } setLoading(false); } catch (err) { setError(String(err)); setLoading(false); } } async function loadWindows() { try { const windowsList = await enumerateWindows(); setWindows(windowsList); if (windowsList.length > 0 && !selectedWindow) { setSelectedWindow(windowsList[0].id); } } catch (err) { console.error("Failed to load windows:", err); } } async function handleStartRecording() { const needsSource = recordingMode === "screen" || recordingMode === "both"; if (needsSource && !screenReadyConfirmed) { setError("Confirm the Screen Recording readiness check before starting."); return; } if (needsSource && (!sidecarReady || !accessibilityReady)) { setError( "Complete Context Sidecar and Accessibility readiness checks before starting." ); return; } if (needsSource) { if (sourceType === "display" && !selectedDisplay) { setError("Please select a display"); return; } if (sourceType === "window" && !selectedWindow) { setError("Please select a window"); return; } } try { setError(null); let newSessionId: string | null = null; const recordingStartMs = Date.now(); if (recordingMode === "screen" || recordingMode === "both") { if (sourceType === "display") { const display = displays.find((d) => d.id === selectedDisplay); if (!display) return; newSessionId = await startScreenRecording( display.id, display.resolution.width, display.resolution.height ); } else { const window = windows.find((w) => w.id === selectedWindow); if (!window) return; const display = displays.find((d) => d.is_primary) || displays[0]; if (!display) return; newSessionId = await startScreenRecording( `window-${window.id}`, Math.round(window.bounds.width), Math.round(window.bounds.height) ); } } if (recordingMode === "terminal" || recordingMode === "both") { if (!newSessionId) { const display = displays[0]; if (display) { newSessionId = await startScreenRecording( display.id, display.resolution.width, display.resolution.height ); } } if (newSessionId) { await startTerminalRecording(newSessionId, 120, 40, recordingStartMs); const wsUrl = await getTerminalWsUrl(newSessionId); setTerminalWsUrl(wsUrl); } } } catch (err) { setError(String(err)); } } async function handleStopRecording() { try { if (recordingMode === "terminal" || recordingMode === "both") { await stopTerminalRecording(); setTerminalWsUrl(null); } const finalSessionId = await stopScreenRecording(); if (finalSessionId) { navigate(`/editor/${finalSessionId}`); } } catch (err) { setError(String(err)); } } const formatTime = (seconds: number) => { const mins = Math.floor(seconds / 60); const secs = seconds % 60; return `${mins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`; }; const groupedWindows = windows.reduce( (acc, window) => { if (!acc[window.owner_name]) { acc[window.owner_name] = []; } acc[window.owner_name].push(window); return acc; }, {} as Record ); const needsScreenSource = recordingMode === "screen" || recordingMode === "both"; const sourceSelectionMissing = needsScreenSource && ((sourceType === "display" && !selectedDisplay) || (sourceType === "window" && !selectedWindow)); const sidecarReady = Boolean( captureReadiness?.sidecar_healthy && captureReadiness?.sidecar_events_healthy ); const accessibilityReady = Boolean(captureReadiness?.accessibility_event_tap_available); const startBlockedByChecklist = needsScreenSource && !screenReadyConfirmed; const startBlockedByRuntimeChecks = needsScreenSource && (!sidecarReady || !accessibilityReady); const canStartRecording = !sourceSelectionMissing && !startBlockedByChecklist && !startBlockedByRuntimeChecks; const startBlockedReason = (() => { if (!needsScreenSource) return null; if (sourceSelectionMissing) return "Select a capture source before starting."; if (startBlockedByChecklist) return "Confirm Screen Recording readiness first."; if (!sidecarReady && !accessibilityReady) { return "Context Sidecar and Accessibility must both be Ready."; } if (!sidecarReady) return "Context Sidecar must be Ready."; if (!accessibilityReady) return "Accessibility Permissions must be Ready."; return null; })(); const selectedDisplayInfo = displays.find((display) => display.id === selectedDisplay); const selectedWindowInfo = windows.find((window) => window.id === selectedWindow); const selectedSourceSummary = sourceType === "display" ? selectedDisplayInfo ? `${selectedDisplayInfo.name} (${selectedDisplayInfo.resolution.width}x${selectedDisplayInfo.resolution.height})` : "Select a display to continue." : selectedWindowInfo ? `${selectedWindowInfo.title || "(Untitled)"} ยท ${selectedWindowInfo.owner_name}` : "Select a window to continue."; const readinessLastChecked = captureReadiness ? new Date(captureReadiness.checked_at_ms).toLocaleTimeString("en-US", { hour: "2-digit", minute: "2-digit", second: "2-digit", }) : null; if (loading) { return (

Loading displays...

); } return (
{/* Sidebar */}
{/* Header */}

ShowRunner

Terminal Recording

{error && (
{error}
)}
Capture Readiness
Confirm required checks before recording. Runtime probes verify sidecar and accessibility readiness.
{readinessLastChecked && (
Last checked: {readinessLastChecked}
)}
{!needsScreenSource ? "Not required" : screenReadyConfirmed ? "Ready" : "Required"}
{needsScreenSource ? selectedSourceSummary : "Terminal-only mode selected. Screen capture is optional."}
Context Sidecar
{captureReadinessLoading ? "Checking..." : sidecarReady ? "Ready" : "Limited"}
{captureReadinessLoading ? "Checking local sidecar health." : sidecarReady ? "Event sidecar is reachable and will enrich context (app focus, shortcuts, file activity)." : captureReadinessError ? `Unable to verify sidecar readiness: ${captureReadinessError}` : "Sidecar appears unavailable. Recording still works, but context capture will be limited."}
Accessibility Permissions
{captureReadinessLoading ? "Checking..." : accessibilityReady ? "Ready" : "Needs setup"}
{captureReadinessLoading ? "Checking event tap availability." : accessibilityReady ? "Accessibility event tap is available for richer click and input semantics." : "Grant access in System Settings > Privacy & Security > Accessibility, then refresh checks."} {!captureReadinessLoading && !accessibilityReady && ( )}
Optional
For web workflows only. Load the extension in Chrome via `chrome://extensions` and the local `/docs` folder.
/Users/farman/Documents/projects/showrunner/docs
{/* Recording Mode */}

Recording Mode

{[ { mode: "screen" as RecordingMode, label: "Screen", icon: icons.screen }, { mode: "terminal" as RecordingMode, label: "Terminal", icon: icons.terminal }, { mode: "both" as RecordingMode, label: "Both", icon: icons.both }, ].map(({ mode, label, icon }) => ( ))}
{/* Source Type (for screen modes) */} {(recordingMode === "screen" || recordingMode === "both") && ( <>

Source Type

{/* Display Selection */} {sourceType === "display" && (

Select Display

{displays.map((display) => ( ))}
)} {/* Window Selection */} {sourceType === "window" && (

Select Window

{Object.entries(groupedWindows).map(([appName, appWindows]) => (
{appName}
{appWindows.map((window) => ( ))}
))} {windows.length === 0 && (
No windows found
)}
)} )} {/* Recording Controls */}
{recording ? ( <>
Recording
{formatTime(elapsedTime)}
) : ( )} {!recording && startBlockedReason && (
{startBlockedReason}
)}
R to toggle recording
{/* Main Content */}
{recording && (recordingMode === "terminal" || recordingMode === "both") && terminalWsUrl ? (
{ console.log("Terminal session ended"); }} onError={(err: string) => setError(err)} />
) : (
{recordingMode === "terminal" || recordingMode === "both" ? ( ) : ( )}

{recordingMode === "terminal" || recordingMode === "both" ? "Terminal will appear here when recording starts" : "Screen recording preview"}

)}
); } export default RecordingView;