diff --git a/frontend/src/lib/components/DiffDrawer.svelte b/frontend/src/lib/components/DiffDrawer.svelte index 120d939f31..545785196e 100644 --- a/frontend/src/lib/components/DiffDrawer.svelte +++ b/frontend/src/lib/components/DiffDrawer.svelte @@ -198,9 +198,13 @@ async function takeLatest() { if (!data || data.mode !== 'normal' || !data.onTakeLatest || takingLatest) return takingLatest = true + // Persisting the base is awaited, and this drawer outlives the editor that filled + // it: close only the opening this action belongs to, or it takes down whichever + // diff was opened meanwhile. + const opening = openingToken try { await data.onTakeLatest(headShown) - diffViewer?.closeDrawer() + if (opening === openingToken) diffViewer?.closeDrawer() } finally { takingLatest = false } diff --git a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte index f14ccc7ee5..c6ac501d70 100644 --- a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte @@ -369,10 +369,12 @@ policy = await updatePolicy($app, policy) // Read the head this deploy is about to append to, so the entry it writes can be // told from one landing beside it (see versionThisDeployWrote). - const headBefore = await AppService.getAppLatestVersion({ - workspace: $workspaceStore!, - path: $appPath - }).catch(() => undefined) + const anchor = ( + await AppService.getAppLatestVersion({ + workspace: $workspaceStore!, + path: $appPath + }).catch(() => undefined) + )?.version await AppService.updateApp({ workspace: $workspaceStore!, path: $appPath!, @@ -405,11 +407,13 @@ }) // `version` is what is deployed now, which is the deploy guard's fallback head and // must stay set; `claimed` is the version this deploy can prove it wrote. - const claimed = versionThisDeployWrote(appHistory, $userStore?.username, headBefore?.version) + const claimed = versionThisDeployWrote(appHistory, $userStore?.username, anchor) version = appHistory[0]?.version - // Without a claim the head is someone else's as far as this editor knows, so it is - // no longer a base it may compare against: `compareVersions` confirms instead. - baseUnknown = claimed === undefined + // A deploy landed beside this one, so the head is someone else's as far as this + // editor knows and is no longer a base it may compare against: `compareVersions` + // confirms instead. A failed anchor read claims nothing either, but it is no + // evidence of that, so it does not arm this. + baseUnknown = claimed === undefined && anchor != null // Re-pin the fork base to the version just written: the editor stays open, so a // follow-up deploy (or a new edit) would otherwise compare against the now- // superseded base and falsely warn. parent_version is in diff --git a/frontend/src/lib/components/apps/editor/appDeploy.svelte.ts b/frontend/src/lib/components/apps/editor/appDeploy.svelte.ts index 0f0e2dea4e..48e9c6fa96 100644 --- a/frontend/src/lib/components/apps/editor/appDeploy.svelte.ts +++ b/frontend/src/lib/components/apps/editor/appDeploy.svelte.ts @@ -7,6 +7,9 @@ export function computeSecretUrl(secretUrl: string) { } /** + * Shared by both app editors: classic and raw apps deploy into the same `app_version` + * table and read the same history, so the rule for claiming a version lives here once. + * * The version a just-finished deploy wrote: this caller's newest entry, and only while it * sits directly on `headBefore`, the head read just before the write. That is what the * write appended, so anything else in between belongs to a deploy this cannot tell from diff --git a/frontend/src/lib/components/common/confirmationModal/DraftEditorModals.svelte b/frontend/src/lib/components/common/confirmationModal/DraftEditorModals.svelte index ea216b6591..53d9fe7696 100644 --- a/frontend/src/lib/components/common/confirmationModal/DraftEditorModals.svelte +++ b/frontend/src/lib/components/common/confirmationModal/DraftEditorModals.svelte @@ -98,10 +98,10 @@ // staleness outright. const useVersion = $derived(draftBaseVersion != null && deployedHeadVersion != null) const isStale = $derived( - // Both paths need a draft to be out of date: the editors clear `draftSavedAt` - // when a deploy consumes theirs, and the version pair stays armed for the draft - // the next edit starts, which is what the prompt would otherwise offer to - // discard seconds after a successful deploy. + // Both paths need a draft to be out of date: the app editors stay open across a + // deploy and clear `draftSavedAt` when it consumes theirs, while the version pair + // stays armed for the draft the next edit starts, which is what the prompt would + // otherwise offer to discard seconds after a successful deploy. !!onLoadLatestDeploy && !!draftSavedAt && (useVersion diff --git a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte index 5303021581..70225b15ba 100644 --- a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte @@ -521,10 +521,12 @@ } // Read the head this deploy is about to append to, so the entry it writes can be // told from one landing beside it (see versionThisDeployWrote). - const headBefore = await AppService.getAppLatestVersion({ - workspace: opWorkspace!, - path: appPath! - }).catch(() => undefined) + const anchor = ( + await AppService.getAppLatestVersion({ + workspace: opWorkspace!, + path: appPath! + }).catch(() => undefined) + )?.version await AppService.updateAppRaw({ workspace: opWorkspace!, path: appPath!, @@ -563,11 +565,13 @@ // draft's base; the head is what is deployed now. The route owns this `version` // prop and re-pushes `parentVersion ?? head` as soon as `onDeploy` returns, so the // guard compares the claimed base when there is one and the head otherwise. - const claimed = versionThisDeployWrote(appHistory, $userStore?.username, headBefore?.version) + const claimed = versionThisDeployWrote(appHistory, $userStore?.username, anchor) version = appHistory[0]?.version - // Without a claim the head is someone else's as far as this editor knows, so it is - // no longer a base it may compare against: `compareVersions` confirms instead. - baseUnknown = claimed === undefined + // A deploy landed beside this one, so the head is someone else's as far as this + // editor knows and is no longer a base it may compare against: `compareVersions` + // confirms instead. A failed anchor read claims nothing either, but it is no + // evidence of that, so it does not arm this. + baseUnknown = claimed === undefined && anchor != null closeSaveDrawer() sendUserToast('App deployed successfully')