From 30c48ab82ed62a068976b384b659bfeab396bfe3 Mon Sep 17 00:00:00 2001 From: Diego Imbert Date: Tue, 9 Jun 2026 14:50:57 +0200 Subject: [PATCH] fix(drafts): hide LocalDraftBanner when deployed and current match the DiffDrawer's compare MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Earlier I gated the banner on `getDeployed() != null`, but the user still saw it fire on entries where 'Show diff' opens to 'No changes detected'. That means `show` (the caller's coarse dirty check) flagged a difference the DiffDrawer treats as a no-op — typically toggle defaults (`false ↔ undefined`), removed empty arrays, or key-ordering noise that `cleanValueProperties + orderedYamlStringify` collapses. Replicate the drawer's comparison inside the banner: stringify both sides through the same pipeline and only render when the keys differ. A single `diffKey()` helper keeps the logic local; the catch-and-empty fallback survives a non-serializable side rather than throwing. --- .../lib/components/LocalDraftBanner.svelte | 42 +++++++++++++++---- 1 file changed, 33 insertions(+), 9 deletions(-) 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()