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 && (
-
- {t('sidebar.indexBuildingProgress', {
- indexed: indexStatus.indexed,
- discovered: indexStatus.discovered,
- })}
-
- )}
{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': '搜索全部聊天内容…',