fix: redeploy older app version from deployment history (#9826)

* fix: redeploy older app version from deployment history

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: apply restored app version to low-code editor on redeploy

Redeploying an older app version from Deployment History fired the
restore callback (toast shown) but the canvas kept displaying the
current version, and Deploy then shipped that current value.

AppEditor seeds its working state from `appDraftHandle.draft ?? app`,
preferring the per-path autosave over the freshly restored `app` prop.
The remount triggered by the restore therefore re-read the stale
pre-restore draft. `reloadDeployed` already clears the draft before
remounting for the reset-to-deployed flow; `onRestore` was missing the
same step.

Drop the autosave in `onRestore` so the remounted editor seeds from the
restored value. Raw apps are unaffected: RawAppEditor binds `files`
directly (no draft precedence), and `extractRawApp` mutates that bound
state in place.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(raw-apps): convert savedNewAppPath event forwarding to a callback prop

`svelte-check` (CI `npm check`) failed with one error: forwarding the
`savedNewAppPath` createEventDispatcher event through the runes-mode
RawAppEditor → RawAppEditorHeader chain types as "not assignable to
never". This is the same legacy-forwarding-through-runes pattern already
removed for `restore` in this PR — `on:savedNewAppPath` would likewise be
dropped at runtime, breaking navigation to the new path after a deploy
that renames the app.

Replace the `on:savedNewAppPath` forwarding with an `onSavedNewAppPath`
callback prop threaded page → RawAppEditor → RawAppEditorHeader, matching
`onRestore`. The header now invokes the callback instead of dispatching,
and its now-unused createEventDispatcher is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-06-28 11:25:59 +02:00
committed by GitHub
parent 003a262a4e
commit c479afab8e
7 changed files with 56 additions and 23 deletions
@@ -83,7 +83,8 @@
onResetToDeployed,
loadedFromDraft = false,
othersDraftsCount = 0,
onOpenOthersDrafts
onOpenOthersDrafts,
onRestore
}: AppEditorProps = $props()
migrateApp(untrack(() => app))
@@ -890,7 +891,7 @@
{loadedFromDraft}
{othersDraftsCount}
{onOpenOthersDrafts}
on:restore
{onRestore}
{policy}
{fromHub}
bind:this={appEditorHeader}
@@ -112,6 +112,10 @@
loadedFromDraft?: boolean
othersDraftsCount?: number
onOpenOthersDrafts?: () => void
// Restoring an older deployment from the history drawer. A callback prop
// (not `on:restore` forwarding): forwarding a `createEventDispatcher`
// event up through these runes-mode components silently drops it.
onRestore?: (restoredApp: any) => void
}
let {
@@ -137,7 +141,8 @@
onResetToDeployed,
loadedFromDraft = false,
othersDraftsCount = 0,
onOpenOthersDrafts
onOpenOthersDrafts,
onRestore
}: Props = $props()
/** Mirror of the path the user is editing in the pen popover. Initialized
@@ -862,7 +867,7 @@
<Drawer bind:open={historyBrowserDrawerOpen} size="1200px">
<DrawerContent title="Deployment History" on:close={() => (historyBrowserDrawerOpen = false)}>
<DeploymentHistory on:restore appPath={$appPath} />
<DeploymentHistory on:restore={(e) => onRestore?.(e.detail)} appPath={$appPath} />
</DrawerContent>
</Drawer>
@@ -176,6 +176,10 @@ export interface AppEditorProps {
loadedFromDraft?: boolean
othersDraftsCount?: number
onOpenOthersDrafts?: () => void
// Restoring an older deployment from the history drawer. Threaded through
// AppEditorHeader as a callback prop rather than `on:restore` forwarding,
// which does not propagate through these runes-mode components.
onRestore?: (restoredApp: any) => void
}
export type App = {
@@ -121,6 +121,14 @@
onOpenOthersDrafts?: () => void
onRuntimeLogRequester?: (requester: RawAppRuntimeLogRequester | undefined) => void
onRunsProvider?: (provider: RawAppRunsProvider | undefined) => void
// Restoring an older deployment from the history drawer. A callback prop
// (not `on:restore` forwarding): forwarding a `createEventDispatcher`
// event up through these runes-mode components silently drops it.
onRestore?: (restoredApp: any) => void
// Deploy created the app at a new path; the page navigates to it. Callback
// prop for the same reason as `onRestore` — `on:savedNewAppPath` forwarding
// through these runes-mode components is dropped.
onSavedNewAppPath?: (path: string) => void
}
let {
@@ -148,7 +156,9 @@
othersDraftsCount = 0,
onOpenOthersDrafts,
onRuntimeLogRequester = undefined,
onRunsProvider = undefined
onRunsProvider = undefined,
onRestore,
onSavedNewAppPath
}: Props = $props()
export const version: number | undefined = undefined
@@ -1596,8 +1606,8 @@
bind:savedApp
bind:summary
bind:pendingDraftPath
on:restore
on:savedNewAppPath
{onRestore}
{onSavedNewAppPath}
{policy}
{diffDrawer}
{newApp}
@@ -26,7 +26,7 @@
Undo,
WandSparkles
} from 'lucide-svelte'
import { createEventDispatcher, untrack } from 'svelte'
import { untrack } from 'svelte'
import { orderedJsonStringify, type Value, replaceFalseWithUndefined } from '../../utils'
import { random_adj } from '$lib/components/random_positive_adjetive'
@@ -151,6 +151,14 @@
loadedFromDraft?: boolean
othersDraftsCount?: number
onOpenOthersDrafts?: () => void
// Restoring an older deployment from the history drawer. A callback prop
// (not `on:restore` forwarding): forwarding a `createEventDispatcher`
// event up through these runes-mode components silently drops it.
onRestore?: (restoredApp: any) => void
// Deploy created the app at a new path; the page navigates to it. Callback
// prop for the same reason as `onRestore` — `on:savedNewAppPath` forwarding
// through these runes-mode components is dropped.
onSavedNewAppPath?: (path: string) => void
}
let {
@@ -184,7 +192,9 @@
onResetToDeployed,
loadedFromDraft = false,
othersDraftsCount = 0,
onOpenOthersDrafts
onOpenOthersDrafts,
onRestore,
onSavedNewAppPath
}: Props = $props()
// Set by the on-behalf-of selector when the publisher picks a user other than
@@ -352,7 +362,7 @@
path: appPath
})
}
dispatch('savedNewAppPath', path)
onSavedNewAppPath?.(path)
onDeploy?.({ path })
} catch (e) {
sendUserToast(`Error creating app: ${e.body ?? e.message}`, true)
@@ -475,7 +485,7 @@
})
}
if (appPath !== npath) {
dispatch('savedNewAppPath', npath)
onSavedNewAppPath?.(npath)
}
onDeploy?.({ path: npath })
}
@@ -575,8 +585,6 @@
}
])
const dispatch = createEventDispatcher()
let customPath = $state(savedApp?.custom_path)
let customPathError = $state('')
@@ -688,7 +696,7 @@
<Drawer bind:open={historyBrowserDrawerOpen} size="1200px">
<DrawerContent title="Deployment History" on:close={() => (historyBrowserDrawerOpen = false)}>
<DeploymentHistory on:restore {appPath} />
<DeploymentHistory on:restore={(e) => onRestore?.(e.detail)} {appPath} />
</DrawerContent>
</Drawer>
@@ -388,9 +388,14 @@
let diffDrawer: DiffDrawer | undefined = $state()
function onRestore(ev: any) {
function onRestore(restoredApp: any) {
sendUserToast('App restored from previous deployment')
app = ev.detail
// Drop the stale pre-restore autosave. The remounted AppEditor seeds its
// state from `appDraftHandle.draft ?? app`, so without this it keeps showing
// the old draft instead of the restored version. Same reason `reloadDeployed`
// removes the draft before remounting.
UserDraft.remove('app', path)
app = restoredApp
// Re-pin the stale-draft fork base to the current head. A restored value
// carries the `parent_version` baked in when that older version was deployed,
// which would make the deploy guard (`compareVersions`) falsely report "not on
@@ -470,7 +475,7 @@
app.path = url
}
}}
on:restore={onRestore}
{onRestore}
summary={app.summary}
app={app.value}
{deployedBaseline}
@@ -449,9 +449,9 @@
let diffDrawer: DiffDrawer | undefined = $state(undefined)
function onRestore(ev: any) {
function onRestore(restoredApp: any) {
sendUserToast('App restored from previous deployment')
let prev = ev.detail
let prev = restoredApp
extractRawApp(prev)
savedApp = {
summary: prev.summary,
@@ -536,12 +536,12 @@
{#key redraw}
<div class="h-screen">
<RawAppEditor
on:savedNewAppPath={(event) => {
onSavedNewAppPath={(savedPath) => {
draftSync.remove()
goto(`/apps_raw/edit/${event.detail}`)
newPath = event.detail
goto(`/apps_raw/edit/${savedPath}`)
newPath = savedPath
}}
on:restore={onRestore}
{onRestore}
bind:files
bind:runnables
bind:data