fix: stop manifest editor base image selection loop
The ManifestEditor had a feedback loop: 1. State change → buildManifest changes → onChange notifies parent 2. Parent updates manifestData → new manifest prop 3. Loading effect sets all state from manifest (arrays get new refs even if same content) 4. New array refs → buildManifest changes → onChange fires again → loop Fix: track the last-sent manifest via a ref and only call onChange when the serialized built manifest actually differs. This breaks the cycle because after the loading effect syncs state, the rebuilt manifest is identical in content so we skip the parent notification.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { useState, useEffect, useCallback, useRef } from "react";
|
||||
import { Icon } from "./icon";
|
||||
import { extractErrorMessage } from "../utils/errors";
|
||||
import {
|
||||
@@ -170,11 +170,20 @@ export const ManifestEditor = ({
|
||||
baseDefinitionId,
|
||||
]);
|
||||
|
||||
// Notify parent of changes
|
||||
// Notify parent of changes — only when built manifest actually differs
|
||||
// from what we last sent, to avoid feedback loops with the manifest prop.
|
||||
const lastSentRef = useRef<string>("");
|
||||
const onChangeRef = useRef(onChange);
|
||||
onChangeRef.current = onChange;
|
||||
|
||||
useEffect(() => {
|
||||
const m = buildManifest();
|
||||
onChange(m);
|
||||
}, [buildManifest, onChange]);
|
||||
const serialized = JSON.stringify(m);
|
||||
if (serialized !== lastSentRef.current) {
|
||||
lastSentRef.current = serialized;
|
||||
onChangeRef.current(m);
|
||||
}
|
||||
}, [buildManifest]);
|
||||
|
||||
const handlePreview = async () => {
|
||||
if (!definitionId) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
ErrorState,
|
||||
LoadingState,
|
||||
} from "../components/data-states";
|
||||
import { ErrorState, LoadingState } from "../components/data-states";
|
||||
import { Icon } from "../components/icon";
|
||||
import { useMobileViewport } from "../hooks/use-mobile-viewport";
|
||||
import { extractErrorMessage } from "../utils/errors";
|
||||
@@ -75,7 +72,6 @@ export const ToolWorkshopPage = () => {
|
||||
const [toolTypeError, setToolTypeError] = useState<string | null>(null);
|
||||
const [toolTypeDirty, setToolTypeDirty] = useState(false);
|
||||
|
||||
|
||||
const selectedToolType =
|
||||
(toolTypes || []).find((t) => t.id === selectedToolTypeId) || null;
|
||||
|
||||
@@ -215,7 +211,9 @@ export const ToolWorkshopPage = () => {
|
||||
return;
|
||||
}
|
||||
} else if (!manifestData) {
|
||||
setToolTypeError("Manifest data is required for manifest definition type");
|
||||
setToolTypeError(
|
||||
"Manifest data is required for manifest definition type",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -300,14 +298,12 @@ export const ToolWorkshopPage = () => {
|
||||
description: toolTypeForm.description.trim() || undefined,
|
||||
category: toolTypeForm.category.trim() || undefined,
|
||||
interface_type: toolTypeForm.interface_type,
|
||||
base_image:
|
||||
(manifestData.base_image as string) || undefined,
|
||||
base_image: (manifestData.base_image as string) || undefined,
|
||||
base_definition_id:
|
||||
(manifestData.base_definition_id as string) || undefined,
|
||||
manifest: manifestData,
|
||||
};
|
||||
const newManifest =
|
||||
await createToolDefinition(manifestPayload);
|
||||
const newManifest = await createToolDefinition(manifestPayload);
|
||||
manifestId = newManifest.id;
|
||||
}
|
||||
}
|
||||
@@ -365,7 +361,6 @@ export const ToolWorkshopPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
if (status === "loading") {
|
||||
return (
|
||||
<div className="container">
|
||||
@@ -935,7 +930,7 @@ export const ToolWorkshopPage = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{(
|
||||
{
|
||||
<form
|
||||
onSubmit={handleToolTypeSubmit}
|
||||
className="stack"
|
||||
@@ -1265,8 +1260,7 @@ export const ToolWorkshopPage = () => {
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user