feat: add notification mode settings and enhance notification handling

This commit is contained in:
Bohdan Triapitsyn
2026-01-14 01:03:26 +02:00
parent 0d43725802
commit b84c17e0e2
6 changed files with 66 additions and 22 deletions
@@ -23,7 +23,6 @@ import {
formatEditOutput,
detectLanguageFromOutput,
formatInputForDisplay,
hasLspDiagnostics,
} from '../toolRenderers';
type ToolStateWithMetadata = ToolStateUnion & { metadata?: Record<string, unknown>; input?: Record<string, unknown>; output?: string; error?: string; time?: { start: number; end?: number } };
@@ -756,7 +755,7 @@ const ToolExpandedContent: React.FC<ToolExpandedContentProps> = ({
);
}
if ((part.tool === 'edit' || part.tool === 'multiedit') && ((!hasStringOutput && diffContent) || (outputString.trim().length === 0 || hasLspDiagnostics(outputString))) && diffContent) {
if ((part.tool === 'edit' || part.tool === 'multiedit') && diffContent) {
return renderScrollableBlock(
<DiffPreview diff={diffContent} syntaxTheme={syntaxTheme} input={input} />,
{ className: 'p-1' }
@@ -1,11 +1,14 @@
import React from 'react';
import { useUIStore } from '@/stores/useUIStore';
import { isWebRuntime } from '@/lib/desktop';
import { Switch } from '@/components/ui/switch';
import { toast } from 'sonner';
export const NotificationSettings: React.FC = () => {
const nativeNotificationsEnabled = useUIStore(state => state.nativeNotificationsEnabled);
const setNativeNotificationsEnabled = useUIStore(state => state.setNativeNotificationsEnabled);
const notificationMode = useUIStore(state => state.notificationMode);
const setNotificationMode = useUIStore(state => state.setNotificationMode);
const [notificationPermission, setNotificationPermission] = React.useState<NotificationPermission>('default');
@@ -59,17 +62,31 @@ export const NotificationSettings: React.FC = () => {
<span className="typography-ui text-foreground">
Enable native notifications
</span>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={nativeNotificationsEnabled && canShowNotifications}
onChange={(e) => handleToggleChange(e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-neutral-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-primary/50 dark:peer-focus:ring-primary/50 rounded-full peer dark:bg-neutral-700 peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-neutral-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-neutral-600 peer-checked:bg-primary" />
</label>
<Switch
checked={nativeNotificationsEnabled && canShowNotifications}
onCheckedChange={handleToggleChange}
className="data-[state=checked]:bg-status-info"
/>
</div>
{nativeNotificationsEnabled && canShowNotifications && (
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<span className="typography-ui text-foreground">
Always notify
</span>
<p className="typography-micro text-muted-foreground">
When off, only notifies if the window is out of focus.
</p>
</div>
<Switch
checked={notificationMode === 'always'}
onCheckedChange={(checked) => setNotificationMode(checked ? 'always' : 'hidden-only')}
className="data-[state=checked]:bg-status-info"
/>
</div>
)}
{notificationPermission === 'denied' && (
<p className="typography-micro text-destructive">
Notification permission denied. Enable notifications in your browser settings.
@@ -16,6 +16,7 @@ interface SectionGroup {
id: OpenChamberSection;
label: string;
items: string[];
webOnly?: boolean;
}
const OPENCHAMBER_SECTION_GROUPS: SectionGroup[] = [
@@ -43,6 +44,7 @@ const OPENCHAMBER_SECTION_GROUPS: SectionGroup[] = [
id: 'notifications',
label: 'Notifications',
items: ['Native'],
webOnly: true,
},
];
@@ -59,12 +61,17 @@ export const OpenChamberSidebar: React.FC<OpenChamberSidebarProps> = ({
});
const isVSCode = React.useMemo(() => isVSCodeRuntime(), []);
const isWeb = React.useMemo(() => isWebRuntime(), []);
React.useEffect(() => {
if (typeof window === 'undefined') return;
setIsDesktopRuntime(typeof window.opencodeDesktop !== 'undefined');
}, []);
const visibleSections = React.useMemo(() => {
return OPENCHAMBER_SECTION_GROUPS.filter((group) => !group.webOnly || isWeb);
}, [isWeb]);
// Desktop app: transparent for blur effect
// VS Code: bg-background (same as page content)
// Web/mobile: bg-sidebar
@@ -77,7 +84,7 @@ export const OpenChamberSidebar: React.FC<OpenChamberSidebarProps> = ({
return (
<div className={cn('flex h-full flex-col', bgClass)}>
<ScrollableOverlay outerClassName="flex-1 min-h-0" className="space-y-1 px-3 py-2 overflow-x-hidden">
{OPENCHAMBER_SECTION_GROUPS.map((group) => {
{visibleSections.map((group) => {
const isSelected = selectedSection === group.id;
return (
<div