fix(drafts): disable Diff button on draft-only items across the 4 editors

Diff has no baseline to compare against on draft-only items — the
button used to be gated by the pre-PR `/add` route's own state, but the
`/add → /edit` redirect landed everything under the regular `/edit`
page where the gate was missing.

- ScriptBuilder: gate the topbar Diff on `savedScript.no_deployed`;
  seed `no_deployed: true` on the route's `new_draft` empty NewScript
  so the gate fires before the first deploy.
- FlowBuilder: gate the topbar Diff on `newFlow` (route already sets
  it from `backendFlow.no_deployed` and the new-draft branch).
- AppEditorHeader: gate both the "Diff" dropdown action and the
  Deploy-drawer's "Diff" button on `newApp`.
- RawAppEditorHeader: gate the topbar Diff + the Deploy-drawer's "Diff"
  button on `newApp`.

Each gate also rewrites the tooltip ("Deploy this … once to compare
against the deployed version") so the hover state explains why.
This commit is contained in:
Diego Imbert
2026-06-07 23:54:26 +02:00
parent 5f94207c02
commit 6c758ef3ff
5 changed files with 23 additions and 13 deletions
@@ -1051,9 +1051,11 @@
variant="default"
unifiedSize="md"
on:click={() => openDiffDrawer()}
disabled={!savedFlow}
disabled={!savedFlow || newFlow}
iconOnly={compactTopbar}
title="Diff"
title={newFlow
? 'Deploy this flow once to compare against the deployed version'
: 'Diff'}
startIcon={{ icon: DiffIcon }}
>
Diff
@@ -1898,13 +1898,16 @@
{/snippet}
{#snippet diffButton()}
{#if customUi?.topBar?.diff != false}
{@const isDraftOnly = (savedScript as any)?.no_deployed === true}
<Button
variant="default"
unifiedSize="md"
on:click={() => openDiffDrawer()}
disabled={!savedScript || !diffDrawer}
disabled={!savedScript || !diffDrawer || isDraftOnly}
iconOnly={compactTopbar}
title="Diff"
title={isDraftOnly
? 'Deploy this script once to compare against the deployed version'
: 'Diff'}
startIcon={{ icon: DiffIcon }}
>
Diff
@@ -526,7 +526,7 @@
displayName: 'Diff',
icon: DiffIcon,
action: async () => {
if (!savedApp) {
if (!savedApp || newApp) {
return
}
@@ -546,7 +546,7 @@
}
})
},
disabled: !savedApp
disabled: !savedApp || newApp
},
// App debug menu
{
@@ -696,9 +696,9 @@
<div class="flex flex-row gap-4">
<Button
variant="accent"
disabled={!savedApp}
disabled={!savedApp || newApp}
on:click={async () => {
if (!savedApp) {
if (!savedApp || newApp) {
return
}
// deployedValue should be syncronized when we open Diff
@@ -551,9 +551,9 @@
<div class="flex flex-row gap-2">
<Button
variant="default"
disabled={!savedApp}
disabled={!savedApp || newApp}
on:click={async () => {
if (!savedApp) {
if (!savedApp || newApp) {
return
}
// deployedValue should be syncronized when we open Diff
@@ -734,9 +734,9 @@
variant="default"
unifiedSize="md"
on:click={() => openDiffDrawer()}
disabled={!savedApp}
disabled={!savedApp || newApp}
iconOnly={compactTopbar}
title="Diff"
title={newApp ? 'Deploy this app once to compare against the deployed version' : 'Diff'}
startIcon={{ icon: DiffIcon }}
>
Diff
@@ -181,7 +181,12 @@
// `JSON.stringify(undefined)` returns the value `undefined`,
// and `JSON.parse(undefined)` coerces to the literal string
// "undefined", throwing the toast "Could not parse code".
schema: emptySchema()
schema: emptySchema(),
// Mirrors the backend's overlay shape for deployed=null
// paths — ScriptBuilder's Diff button gates on this so
// /add (and any other draft-only state) doesn't offer a
// diff that has no baseline to compare against.
no_deployed: true
} as unknown as EditableScript
initialPath = ''
savedScript = structuredClone(empty)