Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions apps/app/src/components/sidebar/SidebarUpdatesBadge.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,34 @@ function providerIssue(
};
}

function missingInstallIssue(
provider: ProviderCliKey,
displayName: string,
): ProviderCliIssue {
return {
provider,
status: {
displayName,
executableName: provider,
executablePath: null,
installed: false,
installSource: "notInstalled",
currentVersion: null,
latestVersion: "1.1.0",
minimumSupportedVersion: null,
npmPackageName: `@example/${provider}`,
npmGlobalPackageVersion: null,
installAction: null,
needsUpdate: false,
versionUnsupported: false,
},
action: null,
title: `${displayName} CLI not installed`,
description: `Install ${displayName} so bb can start ${displayName} sessions.`,
fingerprint: `${provider}:missing:1.1.0`,
};
}

function host(id: string): Host {
return {
id,
Expand Down Expand Up @@ -137,6 +165,37 @@ describe("SidebarUpdatesBadge", () => {
).toBe("Claude Code update available");
});

it("renders no provider chip when a CLI is not installed", () => {
renderBadge({
machines: [
machine({
issues: [missingInstallIssue("claudeCode", "Claude Code")],
}),
],
});

expect(
screen.queryByTestId("sidebar-updates-badge-providers"),
).toBeNull();
expect(screen.queryByTestId("sidebar-updates-badge-bb")).toBeNull();
});

it("still shows the bb chip when the only provider issue is a missing CLI", () => {
renderBadge({
appUpdateAvailable: true,
machines: [
machine({
issues: [missingInstallIssue("codex", "Codex")],
}),
],
});

expect(screen.getByTestId("sidebar-updates-badge-bb")).toBeTruthy();
expect(
screen.queryByTestId("sidebar-updates-badge-providers"),
).toBeNull();
});

it("renders one mark per provider in a stable order when the same CLI is stale on several machines", () => {
renderBadge({
appUpdateAvailable: true,
Expand Down
9 changes: 9 additions & 0 deletions apps/app/src/components/sidebar/SidebarUpdatesBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ interface StaleProvider {
* protocol) and the agent CLIs, which carry their own brand marks so it is
* clear which agent is stale without hovering. Both chips open the
* consolidated Settings → Updates view.
*
* A CLI that is not installed at all is not an update and gets no chip here:
* there is no installed version to stale against, and the Settings → Updates
* page already surfaces the install prompt for it.
*/
export function SidebarUpdatesBadge({ onNavigate }: SidebarUpdatesBadgeProps) {
const inventory = useUpdateInventory();
Expand All @@ -70,9 +74,14 @@ export function SidebarUpdatesBadge({ onNavigate }: SidebarUpdatesBadgeProps) {
stuckDaemonCount;

// One mark per provider, even when the same CLI is stale on several machines.
// Missing CLIs are install prompts, not updates: skip them so the chip never
// claims an update is available for a CLI that isn't installed.
const staleProvidersByKey = new Map<ProviderCliKey, StaleProvider>();
for (const machine of inventory.machines) {
for (const issue of machine.issues) {
if (!issue.status.installed) {
continue;
}
if (!staleProvidersByKey.has(issue.provider)) {
staleProvidersByKey.set(issue.provider, {
provider: issue.provider,
Expand Down
Loading