Header of process and node ports follow each other both ways: a panel edit rewrites only the def line of edited code, a code save reads the header back into input ports and settings (draft and response); Space types in the node editor again
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
@@ -49,6 +49,13 @@ import useCustomToast from "@/hooks/useCustomToast"
|
||||
import { inCodeEditor, useShortcuts } from "@/lib/shortcuts"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { DtypeValue } from "./FlowBoundary"
|
||||
import {
|
||||
IDENTIFIER,
|
||||
portName,
|
||||
processArgs,
|
||||
RESERVED_PARAMS,
|
||||
withHeader,
|
||||
} from "./header"
|
||||
import { JsonField } from "./JsonField"
|
||||
import { MessageSparkline } from "./MessageSparkline"
|
||||
import {
|
||||
@@ -92,12 +99,6 @@ const NO_SECRET = "__none__"
|
||||
|
||||
const SECTION = PANEL_SECTION
|
||||
|
||||
/** Settings the engine reads itself, so they are not the author's to name. */
|
||||
const RESERVED_PARAMS = new Set(["synchronous"])
|
||||
|
||||
/** A setting reaches the function by name, so the name has to be one. */
|
||||
const IDENTIFIER = /^[A-Za-z_]\w*$/
|
||||
|
||||
/** Sentinels for the resources dropdown, which has two options that are not sizes. */
|
||||
const NO_RESOURCES = "__shared__"
|
||||
const CUSTOM_RESOURCES = "__custom__"
|
||||
@@ -582,6 +583,8 @@ function FreeParamsForm({
|
||||
// remount its row and take the half-typed value with it.
|
||||
<div key={`param-${index}`} className="flex items-center gap-1.5">
|
||||
<Input
|
||||
// Its own key, so a name the code's header changed shows up.
|
||||
key={key}
|
||||
defaultValue={key}
|
||||
placeholder="name"
|
||||
aria-label="Setting name"
|
||||
@@ -1364,10 +1367,6 @@ function StoredStateSection({ nodeType }: { nodeType: string }) {
|
||||
)
|
||||
}
|
||||
|
||||
/** The name the node function sees; the engine derives it the same way. */
|
||||
const portName = (spec: MessageSpec) =>
|
||||
spec.port || (spec.name ?? "").split(".").pop() || ""
|
||||
|
||||
/**
|
||||
* Whether a port was named by hand rather than after its message. Nothing
|
||||
* stores that: a port whose name is not the one its message gives it is one.
|
||||
@@ -1376,25 +1375,11 @@ const ownsName = (spec: MessageSpec) =>
|
||||
Boolean(spec.port) && spec.port !== portName({ name: spec.name })
|
||||
|
||||
/**
|
||||
* A `process` that takes this node's inputs and settings, and returns its
|
||||
* outputs.
|
||||
*
|
||||
* Both arrive by name, so both are arguments — the ports first, in the order
|
||||
* they are declared, then the settings.
|
||||
* A `process` that takes this node's inputs and settings (`processArgs`), and
|
||||
* returns its outputs.
|
||||
*/
|
||||
function scaffoldFor(node: NodeDef_Input): string {
|
||||
const args = [
|
||||
...new Set(
|
||||
[
|
||||
...(node.requires ?? []).map(portName),
|
||||
...Object.keys(node.params ?? {}).filter(
|
||||
(name) => !RESERVED_PARAMS.has(name),
|
||||
),
|
||||
]
|
||||
// Anything else cannot be a keyword argument, so it cannot be one.
|
||||
.filter((name) => IDENTIFIER.test(name)),
|
||||
),
|
||||
]
|
||||
const args = processArgs(node)
|
||||
const returns = (node.provides ?? [])
|
||||
.filter((spec) => portName(spec))
|
||||
.map((spec) => `"${portName(spec)}": ${PLACEHOLDER[spec.dtype ?? "float"]}`)
|
||||
@@ -1470,11 +1455,12 @@ function PanelBody({
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the node, and keep an untouched scaffold in step with its ports.
|
||||
* Change the node, and keep its code in step with its ports and settings.
|
||||
*
|
||||
* Only code that is still exactly what this panel generates is rewritten —
|
||||
* one edit of your own and it is yours, ports or no ports. Shared code is
|
||||
* never rewritten either: the other flows using it have their own ports.
|
||||
* Code that is still exactly what this panel generates is generated again,
|
||||
* outputs included. Code of your own only has the header of `process`
|
||||
* rewritten, and the body is left as it is. Shared code is never rewritten:
|
||||
* the other flows using it have their own ports.
|
||||
*/
|
||||
const editNode = (next: NodeDef_Input) => {
|
||||
onChange(next)
|
||||
@@ -1487,8 +1473,10 @@ function PanelBody({
|
||||
current === undefined
|
||||
)
|
||||
return
|
||||
const wanted = scaffoldFor(next)
|
||||
if (current !== wanted && SCAFFOLD_SHAPE.test(current)) editCode(wanted)
|
||||
const wanted = SCAFFOLD_SHAPE.test(current)
|
||||
? scaffoldFor(next)
|
||||
: withHeader(current, processArgs(next))
|
||||
if (current !== wanted) editCode(wanted)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1705,7 +1693,9 @@ function PanelBody({
|
||||
{CODE_LOCKED}
|
||||
</p>
|
||||
)}
|
||||
<div className="min-h-0 flex-1 overflow-hidden rounded-md border border-border">
|
||||
{/* `nokey`: Monaco types through a plain div (EditContext), which
|
||||
the canvas takes for itself, so Space would pan instead. */}
|
||||
<div className="nokey min-h-0 flex-1 overflow-hidden rounded-md border border-border">
|
||||
<Suspense
|
||||
fallback={
|
||||
<div className="h-full w-full animate-pulse bg-muted" />
|
||||
|
||||
Reference in New Issue
Block a user