From 84d7303346538260c065ed53bc32f3cd71420e44 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sun, 28 Jun 2026 01:07:52 +0300 Subject: [PATCH] feat(desktop): add macOS dock badge for chats with unseen activity Show a count of chats (root sessions) with unseen activity on the macOS dock icon. The count is computed in the existing tray snapshot (full cross-project list, not the capped tray view; a subtask's unseen rolls up to its root only when subtask notifications are enabled) and pushed to the main process over the existing desktop_tray_update IPC, which calls app.setBadgeCount (0 clears it). The badge clears as sessions are marked seen on window focus. Add a Dock badge toggle in Appearance settings (default on, persisted, darwin desktop only), localized across all dictionaries, with a matching settings-search entry whose availability mirrors the render guard exactly. --- packages/electron/main.mjs | 11 +++++ .../openchamber/OpenChamberVisualSettings.tsx | 43 +++++++++++++++++++ .../ui/src/components/views/SettingsView.tsx | 8 +++- packages/ui/src/hooks/useTraySync.ts | 29 ++++++++++++- .../ui/src/lib/i18n/messages/en.settings.ts | 2 + .../ui/src/lib/i18n/messages/es.settings.ts | 2 + .../ui/src/lib/i18n/messages/fr.settings.ts | 2 + .../ui/src/lib/i18n/messages/ja.settings.ts | 2 + .../ui/src/lib/i18n/messages/ko.settings.ts | 2 + .../ui/src/lib/i18n/messages/pl.settings.ts | 2 + .../src/lib/i18n/messages/pt-BR.settings.ts | 2 + .../ui/src/lib/i18n/messages/uk.settings.ts | 2 + .../src/lib/i18n/messages/zh-CN.settings.ts | 2 + .../src/lib/i18n/messages/zh-TW.settings.ts | 2 + packages/ui/src/lib/settings/search.ts | 12 ++++++ packages/ui/src/stores/useUIStore.ts | 9 ++++ 16 files changed, 129 insertions(+), 3 deletions(-) diff --git a/packages/electron/main.mjs b/packages/electron/main.mjs index b706fcbc..27312ef4 100644 --- a/packages/electron/main.mjs +++ b/packages/electron/main.mjs @@ -3265,6 +3265,17 @@ const handleInvoke = async (browserWindow, command, args = {}) => { log.warn('[electron] tray update failed', error); } } + // Dock badge: count of chats with unseen activity (0 = cleared, also when + // the user disabled the badge). setBadgeCount drives the macOS dock badge. + try { + const rawCount = args && typeof args.dockBadgeCount === 'number' ? args.dockBadgeCount : 0; + const badgeCount = Number.isFinite(rawCount) ? Math.max(0, Math.floor(rawCount)) : 0; + if (typeof app.setBadgeCount === 'function') { + app.setBadgeCount(badgeCount); + } + } catch (error) { + log.warn('[electron] dock badge update failed', error); + } return null; case 'desktop_clear_cache': diff --git a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx index aea0f322..c1efef3d 100644 --- a/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx +++ b/packages/ui/src/components/sections/openchamber/OpenChamberVisualSettings.tsx @@ -336,6 +336,17 @@ export const OpenChamberVisualSettings: React.FC const macVibrancyEnabled = typeof window !== 'undefined' && window.__OPENCHAMBER_ELECTRON__?.macVibrancy === true; const [vibrancyChecked, setVibrancyChecked] = React.useState(macVibrancyEnabled); const [vibrancyRestarting, setVibrancyRestarting] = React.useState(false); + + // macOS-desktop-only dock badge that counts chats with unseen activity. + // The tray sync (mac-only) pumps the count to the main process, so the + // toggle is offered only where it actually has an effect. No relaunch needed. + const dockBadgeSupported = React.useMemo( + () => isDesktopShell() && typeof window !== 'undefined' + && (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__ === 'darwin', + [], + ); + const dockBadgeEnabled = useUIStore(state => state.dockBadgeEnabled); + const setDockBadgeEnabled = useUIStore(state => state.setDockBadgeEnabled); const [chatRenderPreviewTick, setChatRenderPreviewTick] = React.useState(0); const reportUsage = useUIStore(state => state.reportUsage); const setReportUsage = useUIStore(state => state.setReportUsage); @@ -854,6 +865,38 @@ export const OpenChamberVisualSettings: React.FC )} )} + + {dockBadgeSupported && ( +
+
setDockBadgeEnabled(!dockBadgeEnabled)} + onKeyDown={(event) => { + if (event.key === ' ' || event.key === 'Enter') { + event.preventDefault(); + setDockBadgeEnabled(!dockBadgeEnabled); + } + }} + > + +
+ + {t('settings.openchamber.visual.field.dockBadge')} + + + {t('settings.openchamber.visual.field.dockBadgeHint')} + +
+
+
+ )} )} diff --git a/packages/ui/src/components/views/SettingsView.tsx b/packages/ui/src/components/views/SettingsView.tsx index 25fee971..ed89324f 100644 --- a/packages/ui/src/components/views/SettingsView.tsx +++ b/packages/ui/src/components/views/SettingsView.tsx @@ -330,6 +330,10 @@ export const SettingsView: React.FC = ({ onClose, forceMobile const isDesktopLocalOrigin = React.useMemo(() => { return isDesktopShell() && isDesktopLocalOriginActive(); }, []); + const isMac = React.useMemo(() => { + return isDesktopShell() && typeof window !== 'undefined' + && (window as unknown as { __OPENCHAMBER_PLATFORM__?: string }).__OPENCHAMBER_PLATFORM__ === 'darwin'; + }, []); // keep platform check available for future window chrome tweaks @@ -532,12 +536,12 @@ export const SettingsView: React.FC = ({ onClose, forceMobile const settingsSearchResults = React.useMemo(() => { return buildSettingsSearchResults({ query: settingsSearchQuery, - runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin }, + runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin, isMac }, visiblePageSlugs, t, getPageTitle, }); - }, [getPageTitle, isDesktopLocalOrigin, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]); + }, [getPageTitle, isDesktopLocalOrigin, isMac, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]); const prepareSettingsSearchTarget = React.useCallback((result: SettingsSearchResult): string => { if (result.id.startsWith('agents.')) { diff --git a/packages/ui/src/hooks/useTraySync.ts b/packages/ui/src/hooks/useTraySync.ts index 3f860119..0a10a32e 100644 --- a/packages/ui/src/hooks/useTraySync.ts +++ b/packages/ui/src/hooks/useTraySync.ts @@ -19,6 +19,7 @@ import { QUOTA_PROVIDERS, formatWindowLabel, formatQuotaValueLabel } from '@/lib import { useProjectsStore } from '@/stores/useProjectsStore'; import { useSessionUIStore } from '@/sync/session-ui-store'; import { useGitStore } from '@/stores/useGitStore'; +import { useUIStore } from '@/stores/useUIStore'; import { resolveProjectForSessionDirectory, normalizeProjectPath } from '@/lib/projectResolution'; import type { ProjectEntry } from '@/lib/api/types'; import type { WorktreeMetadata } from '@/types/worktree'; @@ -80,6 +81,9 @@ type TraySnapshot = { // dropdown (same "configured to show" rule as the header/mobile). Empty // groups → the tray omits the Usage submenu entirely. usage: TrayUsage; + // Number of chats (root sessions) with unseen activity, for the macOS dock + // badge. 0 when the user disabled the badge — the main process clears it. + dockBadgeCount: number; }; // focus-session / new-session are routed natively by the main process through @@ -389,7 +393,26 @@ const buildSnapshot = (instanceName: string): TraySnapshot => { const approvals = live.approvals.map((a) => ({ ...a, sessionTitle: titleById.get(a.sessionId) || '' })); - return { sessions, approvals, instanceName, usage: buildUsage() }; + // Dock badge: count chats (root sessions) with unseen activity over the FULL + // cross-project list — not the MAX_SESSIONS-capped `sessions` above — so the + // number is accurate even with many projects. A subtask's unseen rolls up to + // its root only when the user opted into subtask notifications, matching the + // sidebar's needs-attention rule. + const ui = useUIStore.getState(); + let dockBadgeCount = 0; + if (ui.dockBadgeEnabled) { + for (const session of allSessions) { + if (!session?.id || session.parentID) continue; // roots only + let familyUnseen = notif.unseenCount[session.id] ?? 0; + if (familyUnseen === 0 && ui.notifyOnSubtasks) { + familyUnseen = collectDescendants(session.id) + .reduce((sum, id) => sum + (notif.unseenCount[id] ?? 0), 0); + } + if (familyUnseen > 0) dockBadgeCount += 1; + } + } + + return { sessions, approvals, instanceName, usage: buildUsage(), dockBadgeCount }; }; export const useTraySync = (): void => { @@ -491,6 +514,9 @@ export const useTraySync = (): void => { const unsubscribeProjects = useProjectsStore.subscribe(() => scheduleFlush()); const unsubscribeWorktrees = useSessionUIStore.subscribe(() => scheduleFlush()); const unsubscribeGit = useGitStore.subscribe(() => scheduleFlush()); + // The dock-badge toggle and subtask-notification preference live here; a + // change must re-push the snapshot so the badge appears/clears immediately. + const unsubscribeUI = useUIStore.subscribe(() => scheduleFlush()); // Cross-project status map: fed live by the sync dispatcher from the global // event stream, and seeded/reconciled by the poll below. const unsubscribeGlobalStatus = useGlobalSessionStatusStore.subscribe(() => scheduleFlush()); @@ -541,6 +567,7 @@ export const useTraySync = (): void => { unsubscribeProjects(); unsubscribeWorktrees(); unsubscribeGit(); + unsubscribeUI(); unsubscribeGlobalStatus(); unsubscribeQuota(); unsubscribeRegistry?.(); diff --git a/packages/ui/src/lib/i18n/messages/en.settings.ts b/packages/ui/src/lib/i18n/messages/en.settings.ts index 9012bd25..c425b72e 100644 --- a/packages/ui/src/lib/i18n/messages/en.settings.ts +++ b/packages/ui/src/lib/i18n/messages/en.settings.ts @@ -1606,6 +1606,8 @@ export const settingsDict = { 'settings.openchamber.visual.actions.reloadingThemes': 'Reloading themes...', 'settings.openchamber.visual.field.macVibrancy': 'Window transparency', 'settings.openchamber.visual.field.macVibrancyHint': 'Use the native macOS blur (vibrancy) behind the sidebar. Turn off for fully solid, opaque surfaces.', + 'settings.openchamber.visual.field.dockBadge': 'Dock badge', + 'settings.openchamber.visual.field.dockBadgeHint': 'Show a count of chats with unseen activity on the macOS dock icon.', 'settings.openchamber.visual.actions.saveAndRestart': 'Save & restart', 'settings.openchamber.visual.actions.restarting': 'Restarting…', 'settings.openchamber.visual.field.themeImportInfoAria': 'Theme import info', diff --git a/packages/ui/src/lib/i18n/messages/es.settings.ts b/packages/ui/src/lib/i18n/messages/es.settings.ts index 9a2c92f3..06851680 100644 --- a/packages/ui/src/lib/i18n/messages/es.settings.ts +++ b/packages/ui/src/lib/i18n/messages/es.settings.ts @@ -1572,6 +1572,8 @@ export const settingsDict = { "settings.openchamber.visual.actions.reloadThemes": "Recargar temas", "settings.openchamber.visual.field.macVibrancy": "Transparencia de la ventana", "settings.openchamber.visual.field.macVibrancyHint": "Usa el desenfoque nativo de macOS (vibrancy) detrás de la barra lateral. Desactívalo para superficies totalmente sólidas y opacas.", + "settings.openchamber.visual.field.dockBadge": "Insignia en el Dock", + "settings.openchamber.visual.field.dockBadgeHint": "Muestra en el icono del Dock de macOS el número de chats con actividad sin ver.", "settings.openchamber.visual.actions.saveAndRestart": "Guardar y reiniciar", "settings.openchamber.visual.actions.restarting": "Reiniciando…", "settings.openchamber.visual.actions.reloadingThemes": "Recargando temas...", diff --git a/packages/ui/src/lib/i18n/messages/fr.settings.ts b/packages/ui/src/lib/i18n/messages/fr.settings.ts index a8bd66e9..8d1daedd 100644 --- a/packages/ui/src/lib/i18n/messages/fr.settings.ts +++ b/packages/ui/src/lib/i18n/messages/fr.settings.ts @@ -1762,6 +1762,8 @@ export const settingsDict = { 'settings.openchamber.visual.option.mobileLayout.new': 'Nouvelle', 'settings.openchamber.visual.field.macVibrancy': 'Transparence de la fenêtre', 'settings.openchamber.visual.field.macVibrancyHint': 'Utiliser le flou macOS natif (vibrancy) derrière la barre latérale. Désactivez cette option pour des surfaces entièrement opaques.', + 'settings.openchamber.visual.field.dockBadge': 'Badge du Dock', + 'settings.openchamber.visual.field.dockBadgeHint': 'Afficher sur l’icône du Dock de macOS le nombre de discussions avec une activité non vue.', 'settings.openchamber.visual.actions.saveAndRestart': 'Enregistrer et redémarrer', 'settings.openchamber.visual.actions.restarting': 'Redémarrage…', 'settings.openchamber.visual.field.fileEditorKeymap': 'Keymap de l’éditeur de fichiers', diff --git a/packages/ui/src/lib/i18n/messages/ja.settings.ts b/packages/ui/src/lib/i18n/messages/ja.settings.ts index 6437c188..160bcd77 100644 --- a/packages/ui/src/lib/i18n/messages/ja.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ja.settings.ts @@ -1606,6 +1606,8 @@ export const settingsDict = { 'settings.openchamber.visual.actions.reloadingThemes': 'テーマを再読み込み中...', 'settings.openchamber.visual.field.macVibrancy': 'ウィンドウ透過', 'settings.openchamber.visual.field.macVibrancyHint': 'サイドバーの背後に macOS ネイティブのぼかし(Vibrancy)を使用します。完全に不透明なサーフェスにする場合はオフにします。', + 'settings.openchamber.visual.field.dockBadge': 'Dock バッジ', + 'settings.openchamber.visual.field.dockBadgeHint': '未読のアクティビティがあるチャットの数を macOS の Dock アイコンに表示します。', 'settings.openchamber.visual.actions.saveAndRestart': '保存して再起動', 'settings.openchamber.visual.actions.restarting': '再起動中...', 'settings.openchamber.visual.field.themeImportInfoAria': 'テーマインポート情報', diff --git a/packages/ui/src/lib/i18n/messages/ko.settings.ts b/packages/ui/src/lib/i18n/messages/ko.settings.ts index d898681b..7aac2181 100644 --- a/packages/ui/src/lib/i18n/messages/ko.settings.ts +++ b/packages/ui/src/lib/i18n/messages/ko.settings.ts @@ -1572,6 +1572,8 @@ export const settingsDict = { 'settings.openchamber.visual.actions.reloadThemes': '테마 다시 로드', 'settings.openchamber.visual.field.macVibrancy': '창 투명도', 'settings.openchamber.visual.field.macVibrancyHint': '사이드바 뒤에 macOS 기본 블러(vibrancy)를 사용합니다. 완전히 불투명한 표면을 원하면 끄세요.', + 'settings.openchamber.visual.field.dockBadge': 'Dock 배지', + 'settings.openchamber.visual.field.dockBadgeHint': '확인하지 않은 활동이 있는 채팅 수를 macOS Dock 아이콘에 표시합니다.', 'settings.openchamber.visual.actions.saveAndRestart': '저장 후 재시작', 'settings.openchamber.visual.actions.restarting': '재시작 중…', 'settings.openchamber.visual.actions.reloadingThemes': '테마 다시 로드 중...', diff --git a/packages/ui/src/lib/i18n/messages/pl.settings.ts b/packages/ui/src/lib/i18n/messages/pl.settings.ts index da16e9c5..cd62d24d 100644 --- a/packages/ui/src/lib/i18n/messages/pl.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pl.settings.ts @@ -907,6 +907,8 @@ export const settingsDict = { 'settings.openchamber.visual.actions.reloadThemes': 'Przeładuj motywy', 'settings.openchamber.visual.field.macVibrancy': 'Przezroczystość okna', 'settings.openchamber.visual.field.macVibrancyHint': 'Używaj natywnego rozmycia macOS (vibrancy) za panelem bocznym. Wyłącz, aby uzyskać w pełni nieprzezroczyste powierzchnie.', + 'settings.openchamber.visual.field.dockBadge': 'Plakietka w Docku', + 'settings.openchamber.visual.field.dockBadgeHint': 'Pokazuj na ikonie w Docku macOS liczbę czatów z nieprzeczytaną aktywnością.', 'settings.openchamber.visual.actions.saveAndRestart': 'Zapisz i uruchom ponownie', 'settings.openchamber.visual.actions.restarting': 'Ponowne uruchamianie…', 'settings.openchamber.visual.actions.reloadingThemes': 'Przeładowywanie motywów...', diff --git a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts index 4aab66bc..3c38e551 100644 --- a/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts +++ b/packages/ui/src/lib/i18n/messages/pt-BR.settings.ts @@ -1572,6 +1572,8 @@ export const settingsDict = { "settings.openchamber.visual.actions.reloadThemes": "Recarregar temas", "settings.openchamber.visual.field.macVibrancy": "Transparência da janela", "settings.openchamber.visual.field.macVibrancyHint": "Usa o desfoque nativo do macOS (vibrancy) atrás da barra lateral. Desative para superfícies totalmente sólidas e opacas.", + "settings.openchamber.visual.field.dockBadge": "Selo no Dock", + "settings.openchamber.visual.field.dockBadgeHint": "Mostra no ícone do Dock do macOS o número de conversas com atividade não vista.", "settings.openchamber.visual.actions.saveAndRestart": "Salvar e reiniciar", "settings.openchamber.visual.actions.restarting": "Reiniciando…", "settings.openchamber.visual.actions.reloadingThemes": "Recarregando temas...", diff --git a/packages/ui/src/lib/i18n/messages/uk.settings.ts b/packages/ui/src/lib/i18n/messages/uk.settings.ts index e62eda5e..8d4f408d 100644 --- a/packages/ui/src/lib/i18n/messages/uk.settings.ts +++ b/packages/ui/src/lib/i18n/messages/uk.settings.ts @@ -1572,6 +1572,8 @@ export const settingsDict = { "settings.openchamber.visual.actions.reloadThemes": "Перезавантажити теми", "settings.openchamber.visual.field.macVibrancy": "Прозорість вікна", "settings.openchamber.visual.field.macVibrancyHint": "Використовувати нативне розмиття macOS (vibrancy) під сайдбаром. Вимкніть для повністю непрозорих поверхонь.", + "settings.openchamber.visual.field.dockBadge": "Лічильник у доку", + "settings.openchamber.visual.field.dockBadgeHint": "Показувати на іконці в доку кількість чатів із непрочитаною активністю.", "settings.openchamber.visual.actions.saveAndRestart": "Зберегти та перезапустити", "settings.openchamber.visual.actions.restarting": "Перезапуск…", "settings.openchamber.visual.actions.reloadingThemes": "Перезавантаження тем...", diff --git a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts index 6d9ad39b..49a1db3e 100644 --- a/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-CN.settings.ts @@ -1572,6 +1572,8 @@ export const settingsDict = { 'settings.openchamber.visual.actions.reloadThemes': '重新加载主题', 'settings.openchamber.visual.field.macVibrancy': '窗口透明度', 'settings.openchamber.visual.field.macVibrancyHint': '在侧边栏后使用 macOS 原生模糊(vibrancy)。关闭以获得完全不透明的界面。', + 'settings.openchamber.visual.field.dockBadge': 'Dock 角标', + 'settings.openchamber.visual.field.dockBadgeHint': '在 macOS Dock 图标上显示有未读动态的会话数量。', 'settings.openchamber.visual.actions.saveAndRestart': '保存并重启', 'settings.openchamber.visual.actions.restarting': '正在重启…', 'settings.openchamber.visual.actions.reloadingThemes': '正在重新加载主题...', diff --git a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts index 953c7174..24af70a1 100644 --- a/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts +++ b/packages/ui/src/lib/i18n/messages/zh-TW.settings.ts @@ -1491,6 +1491,8 @@ 'settings.openchamber.visual.actions.reloadThemes': '重新載入主題', 'settings.openchamber.visual.field.macVibrancy': '視窗透明度', 'settings.openchamber.visual.field.macVibrancyHint': '在側邊欄後使用 macOS 原生模糊(vibrancy)。關閉以獲得完全不透明的介面。', + 'settings.openchamber.visual.field.dockBadge': 'Dock 標記', + 'settings.openchamber.visual.field.dockBadgeHint': '在 macOS Dock 圖示上顯示有未讀動態的對話數量。', 'settings.openchamber.visual.actions.saveAndRestart': '儲存並重新啟動', 'settings.openchamber.visual.actions.restarting': '正在重新啟動…', 'settings.openchamber.visual.actions.reloadingThemes': '正在重新載入主題...', diff --git a/packages/ui/src/lib/settings/search.ts b/packages/ui/src/lib/settings/search.ts index 3d24931b..3a74ce8e 100644 --- a/packages/ui/src/lib/settings/search.ts +++ b/packages/ui/src/lib/settings/search.ts @@ -20,6 +20,8 @@ export interface SettingsSearchResult extends SettingsSearchItem { interface SettingsSearchAvailabilityContext extends SettingsRuntimeContext { isMobile: boolean; isDesktopLocalOrigin: boolean; + // macOS desktop shell — for controls that only render on darwin (e.g. dock badge). + isMac: boolean; } const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [ @@ -65,6 +67,16 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [ keywords: ['transparent', 'transparency', 'vibrancy', 'blur', 'macos', 'opaque'], isAvailable: (ctx) => ctx.isDesktopLocalOrigin, }, + { + id: 'appearance.dock-badge', + page: 'appearance', + titleKey: 'settings.openchamber.visual.field.dockBadge', + descriptionKey: 'settings.openchamber.visual.field.dockBadgeHint', + keywords: ['dock', 'badge', 'unread', 'unseen', 'counter', 'count', 'notification', 'macos'], + // Exactly matches the render guard in OpenChamberVisualSettings: any darwin + // Electron shell (isMac already implies isDesktopShell), local or remote host. + isAvailable: (ctx) => ctx.isMac, + }, { id: 'appearance.pwa-install-name', page: 'appearance', diff --git a/packages/ui/src/stores/useUIStore.ts b/packages/ui/src/stores/useUIStore.ts index 450ee6e4..6033f607 100644 --- a/packages/ui/src/stores/useUIStore.ts +++ b/packages/ui/src/stores/useUIStore.ts @@ -592,6 +592,8 @@ interface UIStore { nativeNotificationsEnabled: boolean; notificationMode: 'always' | 'hidden-only'; notifyOnSubtasks: boolean; + // Desktop dock badge showing the count of sessions with unseen activity (macOS). + dockBadgeEnabled: boolean; // Event toggles (which events trigger notifications) notifyOnCompletion: boolean; @@ -750,6 +752,7 @@ interface UIStore { setNotificationMode: (mode: 'always' | 'hidden-only') => void; setShowTerminalQuickKeysOnDesktop: (value: boolean) => void; setNotifyOnSubtasks: (value: boolean) => void; + setDockBadgeEnabled: (value: boolean) => void; setNotifyOnCompletion: (value: boolean) => void; setNotifyOnError: (value: boolean) => void; setNotifyOnQuestion: (value: boolean) => void; @@ -877,6 +880,7 @@ export const useUIStore = create()( nativeNotificationsEnabled: false, notificationMode: 'hidden-only', notifyOnSubtasks: true, + dockBadgeEnabled: true, // Event toggles (which events trigger notifications) notifyOnCompletion: true, @@ -1975,6 +1979,10 @@ export const useUIStore = create()( set({ notifyOnSubtasks: value }); }, + setDockBadgeEnabled: (value) => { + set({ dockBadgeEnabled: value }); + }, + setNotifyOnCompletion: (value) => { set({ notifyOnCompletion: value }); }, setNotifyOnError: (value) => { set({ notifyOnError: value }); }, setNotifyOnQuestion: (value) => { set({ notifyOnQuestion: value }); }, @@ -2243,6 +2251,7 @@ export const useUIStore = create()( notificationMode: state.notificationMode, showTerminalQuickKeysOnDesktop: state.showTerminalQuickKeysOnDesktop, notifyOnSubtasks: state.notifyOnSubtasks, + dockBadgeEnabled: state.dockBadgeEnabled, notifyOnCompletion: state.notifyOnCompletion, notifyOnError: state.notifyOnError, notifyOnQuestion: state.notifyOnQuestion,