Fix desktop badge workspace parity

This commit is contained in:
Mohamed Boudra
2026-04-10 19:14:31 +07:00
parent 01af138939
commit a315021c75
3 changed files with 69 additions and 17 deletions
+19 -17
View File
@@ -1,8 +1,14 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { Platform } from "react-native"; import { Platform } from "react-native";
import { useShallow } from "zustand/shallow";
import { getIsElectronRuntimeMac } from "@/constants/layout"; import { getIsElectronRuntimeMac } from "@/constants/layout";
import { useAggregatedAgents } from "./use-aggregated-agents"; import { useAggregatedAgents } from "./use-aggregated-agents";
import { getDesktopHost } from "@/desktop/host"; import { getDesktopHost } from "@/desktop/host";
import { useSessionStore } from "@/stores/session-store";
import {
deriveMacDockBadgeCountFromWorkspaceStatuses,
type DesktopBadgeWorkspaceStatus,
} from "@/utils/desktop-badge-state";
type FaviconStatus = "none" | "running" | "attention"; type FaviconStatus = "none" | "running" | "attention";
type ColorScheme = "dark" | "light"; type ColorScheme = "dark" | "light";
@@ -37,21 +43,6 @@ function deriveFaviconStatus(
return "none"; return "none";
} }
function deriveMacDockBadgeCount(
agents: ReturnType<typeof useAggregatedAgents>["agents"],
): number | undefined {
const attentionCount = agents.filter(
(agent) =>
(agent.pendingPermissionCount ?? 0) > 0 ||
(agent.requiresAttention && agent.attentionReason === "finished"),
).length;
if (attentionCount > 0) {
return attentionCount;
}
return undefined;
}
function getFaviconUri(status: FaviconStatus, colorScheme: ColorScheme): string { function getFaviconUri(status: FaviconStatus, colorScheme: ColorScheme): string {
const image = FAVICON_IMAGES[colorScheme][status]; const image = FAVICON_IMAGES[colorScheme][status];
if (typeof image === "object" && "uri" in image) { if (typeof image === "object" && "uri" in image) {
@@ -112,6 +103,17 @@ async function updateMacDockBadge(count?: number) {
export function useFaviconStatus() { export function useFaviconStatus() {
const { agents } = useAggregatedAgents(); const { agents } = useAggregatedAgents();
const workspaceStatuses = useSessionStore(
useShallow((state) => {
const statuses: DesktopBadgeWorkspaceStatus[] = [];
for (const session of Object.values(state.sessions)) {
for (const workspace of session.workspaces.values()) {
statuses.push(workspace.status);
}
}
return statuses;
}),
);
const [colorScheme, setColorScheme] = useState<ColorScheme>(getSystemColorScheme); const [colorScheme, setColorScheme] = useState<ColorScheme>(getSystemColorScheme);
const lastDockBadgeCountRef = useRef<number | undefined>(undefined); const lastDockBadgeCountRef = useRef<number | undefined>(undefined);
@@ -135,10 +137,10 @@ export function useFaviconStatus() {
const status = deriveFaviconStatus(agents); const status = deriveFaviconStatus(agents);
updateFavicon(status, colorScheme); updateFavicon(status, colorScheme);
const dockBadgeCount = deriveMacDockBadgeCount(agents); const dockBadgeCount = deriveMacDockBadgeCountFromWorkspaceStatuses(workspaceStatuses);
if (dockBadgeCount !== lastDockBadgeCountRef.current) { if (dockBadgeCount !== lastDockBadgeCountRef.current) {
lastDockBadgeCountRef.current = dockBadgeCount; lastDockBadgeCountRef.current = dockBadgeCount;
void updateMacDockBadge(dockBadgeCount); void updateMacDockBadge(dockBadgeCount);
} }
}, [agents, colorScheme]); }, [agents, colorScheme, workspaceStatuses]);
} }
@@ -0,0 +1,34 @@
import { describe, expect, it } from "vitest";
import {
deriveMacDockBadgeCountFromWorkspaceStatuses,
isWorkspaceActionableForDesktopBadge,
} from "./desktop-badge-state";
describe("desktop-badge-state", () => {
it("treats attention-requiring workspace statuses as actionable", () => {
expect(isWorkspaceActionableForDesktopBadge("attention")).toBe(true);
expect(isWorkspaceActionableForDesktopBadge("needs_input")).toBe(true);
expect(isWorkspaceActionableForDesktopBadge("failed")).toBe(true);
});
it("ignores running and done workspace statuses", () => {
expect(isWorkspaceActionableForDesktopBadge("running")).toBe(false);
expect(isWorkspaceActionableForDesktopBadge("done")).toBe(false);
});
it("returns undefined when no visible workspaces need attention", () => {
expect(deriveMacDockBadgeCountFromWorkspaceStatuses(["done", "running"])).toBeUndefined();
});
it("counts only actionable visible workspaces", () => {
expect(
deriveMacDockBadgeCountFromWorkspaceStatuses([
"done",
"attention",
"running",
"needs_input",
"failed",
]),
).toBe(3);
});
});
@@ -0,0 +1,16 @@
import type { WorkspaceDescriptor } from "@/stores/session-store";
export type DesktopBadgeWorkspaceStatus = WorkspaceDescriptor["status"];
export function isWorkspaceActionableForDesktopBadge(
status: DesktopBadgeWorkspaceStatus,
): boolean {
return status === "attention" || status === "needs_input" || status === "failed";
}
export function deriveMacDockBadgeCountFromWorkspaceStatuses(
statuses: readonly DesktopBadgeWorkspaceStatus[],
): number | undefined {
const actionableCount = statuses.filter(isWorkspaceActionableForDesktopBadge).length;
return actionableCount > 0 ? actionableCount : undefined;
}