diff --git a/desktop/src/features/sidebar/ui/WorkspaceRail.test.mjs b/desktop/src/features/sidebar/ui/WorkspaceRail.test.mjs index 8f33ca1cb..358af9c53 100644 --- a/desktop/src/features/sidebar/ui/WorkspaceRail.test.mjs +++ b/desktop/src/features/sidebar/ui/WorkspaceRail.test.mjs @@ -25,22 +25,25 @@ describe("workspaceRailIndicators", () => { it("shows no badge for an observed workspace with unread but no mentions", () => { const r = workspaceRailIndicators({ hasUnread: true, state: "ready" }); assert.equal(r.showBadge, false); + assert.equal(r.showDot, true); assert.equal(r.pending, false); }); - it("shows no badge for an observed workspace with no unread", () => { + it("shows no badge and no dot for an observed workspace with no unread", () => { const r = workspaceRailIndicators({ hasUnread: false, state: "ready" }); assert.equal(r.showBadge, false); + assert.equal(r.showDot, false); assert.equal(r.pending, false); }); - it("shows a mention badge with the count when mentions are present", () => { + it("shows a mention badge with the count when mentions are present — no dot", () => { const r = workspaceRailIndicators({ hasUnread: true, count: 3, state: "ready", }); assert.equal(r.showBadge, true); + assert.equal(r.showDot, false); assert.equal(r.mentionCount, 3); assert.equal(r.badgeLabel, "3"); }); @@ -54,30 +57,33 @@ describe("workspaceRailIndicators", () => { assert.equal(r.badgeLabel, "99+"); }); - it("never reports mentions for an unobserved (unknown) workspace", () => { + it("never reports mentions or dot for an unobserved (unknown) workspace", () => { const r = workspaceRailIndicators({ hasUnread: true, count: 5, state: "unknown", }); assert.equal(r.showBadge, false); + assert.equal(r.showDot, false); assert.equal(r.mentionCount, 0); assert.equal(r.pending, true); }); - it("treats loading as pending, not as no-unread", () => { + it("treats loading as pending — no badge, no dot", () => { const r = workspaceRailIndicators({ hasUnread: false, state: "loading" }); assert.equal(r.pending, true); assert.equal(r.showBadge, false); + assert.equal(r.showDot, false); }); - it("never reports mentions on an errored observation", () => { + it("never reports mentions or dot on an errored observation", () => { const r = workspaceRailIndicators({ hasUnread: true, count: 2, state: "error", }); assert.equal(r.showBadge, false); + assert.equal(r.showDot, false); assert.equal(r.mentionCount, 0); assert.equal(r.pending, false); }); diff --git a/desktop/src/features/sidebar/ui/WorkspaceRail.tsx b/desktop/src/features/sidebar/ui/WorkspaceRail.tsx index 12984f95e..2d6b98cbd 100644 --- a/desktop/src/features/sidebar/ui/WorkspaceRail.tsx +++ b/desktop/src/features/sidebar/ui/WorkspaceRail.tsx @@ -45,21 +45,29 @@ export function workspaceInitials(name: string): string { /** * Presentation decisions for one workspace button, derived from its observed * mention state. Pure so it can be unit-tested without a DOM. The `state` guard - * ensures we NEVER render a mention badge for a relay we could not observe + * ensures we NEVER render any indicator for a relay we could not observe * (`unknown`/`loading`/`error`) — only a `ready` observation is trusted. + * + * Two-tier indicator system: + * - `showBadge`: numeric mention count (mentions/thread-replies present). + * - `showDot`: plain unread dot when there are regular channel unreads but no + * mentions. Mutually exclusive with `showBadge` by construction. */ export function workspaceRailIndicators(unread: WorkspaceUnreadState): { mentionCount: number; showBadge: boolean; + showDot: boolean; pending: boolean; badgeLabel: string; } { const observed = unread.state === "ready"; const mentionCount = observed ? (unread.count ?? 0) : 0; const showBadge = mentionCount > 0; + const showDot = observed && unread.hasUnread && !showBadge; return { mentionCount, showBadge, + showDot, pending: unread.state === "unknown" || unread.state === "loading", badgeLabel: mentionCount > MAX_BADGE ? `${MAX_BADGE}+` : String(mentionCount), @@ -81,12 +89,14 @@ function WorkspaceButton({ onSwitch: () => void; menu: React.ReactNode; }) { - const { mentionCount, showBadge, pending, badgeLabel } = + const { mentionCount, showBadge, showDot, pending, badgeLabel } = workspaceRailIndicators(unread); const tooltipLabel = showBadge ? `${workspace.name} — ${mentionCount} mention${mentionCount === 1 ? "" : "s"}` - : workspace.name; + : showDot + ? `${workspace.name} — unread` + : workspace.name; return ( @@ -129,6 +139,13 @@ function WorkspaceButton({ > {badgeLabel} + ) : showDot ? ( + + unread + ) : null}