mirror of
https://github.com/stablyai/orca.git
synced 2026-11-06 03:57:35 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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(<AiVaultScanIssueBanners scanResult={scanResult} />)
|
||||
|
||||
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(
|
||||
<AiVaultScanIssueBanners
|
||||
scanResult={{ sessions: [], issues: [], scannedAt: '2026-10-15T00:00:00.000Z' }}
|
||||
/>
|
||||
)
|
||||
expect(screen.queryByTestId('ai-vault-scan-notices')).toBeNull()
|
||||
})
|
||||
@@ -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}
|
||||
</div>
|
||||
) : null}
|
||||
{aiVaultScanNoticeIssues(scanResult).map((issue) => (
|
||||
{hasNoticeRows ? (
|
||||
// Why bounded: one notice per partly-read session can outgrow the panel and hide the list.
|
||||
<div
|
||||
// Message is part of the key: one host can report several distinct
|
||||
// messages for the same path, and a colliding key drops those rows.
|
||||
key={`${issue.executionHostId ?? 'local'}:${issue.kind}:${issue.agent}:${issue.path}:${issue.message}`}
|
||||
className={`border-b border-sidebar-border px-3 py-1.5 text-[11px] ${
|
||||
issue.kind === 'host' ? 'text-destructive' : 'text-muted-foreground'
|
||||
}`}
|
||||
data-testid="ai-vault-scan-notices"
|
||||
className="scrollbar-sleek max-h-24 shrink-0 overflow-y-auto"
|
||||
>
|
||||
{issue.message}
|
||||
</div>
|
||||
))}
|
||||
{skippedTranscriptCount > 0 ? (
|
||||
<div className="border-b border-sidebar-border px-3 py-1.5 text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.right.sidebar.AiVaultPanel.transcriptsSkipped',
|
||||
'{{count}} transcript skipped',
|
||||
{ count: skippedTranscriptCount }
|
||||
)}
|
||||
{notices.map((issue) => (
|
||||
<div
|
||||
// Message is part of the key: one host can report several distinct
|
||||
// messages for the same path, and a colliding key drops those rows.
|
||||
key={`${issue.executionHostId ?? 'local'}:${issue.kind}:${issue.agent}:${issue.path}:${issue.message}`}
|
||||
className={`border-b border-sidebar-border px-3 py-1.5 text-[11px] ${
|
||||
issue.kind === 'host' ? 'text-destructive' : 'text-muted-foreground'
|
||||
}`}
|
||||
>
|
||||
{issue.message}
|
||||
</div>
|
||||
))}
|
||||
{skippedTranscriptCount > 0 ? (
|
||||
<div className="border-b border-sidebar-border px-3 py-1.5 text-[11px] text-muted-foreground">
|
||||
{translate(
|
||||
'auto.components.right.sidebar.AiVaultPanel.transcriptsSkipped',
|
||||
'{{count}} transcript skipped',
|
||||
{ count: skippedTranscriptCount }
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
{reasons.map((reason) => (
|
||||
<div
|
||||
key={reason}
|
||||
className="border-b border-sidebar-border px-3 py-1.5 text-[11px] text-muted-foreground"
|
||||
>
|
||||
{reason}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{skippedAiVaultTranscriptReasons(scanResult).map((reason) => (
|
||||
<div
|
||||
key={reason}
|
||||
className="border-b border-sidebar-border px-3 py-1.5 text-[11px] text-muted-foreground"
|
||||
>
|
||||
{reason}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user