Files
windmill/backend/windmill-api/src
Diego Imbert 4e4b2247ef fix: db-backed draft fixes — review-page UX, legacy drafts, session restore (#9600)
* 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>
2026-06-16 00:46:53 +02:00
..
2025-06-02 22:12:33 +02:00
2025-06-02 22:12:33 +02:00
2026-06-15 10:23:16 +02:00
2025-06-02 22:12:33 +02:00
2025-09-04 22:05:22 +00:00
2025-06-02 22:12:33 +02:00
2025-06-02 22:12:33 +02:00
2025-06-02 22:12:33 +02:00
2026-06-15 10:23:16 +02:00