flow: structured dtypes, and the widgets that read them

A series, record or list message declares its shape instead of riding
DType.JSON, so a widget binds a shape rather than some JSON and a wrong
binding is refused before anything runs. A list declares its item type,
which is what keeps list[float] expressible for a pipeline.

On top of that: an agenda over a list, a notification over a record, and
a dashboard alert channel that publishes engine faults as one — so a
panel can show what went wrong without a flow wiring it by hand.

Also: only None means a node published nothing, a falsy value of the
wrong shape is now the named error it always should have been; and the
gauge's readout says its size is viewBox geometry rather than type scale.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-17 15:06:45 +02:00
co-authored by Claude Opus 5
parent 18837e8880
commit 413501c6ce
16 changed files with 751 additions and 43 deletions
+39 -1
View File
@@ -50,7 +50,19 @@ import { PANEL_SECTION, PanelTitle, SidePanel } from "./SidePanel"
const NodeEditor = lazy(() => import("./NodeEditor"))
const DTYPES: DType[] = ["float", "int", "str", "bool", "json"]
const DTYPES: DType[] = [
"float",
"int",
"str",
"bool",
"json",
"series",
"record",
"list",
]
/** What a list may hold. One declared level: no list of lists. */
const ITEM_DTYPES: DType[] = ["record", "float", "int", "str", "bool", "json"]
/** Radix selects cannot hold an empty value, so "no secret" needs a name. */
const NO_SECRET = "__none__"
@@ -274,6 +286,29 @@ function PortList({
))}
</SelectContent>
</Select>
{spec.dtype === "list" ? (
<Select
value={spec.item ?? "record"}
onValueChange={(value) =>
update(index, { item: value as DType })
}
>
<SelectTrigger
className="!h-8 w-[92px] text-sm"
aria-label="Item type"
title="What each item of the list is"
>
<SelectValue />
</SelectTrigger>
<SelectContent>
{ITEM_DTYPES.map((dtype) => (
<SelectItem key={dtype} value={dtype}>
{dtype}
</SelectItem>
))}
</SelectContent>
</Select>
) : null}
<Input
type="number"
min={0}
@@ -765,6 +800,9 @@ const PLACEHOLDER: Record<DType, string> = {
bool: "False",
str: '""',
json: "{}",
series: '{"lines": []}',
record: "{}",
list: "[]",
}
const SCAFFOLD_DOC =