mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 00:03:07 +00:00
* 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>
94 lines
3.2 KiB
Svelte
94 lines
3.2 KiB
Svelte
<script lang="ts">
|
|
import { untrack } from 'svelte'
|
|
import AiChatLayout from './copilot/chat/AiChatLayout.svelte'
|
|
import type { FlowBuilderProps } from './flow_builder'
|
|
import FlowBuilder from './FlowBuilder.svelte'
|
|
import { usePageDraftSync } from './usePageDraftSync.svelte'
|
|
import { workspaceStore } from '$lib/stores'
|
|
import type { OpenFlow } from '$lib/gen'
|
|
|
|
let {
|
|
flowStore: oldFlowStore,
|
|
flowStateStore: oldFlowStateStore,
|
|
disableAi,
|
|
light,
|
|
...props
|
|
}: FlowBuilderProps & { light?: boolean } = $props()
|
|
|
|
let flowStateStore = $state(untrack(() => oldFlowStateStore))
|
|
|
|
let trialRender = $state(true)
|
|
|
|
if (untrack(() => light)) {
|
|
setTimeout(() => {
|
|
trialRender = false
|
|
}, 1000 * 300)
|
|
}
|
|
|
|
// Stable per-user draft storage key. Captured once so editing the flow's path
|
|
// (which lives in `draft_path`, not the storage key) can't re-key the autosave
|
|
// handle and orphan the draft. Mirrors the full-page editor keying on the URL
|
|
// path; falls back through the SDK's path inputs.
|
|
const draftStoragePath = untrack(
|
|
() =>
|
|
props.initialPath ||
|
|
props.pathStoreInit ||
|
|
(oldFlowStore.val as { path?: string } | undefined)?.path ||
|
|
''
|
|
)
|
|
|
|
// Reuse the full-page flow editor's draft orchestration so the SDK gets
|
|
// autosave + the AutosaveIndicator (gated by FlowBuilder on
|
|
// `liveEditorDraftStoragePath`) — and `recordRemoteSync`/`seedBaseline`/
|
|
// `discardIf` if it ever loads a server draft — from one code path. The SDK
|
|
// may mount before login, so `useReactive` hands out a detached local-only
|
|
// handle until `$workspaceStore` resolves (no throw). The builder itself is
|
|
// gated on the workspace below so no edits are made into that detached handle
|
|
// — they'd be lost when it re-keys to the real entry on login.
|
|
// `defaultValue` seeds the handle from the consumer's loaded flow on first
|
|
// acquire (swallowed by the seed guard, never POSTs) — captured once so it
|
|
// doesn't churn the reconcile.
|
|
const initialFlow = untrack(() => oldFlowStore.val)
|
|
const draftSync = usePageDraftSync<OpenFlow>({
|
|
itemKind: 'flow',
|
|
path: () => draftStoragePath,
|
|
workspace: () => $workspaceStore,
|
|
defaultValue: initialFlow
|
|
})
|
|
|
|
// Bound store the builder reads/writes, backed by the draft handle. Falls back
|
|
// to the consumer-provided value in the first-render window before the handle
|
|
// is acquired.
|
|
const flowStore = {
|
|
get val() {
|
|
return draftSync.draft ?? oldFlowStore.val
|
|
},
|
|
set val(v: OpenFlow) {
|
|
draftSync.draft = v
|
|
}
|
|
}
|
|
</script>
|
|
|
|
{#if trialRender}
|
|
<AiChatLayout noPadding={true} {disableAi}>
|
|
{#if light}<div class="bg-red-500 absolute z-10">Trial version</div>{/if}
|
|
<!-- Gate on a resolved workspace: the draft handle is detached (local-only)
|
|
until one exists, so editing before then would be lost on the re-key. -->
|
|
{#if $workspaceStore}
|
|
<FlowBuilder
|
|
{flowStore}
|
|
{flowStateStore}
|
|
{disableAi}
|
|
{...props}
|
|
liveEditorDraftStoragePath={draftStoragePath || undefined}
|
|
/>
|
|
{/if}
|
|
</AiChatLayout>
|
|
{:else}
|
|
<div class="flex flex-col items-center justify-center h-screen">
|
|
<div class="text-2xl font-bold"
|
|
>Windmill Whitelabel SDK is in trial mode and disabled itself after 5 minutes</div
|
|
>
|
|
</div>
|
|
{/if}
|