diff --git a/frontend/src/lib/components/LocalDraftBanner.svelte b/frontend/src/lib/components/LocalDraftBanner.svelte index bc21e97ad5..4aa2206f5b 100644 --- a/frontend/src/lib/components/LocalDraftBanner.svelte +++ b/frontend/src/lib/components/LocalDraftBanner.svelte @@ -2,7 +2,12 @@ import { Button } from '$lib/components/common' import DiffDrawer from '$lib/components/DiffDrawer.svelte' import { classes } from '$lib/components/common/alert/model' - import { type Value } from '$lib/utils' + import { + cleanValueProperties, + orderedYamlStringify, + replaceFalseWithUndefined, + type Value + } from '$lib/utils' import { AlertCircle, Diff } from 'lucide-svelte' import { twMerge } from 'tailwind-merge' import { slide } from 'svelte/transition' @@ -31,14 +36,33 @@ title = 'Deployed <> Local changes' }: Props = $props() - // Suppress the banner when there's no deployed baseline to diff - // against — a brand-new entity (variable/resource/trigger with no - // deployed row yet) has deployed == null, so "Show diff" would do - // nothing (the drawer early-returns) and "Discard changes" is - // semantically backwards (there's nothing to revert to). The - // callers' own `show` is computed off `current != deployed` which - // is trivially true in that case, so the gate has to live here. - let visible = $derived(show && getDeployed() != null) + /** Same cleaning + YAML serialization the DiffDrawer applies before + * comparing. Without it the banner would fire on differences the + * drawer treats as no-op (toggle defaults, `false ↔ undefined`, + * key ordering noise) — exactly the case where the user clicks + * "Show diff" and sees the "No changes detected" empty state. */ + function diffKey(value: unknown): string { + try { + return orderedYamlStringify(cleanValueProperties(replaceFalseWithUndefined(value as Value))) + } catch { + return '' + } + } + + // Suppress the banner when: + // • There's no deployed baseline (brand-new entity — "Show diff" + // would early-return and "Discard" is semantically backwards), + // OR + // • Deployed and current are equal under the DiffDrawer's own + // comparison. The callers' `show` is a coarser "form differs + // from baseline" check that can stale-fire after a save lands + // or when `false`/`undefined` toggle noise flips a field. + let visible = $derived.by(() => { + if (!show) return false + const deployed = getDeployed() + if (deployed == null) return false + return diffKey(deployed) !== diffKey(getCurrent()) + }) let diffDrawer: DiffDrawer | undefined = $state()