Address PR review feedback (#1433)
- always dispose AI design session (try/finally) + rejecting-agent test - prune expired design rate-limit entries per check - apply workflowId precedence in createTaskWithReservedId - compare-and-set migrated project default (no pre-transaction snapshot) - reject fragment ids in setDefaultWorkflowId at the write boundary - guard stripApprovalBypassFlags against malformed template nodes - hoist duplicate stepToFragmentIr call in migration loop - auto-layout preserves unplaced nodes (strictColumnForY) - second same-pair connection births a failure edge for condition-capable sources - insertFragment expands foreach templates into child nodes (lossless round-trip) - copyIrWithFreshIds remaps namespaced foreach child layout keys - discard stale AI-edit results after workflow switch - i18n: returnEmptyString:false so empty locale placeholders fall back to en - test fixes: schema-version titles, migrate idempotency assertion, TaskForm post-resolve + cross-mode regression, deterministic waits
This commit is contained in:
@@ -743,6 +743,11 @@ function InnerEditor({
|
||||
const activeWorkflow = useMemo(() => workflows.find((w) => w.id === activeId), [workflows, activeId]);
|
||||
const isBuiltin = !!activeWorkflow && isBuiltinWorkflowId(activeWorkflow.id);
|
||||
|
||||
// Live mirror of the active workflow id, readable inside async callbacks that
|
||||
// captured an earlier value before an await (e.g. the AI-design round-trip).
|
||||
const activeIdRef = useRef(activeId);
|
||||
activeIdRef.current = activeId;
|
||||
|
||||
// Trivial-graph palette hint (R9): a user-owned workflow whose graph carries no
|
||||
// user-authored node yet (everything is start/end/column-band — column bands map
|
||||
// to data.kind "start"). Disappears as soon as any user node exists; never shows
|
||||
@@ -1216,14 +1221,26 @@ function InnerEditor({
|
||||
}
|
||||
const controller = new AbortController();
|
||||
aiEditAbortRef.current = controller;
|
||||
// Capture the target workflow up-front: if the user switches the active
|
||||
// workflow during the (long) design round-trip, we must NOT apply the result
|
||||
// to whatever workflow happens to be active when it resolves.
|
||||
const targetWorkflow = activeWorkflow;
|
||||
setAiEditBusy(true);
|
||||
setAiEditError(null);
|
||||
try {
|
||||
const result = await designWorkflow(
|
||||
{ prompt: trimmed, workflowId: activeWorkflow.id },
|
||||
{ prompt: trimmed, workflowId: targetWorkflow.id },
|
||||
projectId,
|
||||
controller.signal,
|
||||
);
|
||||
// The active workflow changed mid-flight → discard the stale result.
|
||||
if (activeIdRef.current !== targetWorkflow.id) {
|
||||
addToast(
|
||||
t("workflows.aiStaleDiscarded", "Discarded AI design — you switched workflows"),
|
||||
"warning",
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Always confirm before the destructive replace.
|
||||
const ok = await confirm({
|
||||
title: t("workflows.aiReplaceTitle", "Replace graph?"),
|
||||
@@ -1238,14 +1255,14 @@ function InnerEditor({
|
||||
// Map the returned IR through irToFlow on a definition-shaped object,
|
||||
// mirroring the active-workflow load effect's mapping.
|
||||
const flow = irToFlow({
|
||||
...activeWorkflow,
|
||||
...targetWorkflow,
|
||||
ir: result.ir,
|
||||
layout: result.layout,
|
||||
});
|
||||
setNodes(flow.nodes);
|
||||
setEdges(flow.edges);
|
||||
setColumns(columnsOf({ ...activeWorkflow, ir: result.ir }));
|
||||
setFields(fieldsOf({ ...activeWorkflow, ir: result.ir }));
|
||||
setColumns(columnsOf({ ...targetWorkflow, ir: result.ir }));
|
||||
setFields(fieldsOf({ ...targetWorkflow, ir: result.ir }));
|
||||
setSelectedNodeId(null);
|
||||
setSelectedEdgeId(null);
|
||||
setValidationError(null);
|
||||
|
||||
Reference in New Issue
Block a user