feat(editor): NodeEditorDialog exposes PinnedDataSection per node category

This commit is contained in:
Claude
2026-08-30 14:39:20 +00:00
parent 432782aeb1
commit f7c038e43e
2 changed files with 44 additions and 0 deletions

View File

@@ -249,4 +249,31 @@ describe("NodeEditorDialog", () => {
await userEvent.click(await screen.findByText("email"));
expect(body.value).toBe("{input.email}");
});
it("action nodes show a 'Pinned input' section", () => {
renderDialog();
expect(screen.getByText(/pinned input/i)).toBeInTheDocument();
expect(screen.queryByText(/pinned output/i)).not.toBeInTheDocument();
});
it("trigger nodes show a 'Pinned output' section instead", () => {
const node: Node<VcNodeData> = {
id: "w1", type: "vc",
position: { x: 0, y: 0 },
data: { typeId: "http.webhook", name: "Webhook", config: { path: "hook" }, connection_id: null },
};
renderDialog({ node });
expect(screen.getByText(/pinned output/i)).toBeInTheDocument();
expect(screen.queryByText(/pinned input/i)).not.toBeInTheDocument();
});
it("editing pin JSON flows into node.data.pinnedInput", async () => {
renderControlledDialog();
// The Pinned input textarea has aria-label "Pinned input".
const ta = screen.getByLabelText(/^pinned input$/i) as HTMLTextAreaElement;
await userEvent.type(ta, `{{"a":1}`); // userEvent's {{ escapes to {
// Ends with `{"a":1}` — the textarea value should reflect it.
// Harness applies onPatch, so the controlled value stays synced.
expect(ta.value).toContain(`"a":1`);
});
});

View File

@@ -10,6 +10,7 @@ import { JsonTree } from "@/expr/JsonTree";
import { findLatestIo } from "./nodeIoLookup";
import type { VcNodeData } from "./graphAdapter";
import type { ExpressionFieldHandle } from "@/expr/ExpressionField";
import { PinnedDataSection } from "./PinnedDataSection";
interface Props {
node: Node<VcNodeData>;
@@ -188,6 +189,22 @@ export function NodeEditorDialog({
onFieldFocus={registerFocus}
/>
)}
{nt && (nt.inputCount > 0 || nt.outputCount > 0) && (
<PinnedDataSection
title={nt.category === "Trigger" ? "Pinned output" : "Pinned input"}
workflowId={workflowId}
nodeId={node.id}
side={nt.category === "Trigger" ? "outputs" : "inputs"}
value={nt.category === "Trigger" ? (node.data.pinnedOutput ?? null) : (node.data.pinnedInput ?? null)}
onChange={(v) =>
onPatch(
nt.category === "Trigger"
? { pinnedOutput: v }
: { pinnedInput: v },
)
}
/>
)}
{node.data.typeId === "http.webhook" && (
<WebhookUrlHint
workflowId={workflowId}