Pin windmill-parser-wasm-asset to the published 1.728.1 (was a file: link to a gitignored, CI-unbuilt pkg-asset). Exclude test files from svelte-check (the parity test reads a backend fixture via node:fs, which the browser app tsconfig has no @types/node for; vitest still runs them). Fix pre-existing branch type errors: drop the unsupported 2nd getScriptByPath arg, cast script.schema to Schema for inferArgs, coerce has_preprocessor to a definite boolean, and wrap the cancelJob handler so it isn't possibly-undefined.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): replace other-user draft "View JSON" with "View Diff"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): replace other-user draft "Fork" with in-place "Load"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): detect first overlay edit by value divergence, not a timer
Replaces the 700ms arming timer (which leaked across sessions and silently
swallowed sub-window edits) with a deterministic check: a blocked save opens
the overwrite prompt only once the cell value diverges from the loaded value.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): overlay leak on revisit, diff z-index, home-popover edit affordances
- Clear a stale "editing another user's draft" overlay when its editor is
reloaded without a fresh Load, so returning to the item edits our own draft.
- Open View Diff above the others-drafts modal (close it first) instead of
rendering the drawer behind it.
- Add an Edit button to our own row in the home draft popover; use a pencil
icon (not a download) for Load.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: admin "Migrate" action for legacy drafts (delete / assign to self)
Adds an admin-gated `POST /drafts/migrate_legacy/{kind}/{path}` endpoint to
resolve pre-migration workspace-level drafts (email NULL): delete the row, or
move its value onto the admin's own row. Surfaces a "Migrate" button on legacy
rows in the home-page draft popover and the in-editor others-drafts modal
(workspace admins / superadmins only), opening a modal with Delete and
Assign to self.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): close home draft popover before opening View Diff / Migrate
The hover popover sits above the diff drawer and migrate modal (z-index), so
it covered them. Close it first so they render on top.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): remount the flow builder on "Reset to draft" from an overlay
FlowBuilder captures the flow at mount, so reloading the value alone left the
foreign graph on screen — reset appeared to do nothing. Force a remount
(renderEditor=false → loadFlow) like navigation does. Scripts (imperative
setCode) and apps (redraw++) already remount, so only flows needed this.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): refresh the home row after migrating a legacy draft
invalidateAll() didn't refetch the home list (it loads items client-side), so
the legacy badge entry lingered after delete / assign-to-self. Bubble an
onMigrated callback up to the row's `change` event, reusing the same reload
chain (Item → ItemsList loadScripts/Flows/Apps) as delete/archive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* nit
* nit
* fix(frontend): match app overlay baseline to the migrated value
AppEditor migrateApp()s the app on mount, so the draft cell settles to the
migrated value. The overlay used the raw loaded value as the divergence
baseline, so a post-mount mirror write could trip "Overwrite your current
draft?" before any edit. Migrate the baseline too (like the deployed-baseline
and raw_app bundle do) so it matches the settled cell.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): address review on legacy-draft migrate + overlay
- Legacy "Assign to self" now confirms before replacing an existing own draft
(MigrateLegacyDraftModal gains an `ownDraftExists` step, threaded from the
home badge and the in-editor others-drafts modal).
- Gate overlay mode on a per-response `hasOwnDraft` instead of the sticky
`loadedFromDraft`, so navigating to a no-own-draft item in the same editor
route can't wrongly enter overlay. Fixed in all 4 editor routes.
- Raw-app "View Diff" now projects the deployed app into the flat draft-bundle
shape (via a shared `extractDataConfig`) instead of diffing `.value` against
the bundle, so the drawer shows a real diff.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: add ducklake schema support to the database manager
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: support schema in wmill.ducklake("name:schema") template helper
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: preserve schema when parsing ducklake asset/favorite paths
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: regenerate system prompts for ducklake schema syntax doc
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* 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>
* feat: zero-setup oauth client credentials for registry-declared providers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: support client-credentials-only custom oauth providers
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: add coupa client credentials provider to oauth registry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: clarify oauth resource connect auth-method selection
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: support shared instance-level oauth client credentials
Admins can designate an instance OAuth entry's credentials as client
credentials; the connect dialog then runs the exchange server-side with
them instead of asking each user for their own. Replaces the per-provider
"Support Client Credentials Flow" toggle with a grant-type selector.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: update ee-repo-ref to be9f23b2c06b8b6ee0cd3e4d9f16bcd9e90129fb
This commit updates the EE repository reference after PR #613 was merged in windmill-ee-private.
Previous ee-repo-ref: 05643cbbc8c1bebf3509c691c5811b4057d96485
New ee-repo-ref: be9f23b2c06b8b6ee0cd3e4d9f16bcd9e90129fb
Automated by sync-ee-ref workflow.
* feat: allow both grant types on an instance oauth entry
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: bring-your-own oauth credentials from the others section
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: segmented oauth grant-type selector, always show grant
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: enable client credentials for 5 more oauth providers
Verified against official docs: bitbucket, linkedin, spotify, xero and
zoho support the standard client_credentials grant with a plain
client_id + client_secret, compatible with Windmill's token exchange.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: hide create-manually link on the managed oauth connect path
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: enable client credentials for salesforce and servicenow
Salesforce CC requires the org's My Domain token endpoint (login.salesforce.com
is unsupported for that grant), so add an optional cc_token_url registry field
that the connect form prefills for the client-credentials path instead of the
shared token_url. ServiceNow uses the same instance host for both grants, so it
only needs its token URL and req_body_auth surfaced at the top level.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: add instance-level client-credentials token url override
Some providers use a per-org/instance-specific token endpoint for the
client-credentials grant that differs from the authorization-code URL.
Add an optional cc_token_url on the instance OAuth entry, surfaced in
instance settings (prefilled from the registry template) when client
credentials is selected, and used for the CC exchange and refresh while
auth-code keeps its own token URL.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* style: remove redundant grant-type tags from oauth auth cards
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor: extract reusable RadioCard component for the oauth auth chooser
A token-based selectable card (label, description, selected, onSelect,
optional icon) replacing the inline cards in the connect dialog.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: hide sign-in option on the bring-your-own oauth path
Picking a provider from "Others" means bring your own credentials, so
the auth-code "Sign in" card (which uses the instance client) no longer
shows there — it goes straight to the client-credentials form. The
two-flow chooser stays on the instance-configured path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: restrict client-credentials token url to caller-supplied creds
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: resolve client-credentials id and secret all-or-nothing
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: host-pin client-credentials token url via instance-name input
For registry providers whose CC token URL is instance-templated (Coupa,
Salesforce My Domain, ServiceNow), the connect dialog and instance settings
collect an instance name and the backend substitutes it into the fixed-host
template, validating it as a hostname label. A free-form token URL is no longer
accepted for these providers, so the exchange host cannot be redirected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: client-credentials token url always comes from the registry
Bring-your-own CC is registry-only: the token URL is resolved server-side from
the built-in registry (host-pinned via an instance name for templated providers,
the fixed registry URL otherwise) and rejected for custom resource types. The
caller-supplied token URL field is removed from the connect dialog and the API.
Adds unit tests for the resolver.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address CC review - sandbox CC config and instance-templated providers
Resolve `_sandbox` provider keys to the parent registry entry in the instance
settings and connect-dialog helpers, so salesforce_sandbox (and future sandbox
entries) can enable client credentials. Use the effective CC token URL template
(cc_token_url or token_url) so the instance-name field works for Coupa/ServiceNow,
and hide that field when a connect_config_template already owns the instance input
(ServiceNow). Document the authorization contract on resolve_instance_cc_credentials.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: unify instance-templated oauth onto connect_config_template
Remove the separate cc_token_url and cc_instance config fields. An instance-
templated provider now declares one connect_config_template (auth_url optional
for client-credentials-only providers like Coupa); the CC flow reads its token
URL, label and strip_suffix to host-pin the exchange. Coupa and ServiceNow move
to connect_config_template; Coupa stays drawer-only (no auth_url -> excluded from
instance settings). Salesforce CC is removed for now (its auth-code/CC host split
needs the endpoint-profiles model).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: cc_scopes defaults and instance config for client credentials
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: store empty auth_url for cc-only templated oauth providers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address review nits - sandbox key lookup, template doc, deref specs
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: default shared client-credentials connect to cc_scopes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: support bring-your-own client credentials for instance-configured providers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor: move oauth grant-type help into per-option tooltips
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: keep instance-configured oauth providers selectable from Others
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: preserve admin-configured scopes for custom client-credentials providers
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: use cc scopes on cc refresh and enforce cc grant for bring-your-own
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: require {instance} in leftmost host label for cc token url templates
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: drop token_url from unauthenticated get_connect response
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: fill byo templated resource args from the entered instance
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update ee-repo-ref to 136f4634aca61e74ccb045372358a1e3f6b23e75
This commit updates the EE repository reference after PR #616 was merged in windmill-ee-private.
Previous ee-repo-ref: b5083e266492e908456e39401778a9cdcea46e94
New ee-repo-ref: 136f4634aca61e74ccb045372358a1e3f6b23e75
Automated by sync-ee-ref workflow.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
Previously the AI sessions sidebar section was hidden entirely when AI was
not configured at the workspace level. Now the section stays visible and the
per-session chat input is disabled with an explanatory message, mirroring the
sidebar AI chat behavior.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The localStorage→DB user-draft migration upserted via /drafts/update, whose
SQL always stamped created_at = now(). Every migrated draft therefore
resurfaced to the top as freshly created, regardless of its real age.
Add an optional created_at override to the update_draft request, threaded
into the upsert as COALESCE($8, now()) / created_at = EXCLUDED.created_at.
Normal saves omit it and still stamp now(); the migration passes the draft's
original write time (or epoch 0 when unknown) so migrated drafts keep their age.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three issues from the Codex PR review of the low-code app deploy + summary work:
- [P1] The summary mirror onto the autosaved App value broke the autosave's
no-op detection for deployed apps: `discardIf` compares the live value against
the deployed baseline, but the baseline (the deployed App value) carried no
summary while the live value now always does — so a draft reverted to the
deployed state never compared equal and a no-op draft was persisted instead of
deleted. Carry the deployed summary onto the baseline so the comparison matches
(a summary-only edit still counts as a real change).
- [P2] "Show diff" stayed enabled for view-only (`mine=false`) rows in the
"Show all drafts" view, but the diff only fetches the current user's draft
overlay — wrong diff for another user's deployed-row draft, 404 for their
draft-only row. Hide it for foreign rows; own/legacy rows keep it.
- [P2] Reword the `rawAppDraftValue` doc comment to state the current invariant
(must read a draft's top-level `files`) instead of referencing past drafting
history, per AGENTS.md.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: per-user draft gating, badges and rename display on deploy page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): don't strike the path when a draft adds a summary to a summary-less item
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): don't strike draft-only items' auto-generated path against the pretty path
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy raw-app drafts from top-level files so the bundle isn't dropped
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): share raw-app source→draft-value projection across chat and deploy page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy renamed/new flow, app and raw-app drafts at draft_path, not the temp storage path
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): add a design-system Checkbox and use it for deploy-page row/select-all checkboxes
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: "Show all drafts" toggle on the deploy-drafts page
Replace the deploy-drafts page's legacy-hiding "Only my drafts" toggle with
a "Show all drafts" toggle that switches the listing scope between the
current user's own drafts (+ legacy no-owner rows) and every user's drafts
in the workspace.
Backend (`drafts.rs`, `openapi.yaml`):
- `/drafts/list` gains an `all_users` query param that drops the owner
filter, and a per-row `mine` flag (own draft or legacy no-owner row).
`DISTINCT ON` now prefers the user's own row, then the legacy row, then
another user's, so `mine`/`legacy_draft` describe the kept row.
Frontend (`CompareDrafts.svelte`, `workspaceDrafts.svelte.ts`):
- "Show all drafts" toggle (default off). The all-users superset is fetched
lazily via the shared resource only while the toggle is on, so the page's
fork draft-count (own drafts) is unaffected.
- Other users' drafts are view-only: disabled checkbox + Discard with a
"belongs to another user" tooltip; Show diff stays enabled. Selection,
select-all and the deploy count only ever include the user's own drafts.
The multi-user warning triangle shows on owned rows only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(backend): gate all_users draft listing by read permission
Addresses the PR review on the per-user deploy-drafts page:
- `/drafts/list?all_users=true` previously had only `WHERE workspace_id = $1`
with no read-permission check, so any non-operator could enumerate every
draft's path, summary and authors — including items they can't read. Now
rows the caller doesn't own (`mine = false`) are gated through
`require_can_read_path` (the same gate `/drafts/get` uses) and dropped when
unreadable; both its `NotFound` and `NotAuthorized` denials are treated as
"not visible".
- Skip the per-row `require_can_write_path` probe on those non-owned rows
(they're never selectable — `isSelectable` requires `mine`): set
`can_write = false` directly, removing a redundant N RLS write-probes when
`all_users` is on.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): only confirm destructive draft discards on the deploy page
Discarding a draft is non-destructive in every case except removing the last
draft of a never-deployed item (`draft_only` with no other user's draft),
which permanently deletes it. Confirm only that case; reverting a draft over a
deployed item, or discarding your copy while another user still holds a draft,
now runs immediately (the ⚠️ already signals the multi-user case). Drops the
redundant "other users still have a draft" / "deployed version unaffected"
confirmation branches.
Harden the destructive check: it keyed off `otherDraftUsers()`, which subtracts
`currentUsername`; while `$userStore.username` is unhydrated, your own draft
looked like another user's, flipping a draft-only item to "non-destructive" and
deleting it with no confirmation. Now: deployed counterpart → never destructive;
`draft_only` with unknown `currentUsername` → treated as destructive (confirm).
The delete modal also shows the friendly `draft_path` instead of the raw
`draft_{uuid}` storage path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy low-code app drafts (value + summary persistence)
A visual (low-code) app draft is autosaved as the *bare* App value
(grid/theme/... plus a draft-only `draft_path`), not wrapped in
{ value, summary, policy } like script/flow drafts. The Review & Deploy page
read `requestBody.value = d.value` — undefined for that shape — so deploying any
low-code app draft (created or edited) sent no value and failed. Read the value
from the draft object itself, strip the draft-only `draft_path` from it, and use
that as the deploy path.
Also persist the app summary, which was dropped entirely: the autosave stores
the bare App value (the summary normally lives only in the `app` table column,
set on deploy), so a draft never carried it — reopening a draft or deploying it
lost the summary. Mirror the summary onto the autosaved App (like `draft_path`),
read it back when loading a draft, and on deploy send it as the summary column
while stripping it (and `draft_path`) from the deployed value so the value stays
clean.
Verified end-to-end: a new low-code app with a summary deploys at its pretty
path with the summary set, content intact, and no draft_path/summary leaked into
the deployed value; the draft is cleaned up.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): queue messages typed while ai chat is streaming
* fix(frontend): avoid losing queued chat messages on send early-return
* test(frontend): cover queued chat message semantics in AIChatManager
* fix(frontend): complete ChatLoopResult mock in queued message tests
* feat(frontend): single appendable queued message, send on cancel
* fix(frontend): only auto-send queued message on a user cancel, not programmatic
* chore(frontend): remove queued-message dev preview page
* fix(frontend): clear queued chat message on conversation switch
* fix(frontend): persist raw-app draft path edits in the session editor
Renaming a raw app's path in the session preview editor never triggered a draft
save: the header surfaced the typed path as `pendingDraftPath`, but
RawAppEditorView ignored it (it never reached runtime.rawApp.val), and the
RawAppDraft codec didn't serialize a path field — so the autosave signature
(JSON.stringify(draft)) was unchanged and nothing was written. The rename was
lost and the home/review/Drafts lists kept the original `draft_path`.
- appDraftCodec: make `draft_path` a real draft + runtime field, serialized by
runtimeRawAppToDraft and round-tripped by applyDraftToRuntimeRawApp, so a path
change moves the sig and fires a save.
- sessionRuntime.loadRawApp (+ inline rawApp.val type): seed `draft_path` from
the loaded draft so it survives reloads.
- RawAppEditorView: bind the header's `pendingDraftPath`, mirror it into
runtime.rawApp.val.draft_path (guarded so the initial undefined can't clobber
the seed or fire a spurious save), and seed the path widget from
`draft_path ?? path`. Mirrors the full-page /apps_raw/edit route.
- appDraftCodec.test: add a draft_path round-trip test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): per-line tooltips in workspace item diff rows
The summary line now shows the full summary on hover and the path line the
full path, instead of one row-level title surfacing the path everywhere.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): persist flow/script draft path edits in the session editor
The session sync dedups on a per-kind signature that omitted the path, so a
rename never moved the signature and never autosaved. Add path/draft_path to
the flow signature, and derive draft_path in the script codec (scripts bind
the Path widget to script.path directly) so the rename both autosaves and
shows the typed name in the home/Drafts lists. Mirrors the raw-app fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): commit editor summary edits live instead of on blur
EditableInput only fired onSave on Enter/blur, so the summary in the shared
editor header only updated when the field lost focus. Add an opt-in
commitOnInput that fires onSave per keystroke and enable it for the header
summary, so flow/script/raw-app summaries autosave as you type.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): preserve renamed never-deployed script path on session re-seed
loadScript seeded a draft-only script's baseline path from the storage key, so
re-running it with the draft still in memory (e.g. a script→script switch)
reset the path to draft_<uuid> and the next autosave dropped draft_path,
clobbering the rename. Seed from the draft's own draft_path/path instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): clear raw-app draft_path when the path rename is reverted
The mirror effect only ever set draft_path, so reverting/clearing the path
field left a stale friendly name in the draft (persisted by the codec and shown
in the home/Drafts lists). Track whether a real typed path was surfaced so a
revert clears draft_path while the initial pre-bind undefined still can't
clobber the loadRawApp-seeded value. Mirrors the script codec's drop-on-revert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Save all" spread `...draft.script` into createScript, which carries a `assets`
snapshot that isn't refreshed when the body is edited. So a renamed/removed
output (e.g. an old `CREATE TABLE exciting_en32z9` later changed to
`exciting_880909`) was re-deployed as a phantom write edge and lingered as an
orphan asset on the graph — shown with no producer, and shifting position on
click as the graph re-derived.
saveDraft now re-runs inferAssets on the current body and passes the result as
`assets`, overriding the snapshot — mirroring the per-pane save. The backend
clears+reinserts from the sent set, so a re-deploy drops the stale rows.
Verified: deploying with the fresh asset set removes the orphan from the graph.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Edges could draw straight through an unrelated node (a join fan-out or long
cross-component edge), making it ambiguous whether that node shared the input.
AssetGraphEdge only saw its own endpoints, so it could only detour the
near-vertical same-column skip case.
The canvas now (once per layout, O(edges × nodes) — no per-frame cost) samples
each edge's straight run against every non-incident node center and, on a
crossing, passes a clear gutter lane to the edge via `data.detourX`;
AssetGraphEdge routes the rounded-orthogonal detour through it. Verified: 0
edge↔node box crossings on the orders pipeline.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A pipeline script's asset lineage is load-bearing — a deploy that can't parse
assets silently records no edges. The editor "parsable" dot only reflected
inferArgs (the main function), so a body the asset parser rejects (e.g. a
trailing `/////` in DuckDB) still showed green and deployed with empty lineage.
ScriptEditor gains `requireValidAssets` (set by the pipeline pane); when on, the
EditorBar badge is green only if BOTH the main function and inferAssets parse,
with the tooltip distinguishing "Main function not parsable" / "Assets not
parsable" / "Parsable".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Activity panel (view mode):
- Group cascade runs by the connected component of the asset-dispatch graph
(new GET /jobs/asset_dispatch_edges over the dispatch_event table, incl.
join_pending inputs), headed by the earliest originating run + its trigger,
with a "+N" chip for joins fed by multiple triggers.
- Success/failure count histogram with drag-to-filter brushing, an always-on
time axis + per-bar tooltips, a Reset, and Last hour/24h/48h/7/30/90d ranges.
- Node run-count/status badges now derive from the same merged historic+live
events the panel shows (previously session-only).
Run ↔ graph highlight:
- Hovering a run row (or a group header → the whole cascade) rings the
node(s), animates their incident edges, and borders the adjacent assets in
the edge hue (blue write / gray read); expanding a run pins a soft-blue ring.
- Switching edit→view re-surfaces the Activity feed.
Deploy:
- Live-content autosave for the open pipeline draft + an autosave indicator.
- Re-saving a script now chains off the hash just created instead of a stale
parent_hash (fixes the "lineage must be linear" error on a second save), and
a genuine concurrent deploy opens a keep-mine / view-latest conflict modal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface folder labels in the /folders table via a new "Labels" column
between Name and Scripts, rendered as blue badges with a +N overflow
indicator (first 3 shown), matching the script row pattern. Previously
labels were only visible inside the folder editor drawer.
Fixes WIN-2056
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): session Drafts drawer uses raw_app kind for the raw-app diff
Follow-up to #9601. DraftDiffDrawer mapped a raw_app row back to `app` before
calling getDraftDiffValues(), but that helper sends `rawApp:true` only for the
exact kind `raw_app` (which a never-deployed raw app needs). With `app` it hit
the normal app endpoint and 404'd instead of rendering the added diff. `raw_app`
isn't in the deploy-kind maps anyway, so just pass the row kind through.
Caught by the Codex auto-review on #9601, which posted after that PR had already
merged (locked conversation), so the fix lands separately here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): show friendly draft path + summary for all kinds in session Drafts drawer
A never-deployed app/raw_app is parked at a synthetic `u/.../draft_<uuid>`
storage path with the user's typed name in the draft JSON's `draft_path`; the
Drafts drawer rendered that UUID path. The list endpoint already returns
`draft_path` and `summary` for every kind, but `fetchDrafts` dropped them and
the drawer only had the lazily-derived summary.
Thread both through the shared row: `WorkspaceDiffDrawer` gains optional
`displayPath` (shown in tree/header/search, while `path` stays the storage key
for value-loading, item keys and edit links) and `summary` (preferred over the
value-derived one, shown before the diff loads). `DraftDiffDrawer` populates
them from the draft list (`draft_path ?? path`, `summary`).
Both fields are opt-in via `?? path` / lazy fallback, so ForkDiffDrawer — the
other consumer of the component — is unchanged. The symptom only surfaced for
apps/raw apps because their storage path diverges from the friendly name;
scripts already kept a readable path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): use friendly display path for single-segment draft tree nodes
buildTree splits displayPathOf(d), but the `< 2 parts` branch still named the
file node from the storage `path` — a draft whose friendly path is a bare name
(no `/`) would show `…/draft_<uuid>` in the sidebar tree. Name it from
displayPathOf(d) too, consistent with the rest of the tree/header/search.
Addresses Codex and claude review nits on #9617.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The on:confirmed handler for the delete-fork ConfirmationModal never reset
deleteWorkspaceForkModal to false. Since SidebarContent persists across
workspace switches, the stale true state caused the delete-fork modal to
immediately reappear when a new fork workspace was created. Reset the state
before calling deleteFork(), matching the on:canceled handler and the pattern
in forks/compare/+page.svelte and SessionWrapper.svelte.
Fixes WIN-2057
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): adapt AI-chat/sessions drafts to DB-backed model
PR #9351 dropped UserDraft's localStorage layer; the chat adapter's
synchronous save->read-back threw "Could not read written draft". The
adapter now treats the backend as source of truth (in-tab cell used
opportunistically for live-preview coherence) with conflict-on-save,
and read tools fall back to the backend. Collapses the six writeXDraft
functions onto one generic writeDraft + typed per-kind WriteSpec
constants. Terminology: "local draft" -> "draft" (drafts are server-side).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): autosave indicator + draft-only diff guard in session editors
Thread an explicit (workspace, path) autosave target to the cloud
AutosaveIndicator in the Script/Flow/RawApp session previews so it
watches the same key saves land on (it previously watched an empty path
and never animated). Disable the Diff button with a hint for draft-only
(no_deployed) items consistently across the three editors. Adjust the
script topbar compact breakpoint/layout so the cloud icon is part of the
bar, and stop splitpanes over-constraining session panes on reload.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): session draft diff viewer for schedule/resource/variable
Canonicalize both sides of the draft diff onto one field set and strip
runtime-only fields so rows aren't spuriously marked all-changed; mask
secret values. Map draft itemKinds to deploy-style kinds so the DiffRow
shows the correct icon/label.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): uniform diff-viewer row height regardless of summary
Diff-viewer leaf rows (WorkspaceItemRow) drew two lines when an item had a
summary and one line otherwise, giving unequal heights. Add an opt-in
`uniformHeight` prop that gives the text wrapper a shared min-height and
vertically centers the one-line case; enable it only from the diff viewer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(frontend): address review nits on the drafts diff/guard changes
- Reuse the exported TRIGGER_RUNTIME_IGNORE from utils_deployable instead of
a verbatim copy, so the runtime-field ignore list has one source of truth.
- Drop the now-redundant `(savedApp as any)` cast in RawAppEditorHeader; the
prop type already carries `no_deployed`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): add description parameter to the write_flow chat tool
write_flow had no way to set a flow's top-level description (the sibling
of summary in OpenFlow); patch_flow_json only edits the compact value, so
the field was unreachable from the AI chat. Thread an optional description
end-to-end: tool schema -> persisted draft -> read-back -> deploy body.
Structural patches (patch_flow_json/set_flow_module_code) pass no
description, so a previously-set description is preserved. Adds a
deployRequests regression test asserting a draft description reaches the
deploy body, overriding the deployed one.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): round-trip top-level fields in session preview draft sync
The session preview's two-way draft sync dedups on a per-kind signature
and mirrors fields between the editor store and the shared UserDraft cell.
Both omitted fields the chat can set, so with the preview open a change to
only that field was swallowed (identical signature) and then clobbered by
the editor's outbound save:
- flow: the signature and applyDraftToStore ignored top-level `description`.
- script: the signature keyed on `content` alone, dropping `summary`/`language`.
Add the missing fields to flowDraftSig and the script codec signature, and
copy `description` in the flow codec's applyDraftToStore (mirroring `summary`).
Raw-app already stringifies the whole draft, so it was unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy draft-only flow from the session preview
Deploying a draft-only flow (a draft with no deployed row) from the session
preview hit two gaps the full-page flow editor already handled:
- create vs update: newFlow keyed on `!savedFlow.val`, but a draft-only flow
has a synthesized savedFlow (no_deployed=true), so deploy took updateFlow
against the draft path and 404'd "Flow not found". Key it on no_deployed too.
- friendly name: a brand-new flow is stored under a `draft_<uuid>` path with
its intended name in `draft_path`. Seed the builder's initialPath from
`draft_path` (as the full-page editor does) so the Path widget and deploy
use the friendly name instead of creating a flow named draft_<uuid>.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): deploy draft-only raw app from the session preview
Same create-vs-update bug as the flow session preview: newApp keyed on
`!savedRawApp.val`, but a draft-only app has a truthy synthesized savedApp
(getAppByPath with rawApp:true resolves to the draft kind instead of 404ing,
carrying no_deployed=true), so deploy took updateApp against a path with no
deployed row and 404'd "not found". Key newApp on no_deployed too so a
never-deployed app deploys via createApp. More reachable than the flow case:
it hit any never-deployed app, including chat-created ones at friendly paths.
Keying newApp on no_deployed also exposed that newEditedPath (the breadcrumb
path AND the createApp target) used newApp to mean "brand-new, generate a
random name". A draft-only app is newApp=true but already has a real path
(empty newPath at init, but appPath is set), so it showed and would deploy a
random `*_app` name. Prefer the real appPath before the random fallback, so
only a genuinely new app (appPath === '') still gets a generated suggestion;
the full-page editor is unaffected (it always sets newPath).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): don't re-save a draft after deploying from the session preview
Deploying from a session preview reloaded the editor (expected) but then
immediately POSTed a fresh draft. The full-page editor guards deploy with
discardDraftAfterDeploy (stopSync + arm-restart-on-first-interaction), but the
shared editor header skips that in a session pane (inSessionPane) and routes
post-deploy cleanup through sessionRuntime.syncPreviewWithDeployed, which did
discard + reload without the stopSync guard. UserDraft.discard keeps the cell
entry, so the reload's UserDraft.save fired the cell's reactive effect and
re-POSTed the just-deployed value as a draft.
Wrap the discard + reload in the same UserDraft.stopSync + armRestartOnFirst-
Interaction bracket. One place fixes all three kinds (script/flow/raw_app),
since they all funnel through syncPreviewWithDeployed; autosave resumes on the
next genuine edit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(frontend): address review findings on the session-preview drafts work
- Type `no_deployed` via the GetXByPathResponse/UserDraftOverlay types instead
of `(result as any)`/`(saved as any)` casts at the sites this branch added
(sessionRuntime, ScriptBuilder, FlowBuilder, + widened the script/flow
builder prop types). Pre-existing trigger/variable/resource-editor casts
left untouched.
- Drop a history-narrating comment parenthetical per the AGENTS.md comment
policy (RawAppEditorView).
- Add a unit test covering persistGlobalDraft's conflict-on-save / override
path (conflict-capable updateDraft mock; inert for existing tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): keep the friendly generated path for a brand-new raw app
The earlier draft-only newApp fix made newEditedPath prefer `appPath` before
the random suggestion, but a brand-new app is parked at the storage placeholder
`u/{user}/draft_{uuid}` (the /apps_raw/add redirect target), so it surfaced that
uuid instead of a friendly `<adjective>_app` suggestion. Reject a `draft_`
placeholder segment when choosing the path: a real named/draft-only path is
still kept, a placeholder falls through to the generated suggestion.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): show the Diff-button tooltip when it's disabled
A disabled <button> fires no pointer events and browsers suppress its
native title, so the "deploy once to compare" explanation never showed on
hover for a draft-only item's Diff button. Wrap the button in a titled
element and set pointer-events-none on the button when disabled, so the
hover reaches the wrapper. Applied in ScriptBuilder, FlowBuilder, and
RawAppEditorHeader (covers both the full-page editors and the session preview).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): surface draft save failures and conflicts in the AI chat tools
Addresses Codex + Pi review findings on PR #9601 (three P1s, all in the
DB-backed draft adapter reporting success when the write didn't land):
- persistGlobalDraft reported {status:'saved'} even when UserDraftDbSyncer.save
failed (it records network/5xx into a failure map instead of throwing). Check
getState().state==='failed' after the save and return a new 'error' status;
finishDraftWrite now emits success:false with a retry hint.
- saveGlobalAppDraft dropped the conflict/error status (returned only the item),
so write_app_file/patch_app_file/write_app_runnable reported every stale or
failed write as saved. It now returns the full DraftPersistResult, and the six
app write tools route through a shared finishAppDraftWrite helper.
- fetchBackendDraftValue's catch{} swallowed non-404 errors (403/500/network),
collapsing them to "no draft" so the write merged from the deployed item and
lost in-progress draft edits. Narrow the catch to status===404; propagate the
rest.
Adds unit coverage: save-failure -> 'error', non-404 read -> propagates,
raw-app stale write -> 'conflict'. 71/71 pass, check:fast + full build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): surface failed draft deletes + strip tool-only override from schedule drafts
Addresses Codex's second-round review on PR #9601:
- [P1] deleteGlobalDraft reported success even when the server delete failed or
conflicted (UserDraftDbSyncer.save records failure state instead of throwing) —
so discard_local_draft / deploy_workspace_item / delete_workspace_item / the
/global_drafts delete could report a draft removed while the DB still had it.
Check getState().state and getConflict() after the awaited null save and throw,
mirroring the write-path guard.
- [P2] writeScheduleDraft persisted the tool-only `override` conflict flag into
the schedule draft value (mergeDraftConfig cloned every arg field). Strip
`override` in SCHEDULE_SPEC.buildDraft before merging.
Tests: failed server delete -> throws; schedule draft no longer contains
`override`. 73/73 pass, check:fast + full build clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): /global_drafts "Clear all" deletes persisted drafts, not just cells
Codex review nit (P2): the dev-only global-drafts inspector's "Clear all" called
clearGlobalDrafts(), which only iterates in-tab UserDraft cells — any persisted
backend draft row not currently mounted as a cell survived, so the list re-showed
it after refresh. Iterate the listed drafts and delete each via the backend-aware
deleteGlobalDraft() (continue past per-row failures), matching the per-row delete,
then clear local cells + ephemeral secrets.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When editing a resource whose type definition does not exist in the
workspace (e.g. custom types not yet synced), the JSON fallback editor
rendered empty. The pre-refactor ResourceEditor seeded rawCode from the
resource args in its loadResourceType() catch block; the new
ResourceForm only populated rawCode when the user toggled viewJsonSchema.
Add a reactive effect that seeds rawCode from args when the resource
type schema is unavailable, restoring the old behavior so the resource
data is visible in the JSON editor.
Fixes WIN-2045
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
A draft-only app (one that exists only in the `draft` table and was never
deployed) failed to load when opened from the home list: the row linked to
the viewer `/get/` route, whose `get_app_lite` backend handler 404s when
there is no deployed version.
Route `draft_only` apps to the `/edit/` route instead, matching the
existing behavior in ScriptRow and FlowRow. Covers both raw and regular
draft-only apps.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Add a variable" drawer (and other editors built on `UserDraft.useMany`)
opened empty: for a brand-new item `editPath` is undefined so the spec path is
empty, which routes through `useMany`'s empty-path branch. That branch handed
out a `makeDetachedHandle()` whose cell was initialized to `undefined`,
ignoring the spec's `defaultValue`. The editor binds its form behind
`{#if current}` where `current = states[ws]?.draft`, so an undefined cell left
the drawer with just the title and a Save button.
Seed the detached handle with `defaultValue`, and re-seed it when the caller
supplies a fresh `defaultValue` reference (reopening the drawer clones a new
default) so a reopened editor starts clean instead of replaying the previous
session's edits — the reference is stable within a session, so live edits are
never clobbered. Also drop detached handles that fall out of the specs so they
don't leak.
Regression from #9351 (db-backed user drafts).
Fixes WIN-2054
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pipeline drafts were browser-only (localStorage `pipeline-<folder>`), so they
didn't sync across devices, weren't server-visible, and never showed in the
drafts list. Store them instead as one per-user `draft` row of a new
`data_pipeline` kind, keyed at the folder (`f/<folder>/data_pipeline`), holding
the same `{ drafts, activeDraftPath }` bundle.
Stage 1 — backend kind: add `data_pipeline` to DRAFT_KIND (migration) and
`UserDraftItemKind` (deployed_table=None, private). The list/update handlers
and folder-path access check already cover a backing-table-less kind.
Stage 2 — sync: add `GET /drafts/get_own/{kind}/{path}` so an editor with no
deployed-overlay GET can load its own draft. The pipeline page now hydrates
from the DB on mount (one-time localStorage import for in-flight drafts) and
persists via UserDraftDbSyncer (debounce + optimistic-concurrency), keeping a
localStorage crash mirror.
Stage 3 — surface: the drafts review page renders the bundle as a "pipeline"
row that opens `/pipeline/<folder>` (open-only; excluded from bulk deploy).
Verified end-to-end in-browser: DB-seeded draft hydrates to "Edit (1)", edits
persist back, and the row shows with Open pipeline / Discard.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Self-hosted instances that embed their own UI (Windmill as backend, custom
app at the instance root) can't be redirected back to their app after OAuth
login: isValidLogoutRedirect rejects same-origin absolute URLs, so the login
callback falls back to a client-side goto('/') into Windmill's own dashboard.
Same-origin redirects are never open redirects (and toSameOriginRelativePath
already treats them as safe), so accept them.
Also make the test-setup window global configurable so vitest's stubGlobal
can redefine it across the window-stubbing test suites.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: support temp_script_refs in wmill dev for local relative imports
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: add unit tests for getAllTempScriptRefs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(frontend): strip server-managed fields from value diffs
The script editor's "Deployed <> Current" diff rendered spurious metadata
changes (created_at, created_by, extra_perms, lock_error_logs, ...) the user
never touched. Since #9351 the edit loader fetches the full Script row via
getScriptByPath (instead of the trimmed NewScript-shaped getScriptByPathWithDraft),
so the editing object carries these DB-managed fields. The deployed side is
trimmed in syncWithDeployed, so the two sides no longer match.
Normalize both sides at the shared chokepoint: cleanValueProperties now also
strips created_at, created_by, extra_perms, workspace_id, parent_hashes, lock
and lock_error_logs. These are never user-editable, so this also fixes the
draft<>current diff and unsaved-change detection, and benefits the flow/app
diff viewers that share the helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(frontend): cover cleanValueProperties; keep lock in diffs
Add unit tests for cleanValueProperties asserting the server-managed
bookkeeping keys are stripped while user-editable keys survive.
Keep `lock` out of the stripped set: it was part of the value comparison
before the full-DB-row loader (#9351) and version-to-version diff viewers
(WorkspaceItemDiffViewer) legitimately surface lockfile changes. Only the
fields that the full Script row newly introduced as diff noise are stripped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): keep extra_perms in shared diff, strip script noise at source
Address review: stripping `extra_perms` in the shared `cleanValueProperties`
hid folder sharing-permission changes in workspace/fork diffs (the backend's
compare_two_folders treats folder extra_perms as a real change). Remove it from
the global skip-list so folder diffs surface it again.
The script-editor noise it (and `lock`) would otherwise cause is now stripped at
the source instead: `ScriptBuilder.openDiffDrawer` nulls `lock`/`extra_perms` on
the current side to match the existing deployed-side strip in `syncWithDeployed`.
Also strip the draft-overlay bookkeeping fields the full DB row carries
(`draft_saved_at`, `draft_created_at`, `is_draft`, `other_drafts_users`) — they
leaked into the current side and showed as spurious metadata diffs.
Verified in browser: no-edit diff shows "No changes detected"; a summary edit
surfaces only that change, with no lock/extra_perms/draft metadata noise.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NodeWrapper destructured `moveManager` from `getGraphContext()` unconditionally,
but FlowGraphContext is only set by the flow graph. The app decision-tree editor
reuses NodeWrapper without setting that context, so opening its Graph Editor threw
"Cannot destructure property 'moveManager' of getGraphContext(...) as it is undefined".
Guard the context with `?? {}` since `moveManager` is already used optionally.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(frontend): session-pane draft seeding + restore actions
Seed per-tab last_sync from the server draft's draft_saved_at in the
loadFlow/loadScript "no local draft" branches (mirroring loadRawApp) so the
seeding save attaches a matching last_sync and the server no longer clobbers
an existing server draft with a fresh created_at.
Replace the no-op loadFlow/loadRawApp-based diff-drawer restore handlers with
proper restoreDeployed/restoreDraft that reset the live UserDraft cell (the
inbound sync then updates the preview) and delete the per-user server draft,
mirroring ScriptEditorView. Add rawAppValueToDraft to project a deployed
raw-app value into the draft shape.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(api): move UserDraftOverlay/UserDraftItemKind out of openflow inline block
These two schemas were defined between the python-client's
"# -- INLINE START/END --" markers, whose contents build.sh replaces with the
openflow legacy wildcard $ref. That deleted both definitions during bundling
while ~19 path responses still referenced them, failing the python-client
build. Relocated them after the marker block.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): explain legacy drafts in the draft badge popover
The home-page draft badge lists each draft owner; a workspace-level row from
before the per-user drafts migration shows as "Legacy workspace draft". Add an
info tooltip next to it explaining that a legacy draft isn't tied to any user
(email NULL) so everyone with access to the path sees it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(compare): show friendly draft path on the review & deploy page
list_drafts now surfaces the draft JSON's `draft_path` (when set and different
from the storage path) alongside summary, mirroring the home-page list
endpoints. CompareDrafts displays it instead of the `u/{user}/draft_{uuid}`
storage path, while all fetch/deploy/discard calls keep using the storage path
(the draft's server-side key).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): delete the storage-path draft when deploying a renamed draft
Deploying a draft from the review page replays the editor's create/update at
the draft's friendly path, which deletes the draft server-side only at that
path. A never-deployed item parked at `u/{user}/draft_{uuid}` therefore left
its storage-path draft behind on deploy and kept listing. Delete the
storage-path draft for every kind after a successful deploy, mirroring the
editors' discardDraftAfterDeploy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(compare): badge legacy drafts on the review & deploy page
list_drafts now reports `legacy_draft` (true when the listed row is a
workspace-level NULL-email draft and no per-user row exists at the path).
CompareDrafts shows a "Legacy draft" badge with a hover tooltip explaining
these predate the per-user drafts migration and aren't tied to a user.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): allow discarding a legacy draft from the review page
Legacy drafts (workspace-level, email NULL) aren't owned by the authed user,
so the email-scoped draft delete in update_draft never matched them and the
discard was a silent no-op. Add a delete-only `legacy` flag that retargets the
DELETE (and the conflict re-read) to the NULL-email row, and route the review
page's discard of a legacy draft through it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(backend): prune orphaned sqlx offline cache entries
Re-ran the canonical update_sqlx.sh after rebasing windmill-ee-private onto
origin/main and re-running substitute_ee_code.sh. Compiling the full workspace
with all features recorded every live query and pruned 55 stale cache entries
no longer produced by any query (22 are the removed `draft_only`-on-app
lookups dropped by the db-backed user drafts work; the rest pre-existing
orphans). Orphan entries don't break offline builds — this is cleanup only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(drafts): stop migrated draft-only items flooding the home list
20260609165313_remove_draft_only inserted the legacy (email IS NULL) draft
stubs without an explicit created_at, so every row defaulted to the migration's
now() (transaction_timestamp, constant for the whole transaction) and they all
bunched at the migration instant — flooding the top of the newest-first home
list.
Add a corrective migration that resets those rows' created_at to the epoch so
they sort to the bottom (their real per-item timestamps are unrecoverable —
the source rows were deleted and the draft value carries no timestamp; editing
one bumps created_at to now() and floats it back up). The rows are identified
exactly via _sqlx_migrations.installed_on, which sqlx writes in the same
transaction as the migration so it is byte-identical to the inserted rows'
created_at; rows edited since no longer match and are left alone. Leaving
remove_draft_only intact (rather than neutralizing it) keeps its essential
schema work running everywhere; this migration runs right after and corrects
the timestamps.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(migration): note both timestamps are timestamptz in draft created_at repair
Pre-empt a misread: draft.created_at became TIMESTAMPTZ in
20260514233244, so `created_at = installed_on` is an exact instant comparison,
not a tz-sensitive timestamp/timestamptz cast.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): resolve friendly draft path per kind + strip email from u/ path
list_drafts read the friendly path only from value->>'draft_path', which is
empty for scripts — the script editor binds the Path widget to script.path, so
the typed path round-trips through the draft JSON's own `path` (flows/apps/raw
-apps use draft_path). Read the right field per kind, matching the home-page
list endpoints, so renamed never-deployed scripts show their friendly name.
Also truncate the user segment at `@` when displaying a `u/{user}/…` path:
auto-generated draft slots are `u/{user}/draft_{uuid}`, and in the admins
workspace (or email-as-username setups) `{user}` is the full email
(`u/admin@windmill.dev/…` → `u/admin/…`). Display only — the path/key used for
fetch/deploy/discard is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(raw-app): make diff-drawer "restore to deployed" reset like the autosave indicator
The diff drawer's restoreDeployed ran the same runResetToDeployed as the
AutosaveIndicator's "Reset to deployed", but its onResetToDeployed callback
also did `redraw++`, remounting RawAppEditor mid-reset (inside the stopSync
bracket); the fresh mount's draft write resurrected the draft, so the restore
appeared to do nothing. Extract a single `reloadDeployed` callback (drop the
draft handle + reload without the draft overlay) and use it for the diff
drawer, the conflict modal, and the AutosaveIndicator so all three reset the
same way.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): don't show auto-generated draft path as the bold title
A never-named draft lives at a synthetic `u/{user}/draft_{uuid}` slot. When it
had no summary and no friendly draft path, that uuid showed as the row's bold
title. Return '' from displayPath for auto-generated paths so they aren't
bolded — the row still shows the storage path in its secondary (grey) line.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(diff-drawer): remove obsolete draft-vs-current tab selector
The "Latest saved draft <> Current" comparison is obsolete. Remove the whole
diff-type tab selector; normal-mode diffs now always show deployed-vs-current,
simple-mode shows its single custom diff. Drop the now-unreachable
restore-to-draft button and the `restoreDraft` prop (plus the dead handlers in
the session editor views). The content/metadata selector is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(compare): make "Reset to deployed" work from the diff drawer
Route the raw-app session preview and the low-code app editor diff-drawer
restore through the same reset-to-deployed callback the AutosaveIndicator uses.
- Raw-app session: add a deployedOnly path to loadRawApp that bypasses the
draft (cell + server overlay) and reloads the deployed value; the diff
drawer's restore now runs it via runResetToDeployed instead of rebuilding the
draft shape in place (which hung and never reset). Also wires the in-session
AutosaveIndicator reset.
- Low-code app editor: drop the goto in the diff-drawer restoreDeployed that
re-ran the page load with the draft overlay on and resurrected the draft;
share one reloadDeployed across the diff drawer, AutosaveIndicator and the
load-latest-deploy modal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): add user-level toggle to disable Windmill AI
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(frontend): hide AI sessions sidebar section when AI is disabled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>