Sparkline every message a node names

A number in the panel says where a value is, not where it has been. Each
named port in Consumes and Provides now carries a sparkline under it,
fetched once on open and extended from the socket as values arrive, held
to the same 120-point window the server keeps.

Both lists render through the shared port row, so a sink node shows what
flows through it rather than nothing. A flat series draws its mid line
and drops the redundant range label, a lone reading is just its dot, and
a series spanning decades switches to a log scale with the labels left
in real units.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkmeRiyeYmVZqJVwuyHq9o
This commit is contained in:
Melvin Strobl
2026-08-15 21:59:30 +02:00
co-authored by Claude Opus 5
parent 0154bf3537
commit e3b3d234ce
3 changed files with 260 additions and 33 deletions
+42 -33
View File
@@ -23,6 +23,7 @@ import {
} from "@/components/ui/select"
import { Switch } from "@/components/ui/switch"
import { cn } from "@/lib/utils"
import { MessageSparkline } from "./MessageSparkline"
import { nodeSourceQueryOptions } from "./queries"
import { PANEL_SECTION, SidePanel } from "./SidePanel"
@@ -177,39 +178,47 @@ function PortList({
) : null}
{specs.map((spec, index) => (
<div key={`port-${index}`} className="flex items-center gap-1.5">
<MessageNameInput
value={spec.name ?? ""}
suggestions={suggestions}
placeholder={`name in ${flow}`}
autoFocus={index === freshIndex}
onChange={(name) => update(index, { name, port: "" })}
onRenamed={onRenamed}
/>
<Select
value={spec.dtype ?? "float"}
onValueChange={(value) => update(index, { dtype: value as DType })}
>
<SelectTrigger className="!h-8 w-[92px] text-sm" aria-label="Type">
<SelectValue />
</SelectTrigger>
<SelectContent>
{DTYPES.map((dtype) => (
<SelectItem key={dtype} value={dtype}>
{dtype}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
variant="ghost"
size="icon-sm"
className="text-muted-foreground"
aria-label="Remove port"
onClick={() => onChange(specs.filter((_, i) => i !== index))}
>
<X />
</Button>
<div key={`port-${index}`} className="grid gap-1">
<div className="flex items-center gap-1.5">
<MessageNameInput
value={spec.name ?? ""}
suggestions={suggestions}
placeholder={`name in ${flow}`}
autoFocus={index === freshIndex}
onChange={(name) => update(index, { name, port: "" })}
onRenamed={onRenamed}
/>
<Select
value={spec.dtype ?? "float"}
onValueChange={(value) =>
update(index, { dtype: value as DType })
}
>
<SelectTrigger
className="!h-8 w-[92px] text-sm"
aria-label="Type"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{DTYPES.map((dtype) => (
<SelectItem key={dtype} value={dtype}>
{dtype}
</SelectItem>
))}
</SelectContent>
</Select>
<Button
variant="ghost"
size="icon-sm"
className="text-muted-foreground"
aria-label="Remove port"
onClick={() => onChange(specs.filter((_, i) => i !== index))}
>
<X />
</Button>
</div>
{spec.name ? <MessageSparkline flow={flow} name={spec.name} /> : null}
</div>
))}
</div>