diff --git a/frontend/src/lib/userDraft.svelte.ts b/frontend/src/lib/userDraft.svelte.ts index f0b8ad0568..8087780087 100644 --- a/frontend/src/lib/userDraft.svelte.ts +++ b/frontend/src/lib/userDraft.svelte.ts @@ -355,14 +355,40 @@ export const UserDraft = { // the getter so reactive opts (e.g. `$workspaceStore`) are // captured once at call time — the current contract is "the // handle stays bound to this workspace until the component - // unmounts." Use `useMany` directly if you want spec changes to - // release/acquire entries as you go. + // unmounts." For reactive `(kind, path)` use `useReactive`. const handles = UserDraft.useMany(() => untrack(() => [{ itemKind, path, workspace: opts?.workspace }]) ) return handles[0] }, + /** + * Reactive single-spec variant of `use()`. `getSpec` is read inside the + * `useMany` reconcile, so when the spec's `(workspace, kind, path)` + * changes, the previous entry is released and a new one acquired. The + * returned object is a stable handle proxy — its `draft` getter/setter + * forwards to whichever underlying handle is current, so `bind:` lvalues + * survive re-keying. + * + * Use this when the editor's path is reactive (`/scripts/edit/[...path]` + * navigation between paths must re-key the autosave cell). For a + * non-reactive path, `use()` is simpler. + */ + useReactive( + getSpec: () => { itemKind: UserDraftItemKind; path: string; workspace?: string } + ): UserDraftHandle { + const handles = UserDraft.useMany(() => [getSpec()]) + return { + get draft(): V | undefined { + return handles[0]?.draft + }, + set draft(value: V | undefined) { + const h = handles[0] + if (h) h.draft = value + } + } + }, + useMany( getSpecs: () => { itemKind: UserDraftItemKind diff --git a/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte index 5b434d7edd..4b81089250 100644 --- a/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/flows/edit/[...path]/+page.svelte @@ -18,7 +18,7 @@ import { tick, untrack } from 'svelte' import type { stepState } from '$lib/components/stepHistoryLoader.svelte' import { page } from '$app/state' - import { UserDraft, type UserDraftHandle } from '$lib/userDraft.svelte' + import { UserDraft } from '$lib/userDraft.svelte' import { notifyDraftLoaded } from '$lib/userDraftToast' let version: undefined | number = $state(undefined) @@ -56,18 +56,12 @@ * deploy lands at this path. */ let isNewFlow = $state(false) - // `useMany` keyed off the reactive `flowDraftPath` re-keys the handle on nav; - // `flowHandle` proxies the current handle so `flowStore` keeps a fixed ref. - const flowHandles = UserDraft.useMany(() => [{ itemKind: 'flow', path: flowDraftPath }]) - const flowHandle: UserDraftHandle = { - get draft() { - return flowHandles[0]?.draft - }, - set draft(value) { - const handle = flowHandles[0] - if (handle) handle.draft = value - } - } + // Re-keys the handle on nav (the reactive `flowDraftPath` is read inside + // the reconcile) while keeping `flowStore` a stable ref. + const flowHandle = UserDraft.useReactive(() => ({ + itemKind: 'flow', + path: flowDraftPath + })) function emptyFlow(): Flow { return { diff --git a/frontend/src/routes/(root)/(logged)/scripts/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/scripts/edit/[...path]/+page.svelte index 0a18bc19be..5a31107772 100644 --- a/frontend/src/routes/(root)/(logged)/scripts/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/scripts/edit/[...path]/+page.svelte @@ -15,7 +15,7 @@ import { get } from 'svelte/store' import { untrack } from 'svelte' import { page } from '$app/state' - import { UserDraft, type UserDraftHandle } from '$lib/userDraft.svelte' + import { UserDraft } from '$lib/userDraft.svelte' import { notifyDraftLoaded } from '$lib/userDraftToast' import { UserDraftDbSyncer } from '$lib/userDraftDbSyncer.svelte' @@ -35,20 +35,12 @@ // local draft — that view is read-only relative to drafts. let draftPath = $derived(hash ? '' : (page.params.path ?? '')) - // `useMany` keyed off the reactive `draftPath` re-keys the handle on nav; - // `scriptHandle` proxies the current handle so `bind:script` stays a fixed lvalue. - const scriptHandles = UserDraft.useMany(() => [ - { itemKind: 'script', path: draftPath } - ]) - const scriptHandle: UserDraftHandle = { - get draft() { - return scriptHandles[0]?.draft - }, - set draft(value) { - const handle = scriptHandles[0] - if (handle) handle.draft = value - } - } + // Re-keys the handle on nav (the reactive `draftPath` is read inside + // the reconcile) while keeping `bind:script` a stable lvalue. + const scriptHandle = UserDraft.useReactive(() => ({ + itemKind: 'script', + path: draftPath + })) $effect(() => { if (hash || !$workspaceStore) return