feat(desktop-client): surface account health issues in tray and app icons

Periodically polls runtime snapshot for accounts in expired/revoked/challenge_required states and reflects the count via tray badge, macOS dock badge, and Windows overlay icon so users see authentication problems without opening the app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-27 12:50:45 +08:00
parent 87c902c5da
commit 4d6e6990d4
3 changed files with 183 additions and 4 deletions
+16 -1
View File
@@ -11,6 +11,12 @@ import type {
import type { DesktopAccountInfo, DesktopRuntimeSessionSyncRequest } from "@geo/shared-types";
import { initAccountHealth } from "./account-health";
import {
applyDesktopHealthIndicatorFromSnapshot,
createDesktopHealthIcon,
startDesktopHealthIndicator,
syncDesktopHealthIndicator,
} from "./app-issue-indicator";
import { bindPublishAccount, openPublishAccountConsole } from "./account-binder";
import { installObservedGlobalFetch, registerObservedRequestRendererTarget } from "./network-observer";
import { initProcessMetricsSampler } from "./process-metrics";
@@ -142,6 +148,12 @@ function currentMainWindow(): ElectronBrowserWindow | null {
return mainWindow;
}
function captureRuntimeSnapshot() {
const snapshot = createRuntimeSnapshot();
applyDesktopHealthIndicatorFromSnapshot(snapshot, currentMainWindow());
return snapshot;
}
async function ensureMainWindow(): Promise<ElectronBrowserWindow> {
const existing = currentMainWindow();
if (existing) {
@@ -201,6 +213,7 @@ async function createMainWindow(): Promise<ElectronBrowserWindow> {
height: initial.height,
show: false,
title: "GEO Rankly Desktop",
icon: createDesktopHealthIcon("normal"),
titleBarStyle: "hiddenInset",
trafficLightPosition: { x: 12, y: 14 },
backgroundColor: nativeTheme.shouldUseDarkColors ? "#15191a" : "#eaf2ff",
@@ -287,7 +300,7 @@ function registerBridgeHandlers(): void {
os: process.platform,
cpu_arch: process.arch,
}));
ipcMain.handle("desktop:runtime-snapshot", () => createRuntimeSnapshot());
ipcMain.handle("desktop:runtime-snapshot", () => captureRuntimeSnapshot());
safeHandle("desktop:bind-publish-account", async (_event, platformId: string) => {
const account = (await bindPublishAccount(platformId)) as DesktopAccountInfo;
noteRuntimeAccountBound(account);
@@ -369,6 +382,7 @@ app.whenReady().then(async () => {
startHotViewReaper();
startHiddenPlaywrightReaper();
onRuntimeInvalidated((event) => {
syncDesktopHealthIndicator(currentMainWindow());
if (!mainRendererContents || mainRendererContents.isDestroyed()) {
return;
}
@@ -378,6 +392,7 @@ app.whenReady().then(async () => {
initTray(() => {
revealMainWindowSafely("tray");
});
startDesktopHealthIndicator(() => currentMainWindow());
}).catch((error) => {
console.error("[desktop-main] app.whenReady failed", error);
});