From b577eb42edffe889f63566f2457eacea720f3593 Mon Sep 17 00:00:00 2001 From: Wes Date: Sun, 9 Aug 2026 09:42:07 -0600 Subject: [PATCH] fix(desktop): preserve Welcome banner dismissal Remove the full guidance surface after its banner reaches the hidden state. Keep dismissal scoped to the active identity across both Welcome channels, and assert the starter channel's actual title on re-entry. Co-authored-by: Carl Signed-off-by: Wes --- .../src/features/channels/ui/ChannelPane.tsx | 6 ++- .../channels/ui/WelcomeComposerBanner.tsx | 4 ++ .../channels/ui/useWelcomeComposerBanner.ts | 39 +++++++++++++------ desktop/tests/e2e/onboarding.spec.ts | 4 +- 4 files changed, 39 insertions(+), 14 deletions(-) diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx index 13790d779f..410b05a2cc 100644 --- a/desktop/src/features/channels/ui/ChannelPane.tsx +++ b/desktop/src/features/channels/ui/ChannelPane.tsx @@ -216,7 +216,11 @@ export const ChannelPane = React.memo(function ChannelPane({ bannerState: welcomeComposerBannerState, completeBanner: completeWelcomeComposerBanner, dismissBanner: handleDismissWelcomeBanner, - } = useWelcomeComposerBanner(activeChannelId, isActiveWelcomeChannel); + } = useWelcomeComposerBanner( + activeChannelId, + isActiveWelcomeChannel, + currentPubkey ?? null, + ); const isEditInThread = editTarget != null && threadHeadMessage != null && diff --git a/desktop/src/features/channels/ui/WelcomeComposerBanner.tsx b/desktop/src/features/channels/ui/WelcomeComposerBanner.tsx index f050dd4b9f..9a8db082ed 100644 --- a/desktop/src/features/channels/ui/WelcomeComposerBanner.tsx +++ b/desktop/src/features/channels/ui/WelcomeComposerBanner.tsx @@ -443,6 +443,10 @@ export function WelcomeComposerGuidanceLayer({ settingUp, state, }: WelcomeComposerGuidanceLayerProps) { + if (state === "hidden") { + return null; + } + return (
(); + /** * Manages the Welcome-channel composer hint banner's state machine. * - * Tracks which channels have been completed within the session so the banner - * stays hidden on re-entry. Exposes three transitions: + * Remembers completion across the Welcome experience per identity for this app + * session, so the hint stays hidden while moving between the private and + * starter Welcome channels without leaking dismissal to another identity. * - `completeBanner`: agent-mention path — plays the "Nice work." success * animation before auto-dismissing. * - `dismissBanner`: manual X-button path — immediately begins the slide-down @@ -21,12 +24,12 @@ import { export function useWelcomeComposerBanner( activeChannelId: string | null, isActiveWelcomeChannel: boolean, + identityPubkey: string | null, ): { bannerState: WelcomeComposerBannerState; completeBanner: () => void; dismissBanner: () => void; } { - const completedChannelIdsRef = React.useRef(new Set()); const dismissTimerRef = React.useRef(null); const hideTimerRef = React.useRef(null); const [bannerState, setBannerState] = @@ -48,15 +51,15 @@ export function useWelcomeComposerBanner( React.useEffect(() => { clearTimers(); if ( - activeChannelId && isActiveWelcomeChannel && - completedChannelIdsRef.current.has(activeChannelId) + identityPubkey && + completedWelcomeComposerIdentityPubkeys.has(identityPubkey) ) { setBannerState("hidden"); return; } setBannerState("prompt"); - }, [activeChannelId, clearTimers, isActiveWelcomeChannel]); + }, [clearTimers, identityPubkey, isActiveWelcomeChannel]); const scheduleHide = React.useCallback(() => { hideTimerRef.current = window.setTimeout( @@ -70,30 +73,42 @@ export function useWelcomeComposerBanner( }, []); const completeBanner = React.useCallback(() => { - if (!activeChannelId || !isActiveWelcomeChannel) { + if (!activeChannelId || !isActiveWelcomeChannel || !identityPubkey) { return; } clearTimers(); - completedChannelIdsRef.current.add(activeChannelId); + completedWelcomeComposerIdentityPubkeys.add(identityPubkey); setBannerState("complete"); dismissTimerRef.current = window.setTimeout(() => { setBannerState("dismissing"); dismissTimerRef.current = null; scheduleHide(); }, WELCOME_PERSONA_ROTATION_MS + WELCOME_COMPOSER_BANNER_SUCCESS_SETTLE_MS); - }, [activeChannelId, clearTimers, isActiveWelcomeChannel, scheduleHide]); + }, [ + activeChannelId, + clearTimers, + identityPubkey, + isActiveWelcomeChannel, + scheduleHide, + ]); const dismissBanner = React.useCallback(() => { - if (!activeChannelId || !isActiveWelcomeChannel) { + if (!activeChannelId || !isActiveWelcomeChannel || !identityPubkey) { return; } clearTimers(); - completedChannelIdsRef.current.add(activeChannelId); + completedWelcomeComposerIdentityPubkeys.add(identityPubkey); setBannerState("dismissing"); scheduleHide(); - }, [activeChannelId, clearTimers, isActiveWelcomeChannel, scheduleHide]); + }, [ + activeChannelId, + clearTimers, + identityPubkey, + isActiveWelcomeChannel, + scheduleHide, + ]); return { bannerState, completeBanner, dismissBanner }; } diff --git a/desktop/tests/e2e/onboarding.spec.ts b/desktop/tests/e2e/onboarding.spec.ts index 5e0e6a4fa2..403edbcda1 100644 --- a/desktop/tests/e2e/onboarding.spec.ts +++ b/desktop/tests/e2e/onboarding.spec.ts @@ -3218,7 +3218,9 @@ test("welcome-everywhere banner: dismiss persists after channel re-entry", async // Return — banner must stay hidden. await page.getByTestId("channel-welcome-everyone").click(); - await expect(page.getByTestId("chat-title")).toContainText("Welcome"); + await expect(page.getByTestId("chat-title")).toContainText( + "welcome-everyone", + ); await expect(banner).toHaveCount(0); });