Flow inputs carry an origin hint, run or panel

The Inputs editor and the canvas label show it, and the run forms leave
panel inputs out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
2026-10-15 10:08:46 +02:00
co-authored by Claude Opus 5.5
parent ccc7081b08
commit f5be932df3
10 changed files with 111 additions and 39 deletions
+14
View File
@@ -1355,6 +1355,13 @@ export const FlowInput_InputSchema = {
}
],
title: 'Initial'
},
origin: {
type: 'string',
enum: ['run', 'panel'],
title: 'Origin',
description: 'What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.',
default: 'run'
}
},
type: 'object',
@@ -1376,6 +1383,13 @@ export const FlowInput_OutputSchema = {
}
],
title: 'Initial'
},
origin: {
type: 'string',
enum: ['run', 'panel'],
title: 'Origin',
description: 'What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.',
default: 'run'
}
},
type: 'object',
+13
View File
@@ -391,14 +391,27 @@ export type FlowDetail = {
export type FlowInput_Input = {
spec: MessageSpec;
initial?: (unknown | null);
/**
* What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.
*/
origin?: 'run' | 'panel';
};
/**
* What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.
*/
export type origin = 'run' | 'panel';
/**
* A message the flow starts with rather than computes.
*/
export type FlowInput_Output = {
spec: MessageSpec;
initial?: (unknown | null);
/**
* What writes it. A run input is a parameter a run passes in, a panel input is written by a dashboard or panel control. A hint only: run forms leave panel inputs out, the engine treats both the same.
*/
origin?: 'run' | 'panel';
};
/**
+48 -30
View File
@@ -6,6 +6,7 @@ import { MessagesService } from "@/client"
import { Button } from "@/components/ui/button"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import { Segmented } from "@/components/ui/segmented"
import {
Select,
SelectContent,
@@ -160,6 +161,12 @@ function usePublishInput() {
})
}
/** What writes an input: a run passing it in, or a control on a panel. */
const ORIGINS = [
["run", "run"],
["panel", "panel"],
] as const
function InputRow({
flow,
declared,
@@ -231,34 +238,44 @@ function InputRow({
</Button>
</div>
{live && name ? (
<div className="flex items-center gap-2 pl-1 text-xs text-muted-foreground">
<span className="shrink-0">now</span>
{current === undefined ? (
<span className="flex-1">—</span>
) : (
<span className="min-w-0 flex-1">
<ValuePreview value={current.value} dtype={spec.dtype} />
</span>
)}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon-sm"
aria-label="Publish this value now"
disabled={publish.isPending || declared.initial === null}
onClick={() =>
publish.mutate({ message, value: declared.initial })
}
>
<ArrowUpFromLine />
</Button>
</TooltipTrigger>
<TooltipContent>Put this value into the flow now</TooltipContent>
</Tooltip>
</div>
) : null}
<div className="flex items-center gap-2 pl-1 text-xs text-muted-foreground">
<Segmented
label="Written by"
value={declared.origin ?? "run"}
options={ORIGINS}
onChange={(origin) =>
onChange({ ...declared, origin: origin as typeof declared.origin })
}
/>
{live && name ? (
<>
<span className="shrink-0">now</span>
{current === undefined ? (
<span className="flex-1">—</span>
) : (
<span className="min-w-0 flex-1">
<ValuePreview value={current.value} dtype={spec.dtype} />
</span>
)}
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon-sm"
aria-label="Publish this value now"
disabled={publish.isPending || declared.initial === null}
onClick={() =>
publish.mutate({ message, value: declared.initial })
}
>
<ArrowUpFromLine />
</Button>
</TooltipTrigger>
<TooltipContent>Put this value into the flow now</TooltipContent>
</Tooltip>
</>
) : null}
</div>
</div>
)
}
@@ -338,8 +355,9 @@ export function BoundarySections({
<div className="flex items-center justify-between">
<span className={PANEL_SECTION}>Inputs</span>
<InfoTip label="Inputs">
Messages that arrive from outside, such as a dashboard control, a
run or the API, and the value the flow starts from.
Messages that arrive from outside, and the value the flow starts
from. A run passes a run input in, a dashboard or panel control
writes a panel input.
</InfoTip>
<Button
variant="ghost"
+9 -2
View File
@@ -94,9 +94,16 @@ export function initialText(inputs: FlowInput_Input[]): Record<string, string> {
)
}
/** The declared inputs worth drawing a field for. */
/**
* The declared inputs worth drawing a field for.
*
* A panel input is a control's to write, so a run keeps its starting value
* and the form does not offer it as a parameter.
*/
export function declaredInputs(definition: FlowDef_Input): FlowInput_Input[] {
return (definition.inputs ?? []).filter((one) => one.spec?.name)
return (definition.inputs ?? []).filter(
(one) => one.spec?.name && one.origin !== "panel",
)
}
/**
+5 -2
View File
@@ -91,11 +91,14 @@ export function deriveEdges(nodes: NodeDef_Input[], flow: string): Edge[] {
*/
export function boundaryKey(doc: {
mode?: string | null
inputs?: { spec?: MessageSpec | null }[] | null
inputs?: { spec?: MessageSpec | null; origin?: string }[] | null
outputs?: string[] | null
}): string {
const inputs = (doc.inputs ?? [])
.map((one) => `${one.spec?.name ?? ""}:${one.spec?.dtype ?? ""}`)
.map(
(one) =>
`${one.spec?.name ?? ""}:${one.spec?.dtype ?? ""}:${one.origin ?? ""}`,
)
.join(",")
return `${doc.mode ?? "live"}|${inputs}|${(doc.outputs ?? []).join(",")}`
}
+2 -1
View File
@@ -62,7 +62,8 @@ function boundaryOf(
kind: "input",
id: `input:${message}`,
label: displayName(flow, message),
detail: declared.spec?.dtype ?? "float",
// What writes it, so a control's input does not read as a run's.
detail: `${declared.origin ?? "run"} · ${declared.spec?.dtype ?? "float"}`,
provides: [message],
requires: [],
})