Let a node's failure outlive the run that followed it
A node's error cleared the moment it ran again, so a failure that genuinely fired an alert could leave no trace on the canvas by the time anyone looked. The engine records it now — on the node's status, so it survives a reload and every client agrees — and reading the traceback is what clears it. The seam is the event bus, which is where every failing path already meets: a queued live run, an explicit run, a preview, and a single triggered node all publish `node_error`, while the controller's own observer would have seen only one of them. That was half the confusion. The other half: clicking a failed neuron on Home often landed on a flow where everything looked fine. Nodes merge into one neuron by instance key — every InfluxDB node pointing at the same bucket is one neuron — and the click went to whichever flow contributed a member first, not the one that failed. It now goes to the failing member and selects it, and the canvas marks a failing node rather than leaving it to the dot alone. The inject node emitted one payload to every port it declared, whatever their types, so an inject on a bool port carrying the text "true" raised at publish time. Each port gets its own field now, typed and parsed by that port's dtype, and remembers what it last sent. A port that is renamed carries its value with it; one that is removed takes its value with it. An inject written before this keeps emitting exactly what it did. The derived-cron chip also appeared on the delay node, where `interval` is a rate limit and a schedule derived from it means nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uq8mtNb97A7praJLyeEYgs
This commit is contained in:
@@ -4,8 +4,16 @@ import { Suspense } from "react"
|
||||
import { FlowEditor } from "@/components/Flow/FlowEditor"
|
||||
import { Skeleton } from "@/components/ui/skeleton"
|
||||
|
||||
type Search = { node?: string }
|
||||
|
||||
export const Route = createFileRoute("/_canvas/flows/$flowName")({
|
||||
component: FlowRoute,
|
||||
// Which node to arrive on. A search param rather than a hash so the brain
|
||||
// graph's click-through — and any link out of an alert — is a whole address
|
||||
// someone can send.
|
||||
validateSearch: (search: Record<string, unknown>): Search => ({
|
||||
node: typeof search.node === "string" ? search.node : undefined,
|
||||
}),
|
||||
head: ({ params }) => ({
|
||||
meta: [{ title: `${params.flowName} - Fluksio` }],
|
||||
}),
|
||||
@@ -21,10 +29,11 @@ function Loading() {
|
||||
|
||||
function FlowRoute() {
|
||||
const { flowName } = Route.useParams()
|
||||
const { node } = Route.useSearch()
|
||||
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<FlowEditor flowName={flowName} />
|
||||
<FlowEditor flowName={flowName} focus={node} />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ import { Switch } from "@/components/ui/switch"
|
||||
import useCustomToast from "@/hooks/useCustomToast"
|
||||
|
||||
export const Route = createFileRoute("/_layout/")({
|
||||
component: Dashboard,
|
||||
component: Home,
|
||||
head: () => ({
|
||||
meta: [
|
||||
{
|
||||
title: "Dashboard - Fluksio",
|
||||
title: "Home - Fluksio",
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -50,7 +50,7 @@ function FlowRow({ flow }: { flow: FlowSummary }) {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-3 border-b border-border px-5 py-3 last:border-b-0"
|
||||
data-testid="dashboard-flow-row"
|
||||
data-testid="home-flow-row"
|
||||
>
|
||||
<Link
|
||||
to="/flows/$flowName"
|
||||
@@ -100,7 +100,7 @@ function FlowRow({ flow }: { flow: FlowSummary }) {
|
||||
* it has been doing. The brain and the health screens compose in here rather
|
||||
* than living at routes of their own.
|
||||
*/
|
||||
function Dashboard() {
|
||||
function Home() {
|
||||
// The health block's window: one choice, read by the tiles, the flow table,
|
||||
// the charts and the lists under them.
|
||||
const [range, setRange] = useState(DEFAULT_RANGE)
|
||||
|
||||
Reference in New Issue
Block a user