From cfa4c8eeff6839648f890863a42c02bf786b086a Mon Sep 17 00:00:00 2001 From: Diego Imbert Date: Sun, 7 Jun 2026 22:05:57 +0200 Subject: [PATCH] fix(drafts): key low-code app autosave on the URL path, not the empty string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `AppEditor` keyed its `UserDraft.use` handle on `newApp ? '' : path` — a legacy leftover from when `/apps/add` was its own URL (no path). With the `/add` ⇒ `/edit/u/{user}/draft_{uuid}` redirect, `newApp=true` made autosaves land on the `('app', '')` row instead of the URL path: - The `apps/list?include_draft_only=true` query joins drafts onto `app.path`, surfacing drafts at the URL path. The empty-path row didn't match the user's URL so the draft never appeared in the home list. - Refreshing `/apps/edit/u/{user}/draft_{uuid}` re-fetches at the URL path with `?get_draft=true`, finds nothing, and 404s. Drop the ternary so the handle always uses `path` — the same as scripts/flows/raw_apps. The route's `?new_draft=true` branch already seeds the empty-template baseline, so there's no longer a "the draft sits under '' until first save" race to worry about. --- .../lib/components/apps/editor/AppEditor.svelte | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/components/apps/editor/AppEditor.svelte b/frontend/src/lib/components/apps/editor/AppEditor.svelte index 962e675526..076cf50000 100644 --- a/frontend/src/lib/components/apps/editor/AppEditor.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditor.svelte @@ -92,7 +92,13 @@ // sides' autosaves. Skip UserDraft entirely in that case. const inSessionPane = !!getContext('aiChatManager') - const appDraftPath = newApp ? '' : (path ?? '') + // `path` is the URL path (e.g. `u/{user}/draft_{uuid}` after the + // `/apps/add` redirect, or a deployed app's path on `/apps/edit/...`). + // The autosave keys on it directly so a refresh of + // `/apps/edit/u/{user}/draft_{uuid}` finds the user's saved draft at + // the same path, and the listing's draft-only branch (which scans the + // `draft` table by exact path) picks it up. + const appDraftPath = path ?? '' const appDraftHandle = inSessionPane ? undefined : UserDraft.use('app', appDraftPath) // Suspend autosave around mount — the route may have seeded `app` // from an empty template (e.g. `/apps/add` redirect with @@ -103,10 +109,10 @@ // resumes once all mount-time effects have settled, so the user's // real first edit is the first POST. if (appDraftHandle) UserDraft.stopSync('app', appDraftPath) - // Prefer the persisted autosave over the prop when both exist (e.g. - // /apps/add reload: the route always initializes `app` to an empty - // template, but the user's last session is sitting in LS under the - // empty-path entry). The route is responsible for wiping the entry + // Prefer the persisted autosave over the prop when both exist (the + // route always initializes `app` to an empty template on + // `new_draft=true`, but a prior session at this draft path may have + // left an autosave). The route is responsible for wiping the entry // (`UserDraft.remove`) when it wants to force a fresh start // (template/hub loads, etc.). const stateApp = $state(untrack(() => appDraftHandle?.draft ?? app))