From 7143794883cc41d2587050a9aa37534d5ec48379 Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Mon, 27 Apr 2026 11:53:15 +0530 Subject: [PATCH 1/2] fix: stack impersonation banner with customer alerts The header-alert system previously rendered only the highest-importance banner. Since the impersonation banner has importance 100, it always suppressed payment-failed and other billing alerts (importance 1), making them invisible to operators during impersonation sessions. Now the impersonation banner renders in a separate fixed-position stack alongside the highest-priority customer alert, so operators can see exactly what the customer sees while impersonating. --- src/lib/layout/alertStack.svelte | 71 +++++++++++++++++++++++++++++ src/lib/layout/headerAlert.svelte | 31 +++++++++---- src/lib/layout/index.ts | 1 + src/lib/layout/shell.svelte | 28 ++++++++++-- src/lib/stores/billing.ts | 2 +- src/lib/stores/headerAlert.ts | 18 ++++++++ src/routes/(console)/+layout.svelte | 4 +- 7 files changed, 139 insertions(+), 16 deletions(-) create mode 100644 src/lib/layout/alertStack.svelte diff --git a/src/lib/layout/alertStack.svelte b/src/lib/layout/alertStack.svelte new file mode 100644 index 0000000000..dd94d892c9 --- /dev/null +++ b/src/lib/layout/alertStack.svelte @@ -0,0 +1,71 @@ + + +
+ +
+ + diff --git a/src/lib/layout/headerAlert.svelte b/src/lib/layout/headerAlert.svelte index b092797d5f..6c30af074b 100644 --- a/src/lib/layout/headerAlert.svelte +++ b/src/lib/layout/headerAlert.svelte @@ -5,7 +5,7 @@
-{#if $activeHeaderAlert?.show && !$isNewWizardStatusOpen} - +{#if hasAnyAlert && !$isNewWizardStatusOpen} + + {#if isImpersonating} + + {/if} + {#if $activeHeaderAlert?.show} + + {/if} + {/if}
{#if showHeader && !$showOnboardingAnimation} diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts index 7f85293ec5..7c75642a0d 100644 --- a/src/lib/stores/billing.ts +++ b/src/lib/stores/billing.ts @@ -518,7 +518,7 @@ export async function checkPaymentAuthorizationRequired(org: Models.Organization importance: 8 }); } - activeHeaderAlert.set(headerAlert.get()); + activeHeaderAlert.set(headerAlert.getExcluding('impersonation')); actionRequiredInvoices.set(invoices); } diff --git a/src/lib/stores/headerAlert.ts b/src/lib/stores/headerAlert.ts index 1d5faffeef..1103c1008a 100644 --- a/src/lib/stores/headerAlert.ts +++ b/src/lib/stores/headerAlert.ts @@ -57,6 +57,24 @@ function createHeaderAlertStore() { return n; }); return component as HeaderAlert; + }, + getExcluding: (excludeId: string): HeaderAlert => { + // return highest importance visible component, excluding a specific id + let component = { + id: '', + show: false, + component: null, + importance: 0 + }; + update((n) => { + n.components.forEach((c) => { + if (c.show && c.id !== excludeId && c.importance > component.importance) { + component = c; + } + }); + return n; + }); + return component as HeaderAlert; } }; } diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index 47a5c06899..ea0830ac0c 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -305,7 +305,7 @@ calculateTrialDay(org); } } - $activeHeaderAlert = headerAlert.get(); + $activeHeaderAlert = headerAlert.getExcluding('impersonation'); } } @@ -318,7 +318,7 @@ $registerSearchers(orgSearcher, projectsSearcher); afterUpdate(() => { - $activeHeaderAlert = headerAlert.get(); + $activeHeaderAlert = headerAlert.getExcluding('impersonation'); }); From 57201952380b63598da61cc3ab8c699ff50a863d Mon Sep 17 00:00:00 2001 From: harsh mahajan Date: Mon, 27 Apr 2026 12:03:56 +0530 Subject: [PATCH 2/2] fix: address code review findings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use svelte/store get() for read-only access in headerAlert.get() and getExcluding() to avoid spurious subscriber notifications on every call - Fix bannerSpacing.set(undefined) → set(null) to match the store's declared string | null type (alertStack and headerAlert) - Remove redundant isImpersonating guard around ImpersonationBanner; the component self-manages visibility, eliminating the transient empty-stack layout edge case --- src/lib/layout/alertStack.svelte | 2 +- src/lib/layout/headerAlert.svelte | 2 +- src/lib/layout/shell.svelte | 5 ++-- src/lib/stores/headerAlert.ts | 44 +++++++++---------------------- 4 files changed, 17 insertions(+), 36 deletions(-) diff --git a/src/lib/layout/alertStack.svelte b/src/lib/layout/alertStack.svelte index dd94d892c9..597b5f2fa8 100644 --- a/src/lib/layout/alertStack.svelte +++ b/src/lib/layout/alertStack.svelte @@ -16,7 +16,7 @@ if (alertHeight) { bannerSpacing.set(`${alertHeight}px`); } else { - bannerSpacing.set(undefined); + bannerSpacing.set(null); } const header: HTMLHeadingElement = document.querySelector('main > header'); diff --git a/src/lib/layout/headerAlert.svelte b/src/lib/layout/headerAlert.svelte index 6c30af074b..4916e4f96f 100644 --- a/src/lib/layout/headerAlert.svelte +++ b/src/lib/layout/headerAlert.svelte @@ -39,7 +39,7 @@ // for sidebar and sub-navigation! bannerSpacing.set(`${alertHeight}px`); } else { - bannerSpacing.set(undefined); + bannerSpacing.set(null); } if (header) { diff --git a/src/lib/layout/shell.svelte b/src/lib/layout/shell.svelte index 9fc5bc8b93..0dc0d0bce9 100644 --- a/src/lib/layout/shell.svelte +++ b/src/lib/layout/shell.svelte @@ -229,9 +229,8 @@ {#if hasAnyAlert && !$isNewWizardStatusOpen} - {#if isImpersonating} - - {/if} + + {#if $activeHeaderAlert?.show} {/if} diff --git a/src/lib/stores/headerAlert.ts b/src/lib/stores/headerAlert.ts index 1103c1008a..dbde513359 100644 --- a/src/lib/stores/headerAlert.ts +++ b/src/lib/stores/headerAlert.ts @@ -1,5 +1,5 @@ import type { Component } from 'svelte'; -import { writable } from 'svelte/store'; +import { get as getStore, writable } from 'svelte/store'; export type HeaderAlert = { id: string; @@ -13,13 +13,11 @@ export type HeaderAlertStore = { }; function createHeaderAlertStore() { - const { subscribe, update, set } = writable({ - components: [] - }); + const store = writable({ components: [] }); + const { subscribe, update } = store; return { subscribe, - set, add: (component: HeaderAlert) => { update((n) => { if (n.components.some((c) => c.id === component.id)) return n; @@ -42,37 +40,21 @@ function createHeaderAlertStore() { }, get: (): HeaderAlert => { // return highest importance visible component - let component = { - id: '', - show: false, - component: null, - importance: 0 - }; - update((n) => { - n.components.forEach((c) => { - if (c.show && c.importance > component.importance) { - component = c; - } - }); - return n; + let component = { id: '', show: false, component: null, importance: 0 }; + getStore(store).components.forEach((c) => { + if (c.show && c.importance > component.importance) { + component = c; + } }); return component as HeaderAlert; }, getExcluding: (excludeId: string): HeaderAlert => { // return highest importance visible component, excluding a specific id - let component = { - id: '', - show: false, - component: null, - importance: 0 - }; - update((n) => { - n.components.forEach((c) => { - if (c.show && c.id !== excludeId && c.importance > component.importance) { - component = c; - } - }); - return n; + let component = { id: '', show: false, component: null, importance: 0 }; + getStore(store).components.forEach((c) => { + if (c.show && c.id !== excludeId && c.importance > component.importance) { + component = c; + } }); return component as HeaderAlert; }