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);
});