Skip to content

Commit 9478212

Browse files
authored
feat: Implement badge notifications (#238)
1 parent fc05644 commit 9478212

File tree

6 files changed

+59
-0
lines changed

6 files changed

+59
-0
lines changed

apps/array/src/main/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
} from "electron";
1313
import "./lib/logger";
1414
import { ANALYTICS_EVENTS } from "../types/analytics.js";
15+
import { dockBadgeService } from "./services/dockBadge.js";
1516
import {
1617
cleanupAgentSessions,
1718
registerAgentIpc,
@@ -214,6 +215,9 @@ app.whenReady().then(() => {
214215
createWindow();
215216
ensureClaudeConfigDir();
216217

218+
// Initialize dock badge service for notification badges
219+
dockBadgeService.initialize(() => mainWindow);
220+
217221
// Initialize PostHog analytics
218222
initializePostHog();
219223
trackAppEvent(ANALYTICS_EVENTS.APP_STARTED);

apps/array/src/main/preload.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,8 @@ contextBridge.exposeInMainWorld("electronAPI", {
508508
setTerminalLayout: (mode: "split" | "tabbed"): Promise<void> =>
509509
ipcRenderer.invoke("settings:set-terminal-layout", mode),
510510
},
511+
// Dock Badge API
512+
dockBadge: {
513+
show: (): Promise<void> => ipcRenderer.invoke("dock-badge:show"),
514+
},
511515
});
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { app, type BrowserWindow, ipcMain } from "electron";
2+
import { logger } from "../lib/logger";
3+
4+
const log = logger.scope("dock-badge");
5+
6+
class DockBadgeService {
7+
private hasBadge = false;
8+
9+
initialize(_getMainWindow: () => BrowserWindow | null): void {
10+
app.on("browser-window-focus", () => {
11+
this.clearBadge();
12+
});
13+
14+
ipcMain.handle("dock-badge:show", () => {
15+
this.showBadge();
16+
});
17+
18+
log.info("Dock badge service initialized");
19+
}
20+
21+
showBadge(): void {
22+
if (!this.hasBadge) {
23+
this.hasBadge = true;
24+
if (process.platform === "darwin" || process.platform === "linux") {
25+
app.setBadgeCount(1);
26+
}
27+
log.info("Dock badge shown");
28+
}
29+
}
30+
31+
private clearBadge(): void {
32+
if (this.hasBadge) {
33+
log.info("Clearing dock badge");
34+
this.hasBadge = false;
35+
if (process.platform === "darwin" || process.platform === "linux") {
36+
app.setBadgeCount(0);
37+
}
38+
}
39+
}
40+
}
41+
42+
export const dockBadgeService = new DockBadgeService();

apps/array/src/main/services/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import "./contextMenu.js";
7+
import "./dockBadge.js";
78
import "./externalApps.js";
89
import "./fileWatcher.js";
910
import "./folders.js";

apps/array/src/renderer/features/task-detail/components/TaskLogsPanel.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ export function TaskLogsPanel({ taskId, task }: TaskLogsPanelProps) {
7676
if (isViewingTask) {
7777
markAsViewed(taskId);
7878
}
79+
80+
const isWindowFocused = document.hasFocus();
81+
if (!isWindowFocused) {
82+
window.electronAPI.dockBadge.show();
83+
}
7984
} catch (error) {
8085
log.error("Failed to send prompt", error);
8186
}

apps/array/src/renderer/types/electron.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ declare global {
326326
getTerminalLayout: () => Promise<"split" | "tabbed">;
327327
setTerminalLayout: (mode: "split" | "tabbed") => Promise<void>;
328328
};
329+
dockBadge: {
330+
show: () => Promise<void>;
331+
};
329332
}
330333

331334
interface Window {

0 commit comments

Comments
 (0)