删除realtime 的一个组件

This commit is contained in:
2026-08-14 12:27:12 +08:00
parent 38231dbc1c
commit dfb2de6bf7
24 changed files with 3972 additions and 421 deletions
+12 -1
View File
@@ -15,7 +15,8 @@
"@xyflow/react": "^12.4.4",
"elkjs": "^0.9.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-resizable-panels": "^4.12.2"
},
"devDependencies": {
"@playwright/test": "^1.51.1",
@@ -3396,6 +3397,16 @@
"node": ">=0.10.0"
}
},
"node_modules/react-resizable-panels": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.2.tgz",
"integrity": "sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==",
"license": "MIT",
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+2 -1
View File
@@ -18,7 +18,8 @@
"@xyflow/react": "^12.4.4",
"elkjs": "^0.9.3",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-resizable-panels": "^4.12.2"
},
"devDependencies": {
"@playwright/test": "^1.51.1",
+141 -14
View File
@@ -1,17 +1,144 @@
import {Alert,Box,CircularProgress,Container,Stack,Typography} from "@mui/material";
import {useEffect,useMemo,useState} from "react";
import {Alert, Box, CircularProgress, Container, Stack, Typography, useMediaQuery} from "@mui/material";
import {useTheme} from "@mui/material/styles";
import {useEffect, useMemo, useState} from "react";
import {Group, Panel, Separator} from "react-resizable-panels";
import {Category_Filter} from "./gallery/category_filter";
import {Frame_Mode_Tabs} from "./gallery/frame_mode_tabs";
import {Gallery_Page} from "./gallery/gallery_page";
import {Gallery_Summary} from "./gallery/gallery_summary";
import {Gallery_Toolbar} from "./gallery/gallery_toolbar";
import type {Selected_Plot} from "./gallery/plot_card";
import {use_gallery_catalog} from "./hooks/use_gallery_catalog";
import {Gallery_Toolbar} from "./gallery/gallery_toolbar";import {Gallery_Summary} from "./gallery/gallery_summary";import {Frame_Mode_Tabs} from "./gallery/frame_mode_tabs";import {Category_Filter} from "./gallery/category_filter";import {Gallery_Page} from "./gallery/gallery_page";import type {Selected_Plot} from "./gallery/plot_card";import {Inspector_Drawer} from "./inspector/inspector_drawer";
import {Inspector_Panel} from "./inspector/inspector_panel";
export function App() {
const {catalog,state:socket_state,message:socket_message}=use_gallery_catalog();
const [active_mode,set_active_mode]=useState(""),[active_category,set_active_category]=useState(""),[streams_paused,set_streams_paused]=useState(false),[selected_plot,set_selected_plot]=useState<Selected_Plot|null>(null),[selected_inspector_tab,set_selected_inspector_tab]=useState("controls");
const [,set_visibility_epoch]=useState(0);
useEffect(()=>{const change=()=>set_visibility_epoch(value=>value+1);document.addEventListener("visibilitychange",change);return()=>document.removeEventListener("visibilitychange",change);},[]);
useEffect(()=>{if(catalog){set_active_mode(catalog.frame_modes.some(mode=>mode.id===catalog.navigation.default_mode)?catalog.navigation.default_mode:catalog.frame_modes[0]?.id??"");set_active_category(catalog.navigation.all_categories_label);}},[catalog]);
const modes=useMemo(()=>[...(catalog?.frame_modes??[])].sort((a,b)=>a.order-b.order),[catalog]);
const cases=useMemo(()=>[...(catalog?.cases??[])].sort((a,b)=>a.order-b.order),[catalog]);
const frame_mode=modes.find(mode=>mode.id===active_mode);
const categories=catalog?[catalog.navigation.all_categories_label,...new Set(cases.map(item=>item.category))]:[];
const visible_cases=catalog?cases.filter(item=>active_category===catalog.navigation.all_categories_label||item.category===active_category):[];
return <><Gallery_Toolbar socket_state={socket_state} socket_message={socket_message} streams_paused={streams_paused} on_toggle_streams={()=>set_streams_paused(value=>!value)}/><Container maxWidth={false} sx={{py:3}}><Stack spacing={2.5}>{!catalog&&socket_state!=="error"&&<Box sx={{display:"grid",placeItems:"center",minHeight:"55vh",gap:2}}><CircularProgress/><Typography></Typography></Box>}{!catalog&&socket_state==="error"&&<Alert severity="error">{socket_message}</Alert>}{catalog&&<><Gallery_Summary catalog={catalog}/><Frame_Mode_Tabs frame_modes={modes} active_mode={active_mode} on_change={id=>{set_active_mode(id);set_selected_plot(null);}}/><Category_Filter categories={categories} active_category={active_category} on_change={set_active_category}/>{frame_mode&&<><Typography color="text.secondary">{frame_mode.description}</Typography><Gallery_Page cases={visible_cases} frame_mode={frame_mode} streams_paused={streams_paused} on_open_inspector={set_selected_plot}/></>}</>}</Stack></Container><Inspector_Drawer open={selected_plot!==null} session={selected_plot?.session??null} active_tab={selected_inspector_tab} on_change_tab={set_selected_inspector_tab} on_close={()=>set_selected_plot(null)}/></>;
const {catalog, state: socket_state, message: socket_message} = use_gallery_catalog();
const [active_mode, set_active_mode] = useState("");
const [active_category, set_active_category] = useState("");
const [streams_paused, set_streams_paused] = useState(false);
const [selected_plot, set_selected_plot] = useState<Selected_Plot | null>(null);
const [selected_inspector_tab, set_selected_inspector_tab] = useState("controls");
const [, set_visibility_epoch] = useState(0);
const theme = useTheme();
const compact_layout = useMediaQuery(theme.breakpoints.down("md"));
useEffect(() => {
const change = () => set_visibility_epoch(value => value + 1);
document.addEventListener("visibilitychange", change);
return () => document.removeEventListener("visibilitychange", change);
}, []);
useEffect(() => {
if (!catalog)
return;
set_active_mode(
catalog.frame_modes.some(mode => mode.id === catalog.navigation.default_mode)
? catalog.navigation.default_mode
: catalog.frame_modes[0]?.id ?? "",
);
set_active_category(catalog.navigation.all_categories_label);
}, [catalog]);
const modes = useMemo(
() => [...(catalog?.frame_modes ?? [])].sort((left, right) => left.order - right.order),
[catalog],
);
const cases = useMemo(
() => [...(catalog?.cases ?? [])].sort((left, right) => left.order - right.order),
[catalog],
);
const frame_mode = modes.find(mode => mode.id === active_mode);
const categories = catalog
? [catalog.navigation.all_categories_label, ...new Set(cases.map(item => item.category))]
: [];
const visible_cases = catalog
? cases.filter(item => active_category === catalog.navigation.all_categories_label || item.category === active_category)
: [];
const gallery = <Box sx={{height: "100%", overflow: "auto"}}>
<Container maxWidth={false} sx={{py: 3}}>
<Stack spacing={2.5}>
{!catalog && socket_state !== "error" && <Box sx={{display: "grid", placeItems: "center", minHeight: "55vh", gap: 2}}>
<CircularProgress/>
<Typography></Typography>
</Box>}
{!catalog && socket_state === "error" && <Alert severity="error">{socket_message}</Alert>}
{catalog && <>
<Gallery_Summary catalog={catalog}/>
<Frame_Mode_Tabs
frame_modes={modes}
active_mode={active_mode}
on_change={id => {
set_active_mode(id);
set_selected_plot(null);
}}
/>
<Category_Filter
categories={categories}
active_category={active_category}
on_change={set_active_category}
/>
{frame_mode && <>
<Typography color="text.secondary">{frame_mode.description}</Typography>
<Gallery_Page
cases={visible_cases}
frame_mode={frame_mode}
streams_paused={streams_paused}
on_open_inspector={set_selected_plot}
/>
</>}
</>}
</Stack>
</Container>
</Box>;
return <Box sx={{height: "100dvh", display: "flex", flexDirection: "column", overflow: "hidden"}}>
<Gallery_Toolbar
socket_state={socket_state}
socket_message={socket_message}
streams_paused={streams_paused}
on_toggle_streams={() => set_streams_paused(value => !value)}
/>
<Box component="main" sx={{flex: 1, minHeight: 0}}>
<Group
id="gallery-inspector-layout"
orientation={compact_layout ? "vertical" : "horizontal"}
defaultLayout={selected_plot ? {gallery: 60, inspector: 40} : {gallery: 100}}
style={{height: "100%"}}
>
<Panel id="gallery" minSize={compact_layout ? "220px" : "420px"}>
{gallery}
</Panel>
{selected_plot && <>
<Separator
id="inspector-resize-handle"
aria-label="拖动调整图库和操作面板占比"
style={{
background: "rgba(89, 214, 197, .32)",
width: compact_layout ? "100%" : 8,
height: compact_layout ? 8 : "100%",
cursor: compact_layout ? "row-resize" : "col-resize",
touchAction: "none",
transition: "background-color 120ms ease",
}}
/>
<Panel
id="inspector"
defaultSize="40%"
minSize={compact_layout ? "260px" : "360px"}
maxSize="72%"
collapsible
collapsedSize={0}
>
<Inspector_Panel
session={selected_plot.session}
active_tab={selected_inspector_tab}
on_change_tab={set_selected_inspector_tab}
on_close={() => set_selected_plot(null)}
/>
</Panel>
</>}
</Group>
</Box>
</Box>;
}
+85 -3
View File
@@ -1,3 +1,85 @@
import {Background,Controls,MiniMap,ReactFlow} from "@xyflow/react";import {Box} from "@mui/material";import {useEffect,useMemo,useState} from "react";import type {Gallery_Captured_Frame,Gallery_Node_Statistics,Gallery_Render_Plan} from "../protocol/gallery_types";import {build_dag_model} from "./dag_model";import {layout_dag} from "./dag_layout";import {Dag_Node} from "./dag_node";import type {Dag_View_Model} from "./dag_types";
const node_types={dag_node:Dag_Node};
export function Render_Dag({plan,frame=null,statistics=[],selected_node_id,on_select_node}:{plan:Gallery_Render_Plan;frame?:Gallery_Captured_Frame|null;statistics?:Gallery_Node_Statistics[];selected_node_id:number|null;on_select_node:(id:number)=>void}) {const source=useMemo(()=>build_dag_model(plan,frame,statistics,selected_node_id),[plan,frame,statistics,selected_node_id]);const [model,set_model]=useState<Dag_View_Model>(source);useEffect(()=>{let current=true;void layout_dag(source).then(value=>current&&set_model(value));return()=>{current=false;};},[source]);return <Box sx={{height:520,border:"1px solid rgba(132,179,206,.2)",borderRadius:2,overflow:"hidden"}}><ReactFlow nodes={model.nodes} edges={model.edges} nodeTypes={node_types} fitView nodesDraggable={false} nodesConnectable={false} onNodeClick={(_,node)=>on_select_node(Number(node.id))}><Background/><MiniMap/><Controls/></ReactFlow></Box>;}
import {Background, Controls, MiniMap, ReactFlow} from "@xyflow/react";
import {Box, CircularProgress} from "@mui/material";
import {useEffect, useMemo, useState} from "react";
import type {
Gallery_Captured_Frame,
Gallery_Node_Statistics,
Gallery_Render_Plan,
} from "../protocol/gallery_types";
import {layout_dag} from "./dag_layout";
import {build_dag_model} from "./dag_model";
import {Dag_Node} from "./dag_node";
import type {Dag_View_Model} from "./dag_types";
const node_types = {dag_node: Dag_Node};
export function Render_Dag({
plan,
frame = null,
statistics = [],
selected_node_id,
on_select_node,
}: {
plan: Gallery_Render_Plan;
frame?: Gallery_Captured_Frame | null;
statistics?: Gallery_Node_Statistics[];
selected_node_id: number | null;
on_select_node: (id: number) => void;
}) {
const source = useMemo(
() => build_dag_model(plan, frame, statistics, selected_node_id),
[plan, frame, statistics, selected_node_id],
);
const layout_identity = useMemo(
() => `${plan.nodes.map(node => node.node_id).join(",")}:${plan.edges.map(edge => `${edge.from}>${edge.to}`).join(",")}`,
[plan],
);
const [layout, set_layout] = useState<{identity: string; model: Dag_View_Model} | null>(null);
useEffect(() => {
let current = true;
void layout_dag(source).then(value => {
if (current)
set_layout({identity: layout_identity, model: value});
});
return () => {
current = false;
};
// Selection and telemetry only decorate nodes; they must not restart ELK layout.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [layout_identity]);
const laid_out = layout?.identity === layout_identity ? layout.model : null;
const model = useMemo(() => {
if (!laid_out)
return null;
const positions = new Map(laid_out.nodes.map(node => [node.id, node.position]));
return {
...source,
nodes: source.nodes.map(node => ({
...node,
position: positions.get(node.id) ?? node.position,
})),
};
}, [source, laid_out]);
return <Box sx={{height: 520, border: "1px solid rgba(132,179,206,.2)", borderRadius: 2, overflow: "hidden"}}>
{!model
? <Box sx={{height: "100%", display: "grid", placeItems: "center"}}><CircularProgress size={28}/></Box>
: <ReactFlow
key={layout_identity}
nodes={model.nodes}
edges={model.edges}
nodeTypes={node_types}
fitView
onlyRenderVisibleElements
nodesDraggable={false}
nodesConnectable={false}
onNodeClick={(_, node) => on_select_node(Number(node.id))}
>
<Background/>
<MiniMap/>
<Controls/>
</ReactFlow>}
</Box>;
}
@@ -1,4 +0,0 @@
import CloseIcon from "@mui/icons-material/Close";import RefreshIcon from "@mui/icons-material/Refresh";import RestartAltIcon from "@mui/icons-material/RestartAlt";
import {Box,Drawer,IconButton,Stack,Tooltip,Typography} from "@mui/material";import {useState,useSyncExternalStore} from "react";import type {Gallery_Plot_Session} from "../session/gallery_plot_session";import {EMPTY_PLOT_SNAPSHOT} from "../session/gallery_plot_snapshot";import {Inspector_Tabs} from "./inspector_tabs";import {Controls_Panel} from "./controls_panel";import {Actions_Panel} from "./actions_panel";import {Observer_Panel} from "./observer_panel";import {Performance_Panel} from "./performance_panel";import {Capture_Panel} from "../capture/capture_panel";import {Render_Dag} from "../dag/render_dag";import {Empty_State} from "../common/empty_state";
const empty_subscribe=()=>()=>undefined;const empty_snapshot=()=>EMPTY_PLOT_SNAPSHOT;
export function Inspector_Drawer({open,session,active_tab,on_change_tab,on_close}:{open:boolean;session:Gallery_Plot_Session|null;active_tab:string;on_change_tab:(tab:string)=>void;on_close:()=>void}) {const snapshot=useSyncExternalStore(session?.subscribe??empty_subscribe,session?.get_snapshot??empty_snapshot,session?.get_snapshot??empty_snapshot);const [selected_node_id,set_selected_node_id]=useState<number|null>(null);let body=null;if(session){if(active_tab==="controls")body=<Controls_Panel controls={snapshot.controls} on_patch={(target,patch)=>session.patch(target,patch)}/>;else if(active_tab==="actions")body=<Actions_Panel actions={snapshot.actions} on_action={(action,argument)=>{session.action(action,argument);setTimeout(()=>session.request_frame(performance.now(),true),40);}}/>;else if(active_tab==="observer")body=<Observer_Panel telemetry={snapshot.telemetry}/>;else if(active_tab==="performance")body=<Performance_Panel telemetry={snapshot.telemetry}/>;else if(active_tab==="performance_capture")body=snapshot.performance_capture?<Capture_Panel capture={snapshot.performance_capture} on_capture={(action,count)=>{session.action(action,count);setTimeout(()=>session.request_frame(performance.now(),true),40);}}/>:<Empty_State message="尚无 Performance Capture 数据"/>;else if(active_tab==="render_plan")body=snapshot.render_plan?<Render_Dag plan={snapshot.render_plan} selected_node_id={selected_node_id} on_select_node={set_selected_node_id}/>:<Empty_State message="场景尚未编译 Render Plan"/>;}return <Drawer anchor="right" open={open} onClose={on_close} PaperProps={{sx:{width:{xs:"100%",md:"min(900px,72vw)"}}}}><Stack direction="row" alignItems="center" sx={{p:2,pb:1}}><Box flex={1}><Typography variant="overline" color="primary.main">{session&&`${session.frame_mode.strategy} / ${session.gallery_case.component}`}</Typography><Typography variant="h5">{session?.gallery_case.title??"Inspector"}</Typography></Box><Tooltip title="重置监测"><IconButton onClick={()=>session?.reset_monitoring()}><RestartAltIcon/></IconButton></Tooltip><Tooltip title="刷新当前页"><IconButton onClick={()=>session?.refresh()}><RefreshIcon/></IconButton></Tooltip><IconButton onClick={on_close}><CloseIcon/></IconButton></Stack><Inspector_Tabs active_tab={active_tab} on_change={on_change_tab}/><Box sx={{p:2,overflow:"auto",flex:1}}>{body}</Box></Drawer>;}
@@ -0,0 +1,90 @@
import CloseIcon from "@mui/icons-material/Close";
import RefreshIcon from "@mui/icons-material/Refresh";
import RestartAltIcon from "@mui/icons-material/RestartAlt";
import {Box, IconButton, Paper, Stack, Tooltip, Typography} from "@mui/material";
import {useEffect, useState, useSyncExternalStore} from "react";
import {Capture_Panel} from "../capture/capture_panel";
import {Empty_State} from "../common/empty_state";
import {Render_Dag} from "../dag/render_dag";
import type {Gallery_Plot_Session} from "../session/gallery_plot_session";
import {Actions_Panel} from "./actions_panel";
import {Controls_Panel} from "./controls_panel";
import {Inspector_Tabs} from "./inspector_tabs";
import {Observer_Panel} from "./observer_panel";
import {Performance_Panel} from "./performance_panel";
export function Inspector_Panel({
session,
active_tab,
on_change_tab,
on_close,
}: {
session: Gallery_Plot_Session;
active_tab: string;
on_change_tab: (tab: string) => void;
on_close: () => void;
}) {
const snapshot = useSyncExternalStore(
session.subscribe,
session.get_snapshot,
session.get_snapshot,
);
const [selected_node_id, set_selected_node_id] = useState<number | null>(null);
useEffect(() => set_selected_node_id(null), [session]);
let body = null;
if (active_tab === "controls")
body = <Controls_Panel controls={snapshot.controls} on_patch={(target, patch) => session.patch(target, patch)}/>;
else if (active_tab === "actions")
body = <Actions_Panel actions={snapshot.actions} on_action={(action, argument) => {
session.action(action, argument);
setTimeout(() => session.request_frame(performance.now(), true), 40);
}}/>;
else if (active_tab === "observer")
body = <Observer_Panel telemetry={snapshot.telemetry}/>;
else if (active_tab === "performance")
body = <Performance_Panel telemetry={snapshot.telemetry}/>;
else if (active_tab === "performance_capture")
body = snapshot.performance_capture
? <Capture_Panel capture={snapshot.performance_capture} on_capture={(action, count) => {
session.action(action, count);
setTimeout(() => session.request_frame(performance.now(), true), 40);
}}/>
: <Empty_State message="尚无 Performance Capture 数据"/>;
else if (active_tab === "render_plan")
body = snapshot.render_plan
? <Render_Dag
plan={snapshot.render_plan}
selected_node_id={selected_node_id}
on_select_node={set_selected_node_id}
/>
: <Empty_State message="场景尚未编译 Render Plan"/>;
return <Paper
square
elevation={0}
data-testid="inspector-panel"
sx={{height: "100%", minWidth: 0, display: "flex", flexDirection: "column", overflow: "hidden"}}
>
<Stack direction="row" alignItems="center" sx={{p: 2, pb: 1}}>
<Box minWidth={0} flex={1}>
<Typography variant="overline" color="primary.main" noWrap>
{`${session.frame_mode.strategy} / ${session.gallery_case.component}`}
</Typography>
<Typography variant="h5" noWrap>{session.gallery_case.title}</Typography>
</Box>
<Tooltip title="重置监测">
<IconButton onClick={() => session.reset_monitoring()}><RestartAltIcon/></IconButton>
</Tooltip>
<Tooltip title="刷新当前页">
<IconButton onClick={() => session.refresh()}><RefreshIcon/></IconButton>
</Tooltip>
<Tooltip title="隐藏操作面板">
<IconButton aria-label="隐藏操作面板" onClick={on_close}><CloseIcon/></IconButton>
</Tooltip>
</Stack>
<Inspector_Tabs active_tab={active_tab} on_change={on_change_tab}/>
<Box sx={{p: 2, overflow: "auto", flex: 1, minHeight: 0}}>{body}</Box>
</Paper>;
}
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/theme.ts","./src/capture/capture_frames.tsx","./src/capture/capture_panel.tsx","./src/capture/capture_sessions.tsx","./src/capture/capture_toolbar.tsx","./src/capture/frame_summary.tsx","./src/capture/node_detail.tsx","./src/capture/plan_comparison.tsx","./src/capture/worker_timeline.tsx","./src/common/copy_button.tsx","./src/common/empty_state.tsx","./src/common/json_viewer.tsx","./src/common/metric_grid.tsx","./src/common/metric_value.tsx","./src/common/status_chip.tsx","./src/dag/dag_layout.ts","./src/dag/dag_legend.tsx","./src/dag/dag_model.ts","./src/dag/dag_node.tsx","./src/dag/dag_types.ts","./src/dag/render_dag.tsx","./src/gallery/category_filter.tsx","./src/gallery/frame_mode_tabs.tsx","./src/gallery/gallery_page.tsx","./src/gallery/gallery_summary.tsx","./src/gallery/gallery_toolbar.tsx","./src/gallery/plot_card.tsx","./src/hooks/use_element_size.ts","./src/hooks/use_gallery_catalog.ts","./src/hooks/use_plot_session.ts","./src/inspector/actions_panel.tsx","./src/inspector/control_field.tsx","./src/inspector/controls_panel.tsx","./src/inspector/inspector_drawer.tsx","./src/inspector/inspector_tabs.tsx","./src/inspector/observer_panel.tsx","./src/inspector/performance_panel.tsx","./src/plot/kernel_observer_summary.tsx","./src/plot/performance_strip.tsx","./src/plot/plot_canvas.tsx","./src/plot/plot_status.tsx","./src/protocol/format.ts","./src/protocol/gallery_descriptor.ts","./src/protocol/gallery_messages.ts","./src/protocol/gallery_parser.ts","./src/protocol/gallery_types.ts","./src/protocol/pixel_frame.ts","./src/runtime/client_performance.ts","./src/runtime/frame_request_controller.ts","./src/runtime/pixel_presenter.ts","./src/session/gallery_plot_session.ts","./src/session/gallery_plot_snapshot.ts","./src/transport/catalog_loader.ts","./src/transport/gallery_socket.ts","./tests/setup.ts","./tests/components/control_field.test.tsx","./tests/dag/dag_model.test.ts","./tests/e2e/gallery.spec.ts","./tests/protocol/gallery_parser.test.ts","./tests/protocol/pixel_frame.test.ts","./tests/runtime/client_performance.test.ts","./tests/runtime/frame_request_controller.test.ts","./vite.config.ts"],"version":"5.9.3"}
{"root":["./src/app.tsx","./src/main.tsx","./src/theme.ts","./src/capture/capture_frames.tsx","./src/capture/capture_panel.tsx","./src/capture/capture_sessions.tsx","./src/capture/capture_toolbar.tsx","./src/capture/frame_summary.tsx","./src/capture/node_detail.tsx","./src/capture/plan_comparison.tsx","./src/capture/worker_timeline.tsx","./src/common/copy_button.tsx","./src/common/empty_state.tsx","./src/common/json_viewer.tsx","./src/common/metric_grid.tsx","./src/common/metric_value.tsx","./src/common/status_chip.tsx","./src/dag/dag_layout.ts","./src/dag/dag_legend.tsx","./src/dag/dag_model.ts","./src/dag/dag_node.tsx","./src/dag/dag_types.ts","./src/dag/render_dag.tsx","./src/gallery/category_filter.tsx","./src/gallery/frame_mode_tabs.tsx","./src/gallery/gallery_page.tsx","./src/gallery/gallery_summary.tsx","./src/gallery/gallery_toolbar.tsx","./src/gallery/plot_card.tsx","./src/hooks/use_element_size.ts","./src/hooks/use_gallery_catalog.ts","./src/hooks/use_plot_session.ts","./src/inspector/actions_panel.tsx","./src/inspector/control_field.tsx","./src/inspector/controls_panel.tsx","./src/inspector/inspector_panel.tsx","./src/inspector/inspector_tabs.tsx","./src/inspector/observer_panel.tsx","./src/inspector/performance_panel.tsx","./src/plot/kernel_observer_summary.tsx","./src/plot/performance_strip.tsx","./src/plot/plot_canvas.tsx","./src/plot/plot_status.tsx","./src/protocol/format.ts","./src/protocol/gallery_descriptor.ts","./src/protocol/gallery_messages.ts","./src/protocol/gallery_parser.ts","./src/protocol/gallery_types.ts","./src/protocol/pixel_frame.ts","./src/runtime/client_performance.ts","./src/runtime/frame_request_controller.ts","./src/runtime/pixel_presenter.ts","./src/session/gallery_plot_session.ts","./src/session/gallery_plot_snapshot.ts","./src/transport/catalog_loader.ts","./src/transport/gallery_socket.ts","./tests/setup.ts","./tests/components/control_field.test.tsx","./tests/dag/dag_model.test.ts","./tests/e2e/gallery.spec.ts","./tests/protocol/gallery_parser.test.ts","./tests/protocol/pixel_frame.test.ts","./tests/runtime/client_performance.test.ts","./tests/runtime/frame_request_controller.test.ts","./vite.config.ts"],"version":"5.9.3"}