diff --git a/frontend/src/lib/components/ResourceEditor.svelte b/frontend/src/lib/components/ResourceEditor.svelte index a7d728b7fb..9a5c69dbd2 100644 --- a/frontend/src/lib/components/ResourceEditor.svelte +++ b/frontend/src/lib/components/ResourceEditor.svelte @@ -36,7 +36,7 @@ onChange, defaultValues = undefined, workspace = undefined, - selected = $bindable() + selected: selectedProp = $bindable() }: Props = $props() type ResourceState = { @@ -50,6 +50,10 @@ const dispatch = createEventDispatcher() let effectiveWorkspace = $derived(workspace ?? $workspaceStore!) + // Fallback to `effectiveWorkspace` insulates against reactify-style + // parents that re-spread props without `selected` — otherwise it + // transiently resets and the form below remounts on every keystroke. + let selected = $derived(selectedProp ?? effectiveWorkspace) let initialPath = path // Per-workspace handles are driven by `useMany`. We track the workspace @@ -205,28 +209,25 @@ }) ) - // Bootstrap: ensure selected is set on mount (edit or new) + // New-resource bootstrap: seed empty state per workspace (edit mode + // is seeded by the lazy-fetch effect below). $effect(() => { - selected - if (!effectiveWorkspace) return + if (!selected) return + if (initialPath) return + if (selected in initialStates) return untrack(() => { - if (selected !== undefined) return - selected = effectiveWorkspace - if (!initialPath) { - // New resource - const s: ResourceState = { - path: '', - description: '', - args: (defaultValues && Object.keys(defaultValues).length > 0 - ? defaultValues - : {}) as any, - labels: undefined, - wsSpecific: false - } - ensureHandle(effectiveWorkspace, s) - initialStates[effectiveWorkspace] = structuredClone(s) - existedInitially[effectiveWorkspace] = false + const s: ResourceState = { + path: '', + description: '', + args: (defaultValues && Object.keys(defaultValues).length > 0 + ? defaultValues + : {}) as any, + labels: undefined, + wsSpecific: false } + ensureHandle(selected, s) + initialStates[selected] = structuredClone(s) + existedInitially[selected] = false }) }) @@ -330,11 +331,9 @@ $effect(() => { if (current) - // Snapshot args so the deep read establishes nested dependency - // tracking (the effect re-runs on mutations inside args, not - // just reference changes) and so consumers receive a plain - // object instead of a $state proxy — important for React - // integrations that diff by reference or JSON.stringify. + // $state.snapshot deep-reads (so the effect re-runs on nested + // args mutations) and returns a plain object (React consumers + // can't diff a $state proxy by reference or JSON.stringify). onChange?.({ path: current.path, args: $state.snapshot(current.args) as Record,