import { ArchiveIcon, ArchiveX, ChevronDownIcon, InfoIcon, LoaderIcon, PlusIcon, RefreshCwIcon, Undo2Icon, XIcon, } from "lucide-react"; import { useQueryClient } from "@tanstack/react-query"; import { type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { PROVIDER_DISPLAY_NAMES, type ProviderKind, type ServerProvider, type ServerProviderModel, ThreadId, } from "@t3tools/contracts"; import { DEFAULT_UNIFIED_SETTINGS } from "@t3tools/contracts/settings"; import { normalizeModelSlug } from "@t3tools/shared/model"; import { Equal } from "effect"; import { APP_VERSION } from "../../branding"; import { canCheckForUpdate, getDesktopUpdateButtonTooltip, getDesktopUpdateInstallConfirmationMessage, isDesktopUpdateButtonDisabled, resolveDesktopUpdateButtonAction, } from "../../components/desktopUpdate.logic"; import { ProviderModelPicker } from "../chat/ProviderModelPicker"; import { TraitsPicker } from "../chat/TraitsPicker"; import { resolveAndPersistPreferredEditor } from "../../editorPreferences"; import { isElectron } from "../../env"; import { useTheme } from "../../hooks/useTheme"; import { useSettings, useUpdateSettings } from "../../hooks/useSettings"; import { useThreadActions } from "../../hooks/useThreadActions"; import { setDesktopUpdateStateQueryData, useDesktopUpdateState, } from "../../lib/desktopUpdateReactQuery"; import { MAX_CUSTOM_MODEL_LENGTH, getCustomModelOptionsByProvider, resolveAppModelSelectionState, } from "../../modelSelection"; import { ensureNativeApi, readNativeApi } from "../../nativeApi"; import { useStore } from "../../store"; import { formatRelativeTime, formatRelativeTimeLabel } from "../../timestampFormat"; import { cn } from "../../lib/utils"; import { Button } from "../ui/button"; import { Collapsible, CollapsibleContent } from "../ui/collapsible"; import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty"; import { Input } from "../ui/input"; import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select"; import { Switch } from "../ui/switch"; import { toastManager } from "../ui/toast"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { ProjectFavicon } from "../ProjectFavicon"; import { useServerAvailableEditors, useServerKeybindingsConfigPath, useServerProviders, } from "../../rpc/serverState"; const THEME_OPTIONS = [ { value: "system", label: "System", }, { value: "light", label: "Light", }, { value: "dark", label: "Dark", }, ] as const; const TIMESTAMP_FORMAT_LABELS = { locale: "System default", "12-hour": "12-hour", "24-hour": "24-hour", } as const; type InstallProviderSettings = { provider: ProviderKind; title: string; binaryPlaceholder: string; binaryDescription: ReactNode; homePathKey?: "codexHomePath"; homePlaceholder?: string; homeDescription?: ReactNode; }; const PROVIDER_SETTINGS: readonly InstallProviderSettings[] = [ { provider: "codex", title: "Codex", binaryPlaceholder: "Codex binary path", binaryDescription: "Path to the Codex binary", homePathKey: "codexHomePath", homePlaceholder: "CODEX_HOME", homeDescription: "Optional custom Codex home and config directory.", }, { provider: "claudeAgent", title: "Claude", binaryPlaceholder: "Claude binary path", binaryDescription: "Path to the Claude binary", }, ] as const; const PROVIDER_STATUS_STYLES = { disabled: { dot: "bg-amber-400", }, error: { dot: "bg-destructive", }, ready: { dot: "bg-success", }, warning: { dot: "bg-warning", }, } as const; function getProviderSummary(provider: ServerProvider | undefined) { if (!provider) { return { headline: "Checking provider status", detail: "Waiting for the server to report installation and authentication details.", }; } if (!provider.enabled) { return { headline: "Disabled", detail: provider.message ?? "This provider is installed but disabled for new sessions in T3 Code.", }; } if (!provider.installed) { return { headline: "Not found", detail: provider.message ?? "CLI not detected on PATH.", }; } if (provider.auth.status === "authenticated") { const authLabel = provider.auth.label ?? provider.auth.type; return { headline: authLabel ? `Authenticated · ${authLabel}` : "Authenticated", detail: provider.message ?? null, }; } if (provider.auth.status === "unauthenticated") { return { headline: "Not authenticated", detail: provider.message ?? null, }; } if (provider.status === "warning") { return { headline: "Needs attention", detail: provider.message ?? "The provider is installed, but the server could not fully verify it.", }; } if (provider.status === "error") { return { headline: "Unavailable", detail: provider.message ?? "The provider failed its startup checks.", }; } return { headline: "Available", detail: provider.message ?? "Installed and ready, but authentication could not be verified.", }; } function getProviderVersionLabel(version: string | null | undefined) { if (!version) return null; return version.startsWith("v") ? version : `v${version}`; } function useRelativeTimeTick(intervalMs = 1_000) { const [tick, setTick] = useState(() => Date.now()); useEffect(() => { const id = setInterval(() => setTick(Date.now()), intervalMs); return () => clearInterval(id); }, [intervalMs]); return tick; } function ProviderLastChecked({ lastCheckedAt }: { lastCheckedAt: string | null }) { useRelativeTimeTick(); const lastCheckedRelative = lastCheckedAt ? formatRelativeTime(lastCheckedAt) : null; if (!lastCheckedRelative) { return null; } return ( {lastCheckedRelative.suffix ? ( <> Checked {lastCheckedRelative.value}{" "} {lastCheckedRelative.suffix} ) : ( <>Checked {lastCheckedRelative.value} )} ); } function SettingsSection({ title, icon, headerAction, children, }: { title: string; icon?: ReactNode; headerAction?: ReactNode; children: ReactNode; }) { return (

{icon} {title}

{headerAction}
{children}
); } function SettingsRow({ title, description, status, resetAction, control, children, }: { title: ReactNode; description: string; status?: ReactNode; resetAction?: ReactNode; control?: ReactNode; children?: ReactNode; }) { return (

{title}

{resetAction}

{description}

{status ?
{status}
: null}
{control ? (
{control}
) : null}
{children}
); } function SettingResetButton({ label, onClick }: { label: string; onClick: () => void }) { return ( { event.stopPropagation(); onClick(); }} > } /> Reset to default ); } function SettingsPageContainer({ children }: { children: ReactNode }) { return (
{children}
); } function AboutVersionTitle() { return ( Version {APP_VERSION} ); } function AboutVersionSection() { const queryClient = useQueryClient(); const updateStateQuery = useDesktopUpdateState(); const updateState = updateStateQuery.data ?? null; const handleButtonClick = useCallback(() => { const bridge = window.desktopBridge; if (!bridge) return; const action = updateState ? resolveDesktopUpdateButtonAction(updateState) : "none"; if (action === "download") { void bridge .downloadUpdate() .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); }) .catch((error: unknown) => { toastManager.add({ type: "error", title: "Could not download update", description: error instanceof Error ? error.message : "Download failed.", }); }); return; } if (action === "install") { const confirmed = window.confirm( getDesktopUpdateInstallConfirmationMessage( updateState ?? { availableVersion: null, downloadedVersion: null }, ), ); if (!confirmed) return; void bridge .installUpdate() .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); }) .catch((error: unknown) => { toastManager.add({ type: "error", title: "Could not install update", description: error instanceof Error ? error.message : "Install failed.", }); }); return; } if (typeof bridge.checkForUpdate !== "function") return; void bridge .checkForUpdate() .then((result) => { setDesktopUpdateStateQueryData(queryClient, result.state); if (!result.checked) { toastManager.add({ type: "error", title: "Could not check for updates", description: result.state.message ?? "Automatic updates are not available in this build.", }); } }) .catch((error: unknown) => { toastManager.add({ type: "error", title: "Could not check for updates", description: error instanceof Error ? error.message : "Update check failed.", }); }); }, [queryClient, updateState]); const action = updateState ? resolveDesktopUpdateButtonAction(updateState) : "none"; const buttonTooltip = updateState ? getDesktopUpdateButtonTooltip(updateState) : null; const buttonDisabled = action === "none" ? !canCheckForUpdate(updateState) : isDesktopUpdateButtonDisabled(updateState); const actionLabel: Record = { download: "Download", install: "Install" }; const statusLabel: Record = { checking: "Checking…", downloading: "Downloading…", "up-to-date": "Up to Date", }; const buttonLabel = actionLabel[action] ?? statusLabel[updateState?.status ?? ""] ?? "Check for Updates"; const description = action === "download" || action === "install" ? "Update available." : "Current version of the application."; return ( } description={description} control={ {buttonLabel} } /> {buttonTooltip ? {buttonTooltip} : null} } /> ); } export function useSettingsRestore(onRestored?: () => void) { const { theme, setTheme } = useTheme(); const settings = useSettings(); const { resetSettings } = useUpdateSettings(); const isGitWritingModelDirty = !Equal.equals( settings.textGenerationModelSelection ?? null, DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection ?? null, ); const areProviderSettingsDirty = PROVIDER_SETTINGS.some((providerSettings) => { const currentSettings = settings.providers[providerSettings.provider]; const defaultSettings = DEFAULT_UNIFIED_SETTINGS.providers[providerSettings.provider]; return !Equal.equals(currentSettings, defaultSettings); }); const changedSettingLabels = useMemo( () => [ ...(theme !== "system" ? ["Theme"] : []), ...(settings.timestampFormat !== DEFAULT_UNIFIED_SETTINGS.timestampFormat ? ["Time format"] : []), ...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap ? ["Diff line wrapping"] : []), ...(settings.enableAssistantStreaming !== DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming ? ["Assistant output"] : []), ...(settings.defaultThreadEnvMode !== DEFAULT_UNIFIED_SETTINGS.defaultThreadEnvMode ? ["New thread mode"] : []), ...(settings.confirmThreadArchive !== DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive ? ["Archive confirmation"] : []), ...(settings.confirmThreadDelete !== DEFAULT_UNIFIED_SETTINGS.confirmThreadDelete ? ["Delete confirmation"] : []), ...(isGitWritingModelDirty ? ["Git writing model"] : []), ...(areProviderSettingsDirty ? ["Providers"] : []), ], [ areProviderSettingsDirty, isGitWritingModelDirty, settings.confirmThreadArchive, settings.confirmThreadDelete, settings.defaultThreadEnvMode, settings.diffWordWrap, settings.enableAssistantStreaming, settings.timestampFormat, theme, ], ); const restoreDefaults = useCallback(async () => { if (changedSettingLabels.length === 0) return; const api = readNativeApi(); const confirmed = await (api ?? ensureNativeApi()).dialogs.confirm( ["Restore default settings?", `This will reset: ${changedSettingLabels.join(", ")}.`].join( "\n", ), ); if (!confirmed) return; setTheme("system"); resetSettings(); onRestored?.(); }, [changedSettingLabels, onRestored, resetSettings, setTheme]); return { changedSettingLabels, restoreDefaults, }; } export function GeneralSettingsPanel() { const { theme, setTheme } = useTheme(); const settings = useSettings(); const { updateSettings } = useUpdateSettings(); const [isOpeningKeybindings, setIsOpeningKeybindings] = useState(false); const [openKeybindingsError, setOpenKeybindingsError] = useState(null); const [openProviderDetails, setOpenProviderDetails] = useState>({ codex: Boolean( settings.providers.codex.binaryPath !== DEFAULT_UNIFIED_SETTINGS.providers.codex.binaryPath || settings.providers.codex.homePath !== DEFAULT_UNIFIED_SETTINGS.providers.codex.homePath || settings.providers.codex.customModels.length > 0, ), claudeAgent: Boolean( settings.providers.claudeAgent.binaryPath !== DEFAULT_UNIFIED_SETTINGS.providers.claudeAgent.binaryPath || settings.providers.claudeAgent.customModels.length > 0, ), }); const [customModelInputByProvider, setCustomModelInputByProvider] = useState< Record >({ codex: "", claudeAgent: "", }); const [customModelErrorByProvider, setCustomModelErrorByProvider] = useState< Partial> >({}); const [isRefreshingProviders, setIsRefreshingProviders] = useState(false); const refreshingRef = useRef(false); const modelListRefs = useRef>>({}); const refreshProviders = useCallback(() => { if (refreshingRef.current) return; refreshingRef.current = true; setIsRefreshingProviders(true); void ensureNativeApi() .server.refreshProviders() .catch((error: unknown) => { console.warn("Failed to refresh providers", error); }) .finally(() => { refreshingRef.current = false; setIsRefreshingProviders(false); }); }, []); const keybindingsConfigPath = useServerKeybindingsConfigPath(); const availableEditors = useServerAvailableEditors(); const serverProviders = useServerProviders(); const codexHomePath = settings.providers.codex.homePath; const textGenerationModelSelection = resolveAppModelSelectionState(settings, serverProviders); const textGenProvider = textGenerationModelSelection.provider; const textGenModel = textGenerationModelSelection.model; const textGenModelOptions = textGenerationModelSelection.options; const gitModelOptionsByProvider = getCustomModelOptionsByProvider( settings, serverProviders, textGenProvider, textGenModel, ); const isGitWritingModelDirty = !Equal.equals( settings.textGenerationModelSelection ?? null, DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection ?? null, ); const openKeybindingsFile = useCallback(() => { if (!keybindingsConfigPath) return; setOpenKeybindingsError(null); setIsOpeningKeybindings(true); const editor = resolveAndPersistPreferredEditor(availableEditors ?? []); if (!editor) { setOpenKeybindingsError("No available editors found."); setIsOpeningKeybindings(false); return; } void ensureNativeApi() .shell.openInEditor(keybindingsConfigPath, editor) .catch((error) => { setOpenKeybindingsError( error instanceof Error ? error.message : "Unable to open keybindings file.", ); }) .finally(() => { setIsOpeningKeybindings(false); }); }, [availableEditors, keybindingsConfigPath]); const addCustomModel = useCallback( (provider: ProviderKind) => { const customModelInput = customModelInputByProvider[provider]; const customModels = settings.providers[provider].customModels; const normalized = normalizeModelSlug(customModelInput, provider); if (!normalized) { setCustomModelErrorByProvider((existing) => ({ ...existing, [provider]: "Enter a model slug.", })); return; } if ( serverProviders .find((candidate) => candidate.provider === provider) ?.models.some((option) => !option.isCustom && option.slug === normalized) ) { setCustomModelErrorByProvider((existing) => ({ ...existing, [provider]: "That model is already built in.", })); return; } if (normalized.length > MAX_CUSTOM_MODEL_LENGTH) { setCustomModelErrorByProvider((existing) => ({ ...existing, [provider]: `Model slugs must be ${MAX_CUSTOM_MODEL_LENGTH} characters or less.`, })); return; } if (customModels.includes(normalized)) { setCustomModelErrorByProvider((existing) => ({ ...existing, [provider]: "That custom model is already saved.", })); return; } updateSettings({ providers: { ...settings.providers, [provider]: { ...settings.providers[provider], customModels: [...customModels, normalized], }, }, }); setCustomModelInputByProvider((existing) => ({ ...existing, [provider]: "", })); setCustomModelErrorByProvider((existing) => ({ ...existing, [provider]: null, })); const el = modelListRefs.current[provider]; if (!el) return; const scrollToEnd = () => el.scrollTo({ top: el.scrollHeight, behavior: "smooth" }); requestAnimationFrame(scrollToEnd); const observer = new MutationObserver(() => { scrollToEnd(); observer.disconnect(); }); observer.observe(el, { childList: true, subtree: true }); setTimeout(() => observer.disconnect(), 2_000); }, [customModelInputByProvider, serverProviders, settings, updateSettings], ); const removeCustomModel = useCallback( (provider: ProviderKind, slug: string) => { updateSettings({ providers: { ...settings.providers, [provider]: { ...settings.providers[provider], customModels: settings.providers[provider].customModels.filter( (model) => model !== slug, ), }, }, }); setCustomModelErrorByProvider((existing) => ({ ...existing, [provider]: null, })); }, [settings, updateSettings], ); const providerCards = PROVIDER_SETTINGS.map((providerSettings) => { const liveProvider = serverProviders.find( (candidate) => candidate.provider === providerSettings.provider, ); const providerConfig = settings.providers[providerSettings.provider]; const defaultProviderConfig = DEFAULT_UNIFIED_SETTINGS.providers[providerSettings.provider]; const statusKey = liveProvider?.status ?? (providerConfig.enabled ? "warning" : "disabled"); const summary = getProviderSummary(liveProvider); const models: ReadonlyArray = liveProvider?.models ?? providerConfig.customModels.map((slug) => ({ slug, name: slug, isCustom: true, capabilities: null, })); return { provider: providerSettings.provider, title: providerSettings.title, binaryPlaceholder: providerSettings.binaryPlaceholder, binaryDescription: providerSettings.binaryDescription, homePathKey: providerSettings.homePathKey, homePlaceholder: providerSettings.homePlaceholder, homeDescription: providerSettings.homeDescription, binaryPathValue: providerConfig.binaryPath, isDirty: !Equal.equals(providerConfig, defaultProviderConfig), liveProvider, models, providerConfig, statusStyle: PROVIDER_STATUS_STYLES[statusKey], summary, versionLabel: getProviderVersionLabel(liveProvider?.version), }; }); const lastCheckedAt = serverProviders.length > 0 ? serverProviders.reduce( (latest, provider) => (provider.checkedAt > latest ? provider.checkedAt : latest), serverProviders[0]!.checkedAt, ) : null; return ( setTheme("system")} /> ) : null } control={ } /> updateSettings({ timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat, }) } /> ) : null } control={ } /> updateSettings({ diffWordWrap: DEFAULT_UNIFIED_SETTINGS.diffWordWrap, }) } /> ) : null } control={ updateSettings({ diffWordWrap: Boolean(checked) })} aria-label="Wrap diff lines by default" /> } /> updateSettings({ enableAssistantStreaming: DEFAULT_UNIFIED_SETTINGS.enableAssistantStreaming, }) } /> ) : null } control={ updateSettings({ enableAssistantStreaming: Boolean(checked) }) } aria-label="Stream assistant messages" /> } /> updateSettings({ defaultThreadEnvMode: DEFAULT_UNIFIED_SETTINGS.defaultThreadEnvMode, }) } /> ) : null } control={ } /> updateSettings({ confirmThreadArchive: DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive, }) } /> ) : null } control={ updateSettings({ confirmThreadArchive: Boolean(checked) }) } aria-label="Confirm thread archiving" /> } /> updateSettings({ confirmThreadDelete: DEFAULT_UNIFIED_SETTINGS.confirmThreadDelete, }) } /> ) : null } control={ updateSettings({ confirmThreadDelete: Boolean(checked) }) } aria-label="Confirm thread deletion" /> } /> updateSettings({ textGenerationModelSelection: DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection, }) } /> ) : null } control={
{ updateSettings({ textGenerationModelSelection: resolveAppModelSelectionState( { ...settings, textGenerationModelSelection: { provider, model }, }, serverProviders, ), }); }} /> provider.provider === textGenProvider) ?.models ?? [] } model={textGenModel} prompt="" onPromptChange={() => {}} modelOptions={textGenModelOptions} allowPromptInjectedEffort={false} triggerVariant="outline" triggerClassName="min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground" onModelOptionsChange={(nextOptions) => { updateSettings({ textGenerationModelSelection: resolveAppModelSelectionState( { ...settings, textGenerationModelSelection: { provider: textGenProvider, model: textGenModel, ...(nextOptions ? { options: nextOptions } : {}), }, }, serverProviders, ), }); }} />
} />
void refreshProviders()} aria-label="Refresh provider status" > {isRefreshingProviders ? ( ) : ( )} } /> Refresh provider status } > {providerCards.map((providerCard) => { const customModelInput = customModelInputByProvider[providerCard.provider]; const customModelError = customModelErrorByProvider[providerCard.provider] ?? null; const providerDisplayName = PROVIDER_DISPLAY_NAMES[providerCard.provider] ?? providerCard.title; return (

{providerDisplayName}

{providerCard.versionLabel ? ( {providerCard.versionLabel} ) : null} {providerCard.isDirty ? ( { updateSettings({ providers: { ...settings.providers, [providerCard.provider]: DEFAULT_UNIFIED_SETTINGS.providers[providerCard.provider], }, }); setCustomModelErrorByProvider((existing) => ({ ...existing, [providerCard.provider]: null, })); }} /> ) : null}

{providerCard.summary.headline} {providerCard.summary.detail ? ` - ${providerCard.summary.detail}` : null}

{ const isDisabling = !checked; const shouldClearModelSelection = isDisabling && textGenProvider === providerCard.provider; updateSettings({ providers: { ...settings.providers, [providerCard.provider]: { ...settings.providers[providerCard.provider], enabled: Boolean(checked), }, }, ...(shouldClearModelSelection ? { textGenerationModelSelection: DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection, } : {}), }); }} aria-label={`Enable ${providerDisplayName}`} />
setOpenProviderDetails((existing) => ({ ...existing, [providerCard.provider]: open, })) } >
{providerCard.homePathKey ? (
) : null}
Models
{providerCard.models.length} model {providerCard.models.length === 1 ? "" : "s"} available.
{ modelListRefs.current[providerCard.provider] = el; }} className="mt-2 max-h-40 overflow-y-auto pb-1" > {providerCard.models.map((model) => { const caps = model.capabilities; const capLabels: string[] = []; if (caps?.supportsFastMode) capLabels.push("Fast mode"); if (caps?.supportsThinkingToggle) capLabels.push("Thinking"); if ( caps?.reasoningEffortLevels && caps.reasoningEffortLevels.length > 0 ) { capLabels.push("Reasoning"); } const hasDetails = capLabels.length > 0 || model.name !== model.slug; return (
{model.name} {hasDetails ? ( } >
{model.slug} {capLabels.length > 0 ? (
{capLabels.map((label) => ( {label} ))}
) : null}
) : null} {model.isCustom ? (
custom
) : null}
); })}
{ const value = event.target.value; setCustomModelInputByProvider((existing) => ({ ...existing, [providerCard.provider]: value, })); if (customModelError) { setCustomModelErrorByProvider((existing) => ({ ...existing, [providerCard.provider]: null, })); } }} onKeyDown={(event) => { if (event.key !== "Enter") return; event.preventDefault(); addCustomModel(providerCard.provider); }} placeholder={ providerCard.provider === "codex" ? "gpt-6.7-codex-ultra-preview" : "claude-sonnet-5-0" } spellCheck={false} />
{customModelError ? (

{customModelError}

) : null}
); })}
{keybindingsConfigPath ?? "Resolving keybindings path..."} {openKeybindingsError ? ( {openKeybindingsError} ) : ( Opens in your preferred editor. )} } control={ } /> {isElectron ? ( ) : ( } description="Current version of the application." /> )}
); } export function ArchivedThreadsPanel() { const projects = useStore((store) => store.projects); const threads = useStore((store) => store.threads); const { unarchiveThread, confirmAndDeleteThread } = useThreadActions(); const archivedGroups = useMemo(() => { const projectById = new Map(projects.map((project) => [project.id, project] as const)); return [...projectById.values()] .map((project) => ({ project, threads: threads .filter((thread) => thread.projectId === project.id && thread.archivedAt !== null) .toSorted((left, right) => { const leftKey = left.archivedAt ?? left.createdAt; const rightKey = right.archivedAt ?? right.createdAt; return rightKey.localeCompare(leftKey) || right.id.localeCompare(left.id); }), })) .filter((group) => group.threads.length > 0); }, [projects, threads]); const handleArchivedThreadContextMenu = useCallback( async (threadId: ThreadId, position: { x: number; y: number }) => { const api = readNativeApi(); if (!api) return; const clicked = await api.contextMenu.show( [ { id: "unarchive", label: "Unarchive" }, { id: "delete", label: "Delete", destructive: true }, ], position, ); if (clicked === "unarchive") { try { await unarchiveThread(threadId); } catch (error) { toastManager.add({ type: "error", title: "Failed to unarchive thread", description: error instanceof Error ? error.message : "An error occurred.", }); } return; } if (clicked === "delete") { await confirmAndDeleteThread(threadId); } }, [confirmAndDeleteThread, unarchiveThread], ); return ( {archivedGroups.length === 0 ? ( No archived threads Archived threads will appear here. ) : ( archivedGroups.map(({ project, threads: projectThreads }) => ( } > {projectThreads.map((thread) => (
{ event.preventDefault(); void handleArchivedThreadContextMenu(thread.id, { x: event.clientX, y: event.clientY, }); }} >

{thread.title}

Archived {formatRelativeTimeLabel(thread.archivedAt ?? thread.createdAt)} {" \u00b7 Created "} {formatRelativeTimeLabel(thread.createdAt)}

))}
)) )}
); }