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.
This commit is contained in:
@@ -3265,6 +3265,17 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
|||||||
log.warn('[electron] tray update failed', error);
|
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;
|
return null;
|
||||||
|
|
||||||
case 'desktop_clear_cache':
|
case 'desktop_clear_cache':
|
||||||
|
|||||||
@@ -336,6 +336,17 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
const macVibrancyEnabled = typeof window !== 'undefined' && window.__OPENCHAMBER_ELECTRON__?.macVibrancy === true;
|
const macVibrancyEnabled = typeof window !== 'undefined' && window.__OPENCHAMBER_ELECTRON__?.macVibrancy === true;
|
||||||
const [vibrancyChecked, setVibrancyChecked] = React.useState(macVibrancyEnabled);
|
const [vibrancyChecked, setVibrancyChecked] = React.useState(macVibrancyEnabled);
|
||||||
const [vibrancyRestarting, setVibrancyRestarting] = React.useState(false);
|
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 [chatRenderPreviewTick, setChatRenderPreviewTick] = React.useState(0);
|
||||||
const reportUsage = useUIStore(state => state.reportUsage);
|
const reportUsage = useUIStore(state => state.reportUsage);
|
||||||
const setReportUsage = useUIStore(state => state.setReportUsage);
|
const setReportUsage = useUIStore(state => state.setReportUsage);
|
||||||
@@ -854,6 +865,38 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{dockBadgeSupported && (
|
||||||
|
<div data-settings-item="appearance.dock-badge" className="flex flex-col gap-1.5 border-t border-border/40 pt-3">
|
||||||
|
<div
|
||||||
|
className="group flex cursor-pointer items-start gap-2 py-0.5"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-pressed={dockBadgeEnabled}
|
||||||
|
onClick={() => setDockBadgeEnabled(!dockBadgeEnabled)}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === ' ' || event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
setDockBadgeEnabled(!dockBadgeEnabled);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
checked={dockBadgeEnabled}
|
||||||
|
onChange={setDockBadgeEnabled}
|
||||||
|
ariaLabel={t('settings.openchamber.visual.field.dockBadge')}
|
||||||
|
/>
|
||||||
|
<div className="flex min-w-0 flex-col">
|
||||||
|
<span className="typography-ui-label text-foreground">
|
||||||
|
{t('settings.openchamber.visual.field.dockBadge')}
|
||||||
|
</span>
|
||||||
|
<span className="typography-meta text-muted-foreground">
|
||||||
|
{t('settings.openchamber.visual.field.dockBadgeHint')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -330,6 +330,10 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
|||||||
const isDesktopLocalOrigin = React.useMemo(() => {
|
const isDesktopLocalOrigin = React.useMemo(() => {
|
||||||
return isDesktopShell() && isDesktopLocalOriginActive();
|
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
|
// keep platform check available for future window chrome tweaks
|
||||||
|
|
||||||
@@ -532,12 +536,12 @@ export const SettingsView: React.FC<SettingsViewProps> = ({ onClose, forceMobile
|
|||||||
const settingsSearchResults = React.useMemo(() => {
|
const settingsSearchResults = React.useMemo(() => {
|
||||||
return buildSettingsSearchResults({
|
return buildSettingsSearchResults({
|
||||||
query: settingsSearchQuery,
|
query: settingsSearchQuery,
|
||||||
runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin },
|
runtimeCtx: { ...runtimeCtx, isDesktopLocalOrigin, isMac },
|
||||||
visiblePageSlugs,
|
visiblePageSlugs,
|
||||||
t,
|
t,
|
||||||
getPageTitle,
|
getPageTitle,
|
||||||
});
|
});
|
||||||
}, [getPageTitle, isDesktopLocalOrigin, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]);
|
}, [getPageTitle, isDesktopLocalOrigin, isMac, runtimeCtx, settingsSearchQuery, t, visiblePageSlugs]);
|
||||||
|
|
||||||
const prepareSettingsSearchTarget = React.useCallback((result: SettingsSearchResult): string => {
|
const prepareSettingsSearchTarget = React.useCallback((result: SettingsSearchResult): string => {
|
||||||
if (result.id.startsWith('agents.')) {
|
if (result.id.startsWith('agents.')) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { QUOTA_PROVIDERS, formatWindowLabel, formatQuotaValueLabel } from '@/lib
|
|||||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||||
import { useGitStore } from '@/stores/useGitStore';
|
import { useGitStore } from '@/stores/useGitStore';
|
||||||
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
import { resolveProjectForSessionDirectory, normalizeProjectPath } from '@/lib/projectResolution';
|
import { resolveProjectForSessionDirectory, normalizeProjectPath } from '@/lib/projectResolution';
|
||||||
import type { ProjectEntry } from '@/lib/api/types';
|
import type { ProjectEntry } from '@/lib/api/types';
|
||||||
import type { WorktreeMetadata } from '@/types/worktree';
|
import type { WorktreeMetadata } from '@/types/worktree';
|
||||||
@@ -80,6 +81,9 @@ type TraySnapshot = {
|
|||||||
// dropdown (same "configured to show" rule as the header/mobile). Empty
|
// dropdown (same "configured to show" rule as the header/mobile). Empty
|
||||||
// groups → the tray omits the Usage submenu entirely.
|
// groups → the tray omits the Usage submenu entirely.
|
||||||
usage: TrayUsage;
|
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
|
// 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) || '' }));
|
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 => {
|
export const useTraySync = (): void => {
|
||||||
@@ -491,6 +514,9 @@ export const useTraySync = (): void => {
|
|||||||
const unsubscribeProjects = useProjectsStore.subscribe(() => scheduleFlush());
|
const unsubscribeProjects = useProjectsStore.subscribe(() => scheduleFlush());
|
||||||
const unsubscribeWorktrees = useSessionUIStore.subscribe(() => scheduleFlush());
|
const unsubscribeWorktrees = useSessionUIStore.subscribe(() => scheduleFlush());
|
||||||
const unsubscribeGit = useGitStore.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
|
// Cross-project status map: fed live by the sync dispatcher from the global
|
||||||
// event stream, and seeded/reconciled by the poll below.
|
// event stream, and seeded/reconciled by the poll below.
|
||||||
const unsubscribeGlobalStatus = useGlobalSessionStatusStore.subscribe(() => scheduleFlush());
|
const unsubscribeGlobalStatus = useGlobalSessionStatusStore.subscribe(() => scheduleFlush());
|
||||||
@@ -541,6 +567,7 @@ export const useTraySync = (): void => {
|
|||||||
unsubscribeProjects();
|
unsubscribeProjects();
|
||||||
unsubscribeWorktrees();
|
unsubscribeWorktrees();
|
||||||
unsubscribeGit();
|
unsubscribeGit();
|
||||||
|
unsubscribeUI();
|
||||||
unsubscribeGlobalStatus();
|
unsubscribeGlobalStatus();
|
||||||
unsubscribeQuota();
|
unsubscribeQuota();
|
||||||
unsubscribeRegistry?.();
|
unsubscribeRegistry?.();
|
||||||
|
|||||||
@@ -1606,6 +1606,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.actions.reloadingThemes': 'Reloading themes...',
|
'settings.openchamber.visual.actions.reloadingThemes': 'Reloading themes...',
|
||||||
'settings.openchamber.visual.field.macVibrancy': 'Window transparency',
|
'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.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.saveAndRestart': 'Save & restart',
|
||||||
'settings.openchamber.visual.actions.restarting': 'Restarting…',
|
'settings.openchamber.visual.actions.restarting': 'Restarting…',
|
||||||
'settings.openchamber.visual.field.themeImportInfoAria': 'Theme import info',
|
'settings.openchamber.visual.field.themeImportInfoAria': 'Theme import info',
|
||||||
|
|||||||
@@ -1572,6 +1572,8 @@ export const settingsDict = {
|
|||||||
"settings.openchamber.visual.actions.reloadThemes": "Recargar temas",
|
"settings.openchamber.visual.actions.reloadThemes": "Recargar temas",
|
||||||
"settings.openchamber.visual.field.macVibrancy": "Transparencia de la ventana",
|
"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.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.saveAndRestart": "Guardar y reiniciar",
|
||||||
"settings.openchamber.visual.actions.restarting": "Reiniciando…",
|
"settings.openchamber.visual.actions.restarting": "Reiniciando…",
|
||||||
"settings.openchamber.visual.actions.reloadingThemes": "Recargando temas...",
|
"settings.openchamber.visual.actions.reloadingThemes": "Recargando temas...",
|
||||||
|
|||||||
@@ -1762,6 +1762,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.option.mobileLayout.new': 'Nouvelle',
|
'settings.openchamber.visual.option.mobileLayout.new': 'Nouvelle',
|
||||||
'settings.openchamber.visual.field.macVibrancy': 'Transparence de la fenêtre',
|
'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.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.saveAndRestart': 'Enregistrer et redémarrer',
|
||||||
'settings.openchamber.visual.actions.restarting': 'Redémarrage…',
|
'settings.openchamber.visual.actions.restarting': 'Redémarrage…',
|
||||||
'settings.openchamber.visual.field.fileEditorKeymap': 'Keymap de l’éditeur de fichiers',
|
'settings.openchamber.visual.field.fileEditorKeymap': 'Keymap de l’éditeur de fichiers',
|
||||||
|
|||||||
@@ -1606,6 +1606,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.actions.reloadingThemes': 'テーマを再読み込み中...',
|
'settings.openchamber.visual.actions.reloadingThemes': 'テーマを再読み込み中...',
|
||||||
'settings.openchamber.visual.field.macVibrancy': 'ウィンドウ透過',
|
'settings.openchamber.visual.field.macVibrancy': 'ウィンドウ透過',
|
||||||
'settings.openchamber.visual.field.macVibrancyHint': 'サイドバーの背後に macOS ネイティブのぼかし(Vibrancy)を使用します。完全に不透明なサーフェスにする場合はオフにします。',
|
'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.saveAndRestart': '保存して再起動',
|
||||||
'settings.openchamber.visual.actions.restarting': '再起動中...',
|
'settings.openchamber.visual.actions.restarting': '再起動中...',
|
||||||
'settings.openchamber.visual.field.themeImportInfoAria': 'テーマインポート情報',
|
'settings.openchamber.visual.field.themeImportInfoAria': 'テーマインポート情報',
|
||||||
|
|||||||
@@ -1572,6 +1572,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.actions.reloadThemes': '테마 다시 로드',
|
'settings.openchamber.visual.actions.reloadThemes': '테마 다시 로드',
|
||||||
'settings.openchamber.visual.field.macVibrancy': '창 투명도',
|
'settings.openchamber.visual.field.macVibrancy': '창 투명도',
|
||||||
'settings.openchamber.visual.field.macVibrancyHint': '사이드바 뒤에 macOS 기본 블러(vibrancy)를 사용합니다. 완전히 불투명한 표면을 원하면 끄세요.',
|
'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.saveAndRestart': '저장 후 재시작',
|
||||||
'settings.openchamber.visual.actions.restarting': '재시작 중…',
|
'settings.openchamber.visual.actions.restarting': '재시작 중…',
|
||||||
'settings.openchamber.visual.actions.reloadingThemes': '테마 다시 로드 중...',
|
'settings.openchamber.visual.actions.reloadingThemes': '테마 다시 로드 중...',
|
||||||
|
|||||||
@@ -907,6 +907,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.actions.reloadThemes': 'Przeładuj motywy',
|
'settings.openchamber.visual.actions.reloadThemes': 'Przeładuj motywy',
|
||||||
'settings.openchamber.visual.field.macVibrancy': 'Przezroczystość okna',
|
'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.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.saveAndRestart': 'Zapisz i uruchom ponownie',
|
||||||
'settings.openchamber.visual.actions.restarting': 'Ponowne uruchamianie…',
|
'settings.openchamber.visual.actions.restarting': 'Ponowne uruchamianie…',
|
||||||
'settings.openchamber.visual.actions.reloadingThemes': 'Przeładowywanie motywów...',
|
'settings.openchamber.visual.actions.reloadingThemes': 'Przeładowywanie motywów...',
|
||||||
|
|||||||
@@ -1572,6 +1572,8 @@ export const settingsDict = {
|
|||||||
"settings.openchamber.visual.actions.reloadThemes": "Recarregar temas",
|
"settings.openchamber.visual.actions.reloadThemes": "Recarregar temas",
|
||||||
"settings.openchamber.visual.field.macVibrancy": "Transparência da janela",
|
"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.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.saveAndRestart": "Salvar e reiniciar",
|
||||||
"settings.openchamber.visual.actions.restarting": "Reiniciando…",
|
"settings.openchamber.visual.actions.restarting": "Reiniciando…",
|
||||||
"settings.openchamber.visual.actions.reloadingThemes": "Recarregando temas...",
|
"settings.openchamber.visual.actions.reloadingThemes": "Recarregando temas...",
|
||||||
|
|||||||
@@ -1572,6 +1572,8 @@ export const settingsDict = {
|
|||||||
"settings.openchamber.visual.actions.reloadThemes": "Перезавантажити теми",
|
"settings.openchamber.visual.actions.reloadThemes": "Перезавантажити теми",
|
||||||
"settings.openchamber.visual.field.macVibrancy": "Прозорість вікна",
|
"settings.openchamber.visual.field.macVibrancy": "Прозорість вікна",
|
||||||
"settings.openchamber.visual.field.macVibrancyHint": "Використовувати нативне розмиття macOS (vibrancy) під сайдбаром. Вимкніть для повністю непрозорих поверхонь.",
|
"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.saveAndRestart": "Зберегти та перезапустити",
|
||||||
"settings.openchamber.visual.actions.restarting": "Перезапуск…",
|
"settings.openchamber.visual.actions.restarting": "Перезапуск…",
|
||||||
"settings.openchamber.visual.actions.reloadingThemes": "Перезавантаження тем...",
|
"settings.openchamber.visual.actions.reloadingThemes": "Перезавантаження тем...",
|
||||||
|
|||||||
@@ -1572,6 +1572,8 @@ export const settingsDict = {
|
|||||||
'settings.openchamber.visual.actions.reloadThemes': '重新加载主题',
|
'settings.openchamber.visual.actions.reloadThemes': '重新加载主题',
|
||||||
'settings.openchamber.visual.field.macVibrancy': '窗口透明度',
|
'settings.openchamber.visual.field.macVibrancy': '窗口透明度',
|
||||||
'settings.openchamber.visual.field.macVibrancyHint': '在侧边栏后使用 macOS 原生模糊(vibrancy)。关闭以获得完全不透明的界面。',
|
'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.saveAndRestart': '保存并重启',
|
||||||
'settings.openchamber.visual.actions.restarting': '正在重启…',
|
'settings.openchamber.visual.actions.restarting': '正在重启…',
|
||||||
'settings.openchamber.visual.actions.reloadingThemes': '正在重新加载主题...',
|
'settings.openchamber.visual.actions.reloadingThemes': '正在重新加载主题...',
|
||||||
|
|||||||
@@ -1491,6 +1491,8 @@
|
|||||||
'settings.openchamber.visual.actions.reloadThemes': '重新載入主題',
|
'settings.openchamber.visual.actions.reloadThemes': '重新載入主題',
|
||||||
'settings.openchamber.visual.field.macVibrancy': '視窗透明度',
|
'settings.openchamber.visual.field.macVibrancy': '視窗透明度',
|
||||||
'settings.openchamber.visual.field.macVibrancyHint': '在側邊欄後使用 macOS 原生模糊(vibrancy)。關閉以獲得完全不透明的介面。',
|
'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.saveAndRestart': '儲存並重新啟動',
|
||||||
'settings.openchamber.visual.actions.restarting': '正在重新啟動…',
|
'settings.openchamber.visual.actions.restarting': '正在重新啟動…',
|
||||||
'settings.openchamber.visual.actions.reloadingThemes': '正在重新載入主題...',
|
'settings.openchamber.visual.actions.reloadingThemes': '正在重新載入主題...',
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export interface SettingsSearchResult extends SettingsSearchItem {
|
|||||||
interface SettingsSearchAvailabilityContext extends SettingsRuntimeContext {
|
interface SettingsSearchAvailabilityContext extends SettingsRuntimeContext {
|
||||||
isMobile: boolean;
|
isMobile: boolean;
|
||||||
isDesktopLocalOrigin: 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[] = [
|
const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
||||||
@@ -65,6 +67,16 @@ const SETTINGS_SEARCH_ITEMS: readonly SettingsSearchItem[] = [
|
|||||||
keywords: ['transparent', 'transparency', 'vibrancy', 'blur', 'macos', 'opaque'],
|
keywords: ['transparent', 'transparency', 'vibrancy', 'blur', 'macos', 'opaque'],
|
||||||
isAvailable: (ctx) => ctx.isDesktopLocalOrigin,
|
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',
|
id: 'appearance.pwa-install-name',
|
||||||
page: 'appearance',
|
page: 'appearance',
|
||||||
|
|||||||
@@ -592,6 +592,8 @@ interface UIStore {
|
|||||||
nativeNotificationsEnabled: boolean;
|
nativeNotificationsEnabled: boolean;
|
||||||
notificationMode: 'always' | 'hidden-only';
|
notificationMode: 'always' | 'hidden-only';
|
||||||
notifyOnSubtasks: boolean;
|
notifyOnSubtasks: boolean;
|
||||||
|
// Desktop dock badge showing the count of sessions with unseen activity (macOS).
|
||||||
|
dockBadgeEnabled: boolean;
|
||||||
|
|
||||||
// Event toggles (which events trigger notifications)
|
// Event toggles (which events trigger notifications)
|
||||||
notifyOnCompletion: boolean;
|
notifyOnCompletion: boolean;
|
||||||
@@ -750,6 +752,7 @@ interface UIStore {
|
|||||||
setNotificationMode: (mode: 'always' | 'hidden-only') => void;
|
setNotificationMode: (mode: 'always' | 'hidden-only') => void;
|
||||||
setShowTerminalQuickKeysOnDesktop: (value: boolean) => void;
|
setShowTerminalQuickKeysOnDesktop: (value: boolean) => void;
|
||||||
setNotifyOnSubtasks: (value: boolean) => void;
|
setNotifyOnSubtasks: (value: boolean) => void;
|
||||||
|
setDockBadgeEnabled: (value: boolean) => void;
|
||||||
setNotifyOnCompletion: (value: boolean) => void;
|
setNotifyOnCompletion: (value: boolean) => void;
|
||||||
setNotifyOnError: (value: boolean) => void;
|
setNotifyOnError: (value: boolean) => void;
|
||||||
setNotifyOnQuestion: (value: boolean) => void;
|
setNotifyOnQuestion: (value: boolean) => void;
|
||||||
@@ -877,6 +880,7 @@ export const useUIStore = create<UIStore>()(
|
|||||||
nativeNotificationsEnabled: false,
|
nativeNotificationsEnabled: false,
|
||||||
notificationMode: 'hidden-only',
|
notificationMode: 'hidden-only',
|
||||||
notifyOnSubtasks: true,
|
notifyOnSubtasks: true,
|
||||||
|
dockBadgeEnabled: true,
|
||||||
|
|
||||||
// Event toggles (which events trigger notifications)
|
// Event toggles (which events trigger notifications)
|
||||||
notifyOnCompletion: true,
|
notifyOnCompletion: true,
|
||||||
@@ -1975,6 +1979,10 @@ export const useUIStore = create<UIStore>()(
|
|||||||
set({ notifyOnSubtasks: value });
|
set({ notifyOnSubtasks: value });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setDockBadgeEnabled: (value) => {
|
||||||
|
set({ dockBadgeEnabled: value });
|
||||||
|
},
|
||||||
|
|
||||||
setNotifyOnCompletion: (value) => { set({ notifyOnCompletion: value }); },
|
setNotifyOnCompletion: (value) => { set({ notifyOnCompletion: value }); },
|
||||||
setNotifyOnError: (value) => { set({ notifyOnError: value }); },
|
setNotifyOnError: (value) => { set({ notifyOnError: value }); },
|
||||||
setNotifyOnQuestion: (value) => { set({ notifyOnQuestion: value }); },
|
setNotifyOnQuestion: (value) => { set({ notifyOnQuestion: value }); },
|
||||||
@@ -2243,6 +2251,7 @@ export const useUIStore = create<UIStore>()(
|
|||||||
notificationMode: state.notificationMode,
|
notificationMode: state.notificationMode,
|
||||||
showTerminalQuickKeysOnDesktop: state.showTerminalQuickKeysOnDesktop,
|
showTerminalQuickKeysOnDesktop: state.showTerminalQuickKeysOnDesktop,
|
||||||
notifyOnSubtasks: state.notifyOnSubtasks,
|
notifyOnSubtasks: state.notifyOnSubtasks,
|
||||||
|
dockBadgeEnabled: state.dockBadgeEnabled,
|
||||||
notifyOnCompletion: state.notifyOnCompletion,
|
notifyOnCompletion: state.notifyOnCompletion,
|
||||||
notifyOnError: state.notifyOnError,
|
notifyOnError: state.notifyOnError,
|
||||||
notifyOnQuestion: state.notifyOnQuestion,
|
notifyOnQuestion: state.notifyOnQuestion,
|
||||||
|
|||||||
Reference in New Issue
Block a user