fix(drafts): low-code apps — drop spurious autosave on /edit + remount on Load from server

Two bugs in low-code app editor (raw apps use a separate code path):

1. Every /edit visit looked like an autosave because loadApp() called
   UserDraft.discard('app', path, undefined). The comment claimed
   "this load doesn't POST" but discard always POSTs value: null
   server-side — that surfaced as a DELETE-my-draft on every page
   load AND a flash in the AutosaveIndicator.

   The discard was originally intended to wipe the in-memory cell so
   AppEditor remounts "fresh". But the path-change $effect upstream
   already sets app = undefined before each loadApp, which unmounts
   AppEditor and releases the handle's entry — so a remount via
   app = backendApp naturally starts with an empty handle. Drop the
   discard.

2. The conflict modal's "Load from server" called loadApp() but
   didn't remount AppEditor. Since AppEditor's stateApp is captured
   once at mount and doesn't react to prop changes, the editor kept
   showing the conflicting local edits even after a successful reload.
   Wrap the onLoadFromServer to await loadApp() then bump redraw to
   force a fresh mount.
This commit is contained in:
Diego Imbert
2026-06-09 12:53:51 +02:00
parent 03c236ea2f
commit 0be6bc1426
@@ -166,11 +166,14 @@
policy: backendApp_.policy,
custom_path: backendApp_.custom_path
}
// Backend canonical: wipe the in-memory cell so AppEditor remounts
// fresh from `backendApp.value`. The cell will be re-seeded by
// AppEditor's mirror $effect; the first such write is swallowed
// by `acquireEntry`'s seed guard so this load doesn't POST.
UserDraft.discard('app', path, undefined)
// Backend canonical: assign the fresh response onto `app`. The
// path-change $effect upstream sets `app = undefined` before
// calling loadApp, which unmounts AppEditor and releases the
// UserDraft entry — so when `app = backendApp` triggers a
// remount, the new handle starts fresh with no stale draft to
// dedup against. (`UserDraft.discard` was here previously but
// it POSTs `value: null` server-side, which surfaced as a
// spurious autosave on every /edit visit.)
app = backendApp
}
@@ -225,7 +228,14 @@
{path}
{otherDraftsUsers}
editPathFor={(forkedPath) => `/apps/edit/${forkedPath}`}
onLoadFromServer={() => loadApp()}
onLoadFromServer={async () => {
// AppEditor's `stateApp` is captured once at mount and doesn't
// react to prop changes, so a plain loadApp() reload would update
// `app` but leave the editor displaying the conflicting local
// state. `redraw++` remounts AppEditor against the fresh `app`.
await loadApp()
redraw++
}}
getLocalDraft={() => app?.value}
/>