Component props
JSX element rendering the debug menu dialog
export function DebugMenu({ isOpen, onClose }: Readonly<DebugMenuProps>) {
const {
isStorageDebuggerEnabled,
isLogViewerEnabled,
isStateInspectorEnabled,
isIpcViewerEnabled,
isEventLoggerEnabled,
isPerformanceMonitorEnabled,
} = useDebugState();
const [activePanel, setActivePanel] = useState<string>("");
// Build list of enabled debug panels based on user settings
const panels = useMemo(() => {
const entries: DebugPanelDefinition[] = [];
if (isStorageDebuggerEnabled) {
entries.push({
id: "storage",
label: "Storage Explorer",
description:
"Inspect and edit localStorage and Electron Store entries in real time.",
icon: (
<div className="bg-primary/10 text-primary grid h-10 w-10 place-items-center rounded-xl">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
className="h-5 w-5"
>
<rect x="3" y="4" width="18" height="7" rx="1" />
<rect x="3" y="13" width="18" height="7" rx="1" />
<path d="M7 17h0" />
<path d="M7 8h0" />
</svg>
</div>
),
element: <StorageDebugger />,
});
}
if (isLogViewerEnabled) {
entries.push({
id: "logs",
label: "Log Viewer",
description:
"Review captured console output, filter by severity, and export logs for support.",
icon: (
<div className="grid h-10 w-10 place-items-center rounded-xl bg-purple-500/10 text-purple-500">
<ScrollText className="h-5 w-5" />
</div>
),
element: <LogViewer />,
});
}
if (isStateInspectorEnabled) {
entries.push({
id: "state",
label: "State Inspector",
description:
"Inspect registered application state snapshots and safely mutate values for testing.",
icon: (
<div className="grid h-10 w-10 place-items-center rounded-xl bg-amber-500/10 text-amber-500">
<Braces className="h-5 w-5" />
</div>
),
element: <StateInspector />,
});
}
if (isIpcViewerEnabled) {
entries.push({
id: "ipc",
label: "IPC Traffic",
description:
"Monitor renderer ↔ main process messages, filter by channel, and inspect payloads.",
icon: (
<div className="grid h-10 w-10 place-items-center rounded-xl bg-sky-500/10 text-sky-500">
<Radio className="h-5 w-5" />
</div>
),
element: <IpcViewer />,
});
}
if (isEventLoggerEnabled) {
entries.push({
id: "events",
label: "Event Logger",
description:
"Timeline of captured user actions and application events with advanced filtering.",
icon: (
<div className="grid h-10 w-10 place-items-center rounded-xl bg-emerald-500/10 text-emerald-500">
<ActivitySquare className="h-5 w-5" />
</div>
),
element: <EventLogger />,
});
}
if (isPerformanceMonitorEnabled) {
entries.push({
id: "performance",
label: "Performance Monitor",
description:
"Real-time monitoring of API latency, cache efficiency, matching speed, and memory usage.",
icon: (
<div className="grid h-10 w-10 place-items-center rounded-xl bg-rose-500/10 text-rose-500">
<Gauge className="h-5 w-5" />
</div>
),
element: <PerformanceMonitor />,
});
}
return entries;
}, [
isIpcViewerEnabled,
isEventLoggerEnabled,
isLogViewerEnabled,
isStateInspectorEnabled,
isStorageDebuggerEnabled,
isPerformanceMonitorEnabled,
setActivePanel,
]);
// Ensure activePanel is valid when panels list changes
useEffect(() => {
if (!panels.length) {
setActivePanel("");
return;
}
// Select first panel if currently active panel is no longer available
if (!panels.some((panel) => panel.id === activePanel)) {
setActivePanel(panels[0].id);
}
}, [panels, activePanel, setActivePanel]);
const hasPanels = panels.length > 0;
return (
<Dialog
open={isOpen}
onOpenChange={(open) => {
if (!open) {
onClose();
}
}}
>
<DialogContent className="border-border/60 bg-background/95 lg:max-w-5xl! max-h-[80vh] grid-rows-[auto,1fr] overflow-hidden border p-0 shadow-2xl backdrop-blur-xl md:max-w-[95vw]">
<DialogHeader className="relative overflow-hidden px-8 pb-4 pt-8">
<div className="from-primary/10 bg-linear-to-br absolute inset-0 h-full w-full via-purple-500/10 to-transparent" />
<div className="bg-primary/15 absolute right-10 top-8 h-32 w-32 rounded-full blur-3xl" />
<div className="relative z-10 flex flex-col gap-3">
<DialogTitle className="flex items-center gap-3 text-2xl font-semibold">
<span className="bg-muted/60 text-primary border-primary/20 hidden h-11 w-11 items-center justify-center rounded-xl border shadow-inner sm:flex">
<Bug className="h-5 w-5" />
</span>
<div>
<p className="text-muted-foreground text-xs uppercase tracking-[0.3em]">
Internal Tools
</p>
<span>Debug Command Center</span>
</div>
</DialogTitle>
<DialogDescription className="text-muted-foreground max-w-2xl text-sm">
Access experimental developer utilities. Toggle individual panels
from Settings → Data → Debug Tools.
</DialogDescription>
{hasPanels ? (
<Badge
variant="outline"
className="border-primary/40 bg-primary/10 text-primary w-fit rounded-full text-xs font-semibold"
>
{panels.length} panel{panels.length === 1 ? "" : "s"} enabled
</Badge>
) : (
<Badge
variant="outline"
className="w-fit rounded-full border-orange-300/60 bg-orange-100/40 text-xs font-semibold text-orange-600 dark:border-orange-500/40 dark:bg-orange-900/30 dark:text-orange-200"
>
No panels enabled
</Badge>
)}
</div>
</DialogHeader>
<div className="flex h-full max-h-[60vh] min-h-0 flex-1 flex-col gap-6 px-8 pb-8 pt-6">
{hasPanels ? (
<Tabs
value={activePanel}
onValueChange={setActivePanel}
orientation="vertical"
className="flex min-h-0 flex-1"
>
<div className="flex min-h-0 flex-col gap-6 md:max-w-[87.5vw] lg:flex-row">
<div className="border-border/80 bg-muted/10 relative flex w-full flex-col overflow-hidden rounded-2xl border pr-1 shadow-sm md:min-h-[10vh] lg:w-64">
<ScrollArea type="always" className="flex h-full w-full pr-2">
<div className="p-2">
<TabsList
className={cn("flex h-auto w-full flex-col gap-2")}
aria-label="Debug panel selector"
>
{panels.map((panel) => (
<TabsTrigger
key={panel.id}
id={`panel-${panel.id}`}
value={panel.id}
className={cn(
"group w-full flex-none justify-start gap-3 rounded-xl border px-3 py-3 text-left text-sm transition-all",
"min-h-13 h-auto",
// default card appearance
"bg-background/90 border-border/40",
// active state: stronger background, border, subtle shadow and left accent
"data-[state=active]:bg-background/30! data-[state=active]:border-primary/40! data-[state=active]:text-primary! data-[state=active]:shadow-md!",
"hover:border-primary/30! hover:bg-primary/5!",
// Ensure text can wrap instead of overflowing
"wrap-break-word min-w-0 whitespace-normal",
)}
style={{ position: "relative" }}
>
<div className="flex items-start gap-3">
<div className="shrink-0">{panel.icon}</div>
<div className="min-w-0 flex-1 space-y-1">
<div className="font-semibold leading-tight">
{panel.label}
</div>
<p className="text-muted-foreground text-xs">
{panel.description}
</p>
</div>
</div>
</TabsTrigger>
))}
</TabsList>
</div>
</ScrollArea>
</div>
{/* Content column */}
<div className="border-border/80 bg-background/95 relative flex-1 overflow-hidden rounded-2xl border shadow-inner md:min-h-[40vh]">
<div className="from-primary/10 bg-linear-to-b absolute inset-x-12 top-0 h-24 rounded-b-full to-transparent blur-3xl" />
<div className="relative h-full min-h-0 overflow-y-auto p-4">
{panels.map((panel) => (
<TabsContent
key={panel.id}
value={panel.id}
className="h-full"
aria-labelledby={`panel-${panel.id}`}
>
<div className="flex h-full min-h-[600px] flex-col">
{panel.element}
</div>
</TabsContent>
))}
</div>
</div>
</div>
</Tabs>
) : (
<div className="border-border/60 bg-muted/20 flex h-full flex-col items-center justify-center gap-4 rounded-2xl border border-dashed py-4 text-center">
<p className="text-muted-foreground max-w-md text-sm">
Enable at least one debug panel from{" "}
<strong>Settings → Data → Debug tools</strong> to start using
the command center.
</p>
<Button variant="outline" onClick={onClose}>
Close
</Button>
</div>
)}
</div>
</DialogContent>
</Dialog>
);
}
Debug command center for accessing development utilities. Displays enabled debug panels (Storage Explorer, Log Viewer, State Inspector, IPC Traffic, Event Logger). Panels are toggled from Settings → Data → Debug Tools.