Worker Execution + Spec Curve Hover
runPipeline() in src/ui/store/pipeline.ts:115 calls executePipeline() synchronously on the main thread. Both CSV parsing (PapaParse) and statistical computation freeze the UI until complete.
The Worker infrastructure (stats.worker.ts, protocol.ts) is fully implemented but disconnected.
src/workers/worker-manager.tsSingleton class owning Worker lifecycle and orchestrating execution.
new Worker(new URL('./stats.worker.ts', import.meta.url), { type: 'module' })init() if not yet initialized)LOAD_DATASET { csv } to WorkerDATASET_LOADED { dataset }topologicalSort() on main thread (reuse exported function from executor.ts)executePipeline)Map<string, unknown>)EXECUTE_NODE { node, inputData } to WorkerNODE_COMPLETE or NODE_ERROR responseonNodeComplete(nodeId, result)onNodeError(nodeId, error), mark dependents as upstream-error (skip them)Request/response correlation: Each message has an id field (already in the protocol). WorkerManager maintains a Map<string, { resolve, reject }> of pending requests. Worker onmessage handler looks up the pending promise by id and resolves/rejects it.
Fallback: If new Worker() throws (e.g., test environments), fall back to synchronous executePipeline() on main thread with a console warning.
CSV loading (currently freezes UI):
Pipeline execution (currently synchronous):
src/ui/store/data.tsloadCSVFile() delegates CSV parsing to the Worker. No more parseCSV() call on main thread.
src/ui/store/pipeline.tsrunPipeline(dataset) becomes async:
running, set executing: true (unchanged)workerManager.executePipeline() with streaming callbacks:
onComplete(nodeId, result) → set() to update that node to completeonError(nodeId, error) → set() to update that node to errorexecuting: falsesrc/core/pipeline/executor.tstopologicalSort() (currently module-private) for WorkerManager to reuseexecutePipeline() stays as-is — used by Vitest tests and as Worker fallbackThe existing protocol and Worker implementation already support exactly this flow. LOAD_DATASET accepts CSV string, parses it, responds with Dataset. EXECUTE_NODE accepts a node + inputs, executes, responds with result.
worker-manager.ts: Mock Worker via a minimal MessagePort shim. Verify topological execution order, streaming callbacks, error propagation to dependents, fallback to sync on Worker failure.pipeline/integration.test.ts tests continue to pass (they use the synchronous path).spec-curve.tsx includes hoveredCellCoordinate in renderPlot’s dependency array. On every hover change:
Two full Observable Plot SVG renders per mousemove event (~60Hz) just to change opacity values. Partial fix in place (memoized data, no-op guards) but SVG rebuild still happens.
hoveredCellCoordinate from renderPlot dependenciesChange dependency array from [data, hoveredCellCoordinate] to [data]. SVGs rebuild only when underlying data changes.
data-coordinateAfter each Plot.plot() call, post-process the returned SVG:
sortedColumns map (sort index → coordinate)circle (dots), line (CI rules)rect (cells)cx or x attribute), sort by x, assign data-coordinate from sortedColumns[i].coordinate. Positions are deterministic because Plot uses the same linear scale with a shared xDomain.const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const container = containerRef.current;
if (!container) return;
const elements = container.querySelectorAll('[data-coordinate]');
for (const el of elements) {
const coord = el.getAttribute('data-coordinate');
const match = hoveredCellCoordinate === null
|| coord === hoveredCellCoordinate;
(el as HTMLElement).style.opacity = match ? '' : '0.25';
}
}, [hoveredCellCoordinate]);
Lines 99 and 106 currently compute per-mark opacity based on hoveredCellCoordinate. Remove these — all marks render at full opacity. Hover highlighting is handled entirely by DOM mutation.
chart-container.tsxChartContainer stays unchanged. It re-renders when renderPlot changes, which now only happens on data changes.
After fix:
Zero SVG rebuilds. Direct DOM mutation on <200 elements is effectively free.
These two items are independent and can be implemented in parallel. Recommended sequence: Worker first (higher impact), then spec curve hover (polish).
| File | Change |
|---|---|
src/workers/worker-manager.ts |
NEW Worker singleton + streaming orchestration |
src/ui/store/pipeline.ts |
runPipeline → async, uses WorkerManager |
src/ui/store/data.ts |
loadCSVFile → delegates CSV parsing to Worker |
src/core/pipeline/executor.ts |
Export topologicalSort |
src/ui/components/charts/spec-curve.tsx |
Remove hover from renderPlot deps, add DOM mutation effect, tag SVG elements |