fix: a deploy always names the head it left behind, and pins a base only when it can claim one

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-09-15 15:34:04 +02:00
co-authored by Claude Opus 5
parent c037b3e97b
commit 4f3d99c65b
4 changed files with 22 additions and 13 deletions
@@ -398,12 +398,19 @@
workspace: $workspaceStore!,
path: npath
})
version = versionThisDeployWrote(appHistory, $userStore?.username, headBefore?.version)
// `version` is what is deployed now, which is the deploy guard's fallback head and
// must stay set; the pin below is the version this deploy can prove it wrote.
version = appHistory[0]?.version
// 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
// DRAFT_COMPARE_IGNORED_FIELDS, so this write can't spawn a spurious draft.
if ($app) $app.parent_version = version
if ($app)
$app.parent_version = versionThisDeployWrote(
appHistory,
$userStore?.username,
headBefore?.version
)
closeSaveDrawer()
sendUserToast('App deployed successfully')
@@ -20,8 +20,8 @@ describe('versionThisDeployWrote', () => {
})
it('claims nothing it cannot tell apart from another deploy', () => {
// The same account deploying from elsewhere is indistinguishable by author, so
// the entry has to sit on the head this deploy read, and this one does not.
// Two entries of the caller's sit above the head it read, so neither can be shown
// to be this deploy's: taking either would pin a version it may not have written.
expect(versionThisDeployWrote([alice(9), alice(7), bob(6)], 'alice', 6)).toBe(undefined)
expect(versionThisDeployWrote([bob(8), bob(6)], 'alice', 6)).toBe(undefined)
expect(versionThisDeployWrote([], 'alice', 6)).toBe(undefined)
@@ -552,9 +552,11 @@
workspace: opWorkspace!,
path: npath
})
// The version this deploy wrote, not the head: they differ when someone else's
// deploy landed in between, and this becomes the next draft's base below.
version = versionThisDeployWrote(appHistory, $userStore?.username, headBefore?.version)
// Two different things: `version` is what is deployed now, which is what the deploy
// guard compares against and must stay set, and `claimed` is the version this deploy
// can prove it wrote, which is the next draft's base.
const claimed = versionThisDeployWrote(appHistory, $userStore?.username, headBefore?.version)
version = appHistory[0]?.version
closeSaveDrawer()
sendUserToast('App deployed successfully')
@@ -569,7 +571,7 @@
if (appPath !== npath) {
onSavedNewAppPath?.(npath)
}
onDeploy?.({ path: npath, version, head: appHistory[0]?.version })
onDeploy?.({ path: npath, version: claimed, head: version })
}
async function setPublishState(message?: string) {
@@ -658,11 +658,11 @@
onDeploy={({ version, head }) => {
// 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.
if (version != null) {
parentVersion = version
draftBaseVersion = String(version)
}
// 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.
parentVersion = version
draftBaseVersion = version != null ? String(version) : undefined
if (head != null) {
deployedHeadVersion = String(head)
}