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 <d14dfe033ef0f809866f9f984de04821b0d900d7652fd85a54776ee40ca3a68f@buzz.block.builderlab.xyz>
Co-authored-by: Jitter <d14dfe033ef0f809866f9f984de04821b0d900d7652fd85a54776ee40ca3a68f@buzz.block.builderlab.xyz>
This commit is contained in:
Matt Toohey
2026-08-26 20:19:02 +10:00
committed by GitHub
parent e8172b5bac
commit 4f529a45f2
2 changed files with 78 additions and 2 deletions

View File

@@ -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}
</span>
{ephemeralDisplay ? (
{showsEphemeralBadge && ephemeralDisplay ? (
<EphemeralChannelBadge
display={ephemeralDisplay}
testId={`channel-ephemeral-${channel.name}`}
@@ -358,7 +366,7 @@ export function ChannelMenuButton({
)}
/>
) : null}
{!isActive && channel.channelType !== "dm" && unreadCount > 0 ? (
{showsUnreadCount ? (
<UnreadCountBadge
channelName={channel.name}
className="ml-auto bg-notification text-notification-foreground"

View File

@@ -34,6 +34,8 @@ type MockFeedWindow = Window & {
content: string;
createdAt?: number;
id?: string;
kind?: number;
mentionPubkeys?: string[];
parentEventId?: string;
pubkey?: string;
}) => {
@@ -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 ({