Files
windmill/frontend/src/lib/components/ScriptWrapper.svelte
T
Diego Imbert 25a891041d wire DB-backed autosave into the whitelabel flow SDK (#9637)
* fix(frontend): wire DB-backed autosave into the whitelabel flow SDK

FlowWrapper (the @windmill-labs/components flow editor entry) was never
updated after DB-backed user drafts moved autosave wiring to the page
layer, so the SDK editor had no autosave and never rendered the
AutosaveIndicator. Back the bound store with a per-user UserDraft handle
(workspace-guarded so it no-ops before a workspace exists) and pass
liveEditorDraftStoragePath so the indicator and Ctrl/Cmd+S flush engage.

Also set $workspaceStore on the /test_dev/sdk_flow harness page, which
lives outside the (logged) layout and so had an empty workspace store
(mirrors the sibling sdk_resource page).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): shared test_dev header to log in + set the SDK token

Add a common TestDevHeader (rendered by a test_dev/+layout) that logs in
(email/password → bearer token), lets a token be pasted/set manually,
picks the workspace, loads the user, and persists the session across
reloads — mirroring the React SDK's initializeClients. test_dev routes
live outside the (logged) layout, so this is the single place that wires
OpenAPI.TOKEN + workspaceStore + userStore for the SDK demo pages.

Drop the now-redundant per-page workspace/user wiring from sdk_flow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* refactor(frontend): reuse usePageDraftSync in the flow SDK instead of a parallel copy

FlowWrapper hand-rolled UserDraft.useMany + a manual seed effect, duplicating
the core of usePageDraftSync but dropping recordRemoteSync/seedBaseline/discardIf
— a divergence that would drift. The only reason it couldn't reuse the helper
was that useReactive passes workspace straight into useMany, whose reconcile
called resolveWorkspace() (which throws) before the detached-handle check.

Make reconcile resolve the workspace without throwing and treat an absent
workspace like an empty path — handing out a detached, local-only handle that
re-keys into a real entry once the workspace resolves. FlowWrapper then reuses
usePageDraftSync directly, keeping one code path for the page and SDK editors.

Seed via the spec's defaultValue (threaded through usePageDraftSync ->
useReactive -> useMany, captured once on first acquire and swallowed by the
syncer's seed guard) rather than a manual first-write effect, dropping the
fragile skipNextWrite assumption and the seededPath bookkeeping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): wire DB-backed autosave into the whitelabel script SDK

ScriptWrapper had the same gap FlowWrapper did: ScriptBuilder delegates its
draft handle to the page (it only stop/restart-syncs and flushes by
userDraftPath), so the SDK's plain `bind:script` never reached a UserDraft
handle — no autosave, no indicator. Back it with usePageDraftSync<script>
(bind:script={draftSync.draft}, userDraftPath), seeded from the consumer's
script via defaultValue. Same one-code-path reuse as the flow SDK.

AppWrapper needs no change: AppEditor already self-acquires its handle
(UserDraft.use('app', ...)), so apps autosave already — and now also tolerate
mounting before login via the reconcile change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(frontend): gate SDK editors on a resolved workspace

Before a workspace exists the draft handle is detached (local-only); editing
into it and then having the workspace resolve re-keys to a fresh real entry
seeded from the original value, silently dropping those edits. Gate the flow,
script, and app SDK editors on `$workspaceStore` so no editing happens until
the real draft key exists. Embedders set the workspace before rendering (React
SDK initializeClients); the test_dev header sets it on mount.

AppEditor additionally acquires its handle at init from a non-reactive
workspace, so gating AppWrapper also ensures it mounts with the workspace
already set rather than permanently detached.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(frontend): add sdk_app test_dev page for the app editor SDK

Exercises AppWrapper the same way sdk_flow/sdk_script exercise their editors,
under the shared TestDevHeader. Confirms the app editor's self-managed autosave
+ AutosaveIndicator work via the SDK wrapper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 17:22:42 +00:00

41 lines
1.6 KiB
Svelte

<script lang="ts">
import { untrack } from 'svelte'
import ScriptBuilder from '$lib/components/ScriptBuilder.svelte'
import AiChatLayout from './copilot/chat/AiChatLayout.svelte'
import type { ScriptBuilderProps } from './script_builder'
import { usePageDraftSync } from './usePageDraftSync.svelte'
import { workspaceStore } from '$lib/stores'
let { script: oldScript, disableAi, ...props }: ScriptBuilderProps = $props()
// Stable per-user draft storage key. Mirrors the full-page editor keying on
// the URL path; falls back through the SDK's path inputs.
const draftStoragePath = untrack(() => props.initialPath || oldScript?.path || '')
// Reuse the full-page script editor's draft orchestration (same as the flow
// SDK) so the SDK gets autosave + the AutosaveIndicator (gated by ScriptBuilder
// on `userDraftPath`) from one code path. `defaultValue` seeds the handle from
// the consumer's script on first acquire (swallowed by the syncer's seed guard,
// never POSTs). `useReactive` tolerates mounting before login (detached
// local-only handle, no throw); the builder is gated on the workspace below so
// edits aren't made into that detached handle and lost when it re-keys.
const initialScript = untrack(() => oldScript)
const draftSync = usePageDraftSync<ScriptBuilderProps['script']>({
itemKind: 'script',
path: () => draftStoragePath,
workspace: () => $workspaceStore,
defaultValue: initialScript
})
</script>
<AiChatLayout noPadding {disableAi}>
{#if $workspaceStore && draftSync.draft}
<ScriptBuilder
bind:script={draftSync.draft}
userDraftPath={draftStoragePath}
{disableAi}
{...props}
/>
{/if}
</AiChatLayout>