Compare commits

...

2 Commits

  1. 43
      src/lib/chat/editOperations.test.ts
  2. 19
      src/lib/chat/editOperations.ts
  3. 4
      src/lib/chat/tools.ts

43
src/lib/chat/editOperations.test.ts

@ -381,6 +381,49 @@ describe("applyEditOperations", () => {
expect(updated!.data).toHaveProperty("status", "idle");
});
it("updates node position from top-level position without requiring data changes", () => {
const ops: EditOperation[] = [
{
type: "updateNode",
nodeId: "prompt-1",
position: { x: 420, y: 240 },
data: {},
},
];
const result = applyEditOperations(ops, {
nodes: baseNodes,
edges: baseEdges,
});
const updated = result.nodes.find((n) => n.id === "prompt-1");
expect(updated!.position).toEqual({ x: 420, y: 240 });
expect(updated!.data).toHaveProperty("prompt", "sunset photo");
expect(result.applied).toBe(1);
expect(result.skipped).toEqual([]);
});
it("updates node position from data.position for backwards compatibility", () => {
const ops: EditOperation[] = [
{
type: "updateNode",
nodeId: "prompt-1",
data: { position: { x: 320, y: 180 } },
},
];
const result = applyEditOperations(ops, {
nodes: baseNodes,
edges: baseEdges,
});
const updated = result.nodes.find((n) => n.id === "prompt-1");
expect(updated!.position).toEqual({ x: 320, y: 180 });
expect(updated!.data).not.toHaveProperty("position");
expect(result.applied).toBe(1);
expect(result.skipped).toEqual([]);
});
it("skips with message when nodeId not found", () => {
const ops: EditOperation[] = [
{

19
src/lib/chat/editOperations.ts

@ -23,7 +23,12 @@ export type EditOperation =
data?: Record<string, unknown>;
}
| { type: "removeNode"; nodeId: string }
| { type: "updateNode"; nodeId: string; data: Record<string, unknown> }
| {
type: "updateNode";
nodeId: string;
position?: { x: number; y: number };
data: Record<string, unknown>;
}
| {
type: "addEdge";
source: string;
@ -137,19 +142,27 @@ export function applyEditOperations(
}
const node = nodes[nodeIndex];
const rawPosition =
operation.position ??
(operation.data.position as { x: number; y: number } | undefined);
const hasValidPosition =
rawPosition &&
Number.isFinite(rawPosition.x) &&
Number.isFinite(rawPosition.y);
const safeData = sanitizeChatNodeData(node.type, operation.data);
if (Object.keys(safeData).length === 0) {
if (Object.keys(safeData).length === 0 && !hasValidPosition) {
skipped.push(
`updateNode: no editable fields provided for node "${operation.nodeId}"`
);
break;
}
console.log("Updating node", operation.nodeId, "with data", safeData);
// Update node data immutably
nodes = nodes.map((n) =>
n.id === operation.nodeId
? {
...n,
position: hasValidPosition ? rawPosition : n.position,
data: {
...n.data,
...safeData,

4
src/lib/chat/tools.ts

@ -206,6 +206,10 @@ export function createChatTools(nodeIds: string[]) {
.record(z.string(), z.unknown())
.optional()
.describe("Node data to set/merge for addNode/updateNode"),
position: z
.object({ x: z.number(), y: z.number() })
.optional()
.describe("Canvas position for addNode/updateNode"),
source: z
.string()
.optional()

Loading…
Cancel
Save