History

fix build

Rhys Sullivan committed 8 months ago

Commit 5d27afe1 parent c42ef9d2 files · +51 −43Browse files

Unified · Split

2 files
  1. executor/apps/web/src/components/tools/use/add/source/form-state.ts +49 −41
  2. executor/apps/web/src/lib/openapi/spec-inspector.ts +2 −2
executor/apps/web/src/components/tools/use/add/source/form-state.ts+49−41
@@ -1,4 +1,4 @@
1−import { useDeferredValue, useEffect, useMemo, useState, type Dispatch, type SetStateAction } from "react";
1+import { useDeferredValue, useEffect, useMemo, useRef, useState, type Dispatch, type SetStateAction } from "react";
22 import { useQuery as useTanstackQuery } from "@tanstack/react-query";
33 import { useForm, useWatch } from "react-hook-form";
44 import { fetchAndInspectOpenApiSpec, type InferredSpecAuth } from "@/lib/openapi/spec-inspector";
@@ -117,6 +117,11 @@ export function useAddSourceFormState({ · 112 unmodified lines
117117 const values: SourceFormValues = { ...createDefaultFormValues(), ...watched };
118118 const [ui, setUi] = useState<AddSourceUiState>(() => createDefaultUiState(sourceToEdit ? "custom" : "catalog"));
119119
120+ // Refs to let effects read current values without adding reactive deps that cause loops.
121+ const authManuallyEditedRef = useRef(ui.authManuallyEdited);
122+ authManuallyEditedRef.current = ui.authManuallyEdited;
123+ const appliedDetectionRef = useRef<{ endpoint: string; type: SourceType } | null>(null);
124+
120125 const visibleCatalogItems = useMemo(
121126 () => getVisibleCatalogItems(ui.catalogQuery, ui.catalogSort),
122127 [ui.catalogQuery, ui.catalogSort],
@@ -160,6 +165,9 @@ export function useAddSourceFormState({ · 37 unmodified lines
160165 return;
161166 }
162167
168+ // Clear applied detection tracking when dialog opens/resets
169+ appliedDetectionRef.current = null;
170+
163171 if (sourceToEdit) {
164172 const editValues = sourceToFormValues(sourceToEdit);
165173 form.reset(editValues);
@@ -396,60 +404,60 @@ export function useAddSourceFormState({ · 230 unmodified lines
396404 ? "error"
397405 : "idle";
398406
399− // Auto-set the type when detection succeeds
407+ // Auto-set the type when detection succeeds.
408+ // Uses a ref to track what we last applied, avoiding deps on values the effect itself mutates.
400409 useEffect(() => {
401410 if (!typeDetectionEnabled || !typeDetectionQuery.data) {
402411 return;
403412 }
404413 const detected = typeDetectionQuery.data.type;
405− if (values.type !== detected) {
406− form.setValue("type", detected, { shouldDirty: false, shouldTouch: false });
407−
408− // Trigger the same side-effects as handleTypeChange but without marking as explicitly set
409− if (detected === "openapi") {
410− const inferredBaseUrl = deriveBaseUrlFromEndpoint(values.endpoint);
411− patchUi(setUi, {
412− openApiBaseUrlOptions: inferredBaseUrl ? [inferredBaseUrl] : [],
413− authManuallyEdited: false,
414− mcpOAuthLinkedEndpoint: null,
415− });
416− if (!baseUrlManuallyEdited) {
417− form.setValue("baseUrl", inferredBaseUrl, { shouldDirty: false, shouldTouch: false });
418− }
419− } else {
420− patchUi(setUi, {
421− openApiBaseUrlOptions: [],
422− mcpOAuthLinkedEndpoint: null,
423− // Preserve authManuallyEdited — user may have set credentials before detection
424− });
425− // Only reset auth fields if the user hasn't manually edited them
426− if (!ui.authManuallyEdited) {
427− patchUi(setUi, { authManuallyEdited: false });
428− form.setValue("authType", "none", { shouldDirty: false, shouldTouch: false });
429− form.setValue("authScope", "workspace", { shouldDirty: false, shouldTouch: false });
430− }
414+
415+ // Skip if we already applied this exact detection result for this endpoint
416+ if (
417+ appliedDetectionRef.current
418+ && appliedDetectionRef.current.endpoint === detectionEndpoint
419+ && appliedDetectionRef.current.type === detected
420+ ) {
421+ return;
422+ }
423+
424+ appliedDetectionRef.current = { endpoint: detectionEndpoint, type: detected };
425+ form.setValue("type", detected, { shouldDirty: false, shouldTouch: false });
426+
427+ // Trigger the same side-effects as handleTypeChange but without marking as explicitly set.
428+ // Uses functional setUi to read current state without depending on it reactively.
429+ if (detected === "openapi") {
430+ const inferredBaseUrl = deriveBaseUrlFromEndpoint(detectionEndpoint);
431+ setUi((current) => ({
432+ ...current,
433+ openApiBaseUrlOptions: inferredBaseUrl ? [inferredBaseUrl] : [],
434+ mcpOAuthLinkedEndpoint: null,
435+ }));
436+ if (!baseUrlManuallyEdited) {
437+ form.setValue("baseUrl", inferredBaseUrl, { shouldDirty: false, shouldTouch: false });
438+ }
439+ } else {
440+ setUi((current) => ({
441+ ...current,
442+ openApiBaseUrlOptions: [],
443+ mcpOAuthLinkedEndpoint: null,
444+ }));
445+ // Only reset auth form values if the user hasn't manually set credentials.
446+ // Read from ref to avoid adding a reactive dep that causes render loops.
447+ if (!authManuallyEditedRef.current) {
448+ form.setValue("authType", "none", { shouldDirty: false, shouldTouch: false });
449+ form.setValue("authScope", "workspace", { shouldDirty: false, shouldTouch: false });
431450 }
432451 }
433452
434453 // Also infer name from URL if not already set
435454 if (!nameManuallyEdited) {
436455 const inferredName = inferNameFromUrl(detectionEndpoint);
437− if (inferredName && values.name !== inferredName) {
456+ if (inferredName) {
438457 form.setValue("name", inferredName, { shouldDirty: false, shouldTouch: false });
439458 }
440459 }
441− }, [
442− typeDetectionEnabled,
443− typeDetectionQuery.data,
444− baseUrlManuallyEdited,
445− detectionEndpoint,
446− form,
447− nameManuallyEdited,
448− ui.authManuallyEdited,
449− values.endpoint,
450− values.name,
451− values.type,
452− ]);
460+ }, [typeDetectionEnabled, typeDetectionQuery.data, detectionEndpoint, form, baseUrlManuallyEdited, nameManuallyEdited]);
453461
454462 // Type is "resolved" when explicitly set by the user/catalog, or auto-detected from the URL.
455463 // Type-specific queries (spec inspection, MCP OAuth) should only fire after resolution,
executor/apps/web/src/lib/openapi/spec-inspector.ts+2−2
@@ -17,9 +17,9 @@ type OpenApiInspectionResult = { · 16 unmodified lines
1717 inferredAuth: InferredSpecAuth;
1818 };
1919
20−const recordSchema = z.record(z.unknown());
20+const recordSchema = z.record(z.string(), z.unknown());
2121
22−const securityRequirementSchema = z.record(z.array(z.unknown()).optional());
22+const securityRequirementSchema = z.record(z.string(), z.array(z.unknown()).optional());
2323
2424 const securitySchemeSchema = z.object({
2525 type: z.string().optional(),