* 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>
* fix(nativets): respect custom CA certs in in-process fetch runtime
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(nativets): dedupe CA file paths and clarify DENO_TLS_CA_STORE semantics
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(nativets): resolve CA env vars from worker-group config too
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>
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>
* fix(cli): include __mod/ folder in gitSyncIncludePattern for scripts
Scripts with companion modules use a `__mod/` folder layout on disk
(`path__mod/script.ts`, `path__mod/script.yaml`, ...). The default case of
`gitSyncIncludePattern` returned only `${path}.*`, which does not match files
inside `__mod/`. During git-sync deployment the `extraIncludes` filter then
excluded all module files from the pull, and the subsequent
`git add '${path}**'` failed with "pathspec did not match any files" because
nothing was written to disk.
Add the `${path}__mod/**` pattern so module files are pulled, mirroring the
existing dual-layout handling for flows (`.flow/*,__flow/*`) and apps.
Fixes WIN-2052
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(cli): e2e guard that module scripts' __mod/ files land on git-sync deploy branch
Add a promotion test mirroring the existing trigger/schedule cases: deploy a
script WITH companion modules (one flat, one nested) under use_individual_branch
and assert the `__mod/` entry point and module files land on the wm_deploy
branch. Without the gitSyncIncludePattern `__mod/**` fix the extra-includes
filter matches none of those files and the branch is created without them.
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: 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>
Archiving a workspace sets `deleted = true`, hiding it from the workspace
switcher for everyone (the `user_workspaces` query filters
`workspace.deleted = false`). The archive/delete actions were audited only
inside that same workspace's audit log, which then becomes inaccessible — so
there was no durable, discoverable record of who archived or deleted a
workspace, or when.
Also write these lifecycle events under the instance-level `admins`
workspace, the canonical instance-audit scope (a superadmin querying `admins`
with `all_workspaces=true` sees entries across all workspaces). The target
workspace id is carried in the audit `resource` field and the actor in the
author. For delete, the per-workspace rows are removed in the same
transaction, so the instance-level entry is the sole durable record.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Caching the gte-small embedding model fetched config.json / tokenizer.json /
model.safetensors from HuggingFace with no retry, so a single transient
network error ("error sending request for url ...") failed the whole image
build. Wrap each download in a retry loop (up to 5 attempts, exponential
backoff capped at 8s) that logs each retry and surfaces the error only after
the final attempt. No new dependency.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three independent failures on the latest release commit:
- pypi (Publish python-client): the `UserDraftOverlay`/`UserDraftItemKind`
schema definitions were placed inside the `# -- INLINE START/END --`
markers in openapi.yaml. The python-client build replaces that whole block
with a wildcard import of `openflow.openapi.yaml`'s schemas, which do not
define these two, so every `$ref` to them became unresolvable and the
redocly bundle aborted. Move both definitions outside the markers — they are
windmill-api schemas, not openflow-mirrored ones.
- flows::tests::flowmodule_serde: the expected JSON still carried
`"error_message": null` in three `stop_after_if` blocks, but
StopAfterIf.error_message is now skipped when None. Drop those keys.
- CLI Tests (test-windows): preservePendingScriptLocks mixed the OS path
separator (SEP) into map keys that are always forward-slash normalized,
so on Windows the multi-module suffix match and the lock-file lookup both
failed. Use forward slashes consistently; this also fixes real Windows
git-sync deploys, not just the test.
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>
* fix(cli): preserve committed script.lock on transient NULL lock during git-sync deploy (#9588)
A script's `lock` is NULL on the server only while a relock is mid-flight
(an importer relock after a relative-import dependency changed, or the
script's own first lock job). The git-sync deploy mirror reads the
workspace inside that window, sees no lock, and mirrors the transient
NULL as a deletion of the committed `.script.lock` plus a strip of the
`lock: '!inline …'` line — corrupting the git mirror until the relock
writes the identical lock back seconds later.
When pulling (remote -> local), carry the local committed lock onto the
remote map when the remote lock is NULL, so the diff is a no-op for both
the lock file and the metadata line. An empty-string lock ('') — the real
"no dependencies" state — is left untouched, so genuine lock removals
still propagate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(cli): cover __mod multi-module scripts in pending-lock preservation
Address auto-review on #9593: the lock-file key was reconstructed from the
metadata path (`.script.yaml` -> `.script.lock`), so a multi-module script
whose lock lives at `…__mod/script.lock` fell through unprotected. Derive
the key from the committed `!inline` reference instead (covers both the
dotted and `__mod` folder layouts) and detect the folder-layout metadata
file. The reference is always forward-slash; convert to the OS separator so
the local map lookup matches on Windows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A flow module with a constant-only retry is stored by the frontend as
`{ constant: {...} }`, but round-tripping through the `Retry` struct (e.g.
the dependency/lock job, which re-serialises the flow value) materialised a
full default `exponential` block (`seconds: 0`, `random_factor: null`) and a
`null` `error_message`, because those fields are non-`Option` / `Option`
without `skip_serializing_if`. The defaults then got baked into stored data,
surfaced on `wmill pull`, and were rejected by the linter.
Skip serialising `Retry.constant`/`Retry.exponential` when they equal their
default, and `StopAfterIf.error_message` when it is `None`. Deserialisation is
unchanged (`#[serde(default)]` refills the in-memory structs), so the worker
retry logic and the frontend (which already optional-chains these fields) are
unaffected.
Verified end-to-end against a running backend: a flow created with an explicit
default exponential block + `error_message: null` comes back clean after its
lock job re-serialises it.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(security): sanitize dependency names & connection strings against command/SQL injection
Follow-up to the PowerShell module-name injection fix (#9587, CWE-78): the
same audit surfaced the identical "secondary identifier interpolated into an
interpreter/SQL command without escaping" pattern in a few other executors.
- R executor (the real twin, HIGH): package name/version parsed from a
user-supplied renv.lock were interpolated raw into an `Rscript -e
"...renv::install(\"{pkg}@{version}\"...)"` eval string. A double-quote in
the name/version broke out → arbitrary R (unsandboxed under DISABLE_NSJAIL /
non-Linux). Now validated in parse_renv_lock (charset) and escaped at the
sink as defense-in-depth (also escapes the lib path, which holds backslashes
on Windows).
- DuckDB ATTACH (MED): the connection string built from resource fields
(host/db/user/password) is embedded in a single-quoted DuckDB literal; escape
quotes so a field value can't break out of the ATTACH statement.
- PgDatabase::to_uri: URL-encode host and dbname (user/password already were),
so '@'/'/'/'?'/'&' can't reshape the parsed URI (feeds live PG connect and
DuckDB ATTACH).
- DuckDB CREATE SECRET (FFI): wrap the interpolated S3 key/secret/endpoint in
the existing sql_single_quote() helper, consistent with the resource-limits
setup right above it.
- PowerShell: also escape the configured private repo URL/PAT in the install
template (same sink as the module names; the escape landed after #9587 was
squash-merged so it was not in the merged change).
Adds unit tests for the R validation/escaping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(security): escape ducklake/snowflake/bigquery identifiers; preserve IPv6 host
- to_uri: don't percent-encode bracketed IPv6 literal hosts ([::1]) — encoding
their brackets/colons would stop them parsing as a host (review fix).
- duckdb ducklake ATTACH: the catalog conn string, storage and data_path are
embedded in single-quoted DuckDB literals; escape quotes so a resource field
can't break out (the ducklake path bypassed the ATTACH escape added earlier).
- snowflake: validate account_identifier (it forms the request hostname).
- bigquery: validate project_id (it forms a request URL path segment).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove deprecated enable_1m_context from AI provider code
1M context is now standard on Anthropic models — the beta header
`anthropic-beta: context-1m-2025-08-07` is no longer needed.
Remove the field from ProviderCredentials and AnthropicQueryBuilder,
and stop injecting the beta header in both the API proxy and worker
query builder paths.
The field is kept (as `_enable_1m_context`) on the ProviderResource
deserialization structs in both windmill-api and windmill-ai so
existing resources with the field still deserialize without error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: drop vestigial _enable_1m_context field from AI resources
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: assert legacy enable_1m_context keys still deserialize
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The list_apps and list_search_apps endpoints did not filter returned
rows against the calling token's resource-qualified scope. A token
scoped to apps:read:u/foo/specific_app could list every app in the
workspace, including full app_version.value definitions.
Apply build_scope_path_predicate, mirroring the protection already in
place for script, flow, resource and variable list endpoints.
Fixes WIN-2046
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The list_tokens handlers in scripts.rs and flows.rs accepted only the raw
DB pool, with no ApiAuthed extraction or check_scopes call. Any authenticated
token for the workspace — regardless of its scope restrictions — could
enumerate token metadata (label, prefix, scopes, owner email, timestamps)
for any script or flow path, bypassing the path-scoped read checks enforced
by sibling endpoints like get_script_by_path and get_flow_by_path.
Both handlers now extract ApiAuthed and call check_scopes for
scripts:read:<path> / flows:read:<path> before querying.
Fixes WIN-2047
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>