Skip to content

Commit d9ce2f0

Browse files
committed
refactor(desktop): move trace entry into Settings
Trace is a low-frequency diagnostic feature, so its entry points no longer sit in primary chrome: - Remove the Trace nav item above the session list in the sidebar - Remove the trace open/open-window buttons from the session header - Add a Trace tab in Settings between Token usage and Diagnostics, embedding the existing TraceList page full-bleed - Add settings.tab.trace and drop unused sidebar.traces across locales
1 parent d3d7566 commit d9ce2f0

11 files changed

Lines changed: 31 additions & 122 deletions

File tree

desktop/src/__tests__/generalSettings.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ vi.mock('../pages/ActivitySettings', () => ({
9494
ActivitySettings: () => <div>Activity Settings Mock</div>,
9595
}))
9696

97+
vi.mock('../pages/TraceList', () => ({
98+
TraceList: () => <div>Trace List Mock</div>,
99+
}))
100+
97101
vi.mock('../stores/agentStore', () => ({
98102
useAgentStore: () => ({
99103
activeAgents: [],
@@ -677,6 +681,20 @@ describe('Settings > General tab', () => {
677681
expect(screen.getByText('Activity Settings Mock')).toBeInTheDocument()
678682
})
679683

684+
it('opens the Trace tab from Settings navigation between Token usage and Diagnostics', () => {
685+
render(<Settings />)
686+
687+
const usageTab = screen.getByText('Token usage')
688+
const traceTab = screen.getByText('Trace')
689+
const diagnosticsTab = screen.getByText('Diagnostics')
690+
expect((usageTab.compareDocumentPosition(traceTab) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true)
691+
expect((traceTab.compareDocumentPosition(diagnosticsTab) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true)
692+
693+
fireEvent.click(traceTab)
694+
695+
expect(screen.getByText('Trace List Mock')).toBeInTheDocument()
696+
})
697+
680698
it('lets the user disable WebFetch preflight skipping', () => {
681699
render(<Settings />)
682700

desktop/src/components/layout/Sidebar.tsx

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useUIStore } from '../../stores/uiStore'
55
import { useTranslation, type TranslationKey } from '../../i18n'
66
import { ConfirmDialog } from '../shared/ConfirmDialog'
77
import type { SessionListItem } from '../../types/session'
8-
import { useTabStore, SETTINGS_TAB_ID, SCHEDULED_TAB_ID, TRACE_LIST_TAB_ID } from '../../stores/tabStore'
8+
import { useTabStore, SETTINGS_TAB_ID, SCHEDULED_TAB_ID } from '../../stores/tabStore'
99
import { useChatStore } from '../../stores/chatStore'
1010
import { useOpenTargetStore } from '../../stores/openTargetStore'
1111
import { desktopUiPreferencesApi, type SidebarProjectPreferences } from '../../api/desktopUiPreferences'
@@ -686,21 +686,6 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) {
686686
{t('sidebar.scheduled')}
687687
</NavItem>
688688
)}
689-
{!isMobile && (
690-
<NavItem
691-
active={activeTabId === TRACE_LIST_TAB_ID}
692-
collapsed={!expanded}
693-
label={t('sidebar.traces')}
694-
touchFriendly={isMobile}
695-
onClick={() => {
696-
useTabStore.getState().openTracesTab(t('sidebar.traces'))
697-
closeMobileDrawer()
698-
}}
699-
icon={<span className="material-symbols-outlined text-[18px]">account_tree</span>}
700-
>
701-
{t('sidebar.traces')}
702-
</NavItem>
703-
)}
704689
</div>
705690

706691
{expanded ? (

desktop/src/i18n/locales/en.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export const en = {
2121
// ─── Sidebar ──────────────────────────────────────
2222
'sidebar.newSession': 'New session',
2323
'sidebar.scheduled': 'Scheduled',
24-
'sidebar.traces': 'Trace',
2524
'sidebar.terminal': 'Terminal',
2625
'sidebar.settings': 'Settings',
2726
'sidebar.searchPlaceholder': 'Search sessions...',
@@ -171,6 +170,7 @@ export const en = {
171170
'settings.tab.providers': 'Providers',
172171
'settings.tab.permissions': 'Permissions',
173172
'settings.tab.activity': 'Token usage',
173+
'settings.tab.trace': 'Trace',
174174
'settings.tab.general': 'General',
175175
'settings.tab.h5Access': 'H5 Access',
176176
'settings.tab.terminal': 'Terminal',

desktop/src/i18n/locales/jp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const jp: Record<TranslationKey, string> = {
2323
// ─── Sidebar ──────────────────────────────────────
2424
'sidebar.newSession': '新しいセッション',
2525
'sidebar.scheduled': 'スケジュール',
26-
'sidebar.traces': 'Trace',
2726
'sidebar.terminal': 'ターミナル',
2827
'sidebar.settings': '設定',
2928
'sidebar.searchPlaceholder': 'セッションを検索...',
@@ -173,6 +172,7 @@ export const jp: Record<TranslationKey, string> = {
173172
'settings.tab.providers': 'プロバイダー',
174173
'settings.tab.permissions': '権限',
175174
'settings.tab.activity': 'トークン使用量',
175+
'settings.tab.trace': 'Trace',
176176
'settings.tab.general': '一般',
177177
'settings.tab.h5Access': 'H5 アクセス',
178178
'settings.tab.terminal': 'ターミナル',

desktop/src/i18n/locales/kr.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const kr: Record<TranslationKey, string> = {
2323
// ─── Sidebar ──────────────────────────────────────
2424
'sidebar.newSession': '새 세션',
2525
'sidebar.scheduled': '예약 작업',
26-
'sidebar.traces': 'Trace',
2726
'sidebar.terminal': '터미널',
2827
'sidebar.settings': '설정',
2928
'sidebar.searchPlaceholder': '세션 검색...',
@@ -173,6 +172,7 @@ export const kr: Record<TranslationKey, string> = {
173172
'settings.tab.providers': '공급자',
174173
'settings.tab.permissions': '권한',
175174
'settings.tab.activity': '토큰 사용량',
175+
'settings.tab.trace': 'Trace',
176176
'settings.tab.general': '일반',
177177
'settings.tab.h5Access': 'H5 액세스',
178178
'settings.tab.terminal': '터미널',

desktop/src/i18n/locales/zh-TW.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const zh: Record<TranslationKey, string> = {
2323
// ─── Sidebar ──────────────────────────────────────
2424
'sidebar.newSession': '新建會話',
2525
'sidebar.scheduled': '定時任務',
26-
'sidebar.traces': 'Trace',
2726
'sidebar.terminal': '終端',
2827
'sidebar.settings': '設定',
2928
'sidebar.searchPlaceholder': '搜尋會話...',
@@ -173,6 +172,7 @@ export const zh: Record<TranslationKey, string> = {
173172
'settings.tab.providers': '服務商',
174173
'settings.tab.permissions': '許可權',
175174
'settings.tab.activity': 'Token 用量',
175+
'settings.tab.trace': 'Trace',
176176
'settings.tab.general': '通用',
177177
'settings.tab.h5Access': 'H5 訪問',
178178
'settings.tab.terminal': '終端',

desktop/src/i18n/locales/zh.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export const zh: Record<TranslationKey, string> = {
2323
// ─── Sidebar ──────────────────────────────────────
2424
'sidebar.newSession': '新建会话',
2525
'sidebar.scheduled': '定时任务',
26-
'sidebar.traces': 'Trace',
2726
'sidebar.terminal': '终端',
2827
'sidebar.settings': '设置',
2928
'sidebar.searchPlaceholder': '搜索会话...',
@@ -173,6 +172,7 @@ export const zh: Record<TranslationKey, string> = {
173172
'settings.tab.providers': '服务商',
174173
'settings.tab.permissions': '权限',
175174
'settings.tab.activity': 'Token 用量',
175+
'settings.tab.trace': 'Trace',
176176
'settings.tab.general': '通用',
177177
'settings.tab.h5Access': 'H5 访问',
178178
'settings.tab.terminal': '终端',

desktop/src/pages/ActiveSession.test.tsx

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -186,67 +186,6 @@ describe('ActiveSession task polling', () => {
186186
expect(screen.getByTestId('chat-input')).toHaveAttribute('data-variant', 'default')
187187
})
188188

189-
it('opens the current session trace in a separate live window', () => {
190-
const sessionId = 'trace-window-session'
191-
const openSpy = vi.spyOn(window, 'open').mockImplementation(() => null)
192-
193-
useSessionStore.setState({
194-
sessions: [{
195-
id: sessionId,
196-
title: 'Trace Window Session',
197-
createdAt: '2026-05-07T00:00:00.000Z',
198-
modifiedAt: '2026-05-07T00:00:00.000Z',
199-
messageCount: 1,
200-
projectPath: '/workspace/project',
201-
workDir: '/workspace/project',
202-
workDirExists: true,
203-
}],
204-
activeSessionId: sessionId,
205-
isLoading: false,
206-
error: null,
207-
})
208-
useTabStore.setState({
209-
tabs: [{ sessionId, title: 'Trace Window Session', type: 'session', status: 'idle' }],
210-
activeTabId: sessionId,
211-
})
212-
useChatStore.setState({
213-
sessions: {
214-
[sessionId]: {
215-
messages: [{ id: 'msg-1', type: 'assistant_text', content: 'hello', timestamp: 1 }],
216-
chatState: 'idle',
217-
connectionState: 'connected',
218-
streamingText: '',
219-
streamingToolInput: '',
220-
activeToolUseId: null,
221-
activeToolName: null,
222-
activeThinkingId: null,
223-
pendingPermission: null,
224-
pendingComputerUsePermission: null,
225-
tokenUsage: { input_tokens: 0, output_tokens: 0 },
226-
elapsedSeconds: 0,
227-
statusVerb: '',
228-
slashCommands: [],
229-
agentTaskNotifications: {},
230-
elapsedTimer: null,
231-
},
232-
},
233-
})
234-
235-
try {
236-
render(<ActiveSession />)
237-
fireEvent.click(screen.getByLabelText(/|separate/i))
238-
239-
expect(openSpy).toHaveBeenCalledWith(
240-
expect.stringContaining(`traceSessionId=${sessionId}`),
241-
'_blank',
242-
'noopener,noreferrer',
243-
)
244-
expect(openSpy.mock.calls[0]?.[0]).toContain('traceWindow=1')
245-
} finally {
246-
openSpy.mockRestore()
247-
}
248-
})
249-
250189
it('renders the current goal as a lightweight header strip without a page-level panel', () => {
251190
const sessionId = 'goal-visible-session'
252191

desktop/src/pages/ActiveSession.tsx

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useEffect, useMemo, useRef, useState } from 'react'
2-
import { ExternalLink, RadioTower, Target } from 'lucide-react'
2+
import { Target } from 'lucide-react'
33
import {
44
SCHEDULED_TAB_ID,
55
SETTINGS_TAB_ID,
@@ -32,8 +32,6 @@ import type { ActiveGoalState } from '../types/chat'
3232
import { useMobileViewport } from '../hooks/useMobileViewport'
3333
import { isDesktopRuntime } from '../lib/desktopRuntime'
3434
import { publicAssetPath } from '../lib/publicAsset'
35-
import { getDesktopHost } from '../lib/desktopHost'
36-
import { buildTraceWindowUrl } from '../lib/traceLaunch'
3735

3836
const TASK_POLL_INTERVAL_MS = 1000
3937
const WORKSPACE_RESIZE_STEP = 32
@@ -364,22 +362,6 @@ export function ActiveSession() {
364362
return t('session.timeDays', { n: Math.floor(diff / 86400000) })
365363
}, [session?.modifiedAt, t])
366364

367-
const openTrace = () => {
368-
if (!activeTabId) return
369-
const title = session?.title || t('session.untitled')
370-
useTabStore.getState().openTraceTab(activeTabId, `${t('trace.title')}: ${title}`)
371-
}
372-
373-
const openTraceWindow = () => {
374-
if (!activeTabId) return
375-
const host = getDesktopHost()
376-
if (host.trace) {
377-
void host.trace.openWindow(activeTabId)
378-
return
379-
}
380-
window.open(buildTraceWindowUrl(activeTabId), '_blank', 'noopener,noreferrer')
381-
}
382-
383365
if (!activeTabId) return null
384366

385367
return (
@@ -480,25 +462,6 @@ export function ActiveSession() {
480462
>
481463
{session?.title || t('session.untitled')}
482464
</h1>
483-
<button
484-
type="button"
485-
aria-label={t('trace.open')}
486-
title={t('trace.open')}
487-
onClick={openTrace}
488-
className="inline-flex h-8 shrink-0 items-center gap-1.5 rounded-[8px] border border-[var(--color-border)] px-2.5 text-xs font-semibold text-[var(--color-text-secondary)] transition-colors hover:border-[var(--color-border-focus)] hover:text-[var(--color-text-primary)] active:scale-[0.98]"
489-
>
490-
<RadioTower size={14} strokeWidth={2} aria-hidden="true" />
491-
<span className={showRightPanel ? 'sr-only' : 'hidden sm:inline'}>{t('trace.open')}</span>
492-
</button>
493-
<button
494-
type="button"
495-
aria-label={t('trace.openWindow')}
496-
title={t('trace.openWindow')}
497-
onClick={openTraceWindow}
498-
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-[8px] border border-[var(--color-border)] text-[var(--color-text-secondary)] transition-colors hover:border-[var(--color-border-focus)] hover:text-[var(--color-text-primary)] active:scale-[0.98]"
499-
>
500-
<ExternalLink size={14} strokeWidth={2} aria-hidden="true" />
501-
</button>
502465
</div>
503466
<div
504467
className={

desktop/src/pages/Settings.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import { ComputerUseSettings } from './ComputerUseSettings'
2828
import { McpSettings } from './McpSettings'
2929
import { TerminalSettings } from './TerminalSettings'
3030
import { DiagnosticsSettings } from './DiagnosticsSettings'
31+
import { TraceList } from './TraceList'
3132
import { ActivitySettings } from './ActivitySettings'
3233
import { MemorySettings } from './MemorySettings'
3334
import { useUIStore, type SettingsTab } from '../stores/uiStore'
@@ -166,15 +167,16 @@ export function Settings() {
166167
<TabButton icon="extension" label={t('settings.tab.plugins')} active={activeTab === 'plugins'} onClick={() => setActiveTab('plugins')} />
167168
<TabButton icon="mouse" label={t('settings.tab.computerUse')} active={activeTab === 'computerUse'} onClick={() => setActiveTab('computerUse')} />
168169
<TabButton icon="monitoring" label={t('settings.tab.activity')} active={activeTab === 'activity'} onClick={() => setActiveTab('activity')} />
170+
<TabButton icon="account_tree" label={t('settings.tab.trace')} active={activeTab === 'trace'} onClick={() => setActiveTab('trace')} />
169171
<TabButton icon="monitor_heart" label={t('settings.tab.diagnostics')} active={activeTab === 'diagnostics'} onClick={() => setActiveTab('diagnostics')} />
170172
</div>
171173
<div className="border-t border-[var(--color-border)]/40 pt-1">
172174
<TabButton icon="info" label={t('settings.tab.about')} active={activeTab === 'about'} onClick={() => setActiveTab('about')} />
173175
</div>
174176
</div>
175177

176-
{/* Tab content */}
177-
<div className="flex-1 overflow-y-auto px-8 py-6">
178+
{/* Tab content; trace embeds a full-bleed page that manages its own scroll */}
179+
<div className={activeTab === 'trace' ? 'flex-1 flex min-h-0 flex-col overflow-hidden' : 'flex-1 overflow-y-auto px-8 py-6'}>
178180
{activeTab === 'providers' && <ProviderSettings />}
179181
{activeTab === 'activity' && <ActivitySettings />}
180182
{activeTab === 'general' && <GeneralSettings />}
@@ -187,6 +189,7 @@ export function Settings() {
187189
{activeTab === 'memory' && <MemorySettings />}
188190
{activeTab === 'plugins' && <PluginSettings />}
189191
{activeTab === 'computerUse' && <ComputerUseSettings />}
192+
{activeTab === 'trace' && <TraceList />}
190193
{activeTab === 'diagnostics' && <DiagnosticsSettings />}
191194
{activeTab === 'about' && <AboutSettings />}
192195
</div>

0 commit comments

Comments
 (0)