diff --git a/desktop/src/components/layout/Sidebar.test.tsx b/desktop/src/components/layout/Sidebar.test.tsx index c7c568b9..8d9bff1b 100644 --- a/desktop/src/components/layout/Sidebar.test.tsx +++ b/desktop/src/components/layout/Sidebar.test.tsx @@ -35,10 +35,6 @@ vi.mock('../../i18n', () => ({ 'sidebar.noMatching': 'No matching sessions', 'sidebar.sessionListFailed': 'Session list failed', 'sidebar.refreshSessions': 'Refresh sessions', - 'sidebar.indexBuilding': 'Optimizing history', - 'sidebar.indexBuildingProgress': 'Optimizing history {indexed}/{discovered}', - 'sidebar.indexReady': 'History optimization complete', - 'sidebar.indexOff': 'History optimization inactive', 'sidebar.indexDegraded': 'Using standard history loading', 'search.global.trigger': 'Search chats', 'sidebar.projects': 'Projects', @@ -1294,7 +1290,9 @@ describe('Sidebar', () => { expect(screen.queryByText('No sessions')).not.toBeInTheDocument() }) - it('shows quiet building progress while keeping indexed rows visible', () => { + // Indexing is background housekeeping the user cannot act on, so a partially + // built index must look exactly like a finished one: rows visible, no counter. + it('keeps indexed rows visible while building without surfacing progress', () => { useSessionStore.setState({ sessions: [makeSession('indexed-row', 'Indexed row', '/workspace/alpha', '2026-07-15T00:00:00.000Z')], indexStatus: { @@ -1313,7 +1311,9 @@ describe('Sidebar', () => { render() expect(screen.getByRole('button', { name: /Indexed row/ })).toBeInTheDocument() - expect(screen.getByTestId('sidebar-index-progress')).toHaveTextContent('Optimizing history 2/10') + expect(screen.queryByTestId('sidebar-index-progress')).not.toBeInTheDocument() + expect(screen.queryByText(/2\s*\/\s*10/)).not.toBeInTheDocument() + expect(screen.getByRole('status')).toBeEmptyDOMElement() }) it.each(['ready', 'off'] as const)('hides visible index status when state is %s', (state) => { @@ -1565,14 +1565,17 @@ describe('Sidebar', () => { } }) - it('announces state transitions without exposing numeric progress to the live region', () => { + // The live region exists for the one transition a user can perceive: history + // is being served the slow way. Building/ready/off are silent there too, so a + // screen reader is not told about work that needs no reaction. + it.each(['building', 'ready', 'off'] as const)('stays silent in the live region while %s', (state) => { useSessionStore.setState({ sessions: [makeSession('live-row', 'Live row', '/workspace/alpha', '2026-07-15T00:00:00.000Z')], indexStatus: { - mode: 'on', - state: 'building', + mode: state === 'off' ? 'off' : 'on', + state, discovered: 10, - indexed: 2, + indexed: state === 'building' ? 2 : 10, degradedSources: 0, databaseBytes: 4096, walBytes: 0, @@ -1583,10 +1586,31 @@ describe('Sidebar', () => { render() + expect(screen.getByRole('status')).toBeEmptyDOMElement() + }) + + it('announces the degraded fallback in the live region', () => { + useSessionStore.setState({ + sessions: [makeSession('live-row', 'Live row', '/workspace/alpha', '2026-07-15T00:00:00.000Z')], + indexStatus: { + mode: 'on', + state: 'degraded', + discovered: 10, + indexed: 2, + degradedSources: 1, + databaseBytes: 4096, + walBytes: 0, + lastUpdatedAt: '2026-07-15T00:00:00.000Z', + lastErrorCode: 'source_unreadable', + }, + }) + + render() + const liveRegion = screen.getByRole('status') - expect(liveRegion).toHaveTextContent('Optimizing history') + expect(liveRegion).toHaveTextContent('Using standard history loading') expect(liveRegion).not.toHaveTextContent('2/10') - expect(screen.getByTestId('sidebar-index-progress')).toHaveAttribute('aria-hidden', 'true') + expect(screen.getByTestId('sidebar-index-degraded')).toHaveAttribute('aria-hidden', 'true') }) it('refreshes sessions manually and through low-frequency visible polling', async () => { diff --git a/desktop/src/components/layout/Sidebar.tsx b/desktop/src/components/layout/Sidebar.tsx index 60509962..01ac60e6 100644 --- a/desktop/src/components/layout/Sidebar.tsx +++ b/desktop/src/components/layout/Sidebar.tsx @@ -190,17 +190,12 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) { }, [hiddenProjectKeys, orderedProjectGroups]) const showInitialLoading = isLoading && sessions.length === 0 const showRefreshLoading = showInitialLoading - const showIndexBuilding = indexStatus?.state === 'building' && sessions.length > 0 + // Index building/ready/off are implementation details of how the list is + // loaded, not something the user acts on, so they stay silent in both the + // visible sidebar and the live region. Only `degraded` is announced: there + // the list really is served a different way, which the user can perceive. const showIndexDegraded = indexStatus?.state === 'degraded' - const indexAnnouncement = indexStatus - ? t(indexStatus.state === 'building' - ? 'sidebar.indexBuilding' - : indexStatus.state === 'ready' - ? 'sidebar.indexReady' - : indexStatus.state === 'degraded' - ? 'sidebar.indexDegraded' - : 'sidebar.indexOff') - : '' + const indexAnnouncement = showIndexDegraded ? t('sidebar.indexDegraded') : '' const filteredSessionIds = useMemo(() => filteredSessions.map((session) => session.id), [filteredSessions]) const selectedCount = selectedSessionIds.size const sessionsById = useMemo( @@ -889,18 +884,6 @@ export function Sidebar({ isMobile = false, onRequestClose }: SidebarProps) {
{indexAnnouncement}
- {showIndexBuilding && ( - - )} {showIndexDegraded && (
{ expect(resurrected, `${name} still defines removed installed-skills keys`).toEqual([]) } }) + + // Index building/ready/off went silent: the sidebar no longer tells anyone + // that history is being indexed. Keeping the strings around invites a future + // change to wire them back into the UI, so they must stay deleted. + it('carries no key for the silent index states', () => { + for (const [name, locale] of Object.entries(locales)) { + const resurrected = Object.keys(locale).filter( + key => key.startsWith('sidebar.index') && key !== 'sidebar.indexDegraded', + ) + expect(resurrected, `${name} still defines silent index-status keys`).toEqual([]) + } + }) }) diff --git a/desktop/src/i18n/locales/en.ts b/desktop/src/i18n/locales/en.ts index b6aee775..ca5f5102 100644 --- a/desktop/src/i18n/locales/en.ts +++ b/desktop/src/i18n/locales/en.ts @@ -48,10 +48,6 @@ export const en = { 'sidebar.noMatching': 'No matching sessions', 'sidebar.sessionListFailed': 'Session list failed to load', 'sidebar.refreshSessions': 'Refresh sessions', - 'sidebar.indexBuilding': 'Optimizing history', - 'sidebar.indexBuildingProgress': 'Optimizing history {indexed}/{discovered}', - 'sidebar.indexReady': 'History optimization complete', - 'sidebar.indexOff': 'History optimization inactive', 'sidebar.indexDegraded': 'Using standard history loading', 'search.global.trigger': 'Search chats', 'search.global.placeholder': 'Search all chats…', diff --git a/desktop/src/i18n/locales/jp.ts b/desktop/src/i18n/locales/jp.ts index 6d2d96db..7f1776ea 100644 --- a/desktop/src/i18n/locales/jp.ts +++ b/desktop/src/i18n/locales/jp.ts @@ -50,10 +50,6 @@ export const jp: Record = { 'sidebar.noMatching': '一致するセッションがありません', 'sidebar.sessionListFailed': 'セッション一覧の読み込みに失敗しました', 'sidebar.refreshSessions': 'セッションを更新', - 'sidebar.indexBuilding': '履歴を最適化しています', - 'sidebar.indexBuildingProgress': '履歴を最適化しています {indexed}/{discovered}', - 'sidebar.indexReady': '履歴の最適化が完了しました', - 'sidebar.indexOff': '履歴の最適化は無効です', 'sidebar.indexDegraded': '標準の履歴読み込みを使用しています', 'search.global.trigger': 'チャットを検索', 'search.global.placeholder': 'すべてのチャットを検索…', diff --git a/desktop/src/i18n/locales/kr.ts b/desktop/src/i18n/locales/kr.ts index bdbe8ee4..36951047 100644 --- a/desktop/src/i18n/locales/kr.ts +++ b/desktop/src/i18n/locales/kr.ts @@ -50,10 +50,6 @@ export const kr: Record = { 'sidebar.noMatching': '일치하는 세션이 없습니다', 'sidebar.sessionListFailed': '세션 목록을 불러오지 못했습니다', 'sidebar.refreshSessions': '세션 새로 고침', - 'sidebar.indexBuilding': '기록을 최적화하는 중', - 'sidebar.indexBuildingProgress': '기록을 최적화하는 중 {indexed}/{discovered}', - 'sidebar.indexReady': '기록 최적화가 완료되었습니다', - 'sidebar.indexOff': '기록 최적화가 비활성화되었습니다', 'sidebar.indexDegraded': '표준 기록 불러오기를 사용하고 있습니다', 'search.global.trigger': '대화 검색', 'search.global.placeholder': '모든 대화 검색…', diff --git a/desktop/src/i18n/locales/zh-TW.ts b/desktop/src/i18n/locales/zh-TW.ts index 40f3ea89..d262e0d2 100644 --- a/desktop/src/i18n/locales/zh-TW.ts +++ b/desktop/src/i18n/locales/zh-TW.ts @@ -50,10 +50,6 @@ export const zh: Record = { 'sidebar.noMatching': '沒有匹配的會話', 'sidebar.sessionListFailed': '會話列表載入失敗', 'sidebar.refreshSessions': '重新整理會話列表', - 'sidebar.indexBuilding': '正在最佳化歷史記錄', - 'sidebar.indexBuildingProgress': '正在最佳化歷史記錄 {indexed}/{discovered}', - 'sidebar.indexReady': '歷史記錄最佳化完成', - 'sidebar.indexOff': '歷史記錄最佳化未啟用', 'sidebar.indexDegraded': '正在使用標準歷史記錄載入', 'search.global.trigger': '搜尋聊天', 'search.global.placeholder': '搜尋全部聊天內容…', diff --git a/desktop/src/i18n/locales/zh.ts b/desktop/src/i18n/locales/zh.ts index 24619e52..41e5416c 100644 --- a/desktop/src/i18n/locales/zh.ts +++ b/desktop/src/i18n/locales/zh.ts @@ -50,10 +50,6 @@ export const zh: Record = { 'sidebar.noMatching': '没有匹配的会话', 'sidebar.sessionListFailed': '会话列表加载失败', 'sidebar.refreshSessions': '刷新会话列表', - 'sidebar.indexBuilding': '正在优化历史记录', - 'sidebar.indexBuildingProgress': '正在优化历史记录 {indexed}/{discovered}', - 'sidebar.indexReady': '历史记录优化完成', - 'sidebar.indexOff': '历史记录优化未启用', 'sidebar.indexDegraded': '正在使用标准历史记录加载', 'search.global.trigger': '搜索聊天', 'search.global.placeholder': '搜索全部聊天内容…',