diff --git a/web/src/components/SessionList.test.ts b/web/src/components/SessionList.test.ts index df1dc60c1..598c0bbea 100644 --- a/web/src/components/SessionList.test.ts +++ b/web/src/components/SessionList.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest' import type { SessionSummary } from '@/types/api' -import { deduplicateSessionsByAgentId, getVisibleSessionPreview, normalizeSearch, sessionMatchesQuery } from './SessionList' +import { deduplicateSessionsByAgentId, expandSelectedSessionCollapseOverrides, getVisibleSessionPreview, normalizeSearch, sessionMatchesQuery } from './SessionList' function makeSession(overrides: Partial & { id: string }): SessionSummary { return { @@ -102,7 +102,7 @@ describe('session list search helpers', () => { }) describe('getVisibleSessionPreview', () => { - it('keeps selected and active sessions inside the collapsed preview', () => { + it('does not promote the selected session in collapsed previews', () => { const sessions = Array.from({ length: 6 }, (_, index) => makeSession({ id: `s-${index + 1}`, active: index === 4, @@ -111,11 +111,10 @@ describe('getVisibleSessionPreview', () => { })) const preview = getVisibleSessionPreview(sessions, { - selectedSessionId: 's-6', limit: 3 }) - expect(preview.map(session => session.id)).toEqual(['s-6', 's-5', 's-1']) + expect(preview.map(session => session.id)).toEqual(['s-5', 's-1', 's-2']) }) it('returns all sessions when expanded', () => { @@ -127,3 +126,34 @@ describe('getVisibleSessionPreview', () => { expect(getVisibleSessionPreview(sessions, { expanded: true, limit: 2 })).toHaveLength(4) }) }) + + +describe('expandSelectedSessionCollapseOverrides', () => { + it('expands collapsed project, machine, and session preview overrides for selected sessions', () => { + const overrides = new Map([ + ['machine-1::/work/hapi', true], + ['sessions::machine-1::/work/hapi', true], + ['machine::machine-1', true] + ]) + + const result = expandSelectedSessionCollapseOverrides(overrides, { + key: 'machine-1::/work/hapi', + machineId: 'machine-1' + }) + + expect(result.has('machine-1::/work/hapi')).toBe(false) + expect(result.get('sessions::machine-1::/work/hapi')).toBe(false) + expect(result.has('machine::machine-1')).toBe(false) + }) + + it('sets missing session preview override to expanded', () => { + const overrides = new Map() + + const result = expandSelectedSessionCollapseOverrides(overrides, { + key: 'machine-1::/work/hapi', + machineId: 'machine-1' + }) + + expect(result.get('sessions::machine-1::/work/hapi')).toBe(false) + }) +}) diff --git a/web/src/components/SessionList.tsx b/web/src/components/SessionList.tsx index c7a6356f5..8d3ad16f0 100644 --- a/web/src/components/SessionList.tsx +++ b/web/src/components/SessionList.tsx @@ -176,6 +176,36 @@ function groupSessionsByDirectory(sessions: SessionSummary[]): SessionGroup[] { }) } + +export function expandSelectedSessionCollapseOverrides( + overrides: Map, + group: { key: string; machineId: string | null } +): Map { + const next = new Map(overrides) + let changed = false + + // Expand project group if collapsed. Project and machine keys use true = collapsed. + if (overrides.has(group.key) && overrides.get(group.key)) { + next.delete(group.key) + changed = true + } + + // Session preview keys use inverted semantics: false = expanded, true/missing = collapsed. + const sessionPreviewKey = `sessions::${group.key}` + if (overrides.get(sessionPreviewKey) !== false) { + next.set(sessionPreviewKey, false) + changed = true + } + + const machineKey = `machine::${group.machineId ?? UNKNOWN_MACHINE_ID}` + if (overrides.has(machineKey) && overrides.get(machineKey)) { + next.delete(machineKey) + changed = true + } + + return changed ? next : overrides +} + function groupByMachine( groups: SessionGroup[], resolveMachineLabel: (id: string | null) => string @@ -398,7 +428,6 @@ export function getVisibleSessionPreview( sessions: SessionSummary[], options: { expanded?: boolean - selectedSessionId?: string | null limit?: number } = {} ): SessionSummary[] { @@ -413,11 +442,6 @@ export function getVisibleSessionPreview( visible.push(session) } - const selectedSession = options.selectedSessionId - ? sessions.find(session => session.id === options.selectedSessionId) - : undefined - if (selectedSession) addSession(selectedSession) - for (const session of sessions) { if (visible.length >= limit) break if (session.active) addSession(session) @@ -438,19 +462,21 @@ function SessionListSearch(props: { const { t } = useTranslation() return (
- +
+ +
props.onChange(event.target.value)} placeholder={t('sessions.search.placeholder')} - className="w-full rounded-lg border border-[var(--app-border)] bg-[var(--app-bg)] py-1.5 pl-8 pr-8 text-sm text-[var(--app-fg)] outline-none transition-colors placeholder:text-[var(--app-hint)] focus:border-[var(--app-link)]" + className="w-full appearance-none rounded-lg border border-[var(--app-border)] bg-[var(--app-bg)] py-1.5 pl-8 pr-8 text-sm text-[var(--app-fg)] outline-none transition-colors placeholder:text-[var(--app-hint)] focus:border-[var(--app-link)] [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden" /> {props.value ? (