From b908d929f746a3447dfefcfa5ab1f1d7540d41da Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Thu, 15 Oct 2026 23:16:09 -0700 Subject: [PATCH] fix(session-history): keep scan notices from burying the session list (#29392) Every session the scanner read only partly (for example a Codex transcript on an SSH host with a record over the 10 MiB cap) adds its own notice row above the list, outside the list's scroll area. With dozens of them the panel filled with notices and the sessions could not be reached. Notices now sit in one bounded area that scrolls on its own. Co-authored-by: Claude Opus 5.5 --- .../AiVaultScanIssueBanners.test.tsx | 42 +++++++++++++ .../right-sidebar/AiVaultScanIssueBanners.tsx | 62 +++++++++++-------- 2 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.test.tsx diff --git a/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.test.tsx b/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.test.tsx new file mode 100644 index 00000000000..df15ba89029 --- /dev/null +++ b/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.test.tsx @@ -0,0 +1,42 @@ +// @vitest-environment happy-dom +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, expect, it } from 'vitest' +import type { AiVaultListResult, AiVaultSession } from '../../../../shared/ai-vault-types' +import { AiVaultScanIssueBanners } from './AiVaultScanIssueBanners' + +afterEach(() => { + cleanup() +}) + +it('keeps many partial-read notices in a bounded scroll area above the session list', () => { + const notices = Array.from({ length: 40 }, (_, index) => ({ + executionHostId: 'ssh:box' as const, + agent: 'codex' as const, + kind: 'notice' as const, + path: `/home/u/.codex/sessions/rollout-${index}.jsonl`, + message: `Skipped 1 oversized transcript record over the 10.0 MiB limit (12.0 MiB at byte ${index}). The rest of the session was read.` + })) + const scanResult: AiVaultListResult = { + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: banners only read the session count. + sessions: [{} as AiVaultSession], + issues: [...notices, { agent: 'codex', path: '/broken.jsonl', message: 'Unreadable' }], + scannedAt: '2026-10-15T00:00:00.000Z' + } + + render() + + const region = screen.getByTestId('ai-vault-scan-notices') + expect(region.className).toContain('max-h-24') + expect(region.className).toContain('overflow-y-auto') + expect(region.children).toHaveLength(42) + expect(region.textContent).toContain('1 transcript skipped') +}) + +it('renders no notice area for a clean scan', () => { + render( + + ) + expect(screen.queryByTestId('ai-vault-scan-notices')).toBeNull() +}) diff --git a/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.tsx b/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.tsx index 46171da9cb8..93c76e0437b 100644 --- a/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.tsx +++ b/src/renderer/src/components/right-sidebar/AiVaultScanIssueBanners.tsx @@ -18,6 +18,10 @@ export function AiVaultScanIssueBanners({ const blocking = blockingAiVaultScanIssue(scanResult) const skippedTranscriptCount = skippedAiVaultTranscriptCount(scanResult) + const notices = aiVaultScanNoticeIssues(scanResult) + const reasons = skippedAiVaultTranscriptReasons(scanResult) + const hasNoticeRows = notices.length > 0 || skippedTranscriptCount > 0 || reasons.length > 0 + return ( <> {blocking ? ( @@ -25,35 +29,43 @@ export function AiVaultScanIssueBanners({ {blocking.message} ) : null} - {aiVaultScanNoticeIssues(scanResult).map((issue) => ( + {hasNoticeRows ? ( + // Why bounded: one notice per partly-read session can outgrow the panel and hide the list.
- {issue.message} -
- ))} - {skippedTranscriptCount > 0 ? ( -
- {translate( - 'auto.components.right.sidebar.AiVaultPanel.transcriptsSkipped', - '{{count}} transcript skipped', - { count: skippedTranscriptCount } - )} + {notices.map((issue) => ( +
+ {issue.message} +
+ ))} + {skippedTranscriptCount > 0 ? ( +
+ {translate( + 'auto.components.right.sidebar.AiVaultPanel.transcriptsSkipped', + '{{count}} transcript skipped', + { count: skippedTranscriptCount } + )} +
+ ) : null} + {reasons.map((reason) => ( +
+ {reason} +
+ ))}
) : null} - {skippedAiVaultTranscriptReasons(scanResult).map((reason) => ( -
- {reason} -
- ))} ) }