diff --git a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte index c6ac501d70..d73470edc4 100644 --- a/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte +++ b/frontend/src/lib/components/apps/editor/AppEditorHeader.svelte @@ -409,11 +409,11 @@ // must stay set; `claimed` is the version this deploy can prove it wrote. const claimed = versionThisDeployWrote(appHistory, $userStore?.username, anchor) version = appHistory[0]?.version - // 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 + // With no claim the head may be another deploy's, so it is not a base this editor + // may compare against: `compareVersions` confirms instead until a later deploy + // claims one or the editor is reset. A failed anchor read is indistinguishable + // from that here, and confirming is the side that cannot lose someone's work. + baseUnknown = claimed === undefined // 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/common/confirmationModal/DraftEditorModals.svelte b/frontend/src/lib/components/common/confirmationModal/DraftEditorModals.svelte index 53d9fe7696..20c29286b5 100644 --- a/frontend/src/lib/components/common/confirmationModal/DraftEditorModals.svelte +++ b/frontend/src/lib/components/common/confirmationModal/DraftEditorModals.svelte @@ -98,15 +98,12 @@ // staleness outright. const useVersion = $derived(draftBaseVersion != null && deployedHeadVersion != null) const isStale = $derived( - // 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 ? draftBaseVersion !== deployedHeadVersion - : !!deployedAt && new Date(draftSavedAt).getTime() < new Date(deployedAt).getTime()) + : !!draftSavedAt && + !!deployedAt && + new Date(draftSavedAt).getTime() < new Date(deployedAt).getTime()) ) // Key on the versions (not `draftSavedAt`) in the version path, else every // autosave would mint a new key and re-pop the modal mid-edit. diff --git a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte index 70225b15ba..d515ea2ec0 100644 --- a/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte +++ b/frontend/src/lib/components/raw_apps/RawAppEditorHeader.svelte @@ -567,11 +567,11 @@ // guard compares the claimed base when there is one and the head otherwise. const claimed = versionThisDeployWrote(appHistory, $userStore?.username, anchor) version = appHistory[0]?.version - // 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 + // With no claim the head may be another deploy's, so it is not a base this editor + // may compare against: `compareVersions` confirms instead until a later deploy + // claims one or the editor is reset. A failed anchor read is indistinguishable + // from that here, and confirming is the side that cannot lose someone's work. + baseUnknown = claimed === undefined closeSaveDrawer() sendUserToast('App deployed successfully') diff --git a/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte index a9120f106f..025e2cb2b8 100644 --- a/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps/edit/[...path]/+page.svelte @@ -496,24 +496,23 @@ othersDraftsCount={otherDraftsUsers.length} onOpenOthersDrafts={() => (othersModalOpen = true)} onDeploy={({ version, head, headBy, headAt }) => { - // The editor stays open across a deploy, so what the out-of-date prompt reads - // has to move with it: the base is what the deploy could claim it wrote - // (unknown when another landed beside it), and the head is what is deployed, - // named by whoever deployed it rather than by the page load's author. - draftBaseVersion = version != null ? String(version) : undefined - // The deploy consumed the draft, so nothing here is out of date yet; the pair - // above describes the draft the next edit will start. - if (version != null && head != null && version !== head) { - // The prompt talks about a draft, and there is none: say what happened - // instead, since this deploy is already superseded. - sendUserToast(`Version ${head} was deployed on top of yours (${version})`) - } + // The editor stays open across a deploy and pins what it wrote onto the value + // itself; the prompt's own pair is what the loader knows, and this deploy + // consumed the draft it described, so the route holds no base until it loads + // again (the deploy guard covers that window). + draftBaseVersion = undefined draftSavedAt = undefined if (head != null) { + // Named by whoever deployed the head, not by the page load's author. deployedHeadVersion = String(head) deployedBy = headBy deployedAt = headAt } + // Another deploy landed on top of this one: there is no draft for the prompt + // to talk about, so say what happened instead. + if (version != null && head != null && version !== head) { + sendUserToast(`Version ${head} was deployed on top of yours (${version})`) + } }} /> diff --git a/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte index ed27854e23..2444cb030b 100644 --- a/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/apps_raw/edit/[...path]/+page.svelte @@ -656,27 +656,24 @@ } : undefined} onDeploy={({ version, head, headBy, headAt }) => { - // The version this deploy wrote is the base the next autosave carries; the - // head is what is deployed now. They differ when another deploy landed - // beside this one, and the next draft is then behind from the start. A - // deploy that could not claim a version leaves the base unknown rather - // than keeping the one it just superseded. + // The version this deploy wrote is what the next autosave forks from, so the + // editor carries it; the prompt's own pair is what the loader knows, and this + // deploy consumed the draft it described, so the route holds no base until it + // loads again (the deploy guard covers that window). parentVersion = version - draftBaseVersion = version != null ? String(version) : undefined - // The deploy consumed the draft, so nothing here is out of date yet; the pair - // above describes the draft the next edit will start. - if (version != null && head != null && version !== head) { - // The prompt talks about a draft, and there is none: say what happened - // instead, since this deploy is already superseded. - sendUserToast(`Version ${head} was deployed on top of yours (${version})`) - } + draftBaseVersion = undefined draftSavedAt = undefined if (head != null) { - deployedHeadVersion = String(head) // Named by whoever deployed the head, not by the page load's author. + deployedHeadVersion = String(head) deployedBy = headBy deployedAt = headAt } + // Another deploy landed on top of this one: there is no draft for the prompt + // to talk about, so say what happened instead. + if (version != null && head != null && version !== head) { + sendUserToast(`Version ${head} was deployed on top of yours (${version})`) + } }} onResetToDeployed={reloadDeployed} {loadedFromDraft}