import { useCallback, useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router-dom"; import { open as openPathDialog } from "@tauri-apps/plugin-dialog"; import { listSessions, deleteSession, renameSession, showRecordingOverlay, exportSessionPackage, importSessionPackage, revealInFileManager, getSessionMetadata, onProcessingStarted, onProcessingComplete, onProcessingError, type SessionSummary, } from "../lib/api"; import { useShortcutContext } from "../contexts/ShortcutContext"; import ShareDialog from "../components/ShareDialog"; import LibraryCommandCenter from "../components/LibraryCommandCenter"; import { scanSessionForSensitiveData, type PrivacyScanResult } from "../lib/privacyScan"; import { getWorkflowMetrics, initializeWorkflowMetricsStore, trackWorkflowEvent, type WorkflowMetrics, } from "../lib/usageMetrics"; type ViewMode = "grid" | "list"; type SortMode = "last_edited" | "newest" | "oldest" | "longest" | "shortest"; const sortLabels: Record = { last_edited: "Last edited", newest: "Newest", oldest: "Oldest", longest: "Longest", shortest: "Shortest", }; // Arcade-inspired shell layout (keep existing color tokens) const styles = { shell: { height: "100vh", width: "100vw", display: "flex", overflow: "hidden", position: "fixed" as const, inset: 0, backgroundColor: "var(--bg-base)", color: "var(--text-primary)", fontFamily: "var(--font-sans)", isolation: "isolate" as const, }, sidebar: { width: "280px", minWidth: "280px", maxWidth: "280px", backgroundColor: "var(--bg-surface)", borderRight: "1px solid var(--border-default)", display: "flex", flexDirection: "column" as const, gap: "16px", padding: "16px", overflow: "hidden", }, workspaceRow: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "10px", padding: "10px 10px", borderRadius: "var(--radius-lg)", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", cursor: "default", }, workspaceLeft: { display: "flex", alignItems: "center", gap: "10px", minWidth: 0, }, workspaceAvatar: { width: "22px", height: "22px", borderRadius: "50%", background: "color-mix(in srgb, var(--accent-1) 18%, transparent)", border: "1px solid color-mix(in srgb, var(--accent-1) 45%, transparent)", color: "var(--accent-1)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "12px", fontWeight: 700, flexShrink: 0, }, workspaceName: { fontSize: "12px", fontWeight: 650, color: "var(--text-primary)", whiteSpace: "nowrap" as const, overflow: "hidden", textOverflow: "ellipsis", }, workspaceHint: { marginTop: "2px", fontSize: "11px", color: "var(--text-muted)", whiteSpace: "nowrap" as const, overflow: "hidden", textOverflow: "ellipsis", }, searchWrapper: { position: "relative" as const, display: "flex", alignItems: "center", }, searchIcon: { position: "absolute" as const, left: "12px", color: "var(--text-muted)", fontSize: "14px", pointerEvents: "none" as const, }, searchInput: { width: "100%", height: "34px", padding: "0 12px 0 36px", backgroundColor: "var(--bg-elevated)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-md)", color: "var(--text-primary)", fontSize: "13px", fontFamily: "var(--font-sans)", transition: "border-color 150ms", }, searchKbd: { position: "absolute" as const, right: "10px", display: "flex", alignItems: "center", gap: "2px", pointerEvents: "none" as const, color: "var(--text-muted)", fontSize: "11px", }, navSection: { display: "flex", flexDirection: "column" as const, gap: "6px", }, navLabel: { padding: "6px 10px", fontSize: "11px", fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase" as const, color: "var(--text-muted)", }, navItem: { height: "36px", padding: "0 10px", borderRadius: "var(--radius-lg)", border: "1px solid transparent", background: "transparent", color: "var(--text-secondary)", cursor: "pointer", fontSize: "13px", fontWeight: 600, display: "flex", alignItems: "center", gap: "10px", transition: "all 140ms var(--ease-out)", textAlign: "left" as const, }, navItemActive: { background: "color-mix(in srgb, var(--accent-1) 12%, transparent)", border: "1px solid color-mix(in srgb, var(--accent-1) 30%, transparent)", color: "var(--accent-1)", }, planCard: { marginTop: "auto", borderRadius: "var(--radius-xl)", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", padding: "14px", boxShadow: "0 1px 2px rgba(0,0,0,0.12)", }, planTitleRow: { display: "flex", justifyContent: "space-between", alignItems: "baseline", gap: "10px", }, planTitle: { fontSize: "13px", fontWeight: 700, color: "var(--text-primary)", }, planQuota: { fontSize: "12px", fontFamily: "var(--font-mono)", color: "var(--text-muted)", }, planDesc: { marginTop: "6px", fontSize: "12px", color: "var(--text-tertiary)", lineHeight: 1.4, }, planBar: { marginTop: "12px", height: "8px", borderRadius: "999px", background: "rgba(255,255,255,0.06)", overflow: "hidden", border: "1px solid var(--border-subtle)", }, planBarFill: { height: "100%", width: "33%", background: "var(--accent-1)", borderRadius: "999px", boxShadow: "0 0 0 1px color-mix(in srgb, var(--accent-1) 40%, transparent) inset", }, planBtn: { marginTop: "12px", width: "100%", height: "36px", borderRadius: "var(--radius-lg)", border: "1px solid var(--border-default)", background: "var(--bg-surface)", color: "var(--text-primary)", cursor: "pointer", fontSize: "13px", fontWeight: 700, }, main: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column" as const, overflow: "hidden", }, mainScroll: { flex: 1, minHeight: 0, overflowY: "auto" as const, padding: "28px 32px", }, mainHeaderRow: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: "16px", }, mainTitle: { fontSize: "34px", fontWeight: 750, letterSpacing: "-0.03em", margin: 0, }, toolbarRow: { marginTop: "18px", display: "flex", alignItems: "center", justifyContent: "space-between", gap: "16px", flexWrap: "wrap" as const, }, pillRow: { display: "flex", alignItems: "center", gap: "10px", flexWrap: "wrap" as const, }, pill: { height: "38px", padding: "0 14px", borderRadius: "12px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-primary)", cursor: "pointer", fontSize: "13px", fontWeight: 650, display: "flex", alignItems: "center", gap: "10px", }, pillIcon: { width: "22px", height: "22px", borderRadius: "8px", background: "rgba(255,255,255,0.06)", border: "1px solid var(--border-subtle)", display: "flex", alignItems: "center", justifyContent: "center", color: "var(--text-secondary)", }, sortRow: { display: "flex", alignItems: "center", gap: "10px", }, viewToggle: { display: "flex", alignItems: "center", gap: "4px", padding: "4px", borderRadius: "12px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", }, viewToggleBtn: { width: "40px", height: "34px", borderRadius: "10px", border: "none", background: "transparent", color: "var(--text-secondary)", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", transition: "background 140ms var(--ease-out), color 140ms var(--ease-out)", }, viewToggleBtnActive: { background: "rgba(255,255,255,0.08)", color: "var(--text-primary)", }, createBtn: { height: "40px", padding: "0 16px", borderRadius: "12px", border: "1px solid color-mix(in srgb, var(--accent-1) 35%, transparent)", background: "var(--accent-1)", color: "#fff", cursor: "pointer", fontSize: "14px", fontWeight: 750, display: "flex", alignItems: "center", gap: "10px", boxShadow: "0 10px 22px rgba(0,0,0,0.18)", transition: "transform 140ms var(--ease-out), opacity 140ms var(--ease-out)", flexShrink: 0, }, secondaryBtn: { height: "40px", padding: "0 16px", borderRadius: "12px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-primary)", cursor: "pointer", fontSize: "14px", fontWeight: 700, display: "flex", alignItems: "center", gap: "8px", flexShrink: 0, transition: "border-color 140ms var(--ease-out), opacity 140ms var(--ease-out)", }, headerBtnGroup: { display: "flex", alignItems: "center", gap: "10px", }, statusBanner: { padding: "12px 16px", borderRadius: "var(--radius-lg)", marginBottom: "18px", fontSize: "13px", display: "flex", alignItems: "center", gap: "12px", }, statusBannerSuccess: { backgroundColor: "var(--status-success-muted)", border: "1px solid var(--status-success)", color: "var(--status-success)", }, statusBannerError: { backgroundColor: "var(--status-error-muted)", border: "1px solid var(--status-error)", color: "var(--status-error)", }, privacyPanel: { marginBottom: "18px", borderRadius: "var(--radius-lg)", border: "1px solid var(--status-warning)", backgroundColor: "var(--status-warning-muted)", padding: "12px 14px", }, privacyPanelTitle: { fontSize: "13px", fontWeight: 700, color: "var(--status-warning)", }, privacyPanelMeta: { marginTop: "6px", fontSize: "12px", color: "var(--text-secondary)", lineHeight: 1.45, }, privacyFindingList: { marginTop: "10px", display: "flex", flexDirection: "column" as const, gap: "8px", maxHeight: "170px", overflowY: "auto" as const, }, privacyFinding: { border: "1px solid var(--border-default)", borderRadius: "10px", background: "rgba(0, 0, 0, 0.18)", padding: "8px 10px", }, privacyFindingRule: { fontSize: "11px", color: "var(--status-warning)", fontWeight: 700, textTransform: "uppercase" as const, letterSpacing: "0.05em", }, privacyFindingPath: { marginTop: "4px", color: "var(--text-muted)", fontSize: "11px", fontFamily: "var(--font-mono)", }, privacyFindingSnippet: { marginTop: "5px", color: "var(--text-primary)", fontSize: "12px", lineHeight: 1.35, fontFamily: "var(--font-mono)", }, privacyActions: { marginTop: "12px", display: "flex", gap: "8px", flexWrap: "wrap" as const, }, privacyActionPrimary: { height: "34px", borderRadius: "9px", border: "1px solid color-mix(in srgb, var(--accent-1) 35%, transparent)", background: "var(--accent-1)", color: "#fff", padding: "0 12px", fontSize: "12px", fontWeight: 700, cursor: "pointer", }, privacyActionSecondary: { height: "34px", borderRadius: "9px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-primary)", padding: "0 12px", fontSize: "12px", fontWeight: 650, cursor: "pointer", }, modalBackdrop: { position: "fixed" as const, inset: 0, background: "rgba(6, 10, 18, 0.64)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 1200, padding: "24px", }, modalCard: { width: "100%", maxWidth: "560px", borderRadius: "var(--radius-xl)", border: "1px solid var(--border-default)", background: "var(--bg-surface)", boxShadow: "0 18px 38px rgba(0,0,0,0.35)", padding: "18px", }, modalHeader: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: "12px", }, modalTitle: { margin: 0, fontSize: "18px", fontWeight: 750, color: "var(--text-primary)", }, modalBody: { marginTop: "12px", color: "var(--text-tertiary)", fontSize: "13px", lineHeight: 1.5, }, modalError: { marginTop: "10px", fontSize: "12px", color: "var(--status-error)", }, modalRow: { marginTop: "14px", display: "flex", gap: "8px", alignItems: "center", }, modalInput: { flex: 1, height: "38px", borderRadius: "10px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-primary)", padding: "0 10px", fontSize: "13px", fontFamily: "var(--font-mono)", }, modalButton: { height: "38px", borderRadius: "10px", border: "1px solid var(--border-default)", background: "var(--bg-elevated)", color: "var(--text-primary)", padding: "0 12px", fontSize: "13px", fontWeight: 650, cursor: "pointer", }, modalActions: { marginTop: "16px", display: "flex", justifyContent: "flex-end", gap: "10px", }, modalPrimaryBtn: { height: "38px", borderRadius: "10px", border: "1px solid color-mix(in srgb, var(--accent-1) 30%, transparent)", background: "var(--accent-1)", color: "#fff", padding: "0 14px", fontSize: "13px", fontWeight: 700, cursor: "pointer", }, errorBanner: { padding: "12px 16px", backgroundColor: "var(--status-error-muted)", border: "1px solid var(--status-error)", borderRadius: "var(--radius-lg)", marginBottom: "18px", color: "var(--status-error)", fontSize: "13px", display: "flex", alignItems: "center", gap: "12px", }, grid: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: "18px", marginTop: "18px", }, card: { backgroundColor: "var(--bg-surface)", border: "1px solid var(--border-default)", borderRadius: "var(--radius-xl)", overflow: "hidden", cursor: "pointer", transition: "all 150ms var(--ease-out)", }, cardHover: { border: "1px solid var(--accent-1)", boxShadow: "var(--shadow-glow)", transform: "translateY(-2px)", }, thumbnail: { height: "170px", background: "var(--bg-elevated)", display: "flex", alignItems: "center", justifyContent: "center", position: "relative" as const, overflow: "hidden", }, thumbnailIcon: { fontSize: "2.5rem", opacity: 0.2, color: "var(--text-muted)", }, thumbnailImage: { width: "100%", height: "100%", objectFit: "cover" as const, }, cardDetails: { padding: "14px 14px 12px", }, sessionTitle: { fontSize: "14px", fontWeight: 700, marginBottom: "6px", color: "var(--text-primary)", display: "flex", alignItems: "center", gap: "8px", }, titleInput: { width: "100%", padding: "8px 12px", backgroundColor: "var(--bg-elevated)", border: "1px solid var(--accent-1)", borderRadius: "var(--radius-md)", color: "var(--text-primary)", fontSize: "14px", fontWeight: 500, marginBottom: "8px", }, metadata: { display: "flex", flexWrap: "wrap" as const, gap: "8px", fontSize: "12px", color: "var(--text-muted)", marginBottom: "12px", alignItems: "center", }, metaSeparator: { color: "var(--text-muted)", }, terminalBadge: { display: "inline-flex", alignItems: "center", gap: "4px", padding: "3px 8px", backgroundColor: "var(--status-success-muted)", color: "var(--status-success)", borderRadius: "var(--radius-sm)", fontSize: "11px", fontWeight: 500, fontFamily: "var(--font-mono)", }, gitBadge: { display: "inline-flex", alignItems: "center", gap: "4px", padding: "3px 8px", backgroundColor: "var(--accent-4)", color: "var(--accent-1)", borderRadius: "var(--radius-sm)", fontSize: "11px", fontWeight: 500, fontFamily: "var(--font-mono)", }, processingBadge: { display: "inline-flex", alignItems: "center", gap: "4px", padding: "3px 8px", backgroundColor: "var(--status-warning-muted)", color: "var(--status-warning)", borderRadius: "var(--radius-sm)", fontSize: "11px", fontWeight: 500, }, processingOverlay: { position: "absolute" as const, inset: 0, background: "rgba(0, 0, 0, 0.6)", display: "flex", alignItems: "center", justifyContent: "center", flexDirection: "column" as const, gap: "8px", }, processingSpinner: { width: "24px", height: "24px", border: "2px solid rgba(255, 255, 255, 0.3)", borderTopColor: "var(--status-warning)", borderRadius: "50%", animation: "spin 0.8s linear infinite", }, processingText: { color: "#fff", fontSize: "12px", fontWeight: 500, }, actions: { display: "flex", gap: "8px", }, actionIconBtn: { width: "36px", height: "34px", borderRadius: "10px", backgroundColor: "var(--bg-elevated)", border: "1px solid var(--border-default)", color: "var(--text-secondary)", cursor: "pointer", transition: "all 150ms var(--ease-out)", display: "flex", alignItems: "center", justifyContent: "center", }, emptyState: { display: "flex", flexDirection: "column" as const, alignItems: "center", justifyContent: "center", padding: "80px 32px", textAlign: "center" as const, }, emptyIcon: { width: "72px", height: "72px", borderRadius: "var(--radius-xl)", backgroundColor: "var(--bg-elevated)", border: "1px solid var(--border-default)", display: "flex", alignItems: "center", justifyContent: "center", marginBottom: "24px", }, emptyIconInner: { fontSize: "1.5rem", opacity: 0.4, color: "var(--accent-1)", }, emptyTitle: { fontSize: "16px", fontWeight: 500, color: "var(--text-primary)", marginBottom: "8px", }, emptyDescription: { fontSize: "14px", color: "var(--text-tertiary)", marginBottom: "24px", maxWidth: "340px", lineHeight: 1.6, }, emptyActionsRow: { display: "flex", flexWrap: "wrap" as const, alignItems: "center", justifyContent: "center", gap: "10px", }, emptyBtn: { height: "42px", padding: "0 24px", background: "var(--accent-1)", border: "none", color: "#fff", borderRadius: "var(--radius-lg)", cursor: "pointer", fontSize: "14px", fontWeight: 600, display: "flex", alignItems: "center", gap: "8px", transition: "all 150ms var(--ease-out)", boxShadow: "var(--shadow-glow)", }, emptySecondaryBtn: { height: "42px", padding: "0 16px", background: "var(--bg-elevated)", border: "1px solid var(--border-default)", color: "var(--text-primary)", borderRadius: "var(--radius-lg)", cursor: "pointer", fontSize: "13px", fontWeight: 700, display: "inline-flex", alignItems: "center", gap: "8px", transition: "all 150ms var(--ease-out)", }, emptyChecklist: { marginTop: "18px", width: "min(560px, 100%)", borderRadius: "var(--radius-xl)", border: "1px solid var(--border-default)", background: "linear-gradient(140deg, rgba(94, 106, 210, 0.12), rgba(0, 0, 0, 0.18))", padding: "14px", textAlign: "left" as const, }, emptyChecklistTitle: { fontSize: "12px", fontWeight: 800, textTransform: "uppercase" as const, letterSpacing: "0.06em", color: "var(--text-muted)", }, emptyChecklistList: { marginTop: "10px", display: "grid", gap: "8px", }, emptyChecklistItem: { display: "grid", gridTemplateColumns: "20px minmax(0, 1fr) auto", alignItems: "center", gap: "10px", fontSize: "13px", color: "var(--text-secondary)", padding: "8px 10px", borderRadius: "var(--radius-lg)", border: "1px solid var(--border-subtle)", background: "rgba(0, 0, 0, 0.14)", }, emptyChecklistIndex: { width: "20px", height: "20px", borderRadius: "999px", display: "grid", placeItems: "center", fontSize: "11px", fontFamily: "var(--font-mono)", color: "var(--accent-1)", border: "1px solid color-mix(in srgb, var(--accent-1) 45%, transparent)", background: "color-mix(in srgb, var(--accent-1) 14%, transparent)", }, emptyChecklistHint: { fontSize: "11px", color: "var(--text-muted)", fontFamily: "var(--font-mono)", whiteSpace: "nowrap" as const, }, loadingState: { display: "flex", flexDirection: "column" as const, alignItems: "center", justifyContent: "center", height: "100%", width: "100%", gap: "16px", }, loadingSpinner: { width: "28px", height: "28px", border: "2px solid var(--border-strong)", borderTopColor: "var(--accent-1)", borderRadius: "50%", animation: "spin 0.8s linear infinite", }, loadingText: { color: "var(--text-tertiary)", fontSize: "13px", }, list: { display: "flex", flexDirection: "column" as const, gap: "10px", marginTop: "18px", }, listRow: { display: "flex", gap: "14px", alignItems: "center", padding: "10px 12px", borderRadius: "var(--radius-xl)", border: "1px solid var(--border-default)", background: "var(--bg-surface)", cursor: "pointer", transition: "border-color 140ms var(--ease-out), transform 140ms var(--ease-out)", overflow: "hidden", }, listThumb: { width: "140px", height: "78px", borderRadius: "var(--radius-lg)", background: "var(--bg-elevated)", border: "1px solid var(--border-subtle)", overflow: "hidden", position: "relative" as const, flexShrink: 0, }, listBody: { flex: 1, minWidth: 0, }, listTitle: { fontSize: "14px", fontWeight: 750, color: "var(--text-primary)", whiteSpace: "nowrap" as const, overflow: "hidden", textOverflow: "ellipsis", }, listMeta: { marginTop: "6px", fontSize: "12px", color: "var(--text-muted)", display: "flex", gap: "10px", flexWrap: "wrap" as const, alignItems: "center", fontFamily: "var(--font-mono)", }, }; function LibraryView() { const navigate = useNavigate(); const [sessions, setSessions] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const [editingId, setEditingId] = useState(null); const [editTitle, setEditTitle] = useState(""); const [sharingId, setSharingId] = useState(null); const [hoveredCard, setHoveredCard] = useState(null); const [processingSessions, setProcessingSessions] = useState>(new Set()); const [exportingSessionId, setExportingSessionId] = useState(null); const [importingPackage, setImportingPackage] = useState(false); const [showImportModal, setShowImportModal] = useState(false); const [importPackagePath, setImportPackagePath] = useState(""); const [importModalError, setImportModalError] = useState(null); const [workflowMetrics, setWorkflowMetrics] = useState(() => getWorkflowMetrics(30) ); const [exportPrivacyOverrideSessionId, setExportPrivacyOverrideSessionId] = useState< string | null >(null); const [privacyScanSessionId, setPrivacyScanSessionId] = useState(null); const [exportPrivacyAlert, setExportPrivacyAlert] = useState<{ session: SessionSummary; scan: PrivacyScanResult; } | null>(null); const [statusMessage, setStatusMessage] = useState<{ tone: "success" | "error"; text: string; } | null>(null); const [viewMode, setViewMode] = useState("grid"); const [sortMode, setSortMode] = useState("last_edited"); const { registerCategory, unregisterCategory, setShowHelp } = useShortcutContext(); const refreshWorkflowMetrics = useCallback(() => { setWorkflowMetrics(getWorkflowMetrics(30)); }, []); useEffect(() => { void loadSessions(); }, []); useEffect(() => { refreshWorkflowMetrics(); }, [sessions, refreshWorkflowMetrics]); useEffect(() => { void initializeWorkflowMetricsStore().then(() => { refreshWorkflowMetrics(); }); }, [refreshWorkflowMetrics]); useEffect(() => { if (!exportPrivacyAlert) return; const stillPresent = sessions.some( (session) => session.session_id === exportPrivacyAlert.session.session_id ); if (!stillPresent) { setExportPrivacyAlert(null); setExportPrivacyOverrideSessionId(null); } }, [sessions, exportPrivacyAlert]); // Subscribe to processing events useEffect(() => { const unlisteners: (() => void)[] = []; onProcessingStarted((sessionId) => { setProcessingSessions((prev) => new Set(prev).add(sessionId)); }).then((unlisten) => unlisteners.push(unlisten)); onProcessingComplete((sessionId) => { setProcessingSessions((prev) => { const next = new Set(prev); next.delete(sessionId); return next; }); setStatusMessage({ tone: "success", text: `Session ${sessionId.slice(0, 8)} is ready for review.`, }); // Refresh session list to get updated metadata void loadSessions(); }).then((unlisten) => unlisteners.push(unlisten)); onProcessingError((processingError) => { setStatusMessage({ tone: "error", text: `Session processing failed: ${processingError}`, }); void loadSessions(); }).then((unlisten) => unlisteners.push(unlisten)); return () => { unlisteners.forEach((unlisten) => unlisten()); }; }, []); // Register keyboard shortcuts useEffect(() => { registerCategory({ name: "Navigation", shortcuts: { help: { key: "?", meta: true, shift: true, handler: () => setShowHelp(true), description: "Show keyboard shortcuts", }, "new-recording": { key: "n", meta: true, handler: () => void handleStartCapture(), description: "New recording", }, search: { key: "f", meta: true, handler: () => document.getElementById("search-input")?.focus(), description: "Focus search", }, }, }); return () => { unregisterCategory("Navigation"); }; }, [registerCategory, unregisterCategory, setShowHelp, navigate]); async function loadSessions() { try { setError(null); const data = await listSessions(); setSessions(data); setLoading(false); // Initialize processingSessions from persisted metadata // This handles app restarts during conversion const stillConverting = new Set(); for (const session of data) { if (session.processing_status === "converting") { stillConverting.add(session.session_id); } } setProcessingSessions((prev) => { // Merge: keep event-based additions, add persisted ones const merged = new Set(prev); stillConverting.forEach((id) => merged.add(id)); return merged; }); } catch (err) { setError(String(err)); setLoading(false); } } async function handleDelete(sessionId: string) { try { await deleteSession(sessionId); await loadSessions(); } catch (err) { console.error("Delete failed:", err); setStatusMessage({ tone: "error", text: `Failed to delete session: ${String(err)}`, }); } } async function handleRename(sessionId: string) { if (!editTitle.trim()) { setEditingId(null); return; } try { await renameSession(sessionId, editTitle); await loadSessions(); setEditingId(null); } catch (err) { alert(`Failed to rename session: ${err}`); } } async function handleExportPackage(session: SessionSummary, forceOverride = false) { if (processingSessions.has(session.session_id)) { setStatusMessage({ tone: "error", text: "Package export is unavailable while the session is still converting.", }); return; } const overrideRequested = forceOverride || exportPrivacyOverrideSessionId === session.session_id; if (!overrideRequested) { try { setPrivacyScanSessionId(session.session_id); const metadata = await getSessionMetadata(session.session_id); const privacyScan = await scanSessionForSensitiveData(metadata); if (privacyScan.highSeverityCount > 0) { setExportPrivacyOverrideSessionId(session.session_id); setExportPrivacyAlert({ session, scan: privacyScan }); setStatusMessage({ tone: "error", text: `Potential secrets detected (${privacyScan.highSeverityCount}). Review or redact first, then export again to confirm override.`, }); return; } setExportPrivacyAlert(null); } catch (err) { setStatusMessage({ tone: "error", text: `Could not run privacy check before export: ${String(err)}`, }); return; } finally { setPrivacyScanSessionId(null); } } else { setExportPrivacyOverrideSessionId(null); setExportPrivacyAlert(null); } try { setStatusMessage(null); setExportingSessionId(session.session_id); const packagePath = await exportSessionPackage(session.session_id); trackWorkflowEvent("session_exported", { sessionId: session.session_id, metadata: { format: "package", override: overrideRequested }, }); setStatusMessage({ tone: "success", text: `Package exported for "${session.title || session.session_id.slice(0, 8)}"${overrideRequested ? " (override)." : "."}`, }); await revealInFileManager(packagePath); } catch (err) { setStatusMessage({ tone: "error", text: `Failed to export package: ${String(err)}`, }); } finally { setExportingSessionId(null); refreshWorkflowMetrics(); } } async function handleImportPackage() { setStatusMessage(null); setImportModalError(null); setShowImportModal(true); } async function handlePickImportPackagePath() { try { const selectedPath = await openPathDialog({ title: "Select ShowRunner package folder", directory: true, multiple: false, defaultPath: importPackagePath.trim() || undefined, }); if (typeof selectedPath === "string") { setImportPackagePath(selectedPath); setImportModalError(null); } } catch (err) { setImportModalError(`Failed to open import dialog: ${String(err)}`); } } async function handleConfirmImportPackage() { const packagePath = importPackagePath.trim(); if (!packagePath) { setImportModalError("Select a package folder before importing."); return; } try { setStatusMessage(null); setImportModalError(null); setImportingPackage(true); const imported = await importSessionPackage(packagePath); setShowImportModal(false); setImportPackagePath(""); setImportModalError(null); setStatusMessage({ tone: "success", text: `Package imported (${imported.imported_files} files).`, }); await loadSessions(); navigate(`/editor/${imported.session_id}`); } catch (err) { setImportModalError(`Failed to import package: ${String(err)}`); } finally { setImportingPackage(false); } } function handleStartCapture() { void (async () => { try { await showRecordingOverlay(); } catch (err) { setStatusMessage({ tone: "error", text: `Failed to open recorder: ${String(err)}`, }); return; } // Best effort: hide Library so capture starts on a clean desktop. const isTauriRuntime = typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; if (!isTauriRuntime) return; try { const { getCurrentWindow } = await import("@tauri-apps/api/window"); await getCurrentWindow().hide(); } catch { // Ignore hide failures; overlay is already visible. } })(); } function handleDismissExportPrivacyAlert() { setExportPrivacyAlert(null); setExportPrivacyOverrideSessionId(null); } function handleOpenPrivacySession() { if (!exportPrivacyAlert) return; navigate(`/editor/${exportPrivacyAlert.session.session_id}`); } function handleExportAnywayFromAlert() { if (!exportPrivacyAlert) return; void handleExportPackage(exportPrivacyAlert.session, true); } function formatDuration(ms: number | null): string { if (!ms) return "--:--"; const seconds = Math.floor(ms / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); if (hours > 0) { return `${hours}h ${minutes % 60}m`; } if (minutes > 0) { return `${minutes}m ${seconds % 60}s`; } return `${seconds}s`; } function formatDate(isoString: string): string { try { const timestamp = parseInt(isoString); const date = new Date(timestamp * 1000); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); if (diffDays === 0) return "Today"; if (diffDays === 1) return "Yesterday"; if (diffDays < 7) return `${diffDays}d ago`; return date.toLocaleDateString("en-US", { month: "short", day: "numeric", }); } catch { return isoString; } } const filteredSessions = useMemo(() => { const query = searchQuery.trim().toLowerCase(); const filtered = sessions.filter((session) => { if (!query) return true; return ( session.title?.toLowerCase().includes(query) || session.session_id.toLowerCase().includes(query) ); }); const createdAtSeconds = (created_at: string) => { const value = Number(created_at); return Number.isFinite(value) ? value : 0; }; const sorted = [...filtered].sort((a, b) => { if (sortMode === "last_edited" || sortMode === "newest") { return createdAtSeconds(b.created_at) - createdAtSeconds(a.created_at); } if (sortMode === "oldest") { return createdAtSeconds(a.created_at) - createdAtSeconds(b.created_at); } if (sortMode === "longest") { return (b.duration_ms ?? 0) - (a.duration_ms ?? 0); } if (sortMode === "shortest") { return (a.duration_ms ?? 0) - (b.duration_ms ?? 0); } return 0; }); return sorted; }, [sessions, searchQuery, sortMode]); const newestFirstSessions = useMemo(() => { const createdAtSeconds = (created_at: string) => { const value = Number(created_at); return Number.isFinite(value) ? value : 0; }; return [...sessions].sort( (a, b) => createdAtSeconds(b.created_at) - createdAtSeconds(a.created_at) ); }, [sessions]); const readySessionCount = useMemo( () => sessions.filter((session) => !processingSessions.has(session.session_id)).length, [sessions, processingSessions] ); const convertingSessionCount = useMemo( () => sessions.filter((session) => processingSessions.has(session.session_id)).length, [sessions, processingSessions] ); const terminalSessionCount = useMemo( () => sessions.filter((session) => session.has_terminal).length, [sessions] ); const latestReadySessionId = useMemo(() => { return ( newestFirstSessions.find((session) => !processingSessions.has(session.session_id)) ?.session_id ?? null ); }, [newestFirstSessions, processingSessions]); function handleOpenLatestReadySession() { if (!latestReadySessionId) { setStatusMessage({ tone: "error", text: "No ready sessions yet. Finish a recording to unlock quick resume.", }); return; } navigate(`/editor/${latestReadySessionId}`); } if (loading) { return (
Loading sessions...
); } return (
{/* Sidebar */} {/* Main */}

My Library

{error && (
{error}
)} {statusMessage && (
{statusMessage.tone === "success" ? ( <> ) : ( <> )} {statusMessage.text}
)} {exportPrivacyAlert && (
Privacy check flagged potential secrets
{exportPrivacyAlert.scan.highSeverityCount} high-severity match {exportPrivacyAlert.scan.highSeverityCount === 1 ? "" : "es"} in session{" "} {exportPrivacyAlert.session.title || exportPrivacyAlert.session.session_id.slice(0, 8)} . Review findings before exporting.
{exportPrivacyAlert.scan.findings .slice(0, 6) .map((finding, idx) => (
{finding.rule}
{finding.path}
{finding.snippet}
))}
)} {filteredSessions.length === 0 ? (

{searchQuery ? "No sessions found" : "No recordings yet"}

{searchQuery ? "Try a different search term" : "Capture your screen, terminal, or both to create developer-friendly walkthroughs."}

{!searchQuery && ( <>
60-second launch checklist
1 Record a complete dev task in one take ~25s
2 Clean up step titles and hotspot copy ~20s
3 Share link, localized variant, or embed ~15s
)}
) : ( <> {viewMode === "grid" ? (
{filteredSessions.map((session, index) => (
setHoveredCard(session.session_id)} onMouseLeave={() => setHoveredCard(null)} onClick={() => navigate(`/editor/${session.session_id}`) } >
{session.thumbnail_path ? ( Session thumbnail ) : ( )} {processingSessions.has(session.session_id) && (
Converting...
)}
{formatDuration(session.duration_ms)}
{editingId === session.session_id ? ( setEditTitle(e.target.value) } onBlur={() => handleRename(session.session_id) } onKeyDown={(e) => { if (e.key === "Enter") handleRename(session.session_id); if (e.key === "Escape") setEditingId(null); }} autoFocus style={styles.titleInput} /> ) : (
{ e.stopPropagation(); setEditingId(session.session_id); setEditTitle(session.title || ""); }} style={styles.sessionTitle} title="Click to rename" > {session.title || `Session ${session.session_id.substring( 0, 8 )}`}
)}
Edited {formatDate(session.created_at)}
{session.has_terminal && ( Terminal )} {session.git_branch && ( {session.git_branch} )}
))}
) : (
{filteredSessions.map((session) => (
navigate(`/editor/${session.session_id}`) } onMouseEnter={(e) => { e.currentTarget.style.borderColor = "var(--accent-1)"; e.currentTarget.style.transform = "translateY(-1px)"; }} onMouseLeave={(e) => { e.currentTarget.style.borderColor = "var(--border-default)"; e.currentTarget.style.transform = "translateY(0)"; }} >
{session.thumbnail_path ? ( Session thumbnail ) : (
)} {processingSessions.has(session.session_id) && (
Converting...
)}
{session.title || `Session ${session.session_id.substring(0, 8)}`}
{formatDate(session.created_at)} · {formatDuration(session.duration_ms)} · {session.step_count}{" "} {session.step_count === 1 ? "step" : "steps"} {session.has_terminal && ( <> · terminal )} {session.git_branch && ( <> · {session.git_branch} )}
))}
)} )}
{/* Share Dialog */} {sharingId && ( setSharingId(null)} onShared={refreshWorkflowMetrics} /> )} {showImportModal && (
{ if (!importingPackage) { setImportModalError(null); setShowImportModal(false); } }} >
e.stopPropagation()}>

Import Package

Select the package folder that contains{" "} workflow.package.json, or paste a path manually.
{importModalError && (
{importModalError}
)}
{ setImportPackagePath(e.target.value); if (importModalError) { setImportModalError(null); } }} placeholder="/absolute/path/to/package/folder" style={styles.modalInput} autoFocus onKeyDown={(e) => { if (e.key === "Enter" && !importingPackage) { void handleConfirmImportPackage(); } if (e.key === "Escape" && !importingPackage) { setImportModalError(null); setShowImportModal(false); } }} />
)}
); } export default LibraryView;