mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): preserve Welcome banner dismissal (#5406)
## Summary - remove the complete Welcome guidance surface when dismissal reaches `hidden` - preserve dismissal across the private and starter Welcome channels for the active identity - assert the starter channel's actual `welcome-everyone` title on re-entry ## Why PR #5330 introduced two deterministic Desktop E2E failures: - the inner banner unmounted, but `welcome-composer-guidance-layer` remained - the re-entry test expected case-sensitive `Welcome` while navigating to `welcome-everyone` The state hook also scoped completion to channel IDs while `ChannelPane` remounts during navigation. The Welcome guidance is one experience spanning both Welcome channels, so completion now survives that remount while remaining identity-scoped. ## Validation At `b577eb42edffe889f63566f2457eacea720f3593`: - `pnpm -C desktop typecheck` - focused Biome check for all four changed files - E2E build - both `welcome-everywhere banner` integration tests repeated three times: **6/6 passed** - mandatory pre-push desktop check, typecheck, and full desktop unit suite: **4,535 passed** - `git diff --check` Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -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 &&
|
||||
|
||||
@@ -443,6 +443,10 @@ export function WelcomeComposerGuidanceLayer({
|
||||
settingUp,
|
||||
state,
|
||||
}: WelcomeComposerGuidanceLayerProps) {
|
||||
if (state === "hidden") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative" data-testid="welcome-composer-guidance-layer">
|
||||
<ComposerDockGlassBackdrop
|
||||
|
||||
@@ -8,11 +8,14 @@ import {
|
||||
type WelcomeComposerBannerState,
|
||||
} from "@/features/channels/ui/WelcomeComposerBanner";
|
||||
|
||||
const completedWelcomeComposerIdentityPubkeys = new Set<string>();
|
||||
|
||||
/**
|
||||
* 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<string>());
|
||||
const dismissTimerRef = React.useRef<number | null>(null);
|
||||
const hideTimerRef = React.useRef<number | null>(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 };
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user