diff --git a/packages/desktop/src/renderer/App.tsx b/packages/desktop/src/renderer/App.tsx index 68b811f83..477aabee6 100644 --- a/packages/desktop/src/renderer/App.tsx +++ b/packages/desktop/src/renderer/App.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { Toaster } from 'sonner'; +import { Toaster, TOAST_DURATION_MS, TOAST_OPTIONS } from '@/lib/toast'; import LeftNav from './layouts/LeftNav'; import MainArea from './layouts/MainArea'; import RightPanel from './layouts/RightPanel'; @@ -8,7 +8,7 @@ import Setup from './layouts/Setup'; import Settings from './layouts/Settings'; import ApprovalDialog from './components/ApprovalDialog'; import CommandPalette from './components/CommandPalette'; -import { useUiStore } from './stores/uiStore'; +import { useUiStore, type Theme } from './stores/uiStore'; import { useTaskStore } from './stores/taskStore'; import { useFileStore } from './stores/fileStore'; import { composer } from './platform'; @@ -20,6 +20,19 @@ import { TooltipProvider } from '@/components/ui/tooltip'; import { motionDuration, motionEase } from '@/styles/design-tokens'; import { useSettingsStore } from './stores/settingsStore'; +function AppToaster({ themeMode }: { themeMode: Theme }) { + return ( + + ); +} + export default function App() { const [ready, setReady] = useState(false); const [needsSetup, setNeedsSetup] = useState(false); @@ -204,17 +217,7 @@ export default function App() { setReady(true); }} /> - + ); } @@ -268,17 +271,7 @@ export default function App() { )} - + diff --git a/packages/desktop/src/renderer/components/AgentBuilderDialog/index.tsx b/packages/desktop/src/renderer/components/AgentBuilderDialog/index.tsx index 7d84d92e9..f93678de1 100644 --- a/packages/desktop/src/renderer/components/AgentBuilderDialog/index.tsx +++ b/packages/desktop/src/renderer/components/AgentBuilderDialog/index.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { Sparkles, Send, Loader2, Bot, User, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; diff --git a/packages/desktop/src/renderer/components/ChatInput/index.tsx b/packages/desktop/src/renderer/components/ChatInput/index.tsx index 690fd2d3c..c35e7d56d 100644 --- a/packages/desktop/src/renderer/components/ChatInput/index.tsx +++ b/packages/desktop/src/renderer/components/ChatInput/index.tsx @@ -16,7 +16,7 @@ import { } from 'lucide-react'; import { type KeyboardEvent, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import AgentIcon from '@/components/AgentIcon'; import ToolbarButton from '@/components/semantic/ToolbarButton'; import { Button } from '@/components/ui/button'; diff --git a/packages/desktop/src/renderer/components/ChatInput/useChatSend.ts b/packages/desktop/src/renderer/components/ChatInput/useChatSend.ts index 8a0d29d10..07004b96c 100644 --- a/packages/desktop/src/renderer/components/ChatInput/useChatSend.ts +++ b/packages/desktop/src/renderer/components/ChatInput/useChatSend.ts @@ -9,7 +9,7 @@ import { type SetStateAction, } from 'react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import type { Task, AgentInfo, diff --git a/packages/desktop/src/renderer/components/ChatInput/useContextFolders.ts b/packages/desktop/src/renderer/components/ChatInput/useContextFolders.ts index 3bd14e94a..7671d1f03 100644 --- a/packages/desktop/src/renderer/components/ChatInput/useContextFolders.ts +++ b/packages/desktop/src/renderer/components/ChatInput/useContextFolders.ts @@ -1,6 +1,6 @@ import { useState, useCallback, useEffect, useRef } from 'react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import type { FileIndexEntry } from '@clawwork/shared'; import { useTaskStore } from '../../stores/taskStore'; diff --git a/packages/desktop/src/renderer/components/ChatInput/utils.ts b/packages/desktop/src/renderer/components/ChatInput/utils.ts index 7e99e5529..084d2c82d 100644 --- a/packages/desktop/src/renderer/components/ChatInput/utils.ts +++ b/packages/desktop/src/renderer/components/ChatInput/utils.ts @@ -1,4 +1,4 @@ -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import type { PendingAttachment } from './types'; import { MAX_ATTACHMENT_SIZE, GATEWAY_INJECTED_MODEL } from './constants'; diff --git a/packages/desktop/src/renderer/components/ChatMessage.tsx b/packages/desktop/src/renderer/components/ChatMessage.tsx index 8fd76d3e1..e115b56c3 100644 --- a/packages/desktop/src/renderer/components/ChatMessage.tsx +++ b/packages/desktop/src/renderer/components/ChatMessage.tsx @@ -4,7 +4,7 @@ import { motion } from 'framer-motion'; import type { Message } from '@clawwork/shared'; import { Check, Copy, File, FileCode, Loader2, Save } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { cn } from '@/lib/utils'; import { motion as motionPresets } from '@/styles/design-tokens'; import { copyTextToClipboard } from '@/lib/clipboard'; diff --git a/packages/desktop/src/renderer/components/TeamBuilderDialog/index.tsx b/packages/desktop/src/renderer/components/TeamBuilderDialog/index.tsx index 4b76edd00..4fc8c3e9a 100644 --- a/packages/desktop/src/renderer/components/TeamBuilderDialog/index.tsx +++ b/packages/desktop/src/renderer/components/TeamBuilderDialog/index.tsx @@ -14,7 +14,7 @@ import { ChevronRight, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import type { ModelCatalogEntry, InstallEvent } from '@clawwork/shared'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; diff --git a/packages/desktop/src/renderer/hooks/useGatewayDispatcherSetup.ts b/packages/desktop/src/renderer/hooks/useGatewayDispatcherSetup.ts index 41ab6df14..7738b40e0 100644 --- a/packages/desktop/src/renderer/hooks/useGatewayDispatcherSetup.ts +++ b/packages/desktop/src/renderer/hooks/useGatewayDispatcherSetup.ts @@ -1,7 +1,7 @@ import { createGatewayDispatcher } from '@clawwork/core'; import type { ExecApprovalRequest, ModelCatalogEntry, AgentInfo } from '@clawwork/shared'; import { parseAgentIdFromSessionKey, parseTaskIdFromSessionKey } from '@clawwork/shared'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { hydrateFromLocal, retrySyncPending, syncFromGateway, syncSessionMessages } from '../lib/session-sync'; import i18n from '../i18n'; import { composer, composerBridge, ports, useMessageStore, useTaskStore, useUiStore, useRoomStore } from '../platform'; diff --git a/packages/desktop/src/renderer/layouts/CronPanel/index.tsx b/packages/desktop/src/renderer/layouts/CronPanel/index.tsx index 518f85f09..41c2dfe93 100644 --- a/packages/desktop/src/renderer/layouts/CronPanel/index.tsx +++ b/packages/desktop/src/renderer/layouts/CronPanel/index.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { Clock, Plus, Search, RefreshCw, ChevronDown, Server, Loader2 } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { useUiStore } from '@/stores/uiStore'; import { cn } from '@/lib/utils'; import { motion as motionPresets, STAGGER_STEP } from '@/styles/design-tokens'; diff --git a/packages/desktop/src/renderer/layouts/Settings/components/PairMobileDialog.tsx b/packages/desktop/src/renderer/layouts/Settings/components/PairMobileDialog.tsx index c43a1639b..bb85dfff8 100644 --- a/packages/desktop/src/renderer/layouts/Settings/components/PairMobileDialog.tsx +++ b/packages/desktop/src/renderer/layouts/Settings/components/PairMobileDialog.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { QRCodeSVG } from 'qrcode.react'; import { Smartphone, Loader2 } from 'lucide-react'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { useTranslation } from 'react-i18next'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; diff --git a/packages/desktop/src/renderer/layouts/Settings/sections/AboutSection.tsx b/packages/desktop/src/renderer/layouts/Settings/sections/AboutSection.tsx index a5304ea71..4ecc9dd81 100644 --- a/packages/desktop/src/renderer/layouts/Settings/sections/AboutSection.tsx +++ b/packages/desktop/src/renderer/layouts/Settings/sections/AboutSection.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { Star, Bug, RefreshCw, Loader2, Download, RotateCcw, X } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import SettingRow from '@/components/semantic/SettingRow'; diff --git a/packages/desktop/src/renderer/layouts/Settings/sections/AgentsSection.tsx b/packages/desktop/src/renderer/layouts/Settings/sections/AgentsSection.tsx index 817c49e83..03850d1cb 100644 --- a/packages/desktop/src/renderer/layouts/Settings/sections/AgentsSection.tsx +++ b/packages/desktop/src/renderer/layouts/Settings/sections/AgentsSection.tsx @@ -17,7 +17,7 @@ import { Sparkles, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { cn } from '@/lib/utils'; import { motion as motionPresets } from '@/styles/design-tokens'; import { Button } from '@/components/ui/button'; diff --git a/packages/desktop/src/renderer/layouts/Settings/sections/GatewaysSection.tsx b/packages/desktop/src/renderer/layouts/Settings/sections/GatewaysSection.tsx index 9bdb8d99b..0b4c1392d 100644 --- a/packages/desktop/src/renderer/layouts/Settings/sections/GatewaysSection.tsx +++ b/packages/desktop/src/renderer/layouts/Settings/sections/GatewaysSection.tsx @@ -14,7 +14,7 @@ import { X, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { cn } from '@/lib/utils'; import { motion as motionPresets } from '@/styles/design-tokens'; import { Button } from '@/components/ui/button'; diff --git a/packages/desktop/src/renderer/layouts/Settings/sections/GeneralSection.tsx b/packages/desktop/src/renderer/layouts/Settings/sections/GeneralSection.tsx index 720a6b53d..90637a4de 100644 --- a/packages/desktop/src/renderer/layouts/Settings/sections/GeneralSection.tsx +++ b/packages/desktop/src/renderer/layouts/Settings/sections/GeneralSection.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import { Moon, Sun, Monitor, Bell, Smartphone } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { modKey } from '@/lib/utils'; import { useUiStore, diff --git a/packages/desktop/src/renderer/layouts/Settings/sections/SkillsSection.tsx b/packages/desktop/src/renderer/layouts/Settings/sections/SkillsSection.tsx index 93a13d0f4..e950f7519 100644 --- a/packages/desktop/src/renderer/layouts/Settings/sections/SkillsSection.tsx +++ b/packages/desktop/src/renderer/layouts/Settings/sections/SkillsSection.tsx @@ -16,7 +16,7 @@ import { Settings2, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { cn } from '@/lib/utils'; import { motion as motionPresets, motionDuration } from '@/styles/design-tokens'; import { useUiStore } from '@/stores/uiStore'; diff --git a/packages/desktop/src/renderer/layouts/Settings/sections/SystemSection.tsx b/packages/desktop/src/renderer/layouts/Settings/sections/SystemSection.tsx index 05a4416cc..812a4f925 100644 --- a/packages/desktop/src/renderer/layouts/Settings/sections/SystemSection.tsx +++ b/packages/desktop/src/renderer/layouts/Settings/sections/SystemSection.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback } from 'react'; import { MonitorDot, Zap, FolderOpen, Loader2, ExternalLink } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; import Toggle from '../components/Toggle'; diff --git a/packages/desktop/src/renderer/layouts/TeamsPanel/TeamDetailView.tsx b/packages/desktop/src/renderer/layouts/TeamsPanel/TeamDetailView.tsx index 1f752d95b..f78c03bb5 100644 --- a/packages/desktop/src/renderer/layouts/TeamsPanel/TeamDetailView.tsx +++ b/packages/desktop/src/renderer/layouts/TeamsPanel/TeamDetailView.tsx @@ -1,7 +1,7 @@ import { useState, useCallback, useEffect, useMemo, useRef } from 'react'; import { ArrowLeft, MessageSquare, Pencil, Check, X, Loader2, Puzzle, Package } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import type { Team } from '@clawwork/shared'; import { Button } from '@/components/ui/button'; import { ScrollArea } from '@/components/ui/scroll-area'; diff --git a/packages/desktop/src/renderer/layouts/TeamsPanel/TeamHubDetailView.tsx b/packages/desktop/src/renderer/layouts/TeamsPanel/TeamHubDetailView.tsx index 4eaba9b34..f791f9b17 100644 --- a/packages/desktop/src/renderer/layouts/TeamsPanel/TeamHubDetailView.tsx +++ b/packages/desktop/src/renderer/layouts/TeamsPanel/TeamHubDetailView.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; import { ArrowLeft, Download, Check, Loader2, Package, User, Tag } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import type { TeamHubEntry, ParsedTeam, AgentFileSet } from '@clawwork/shared'; import { extractSkillSlugs } from '@clawwork/core'; import { Button } from '@/components/ui/button'; diff --git a/packages/desktop/src/renderer/layouts/TeamsPanel/TeamsHubTab.tsx b/packages/desktop/src/renderer/layouts/TeamsPanel/TeamsHubTab.tsx index 944549cd3..3cf04965a 100644 --- a/packages/desktop/src/renderer/layouts/TeamsPanel/TeamsHubTab.tsx +++ b/packages/desktop/src/renderer/layouts/TeamsPanel/TeamsHubTab.tsx @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; import { Search, Settings, RefreshCw, Loader2, Store } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import type { TeamHubRegistry, TeamHubEntry } from '@clawwork/shared'; import { cn } from '@/lib/utils'; import { Button } from '@/components/ui/button'; diff --git a/packages/desktop/src/renderer/layouts/TeamsPanel/index.tsx b/packages/desktop/src/renderer/layouts/TeamsPanel/index.tsx index 2a844c868..1aadf88cd 100644 --- a/packages/desktop/src/renderer/layouts/TeamsPanel/index.tsx +++ b/packages/desktop/src/renderer/layouts/TeamsPanel/index.tsx @@ -1,7 +1,7 @@ import { useState, useCallback, useEffect, useMemo } from 'react'; import { Users, Plus, Sparkles } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import type { Team, TeamHubEntry } from '@clawwork/shared'; import { cn } from '@/lib/utils'; import WindowTitlebar from '@/components/semantic/WindowTitlebar'; diff --git a/packages/desktop/src/renderer/lib/export-session.ts b/packages/desktop/src/renderer/lib/export-session.ts index 03eff81c8..f1e6e3cfc 100644 --- a/packages/desktop/src/renderer/lib/export-session.ts +++ b/packages/desktop/src/renderer/lib/export-session.ts @@ -1,4 +1,4 @@ -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import i18n from '../i18n'; export function exportToFiles(taskId: string): void { diff --git a/packages/desktop/src/renderer/lib/toast.ts b/packages/desktop/src/renderer/lib/toast.ts new file mode 100644 index 000000000..21bccf23f --- /dev/null +++ b/packages/desktop/src/renderer/lib/toast.ts @@ -0,0 +1,38 @@ +import { toast as sonnerToast } from 'sonner'; +import type { ExternalToast, ToasterProps } from 'sonner'; + +export { Toaster } from 'sonner'; + +export const TOAST_DURATION_MS = 4000; +const WARNING_TOAST_DURATION_MS = 8000; +const ERROR_TOAST_DURATION_MS = 10000; +const TOAST_CLOSE_BUTTON_CLASS = + '!left-auto !right-3 !top-1/2 !h-7 !w-7 !-translate-y-1/2 !translate-x-0 !border-0 !bg-transparent !text-[var(--text-muted)] hover:!bg-[var(--bg-hover)] hover:!text-[var(--text-primary)]'; + +export const TOAST_OPTIONS: NonNullable = { + closeButton: true, + closeButtonAriaLabel: 'Close notification', + classNames: { + toast: 'pr-11', + closeButton: TOAST_CLOSE_BUTTON_CLASS, + }, + style: { + background: 'var(--bg-elevated)', + border: '1px solid var(--border)', + color: 'var(--text-primary)', + }, +}; + +type ToastMessage = Parameters[0]; +type RendererToast = typeof sonnerToast; + +export const toast: RendererToast = Object.assign( + (message: ToastMessage, data?: ExternalToast) => sonnerToast(message, data), + sonnerToast, + { + warning: (message: ToastMessage, data?: ExternalToast) => + sonnerToast.warning(message, { duration: WARNING_TOAST_DURATION_MS, ...data }), + error: (message: ToastMessage, data?: ExternalToast) => + sonnerToast.error(message, { duration: ERROR_TOAST_DURATION_MS, ...data }), + }, +); diff --git a/packages/desktop/src/renderer/platform/index.ts b/packages/desktop/src/renderer/platform/index.ts index cc7c7305f..14ee52449 100644 --- a/packages/desktop/src/renderer/platform/index.ts +++ b/packages/desktop/src/renderer/platform/index.ts @@ -20,7 +20,7 @@ import type { SystemSessionState, SystemSessionService, } from '@clawwork/core'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import { createElectronPorts } from './electron-adapter'; import i18n from '../i18n'; import { syncSettingsUpdate } from '../stores/settingsStore'; diff --git a/packages/desktop/src/renderer/stores/approvalStore.ts b/packages/desktop/src/renderer/stores/approvalStore.ts index 2cdc60632..f3303b4ea 100644 --- a/packages/desktop/src/renderer/stores/approvalStore.ts +++ b/packages/desktop/src/renderer/stores/approvalStore.ts @@ -1,7 +1,7 @@ import { create } from 'zustand'; import { parseTaskIdFromSessionKey } from '@clawwork/shared'; import type { ApprovalDecision, ExecApprovalRequest } from '@clawwork/shared'; -import { toast } from 'sonner'; +import { toast } from '@/lib/toast'; import i18n from '../i18n'; import { useTaskStore } from './taskStore'; import { useUiStore } from './uiStore'; diff --git a/packages/desktop/test/conductor-catalog-fallback.test.tsx b/packages/desktop/test/conductor-catalog-fallback.test.tsx index 0928e1836..9cf399328 100644 --- a/packages/desktop/test/conductor-catalog-fallback.test.tsx +++ b/packages/desktop/test/conductor-catalog-fallback.test.tsx @@ -79,7 +79,7 @@ vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }), })); -vi.mock('sonner', () => ({ +vi.mock('../src/renderer/lib/toast', () => ({ toast: { error: vi.fn(), }, @@ -112,7 +112,7 @@ vi.mock('../src/renderer/platform', () => ({ useTeamStore: makeStoreMock(mocks.teamState), })); -import { toast } from 'sonner'; +import { toast } from '../src/renderer/lib/toast'; import { useChatSend } from '../src/renderer/components/ChatInput/useChatSend'; /* ---------- helpers ---------- */