feat: add OpenCode update notification setting

Adds a setting to show or hide OpenCode update notifications
Dismisses the active update toast when notifications are disabled
Adds localized settings labels
This commit is contained in:
Bohdan Triapitsyn
2026-05-15 21:38:57 +03:00
parent 59bc0c6c06
commit 040b6a9dc6
10 changed files with 52 additions and 2 deletions
@@ -1,11 +1,13 @@
import * as React from 'react'; import * as React from 'react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'; import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { Icon } from "@/components/icon/Icon"; import { Icon } from "@/components/icon/Icon";
import { isDesktopShell, isTauriShell } from '@/lib/desktop'; import { isDesktopShell, isTauriShell } from '@/lib/desktop';
import { updateDesktopSettings } from '@/lib/persistence'; import { updateDesktopSettings } from '@/lib/persistence';
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore'; import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
import { useUIStore } from '@/stores/useUIStore';
import { useI18n } from '@/lib/i18n'; import { useI18n } from '@/lib/i18n';
export const OpenCodeCliSettings: React.FC = () => { export const OpenCodeCliSettings: React.FC = () => {
@@ -13,6 +15,8 @@ export const OpenCodeCliSettings: React.FC = () => {
const [value, setValue] = React.useState(''); const [value, setValue] = React.useState('');
const [isLoading, setIsLoading] = React.useState(true); const [isLoading, setIsLoading] = React.useState(true);
const [isSaving, setIsSaving] = React.useState(false); const [isSaving, setIsSaving] = React.useState(false);
const showOpenCodeUpdateNotifications = useUIStore((state) => state.showOpenCodeUpdateNotifications);
const setShowOpenCodeUpdateNotifications = useUIStore((state) => state.setShowOpenCodeUpdateNotifications);
React.useEffect(() => { React.useEffect(() => {
let cancelled = false; let cancelled = false;
@@ -148,6 +152,17 @@ export const OpenCodeCliSettings: React.FC = () => {
</div> </div>
</div> </div>
<label className="flex cursor-pointer items-center gap-2 py-1.5">
<Checkbox
checked={showOpenCodeUpdateNotifications}
onChange={setShowOpenCodeUpdateNotifications}
ariaLabel={t('settings.openchamber.opencodeCli.field.showUpdateNotificationsAria')}
/>
<span className="typography-ui-label text-foreground">
{t('settings.openchamber.opencodeCli.field.showUpdateNotifications')}
</span>
</label>
<div className="flex justify-start py-1.5"> <div className="flex justify-start py-1.5">
<Button <Button
type="button" type="button"
@@ -2,6 +2,7 @@ import * as React from 'react';
import { Icon } from '@/components/icon/Icon'; import { Icon } from '@/components/icon/Icon';
import { toast } from '@/components/ui/toast'; import { toast } from '@/components/ui/toast';
import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore'; import { reloadOpenCodeConfiguration } from '@/stores/useAgentsStore';
import { useUIStore } from '@/stores/useUIStore';
import { useI18n } from '@/lib/i18n'; import { useI18n } from '@/lib/i18n';
type OpenCodeUpdateAvailableEvent = CustomEvent<{ version?: unknown }>; type OpenCodeUpdateAvailableEvent = CustomEvent<{ version?: unknown }>;
@@ -17,9 +18,16 @@ const CHECK_RETRY_DELAYS_MS = [10_000, 60_000];
export const OpenCodeUpdateToast: React.FC = () => { export const OpenCodeUpdateToast: React.FC = () => {
const { t } = useI18n(); const { t } = useI18n();
const showOpenCodeUpdateNotifications = useUIStore((state) => state.showOpenCodeUpdateNotifications);
const seenVersionsRef = React.useRef(new Set<string>()); const seenVersionsRef = React.useRef(new Set<string>());
const upgradingRef = React.useRef(false); const upgradingRef = React.useRef(false);
React.useEffect(() => {
if (!showOpenCodeUpdateNotifications) {
toast.dismiss(UPDATE_TOAST_ID);
}
}, [showOpenCodeUpdateNotifications]);
const reloadOpenCode = React.useCallback(() => { const reloadOpenCode = React.useCallback(() => {
toast.dismiss(UPGRADE_TOAST_ID); toast.dismiss(UPGRADE_TOAST_ID);
void reloadOpenCodeConfiguration({ void reloadOpenCodeConfiguration({
@@ -79,6 +87,10 @@ export const OpenCodeUpdateToast: React.FC = () => {
React.useEffect(() => { React.useEffect(() => {
const showUpdateAvailableToast = (version: string) => { const showUpdateAvailableToast = (version: string) => {
if (!useUIStore.getState().showOpenCodeUpdateNotifications) {
toast.dismiss(UPDATE_TOAST_ID);
return;
}
if (!version) { if (!version) {
return; return;
} }
@@ -125,7 +137,9 @@ export const OpenCodeUpdateToast: React.FC = () => {
} }
}; };
timeoutIds.push(setTimeout(() => { void checkForUpdate(0); }, INITIAL_CHECK_DELAY_MS)); if (showOpenCodeUpdateNotifications) {
timeoutIds.push(setTimeout(() => { void checkForUpdate(0); }, INITIAL_CHECK_DELAY_MS));
}
window.addEventListener('openchamber:opencode-update-available', onUpdateAvailable); window.addEventListener('openchamber:opencode-update-available', onUpdateAvailable);
return () => { return () => {
@@ -133,7 +147,7 @@ export const OpenCodeUpdateToast: React.FC = () => {
for (const timeoutId of timeoutIds) clearTimeout(timeoutId); for (const timeoutId of timeoutIds) clearTimeout(timeoutId);
window.removeEventListener('openchamber:opencode-update-available', onUpdateAvailable); window.removeEventListener('openchamber:opencode-update-available', onUpdateAvailable);
}; };
}, [runUpgrade, t]); }, [runUpgrade, showOpenCodeUpdateNotifications, t]);
return null; return null;
}; };
@@ -709,6 +709,8 @@ export const settingsDict = {
'settings.openchamber.opencodeCli.tooltipSuffix': 'binary.', 'settings.openchamber.opencodeCli.tooltipSuffix': 'binary.',
'settings.openchamber.opencodeCli.field.binaryPath': 'OpenCode Binary Path', 'settings.openchamber.opencodeCli.field.binaryPath': 'OpenCode Binary Path',
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode', 'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
'settings.openchamber.opencodeCli.field.showUpdateNotifications': 'Show OpenCode update notifications',
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': 'Show OpenCode update notifications',
'settings.openchamber.opencodeCli.actions.browseAria': 'Browse for OpenCode binary path', 'settings.openchamber.opencodeCli.actions.browseAria': 'Browse for OpenCode binary path',
'settings.openchamber.opencodeCli.actions.browse': 'Browse', 'settings.openchamber.opencodeCli.actions.browse': 'Browse',
'settings.openchamber.opencodeCli.actions.saveAndReload': 'Save + Reload', 'settings.openchamber.opencodeCli.actions.saveAndReload': 'Save + Reload',
@@ -709,6 +709,8 @@ export const settingsDict = {
"settings.openchamber.opencodeCli.tooltipSuffix": "ejecutable.", "settings.openchamber.opencodeCli.tooltipSuffix": "ejecutable.",
"settings.openchamber.opencodeCli.field.binaryPath": "Ruta del ejecutable de OpenCode", "settings.openchamber.opencodeCli.field.binaryPath": "Ruta del ejecutable de OpenCode",
"settings.openchamber.opencodeCli.field.binaryPathPlaceholder": "/Users/you/.bun/bin/opencode", "settings.openchamber.opencodeCli.field.binaryPathPlaceholder": "/Users/you/.bun/bin/opencode",
"settings.openchamber.opencodeCli.field.showUpdateNotifications": "Mostrar notificaciones de actualización de OpenCode",
"settings.openchamber.opencodeCli.field.showUpdateNotificationsAria": "Mostrar notificaciones de actualización de OpenCode",
"settings.openchamber.opencodeCli.actions.browseAria": "Buscar ruta del ejecutable de OpenCode", "settings.openchamber.opencodeCli.actions.browseAria": "Buscar ruta del ejecutable de OpenCode",
"settings.openchamber.opencodeCli.actions.browse": "Buscar", "settings.openchamber.opencodeCli.actions.browse": "Buscar",
"settings.openchamber.opencodeCli.actions.saveAndReload": "Guardar + Recargar", "settings.openchamber.opencodeCli.actions.saveAndReload": "Guardar + Recargar",
@@ -709,6 +709,8 @@ export const settingsDict = {
'settings.openchamber.opencodeCli.tooltipSuffix': 'binary.', 'settings.openchamber.opencodeCli.tooltipSuffix': 'binary.',
'settings.openchamber.opencodeCli.field.binaryPath': 'OpenCode binary 경로', 'settings.openchamber.opencodeCli.field.binaryPath': 'OpenCode binary 경로',
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode', 'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
'settings.openchamber.opencodeCli.field.showUpdateNotifications': 'OpenCode 업데이트 알림 표시',
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': 'OpenCode 업데이트 알림 표시',
'settings.openchamber.opencodeCli.actions.browseAria': 'OpenCode binary 경로 찾아보기', 'settings.openchamber.opencodeCli.actions.browseAria': 'OpenCode binary 경로 찾아보기',
'settings.openchamber.opencodeCli.actions.browse': '찾아보기', 'settings.openchamber.opencodeCli.actions.browse': '찾아보기',
'settings.openchamber.opencodeCli.actions.saveAndReload': '저장 + 다시 로드', 'settings.openchamber.opencodeCli.actions.saveAndReload': '저장 + 다시 로드',
@@ -634,6 +634,8 @@ export const settingsDict = {
'settings.openchamber.opencodeCli.dialog.selectBinaryTitle': 'Wybierz plik binarny opencode', 'settings.openchamber.opencodeCli.dialog.selectBinaryTitle': 'Wybierz plik binarny opencode',
'settings.openchamber.opencodeCli.field.binaryPath': 'Ścieżka do pliku binarnego OpenCode', 'settings.openchamber.opencodeCli.field.binaryPath': 'Ścieżka do pliku binarnego OpenCode',
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode', 'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
'settings.openchamber.opencodeCli.field.showUpdateNotifications': 'Pokazuj powiadomienia o aktualizacjach OpenCode',
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': 'Pokazuj powiadomienia o aktualizacjach OpenCode',
'settings.openchamber.opencodeCli.tipMiddle': 'zmienna środowiskowa, ale to ustawienie jest zapisywane w', 'settings.openchamber.opencodeCli.tipMiddle': 'zmienna środowiskowa, ale to ustawienie jest zapisywane w',
'settings.openchamber.opencodeCli.tipPrefix': 'Wskazówka: możesz również użyć', 'settings.openchamber.opencodeCli.tipPrefix': 'Wskazówka: możesz również użyć',
'settings.openchamber.opencodeCli.title': 'OpenCode CLI', 'settings.openchamber.opencodeCli.title': 'OpenCode CLI',
@@ -709,6 +709,8 @@ export const settingsDict = {
"settings.openchamber.opencodeCli.tooltipSuffix": "executável.", "settings.openchamber.opencodeCli.tooltipSuffix": "executável.",
"settings.openchamber.opencodeCli.field.binaryPath": "Caminho do executável do OpenCode", "settings.openchamber.opencodeCli.field.binaryPath": "Caminho do executável do OpenCode",
"settings.openchamber.opencodeCli.field.binaryPathPlaceholder": "/Users/you/.bun/bin/opencode", "settings.openchamber.opencodeCli.field.binaryPathPlaceholder": "/Users/you/.bun/bin/opencode",
"settings.openchamber.opencodeCli.field.showUpdateNotifications": "Mostrar notificações de atualização do OpenCode",
"settings.openchamber.opencodeCli.field.showUpdateNotificationsAria": "Mostrar notificações de atualização do OpenCode",
"settings.openchamber.opencodeCli.actions.browseAria": "Buscar caminho do executável do OpenCode", "settings.openchamber.opencodeCli.actions.browseAria": "Buscar caminho do executável do OpenCode",
"settings.openchamber.opencodeCli.actions.browse": "Buscar", "settings.openchamber.opencodeCli.actions.browse": "Buscar",
"settings.openchamber.opencodeCli.actions.saveAndReload": "Salvar + Recarregar", "settings.openchamber.opencodeCli.actions.saveAndReload": "Salvar + Recarregar",
@@ -709,6 +709,8 @@ export const settingsDict = {
"settings.openchamber.opencodeCli.tooltipSuffix": "бінарного файлу.", "settings.openchamber.opencodeCli.tooltipSuffix": "бінарного файлу.",
"settings.openchamber.opencodeCli.field.binaryPath": "Шлях до бінарного файлу OpenCode", "settings.openchamber.opencodeCli.field.binaryPath": "Шлях до бінарного файлу OpenCode",
"settings.openchamber.opencodeCli.field.binaryPathPlaceholder": "/Users/you/.bun/bin/opencode", "settings.openchamber.opencodeCli.field.binaryPathPlaceholder": "/Users/you/.bun/bin/opencode",
"settings.openchamber.opencodeCli.field.showUpdateNotifications": "Показувати сповіщення про оновлення OpenCode",
"settings.openchamber.opencodeCli.field.showUpdateNotificationsAria": "Показувати сповіщення про оновлення OpenCode",
"settings.openchamber.opencodeCli.actions.browseAria": "Вибрати шлях до виконуваного файла OpenCode", "settings.openchamber.opencodeCli.actions.browseAria": "Вибрати шлях до виконуваного файла OpenCode",
"settings.openchamber.opencodeCli.actions.browse": "Огляд", "settings.openchamber.opencodeCli.actions.browse": "Огляд",
"settings.openchamber.opencodeCli.actions.saveAndReload": "Зберегти й перезавантажити", "settings.openchamber.opencodeCli.actions.saveAndReload": "Зберегти й перезавантажити",
@@ -709,6 +709,8 @@ export const settingsDict = {
'settings.openchamber.opencodeCli.tooltipSuffix': '二进制绝对路径。', 'settings.openchamber.opencodeCli.tooltipSuffix': '二进制绝对路径。',
'settings.openchamber.opencodeCli.field.binaryPath': 'OpenCode 可执行文件路径', 'settings.openchamber.opencodeCli.field.binaryPath': 'OpenCode 可执行文件路径',
'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode', 'settings.openchamber.opencodeCli.field.binaryPathPlaceholder': '/Users/you/.bun/bin/opencode',
'settings.openchamber.opencodeCli.field.showUpdateNotifications': '显示 OpenCode 更新通知',
'settings.openchamber.opencodeCli.field.showUpdateNotificationsAria': '显示 OpenCode 更新通知',
'settings.openchamber.opencodeCli.actions.browseAria': '浏览 OpenCode 可执行文件路径', 'settings.openchamber.opencodeCli.actions.browseAria': '浏览 OpenCode 可执行文件路径',
'settings.openchamber.opencodeCli.actions.browse': '浏览', 'settings.openchamber.opencodeCli.actions.browse': '浏览',
'settings.openchamber.opencodeCli.actions.saveAndReload': '保存并重载', 'settings.openchamber.opencodeCli.actions.saveAndReload': '保存并重载',
+7
View File
@@ -575,6 +575,7 @@ interface UIStore {
showTerminalQuickKeysOnDesktop: boolean; showTerminalQuickKeysOnDesktop: boolean;
persistChatDraft: boolean; persistChatDraft: boolean;
showOpenCodeUpdateNotifications: boolean;
inputSpellcheckEnabled: boolean; inputSpellcheckEnabled: boolean;
wideChatLayoutEnabled: boolean; wideChatLayoutEnabled: boolean;
showToolFileIcons: boolean; showToolFileIcons: boolean;
@@ -704,6 +705,7 @@ interface UIStore {
setSummaryLength: (value: number) => void; setSummaryLength: (value: number) => void;
setMaxLastMessageLength: (value: number) => void; setMaxLastMessageLength: (value: number) => void;
setPersistChatDraft: (value: boolean) => void; setPersistChatDraft: (value: boolean) => void;
setShowOpenCodeUpdateNotifications: (value: boolean) => void;
setInputSpellcheckEnabled: (value: boolean) => void; setInputSpellcheckEnabled: (value: boolean) => void;
setWideChatLayoutEnabled: (value: boolean) => void; setWideChatLayoutEnabled: (value: boolean) => void;
setShowToolFileIcons: (value: boolean) => void; setShowToolFileIcons: (value: boolean) => void;
@@ -828,6 +830,7 @@ export const useUIStore = create<UIStore>()(
showTerminalQuickKeysOnDesktop: false, showTerminalQuickKeysOnDesktop: false,
persistChatDraft: true, persistChatDraft: true,
showOpenCodeUpdateNotifications: true,
inputSpellcheckEnabled: false, inputSpellcheckEnabled: false,
wideChatLayoutEnabled: false, wideChatLayoutEnabled: false,
showToolFileIcons: true, showToolFileIcons: true,
@@ -1847,6 +1850,9 @@ export const useUIStore = create<UIStore>()(
setPersistChatDraft: (value) => { setPersistChatDraft: (value) => {
set({ persistChatDraft: value }); set({ persistChatDraft: value });
}, },
setShowOpenCodeUpdateNotifications: (value) => {
set({ showOpenCodeUpdateNotifications: value });
},
setInputSpellcheckEnabled: (value) => { setInputSpellcheckEnabled: (value) => {
set({ inputSpellcheckEnabled: value }); set({ inputSpellcheckEnabled: value });
}, },
@@ -2073,6 +2079,7 @@ export const useUIStore = create<UIStore>()(
summaryLength: state.summaryLength, summaryLength: state.summaryLength,
maxLastMessageLength: state.maxLastMessageLength, maxLastMessageLength: state.maxLastMessageLength,
persistChatDraft: state.persistChatDraft, persistChatDraft: state.persistChatDraft,
showOpenCodeUpdateNotifications: state.showOpenCodeUpdateNotifications,
inputSpellcheckEnabled: state.inputSpellcheckEnabled, inputSpellcheckEnabled: state.inputSpellcheckEnabled,
wideChatLayoutEnabled: state.wideChatLayoutEnabled, wideChatLayoutEnabled: state.wideChatLayoutEnabled,
showToolFileIcons: state.showToolFileIcons, showToolFileIcons: state.showToolFileIcons,