diff --git a/src/renderer/src/components/mobile/MobileHeroPaired.tsx b/src/renderer/src/components/mobile/MobileHeroPaired.tsx deleted file mode 100644 index 807c34bb0c1..00000000000 --- a/src/renderer/src/components/mobile/MobileHeroPaired.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { Smartphone, Trash2 } from 'lucide-react' -import type { PairedDevice } from './MobileHero' -import { translate } from '@/i18n/i18n' - -type HeroPairedProps = { - devices: readonly PairedDevice[] - onPairAnother: () => void - onRevoke: (deviceId: string) => void - revokingDeviceIds: readonly string[] -} - -export function HeroPaired({ - devices, - onPairAnother, - onRevoke, - revokingDeviceIds -}: HeroPairedProps): React.JSX.Element { - return ( -
-
- - {translate('auto.components.mobile.MobileHero.5410d55d79', 'Orca Mobile')} - -
-

- {devices.length === 1 - ? translate('auto.components.mobile.MobileHero.051978a785', 'Your phone is paired.') - : translate('auto.components.mobile.MobileHero.d0b52871ce', 'Your phones are paired.')} -

-

- {translate( - 'auto.components.mobile.MobileHero.266c18c105', - 'Open Orca Mobile to pick up where you left off, or pair another device.' - )} -

- -
- - -
-
- ) -} diff --git a/src/renderer/src/components/onboarding/ThemeStepGhosttyDiscoveryRow.tsx b/src/renderer/src/components/onboarding/ThemeStepGhosttyDiscoveryRow.tsx deleted file mode 100644 index 7020c52155a..00000000000 --- a/src/renderer/src/components/onboarding/ThemeStepGhosttyDiscoveryRow.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Check } from 'lucide-react' -import type { GhosttyImportPreview } from '../../../../shared/types' -import ghosttyIcon from '../../../../../resources/ghostty.svg' -import type { DiscoveryState } from './ThemeStep' -import { translate } from '@/i18n/i18n' - -type GhosttyDiscoveryRowProps = { - discovery: DiscoveryState - importing: boolean - disabled: boolean - onImport: (preview: GhosttyImportPreview) => void -} - -export function GhosttyDiscoveryRow({ - discovery, - importing, - disabled, - onImport -}: GhosttyDiscoveryRowProps): React.JSX.Element | null { - if (discovery.status === 'absent' || discovery.status === 'idle') { - return null - } - - if (discovery.status === 'detecting') { - return ( -
- - {translate( - 'auto.components.onboarding.ThemeStep.2c3aa538f8', - 'Looking for a Ghostty config…' - )} -
- ) - } - - if (discovery.status === 'imported') { - return ( -
- - - - {translate('auto.components.onboarding.ThemeStep.78b6386140', 'Imported from Ghostty.')} - - {discovery.fields.length > 0 && ( - {discovery.fields.join(' · ')} - )} - -
- ) - } - - const { preview, fields } = discovery - return ( -
- -
-
- - {translate( - 'auto.components.onboarding.ThemeStep.7ee9234e54', - 'Ghostty config detected.' - )} - {' '} - - {translate('auto.components.onboarding.ThemeStep.248c812283', 'Import')}{' '} - {fields.length > 0 - ? fields.map((f) => f.toLowerCase()).join(', ') - : translate('auto.components.onboarding.ThemeStep.906c4373fe', 'settings')} - ? - -
- {preview.configPath && ( -
- {preview.configPath} -
- )} -
- -
- ) -} diff --git a/src/renderer/src/components/right-sidebar/HostedReviewDeleteWorkspaceButton.tsx b/src/renderer/src/components/right-sidebar/HostedReviewDeleteWorkspaceButton.tsx deleted file mode 100644 index f0aaf4dfbfa..00000000000 --- a/src/renderer/src/components/right-sidebar/HostedReviewDeleteWorkspaceButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { LoaderCircle, Trash2 } from 'lucide-react' -import { Button } from '@/components/ui/button' -import { translate } from '@/i18n/i18n' - -type HostedReviewDeleteWorkspaceButtonProps = { - deleting: boolean - onDelete: () => void -} - -export function HostedReviewDeleteWorkspaceButton({ - deleting, - onDelete -}: HostedReviewDeleteWorkspaceButtonProps): React.JSX.Element { - return ( - - ) -} diff --git a/src/renderer/src/components/right-sidebar/HostedReviewReopenButton.tsx b/src/renderer/src/components/right-sidebar/HostedReviewReopenButton.tsx deleted file mode 100644 index 35411fb9aba..00000000000 --- a/src/renderer/src/components/right-sidebar/HostedReviewReopenButton.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { CircleDot, LoaderCircle } from 'lucide-react' -import { Button } from '@/components/ui/button' -import { translate } from '@/i18n/i18n' - -type HostedReviewReopenButtonProps = { - label: string - reopening: boolean - disabled: boolean - onReopen: () => void -} - -export function HostedReviewReopenButton({ - label, - reopening, - disabled, - onReopen -}: HostedReviewReopenButtonProps): React.JSX.Element { - return ( - - ) -} diff --git a/src/renderer/src/components/settings/McpConfigSection.tsx b/src/renderer/src/components/settings/McpConfigSection.tsx index 2b02cca478a..8e775a5c69b 100644 --- a/src/renderer/src/components/settings/McpConfigSection.tsx +++ b/src/renderer/src/components/settings/McpConfigSection.tsx @@ -2,7 +2,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { AlertCircle, FileCode2, LoaderCircle, Plus, RefreshCw } from 'lucide-react' import { toast } from 'sonner' import { useMountedRef } from '@/hooks/useMountedRef' -import type { Repo } from '../../../../shared/types' +import type { Repo, Worktree } from '../../../../shared/types' +import { getRepoIdFromWorktreeId } from '../../../../shared/worktree-id' import { canInspectLocalMcpConfigRoot, inspectMcpConfigContent, @@ -15,11 +16,6 @@ import { extractIpcErrorMessage } from '../../lib/ipc-error' import { Button } from '../ui/button' import { isWindowsUserAgent } from '../terminal-pane/pane-helpers' import { McpConfigFileRow, type LoadedMcpConfigInspection } from './McpConfigFileRow' -import { - EMPTY_MCP_WORKTREES, - countMcpConfigServers, - selectMcpTargetWorktree -} from './mcp-config-inspection-state' import { McpMissingConfigList } from './McpMissingConfigList' import { loadMcpConfigInspections } from './mcp-config-inspection' import { translate } from '@/i18n/i18n' @@ -28,15 +24,19 @@ type McpConfigSectionProps = { repo: Repo } +const EMPTY_WORKTREES: Worktree[] = [] + +function countServers(configs: LoadedMcpConfigInspection[]): number { + return configs.reduce((sum, config) => sum + config.servers.length, 0) +} + export function McpConfigSection({ repo }: McpConfigSectionProps): React.JSX.Element { const openFile = useAppStore((state) => state.openFile) const setActiveView = useAppStore((state) => state.setActiveView) const setActiveWorktree = useAppStore((state) => state.setActiveWorktree) const ensureWorktreeRootGroup = useAppStore((state) => state.ensureWorktreeRootGroup) const activeWorktreeId = useAppStore((state) => state.activeWorktreeId) - const worktreesForRepo = useAppStore( - (state) => state.worktreesByRepo[repo.id] ?? EMPTY_MCP_WORKTREES - ) + const worktreesForRepo = useAppStore((state) => state.worktreesByRepo[repo.id] ?? EMPTY_WORKTREES) const sshConnectionStatus = useAppStore((state) => repo.connectionId ? state.sshConnectionStates.get(repo.connectionId)?.status : null ) @@ -51,10 +51,21 @@ export function McpConfigSection({ repo }: McpConfigSectionProps): React.JSX.Ele const connectionId = repo.connectionId ?? undefined const isWindows = isWindowsUserAgent() - const targetWorktree = useMemo( - () => selectMcpTargetWorktree(repo, worktreesForRepo, activeWorktreeId), - [activeWorktreeId, repo, worktreesForRepo] - ) + const targetWorktree = useMemo(() => { + if (activeWorktreeId && getRepoIdFromWorktreeId(activeWorktreeId) === repo.id) { + return ( + worktreesForRepo.find((worktree) => worktree.id === activeWorktreeId) ?? { + id: activeWorktreeId, + path: repo.path + } + ) + } + return ( + worktreesForRepo.find((worktree) => worktree.isMainWorktree) ?? + worktreesForRepo.find((worktree) => worktree.path === repo.path) ?? + worktreesForRepo[0] ?? { id: `${repo.id}::${repo.path}`, path: repo.path } + ) + }, [activeWorktreeId, repo.id, repo.path, worktreesForRepo]) const targetWorktreeId = targetWorktree.id const targetRootPath = targetWorktree.path const detectedCount = useMemo(() => configs.filter((config) => config.exists).length, [configs]) @@ -85,7 +96,7 @@ export function McpConfigSection({ repo }: McpConfigSectionProps): React.JSX.Ele ), [targetRootPath] ) - const serverCount = useMemo(() => countMcpConfigServers(configs), [configs]) + const serverCount = useMemo(() => countServers(configs), [configs]) const canCreateStarter = detectedCount === 0 && !inspectionUnavailable const loadConfigs = useCallback(async (): Promise => { diff --git a/src/renderer/src/components/settings/mcp-config-inspection-state.ts b/src/renderer/src/components/settings/mcp-config-inspection-state.ts deleted file mode 100644 index 0abb25b62b5..00000000000 --- a/src/renderer/src/components/settings/mcp-config-inspection-state.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { Repo, Worktree } from '../../../../shared/types' -import { getRepoIdFromWorktreeId } from '../../../../shared/worktree-id' -import type { LoadedMcpConfigInspection } from './McpConfigFileRow' - -export const EMPTY_MCP_WORKTREES: Worktree[] = [] - -export function isMissingMcpConfigFileError(error: unknown): boolean { - const message = error instanceof Error ? error.message : String(error) - return /ENOENT|no such file|not found/i.test(message) -} - -export function countMcpConfigServers(configs: LoadedMcpConfigInspection[]): number { - return configs.reduce((sum, config) => sum + config.servers.length, 0) -} - -export function selectMcpTargetWorktree( - repo: Repo, - worktreesForRepo: readonly Worktree[], - activeWorktreeId: string | null -): Pick { - if (activeWorktreeId && getRepoIdFromWorktreeId(activeWorktreeId) === repo.id) { - return ( - worktreesForRepo.find((worktree) => worktree.id === activeWorktreeId) ?? { - id: activeWorktreeId, - path: repo.path - } - ) - } - return ( - worktreesForRepo.find((worktree) => worktree.isMainWorktree) ?? - worktreesForRepo.find((worktree) => worktree.path === repo.path) ?? - worktreesForRepo[0] ?? { id: `${repo.id}::${repo.path}`, path: repo.path } - ) -} diff --git a/src/renderer/src/components/stats/ClaudeUsagePane.tsx b/src/renderer/src/components/stats/ClaudeUsagePane.tsx index 946104bbadd..32536628f8d 100644 --- a/src/renderer/src/components/stats/ClaudeUsagePane.tsx +++ b/src/renderer/src/components/stats/ClaudeUsagePane.tsx @@ -5,18 +5,29 @@ import { DatabaseZap, FolderKanban, Gauge, + RefreshCw, + SlidersHorizontal, Sparkles, Waypoints } from 'lucide-react' import type { ClaudeUsageRange, ClaudeUsageScope } from '../../../../shared/claude-usage-types' import { useAppStore } from '../../store' +import { Button } from '../ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '../ui/dropdown-menu' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip' import { ClaudeUsageDetails } from './ClaudeUsageDetails' import { ClaudeUsageLoadingState } from './ClaudeUsageLoadingState' import { ShareUsageButton } from './ShareUsageButton' import { StatCard } from './StatCard' -import { UsagePaneFilterControls } from './UsagePaneFilterControls' -import { UsageTrackingDisabledCard } from './UsageTrackingDisabledCard' -import { formatCost, formatTokens, formatUpdatedAt } from './usage-display-formatting' +import { formatCost, formatTokens, formatUpdatedAt } from './usage-formatters' import { translate } from '@/i18n/i18n' const RANGE_OPTIONS: ClaudeUsageRange[] = ['7d', '30d', '90d', 'all'] @@ -76,21 +87,37 @@ export function ClaudeUsagePane(): React.JSX.Element { if (!scanState?.enabled) { return ( - handleSetEnabled(true)} - /> +
+
+
+

+ {translate( + 'auto.components.stats.ClaudeUsagePane.6afacbee37', + 'Claude Usage Tracking' + )} +

+

+ {translate( + 'auto.components.stats.ClaudeUsagePane.0cb1a36d7d', + 'Reads local Claude usage logs to show token, model, and session stats.' + )} +

+
+ +
+
) } @@ -122,30 +149,92 @@ export function ClaudeUsagePane(): React.JSX.Element { {summary && daily.length > 0 && ( )} - + + + + + + + + + {translate('auto.components.stats.ClaudeUsagePane.dd29209b21', 'Filters')} + + + + + + {translate('auto.components.stats.ClaudeUsagePane.f61cffb9c8', 'Scope')} + + void setClaudeUsageScope(value as ClaudeUsageScope)} + > + {SCOPE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + + + {translate('auto.components.stats.ClaudeUsagePane.505be9aac4', 'Range')} + + void setClaudeUsageRange(value as ClaudeUsageRange)} + > + {RANGE_OPTIONS.map((option) => ( + + {RANGE_LABELS[option]} + + ))} + + + + + + + + + + {translate('auto.components.stats.ClaudeUsagePane.8d18bbb771', 'Refresh')} + + + + diff --git a/src/renderer/src/components/stats/CodexUsagePane.tsx b/src/renderer/src/components/stats/CodexUsagePane.tsx index 255d9334030..2ebd6913d44 100644 --- a/src/renderer/src/components/stats/CodexUsagePane.tsx +++ b/src/renderer/src/components/stats/CodexUsagePane.tsx @@ -1,14 +1,32 @@ import { useEffect } from 'react' -import { Activity, Brain, Coins, DatabaseZap, FolderKanban, Sparkles } from 'lucide-react' +import { + Activity, + Brain, + Coins, + DatabaseZap, + FolderKanban, + RefreshCw, + SlidersHorizontal, + Sparkles +} from 'lucide-react' import type { CodexUsageRange, CodexUsageScope } from '../../../../shared/codex-usage-types' import { useAppStore } from '../../store' +import { Button } from '../ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '../ui/dropdown-menu' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip' import { ClaudeUsageLoadingState } from './ClaudeUsageLoadingState' import { CodexUsageDetails } from './CodexUsageDetails' import { ShareUsageButton } from './ShareUsageButton' import { StatCard } from './StatCard' -import { UsagePaneFilterControls } from './UsagePaneFilterControls' -import { UsageTrackingDisabledCard } from './UsageTrackingDisabledCard' -import { formatCost, formatTokens, formatUpdatedAt } from './usage-display-formatting' +import { formatCost, formatTokens, formatUpdatedAt } from './usage-formatters' import { translate } from '@/i18n/i18n' const RANGE_OPTIONS: CodexUsageRange[] = ['7d', '30d', '90d', 'all'] @@ -68,18 +86,34 @@ export function CodexUsagePane(): React.JSX.Element { if (!scanState?.enabled) { return ( - handleSetEnabled(true)} - /> +
+
+
+

+ {translate('auto.components.stats.CodexUsagePane.408210470c', 'Codex Usage Tracking')} +

+

+ {translate( + 'auto.components.stats.CodexUsagePane.13badcd8f2', + 'Reads local Codex usage logs to show token, model, and session stats.' + )} +

+
+ +
+
) } @@ -117,30 +151,92 @@ export function CodexUsagePane(): React.JSX.Element { {summary && daily.length > 0 && ( )} - + + + + + + + + + {translate('auto.components.stats.CodexUsagePane.1af1a39b2f', 'Filters')} + + + + + + {translate('auto.components.stats.CodexUsagePane.6d68e8399a', 'Scope')} + + void setCodexUsageScope(value as CodexUsageScope)} + > + {SCOPE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + + + {translate('auto.components.stats.CodexUsagePane.89162e019b', 'Range')} + + void setCodexUsageRange(value as CodexUsageRange)} + > + {RANGE_OPTIONS.map((option) => ( + + {RANGE_LABELS[option]} + + ))} + + + + + + + + + + {translate('auto.components.stats.CodexUsagePane.3022cda443', 'Refresh')} + + + + diff --git a/src/renderer/src/components/stats/OpenCodeUsagePane.tsx b/src/renderer/src/components/stats/OpenCodeUsagePane.tsx index b23689fea03..c09f2a5a179 100644 --- a/src/renderer/src/components/stats/OpenCodeUsagePane.tsx +++ b/src/renderer/src/components/stats/OpenCodeUsagePane.tsx @@ -1,16 +1,34 @@ import { useEffect } from 'react' -import { Activity, Brain, Coins, DatabaseZap, FolderKanban, Sparkles } from 'lucide-react' +import { + Activity, + Brain, + Coins, + DatabaseZap, + FolderKanban, + RefreshCw, + SlidersHorizontal, + Sparkles +} from 'lucide-react' import type { OpenCodeUsageRange, OpenCodeUsageScope } from '../../../../shared/opencode-usage-types' import { useAppStore } from '../../store' +import { Button } from '../ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuLabel, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuSeparator, + DropdownMenuTrigger +} from '../ui/dropdown-menu' +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip' import { ClaudeUsageLoadingState } from './ClaudeUsageLoadingState' import { OpenCodeUsageDetails } from './OpenCodeUsageDetails' import { StatCard } from './StatCard' -import { UsagePaneFilterControls } from './UsagePaneFilterControls' -import { UsageTrackingDisabledCard } from './UsageTrackingDisabledCard' -import { formatCost, formatTokens, formatUpdatedAt } from './usage-display-formatting' +import { formatCost, formatTokens, formatUpdatedAt } from './usage-formatters' import { translate } from '@/i18n/i18n' const RANGE_OPTIONS: OpenCodeUsageRange[] = ['7d', '30d', '90d', 'all'] @@ -73,21 +91,37 @@ export function OpenCodeUsagePane(): React.JSX.Element { if (!scanState?.enabled) { return ( - handleSetEnabled(true)} - /> +
+
+
+

+ {translate( + 'auto.components.stats.OpenCodeUsagePane.bea80ceae0', + 'OpenCode Usage Tracking' + )} +

+

+ {translate( + 'auto.components.stats.OpenCodeUsagePane.b8b3522436', + 'Reads local OpenCode usage logs to show token, model, and session stats.' + )} +

+
+ +
+
) } @@ -127,30 +161,94 @@ export function OpenCodeUsagePane(): React.JSX.Element { : ''}

- void setOpenCodeUsageScope(value)} - onRangeChange={(value) => void setOpenCodeUsageRange(value)} - onRefresh={() => void refreshOpenCodeUsage()} - onDisable={() => handleSetEnabled(false)} - /> +
+ + + + + + + + + + {translate('auto.components.stats.OpenCodeUsagePane.01583b30aa', 'Filters')} + + + + + + {translate('auto.components.stats.OpenCodeUsagePane.40d283c837', 'Scope')} + + void setOpenCodeUsageScope(value as OpenCodeUsageScope)} + > + {SCOPE_OPTIONS.map((option) => ( + + {option.label} + + ))} + + + + {translate('auto.components.stats.OpenCodeUsagePane.b5ed5c9fd0', 'Range')} + + void setOpenCodeUsageRange(value as OpenCodeUsageRange)} + > + {RANGE_OPTIONS.map((option) => ( + + {RANGE_LABELS[option]} + + ))} + + + + + + + + + + {translate('auto.components.stats.OpenCodeUsagePane.603cd138dc', 'Refresh')} + + + + +
diff --git a/src/renderer/src/components/stats/UsagePaneFilterControls.tsx b/src/renderer/src/components/stats/UsagePaneFilterControls.tsx deleted file mode 100644 index 5b64e9b0802..00000000000 --- a/src/renderer/src/components/stats/UsagePaneFilterControls.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { RefreshCw, SlidersHorizontal } from 'lucide-react' -import { Button } from '../ui/button' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuLabel, - DropdownMenuRadioGroup, - DropdownMenuRadioItem, - DropdownMenuSeparator, - DropdownMenuTrigger -} from '../ui/dropdown-menu' -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/tooltip' - -type UsagePaneFilterControlsProps = { - scope: Scope - range: Range - scopeOptions: { value: Scope; label: string }[] - rangeOptions: Range[] - rangeLabels: Record - isScanning: boolean - optionsLabel: string - filtersLabel: string - scopeLabel: string - rangeLabel: string - refreshLabel: string - enableLabel: string - onScopeChange: (value: Scope) => void - onRangeChange: (value: Range) => void - onRefresh: () => void - onDisable: () => void -} - -export function UsagePaneFilterControls({ - scope, - range, - scopeOptions, - rangeOptions, - rangeLabels, - isScanning, - optionsLabel, - filtersLabel, - scopeLabel, - rangeLabel, - refreshLabel, - enableLabel, - onScopeChange, - onRangeChange, - onRefresh, - onDisable -}: UsagePaneFilterControlsProps): React.JSX.Element { - return ( -
- - - - - - - - - - {filtersLabel} - - - - - {scopeLabel} - onScopeChange(value as Scope)} - > - {scopeOptions.map((option) => ( - - {option.label} - - ))} - - - {rangeLabel} - onRangeChange(value as Range)} - > - {rangeOptions.map((option) => ( - - {rangeLabels[option]} - - ))} - - - - - - - - - - {refreshLabel} - - - - -
- ) -} diff --git a/src/renderer/src/components/stats/UsageTrackingDisabledCard.tsx b/src/renderer/src/components/stats/UsageTrackingDisabledCard.tsx deleted file mode 100644 index 1bed4177439..00000000000 --- a/src/renderer/src/components/stats/UsageTrackingDisabledCard.tsx +++ /dev/null @@ -1,34 +0,0 @@ -type UsageTrackingDisabledCardProps = { - title: string - description: string - enableLabel: string - onEnable: () => void -} - -export function UsageTrackingDisabledCard({ - title, - description, - enableLabel, - onEnable -}: UsageTrackingDisabledCardProps): React.JSX.Element { - return ( -
-
-
-

{title}

-

{description}

-
- -
-
- ) -} diff --git a/src/renderer/src/components/stats/usage-display-formatting.ts b/src/renderer/src/components/stats/usage-display-formatting.ts deleted file mode 100644 index ae3345a1e60..00000000000 --- a/src/renderer/src/components/stats/usage-display-formatting.ts +++ /dev/null @@ -1,36 +0,0 @@ -export function formatTokens(value: number): string { - if (value >= 1_000_000) { - return `${(value / 1_000_000).toFixed(1)}M` - } - if (value >= 1_000) { - return `${(value / 1_000).toFixed(1)}k` - } - return value.toLocaleString() -} - -export function formatCost(value: number | null): string { - if (value === null) { - return 'n/a' - } - return value < 0.01 ? `$${value.toFixed(4)}` : `$${value.toFixed(2)}` -} - -export function formatUpdatedAt(timestamp: number | null): string { - if (!timestamp) { - return 'Not scanned yet' - } - return `Updated ${new Date(timestamp).toLocaleString()}` -} - -export function formatSessionTime(timestamp: string): string { - const parsed = new Date(timestamp) - if (Number.isNaN(parsed.getTime())) { - return timestamp - } - return parsed.toLocaleString(undefined, { - month: 'short', - day: 'numeric', - hour: 'numeric', - minute: '2-digit' - }) -} diff --git a/src/renderer/src/lib/folder-workspace-path-status-cache-expiry.ts b/src/renderer/src/lib/folder-workspace-path-status-cache-expiry.ts index 563daae5bb9..513a44d80d7 100644 --- a/src/renderer/src/lib/folder-workspace-path-status-cache-expiry.ts +++ b/src/renderer/src/lib/folder-workspace-path-status-cache-expiry.ts @@ -6,14 +6,14 @@ type FolderWorkspacePathStatusCacheClockEntry = { } export function useFolderWorkspacePathStatusCacheExpiryTick( - entries: Record | null | undefined + entries: Record ): number { const [tick, setTick] = useState(0) useEffect(() => { const now = Date.now() let nextDelayMs = Number.POSITIVE_INFINITY - for (const entry of Object.values(entries ?? {})) { + for (const entry of Object.values(entries)) { const delayMs = entry.checkedAt + FOLDER_WORKSPACE_PATH_STATUS_TTL_MS - now if (delayMs > 0) { nextDelayMs = Math.min(nextDelayMs, delayMs)