From 4f529a45f219f55bc6038f304cd69e3f51ec0533 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Wed, 26 Aug 2026 20:19:02 +1000 Subject: [PATCH] fix(desktop): prioritize sidebar channel status (#6861) ## Summary - Treat the sidebar temp-channel icon as a fallback behind working, mute, numeric unread, and thread-unread indicators. - Keep the header affordance and the sidebar temp icon when it is the only row status. - Extend the ephemeral-channel browser scenario across working, idle, collapsed-sidebar, and unread states. ## Before / After | Before: five temp icons compete with duration and unread states | After: all five duration and unread states remain clear | | --- | --- | | ![Before: five long temporary channel rows each show a temp icon, long duration, and unread state](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6861/temp-icon-before-five.png) | ![After: the same five long temporary channel rows retain long durations and unread states without temp icons](https://d24qwcpro867f5.cloudfront.net/repos/buzz/prs/6861/temp-icon-after-five.png) | ## Testing - `node --import ./test-loader.mjs --experimental-strip-types --test "src/**/*.test.mjs"` (5,556 passed) - `tsc --noEmit -p desktop/tsconfig.json` - `biome check desktop/src/features/sidebar/ui/SidebarSection.tsx desktop/tests/e2e/channels.spec.ts` - `vite build --mode e2e` - Isolated Playwright run of `create ephemeral stream shows sidebar and header affordances` (1 passed) ## Duplicate check No matching open PR or issue found for `ephemeral sidebar icon`. Signed-off-by: Jitter Co-authored-by: Jitter --- .../features/sidebar/ui/SidebarSection.tsx | 12 +++- desktop/tests/e2e/channels.spec.ts | 68 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/desktop/src/features/sidebar/ui/SidebarSection.tsx b/desktop/src/features/sidebar/ui/SidebarSection.tsx index 0c99eaf16d..c4c459aeff 100644 --- a/desktop/src/features/sidebar/ui/SidebarSection.tsx +++ b/desktop/src/features/sidebar/ui/SidebarSection.tsx @@ -294,6 +294,14 @@ export function ChannelMenuButton({ (hasSidebarUnreadProjections ? unreadThreadChannelIds.has(channel.id) : hasUnread); + const showsUnreadCount = + !isActive && channel.channelType !== "dm" && unreadCount > 0; + const showsEphemeralBadge = + Boolean(ephemeralDisplay) && + !activeWorking && + !isMuted && + !showsUnreadCount && + !hasThreadUnread; const inactiveContentOpacity = cn( !isActive && !hasTopLevelUnread && !isMuted && "opacity-80", !isActive && @@ -334,7 +342,7 @@ export function ChannelMenuButton({ > {resolvedLabel} - {ephemeralDisplay ? ( + {showsEphemeralBadge && ephemeralDisplay ? ( ) : null} - {!isActive && channel.channelType !== "dm" && unreadCount > 0 ? ( + {showsUnreadCount ? ( { @@ -1581,12 +1583,78 @@ test("create ephemeral stream shows sidebar and header affordances", async ({ /Ephemeral channel\. Cleans up in 14 days\./, ); + const channelRow = page.getByTestId(`channel-${channelName}`); + const channelId = await channelRow.getAttribute("data-channel-id"); + if (!channelId) { + throw new Error("Created ephemeral channel is missing its channel id."); + } + + await waitForMockLiveSubscription(page, channelName); + await page.getByTestId("channel-general").click(); + await page.evaluate( + ({ agentPubkey, channelId: activeChannelId }) => { + (window as MockFeedWindow).__BUZZ_E2E_SEED_ACTIVE_TURNS__?.({ + agentPubkey, + channelId: activeChannelId, + turnId: "ephemeral-sidebar-status", + }); + }, + { agentPubkey: OWNED_RELAY_AGENT_PUBKEY, channelId }, + ); + + await expect( + page.getByTestId(`channel-working-${channelName}`), + ).toBeVisible(); + await expect( + page.getByTestId(`channel-ephemeral-${channelName}`), + ).toHaveCount(0); + + await page.evaluate( + ({ agentPubkey, channelId: activeChannelId }) => { + (window as MockFeedWindow).__BUZZ_E2E_SEED_ACTIVE_TURNS__?.({ + agentPubkey, + channelId: activeChannelId, + turnId: "ephemeral-sidebar-status", + kind: "turn_completed", + }); + }, + { agentPubkey: OWNED_RELAY_AGENT_PUBKEY, channelId }, + ); + await expect( + page.getByTestId(`channel-ephemeral-${channelName}`), + ).toBeVisible(); + await page .getByRole("button", { name: "Toggle Sidebar", exact: true }) .click(); await expect( page.getByTestId(`channel-ephemeral-${channelName}`), ).toBeVisible(); + await page + .getByRole("button", { name: "Toggle Sidebar", exact: true }) + .click(); + + await page.evaluate( + ({ channelName: targetChannelName, mentionPubkey, senderPubkey }) => { + (window as MockFeedWindow).__BUZZ_E2E_EMIT_MOCK_MESSAGE__?.({ + channelName: targetChannelName, + content: "Unread mention in an ephemeral channel", + kind: 40002, + mentionPubkeys: [mentionPubkey], + pubkey: senderPubkey, + }); + }, + { + channelName, + mentionPubkey: MOCK_IDENTITY_PUBKEY, + senderPubkey: TEST_IDENTITIES.alice.pubkey, + }, + ); + + await expect(page.getByTestId(`channel-unread-${channelName}`)).toBeVisible(); + await expect( + page.getByTestId(`channel-ephemeral-${channelName}`), + ).toHaveCount(0); }); test("ephemeral countdown refreshes when switching channels after a clock jump", async ({