fix(web): drop Idle session-list badge (keep working/pending) (#1366)

* fix(web): drop Idle session-list badge (keep working/pending)

Quiet active rows already read as the default via full opacity vs faded
archived; labeling Idle was badge inflation. Pin-in-progress now only
surfaces working/pending so the section does not advertise lack-of-state.

Fixes #1362

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): include deliveryMode on abort send-error restore

Unblocks web typecheck: RawSendError requires deliveryMode, and the
abort-restore path was omitting it (already red on upstream/main CI).

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
HeavyGee
2026-08-05 09:16:41 +08:00
committed by GitHub
co-authored by Cursor
parent 229766dd21
commit 27bc6bade3
6 changed files with 68 additions and 21 deletions
@@ -460,6 +460,58 @@ describe('SessionList collapse behavior', () => {
expect(getProjectPanel().getAttribute('data-open')).toBeNull()
})
it('does not label quiet active sessions as Idle', () => {
const sessions = [
makeSession({
id: 'session-quiet',
active: true,
updatedAt: 100,
metadata: { path: '/work/hapi', name: 'Quiet task', flavor: 'codex' },
}),
]
render(renderSessionList(sessions, null))
expect(screen.getByRole('button', { name: /Quiet task/ })).toBeInTheDocument()
expect(screen.queryByText('Idle')).toBeNull()
expect(screen.queryByTitle('Idle')).toBeNull()
})
it('keeps quiet active sessions in directory groups when pin-in-progress is on', () => {
localStorage.setItem('hapi-pin-in-progress-sessions', 'true')
const sessions = [
makeSession({
id: 'session-running',
active: true,
thinking: true,
updatedAt: 100,
metadata: { path: '/work/hapi', name: 'Running task', flavor: 'codex' },
}),
makeSession({
id: 'session-quiet',
active: true,
updatedAt: 90,
metadata: { path: '/work/hapi', name: 'Quiet task', flavor: 'codex' },
}),
makeSession({
id: 'session-pending',
active: true,
pendingRequestsCount: 1,
updatedAt: 80,
metadata: { path: '/work/other', name: 'Pending task', flavor: 'codex' },
}),
]
render(renderSessionList(sessions, null))
expect(screen.getByTitle('In progress')).toBeInTheDocument()
expect(screen.getByText(/Running \(1\)/)).toBeInTheDocument()
expect(screen.getByText(/pending \(1\)/)).toBeInTheDocument()
expect(screen.queryByText(/Idle \(/)).toBeNull()
// Quiet active stays under its project directory, not an Idle pin bucket.
expect(screen.getByTitle('/work/hapi')).toBeInTheDocument()
expect(screen.getByRole('button', { name: /Quiet task/ })).toBeInTheDocument()
expect(getProjectPanel().getAttribute('data-open')).toBe('true')
})
it('auto-expands the path again when the selected session changes', async () => {
const sessions = [
makeSession({
+13 -7
View File
@@ -45,9 +45,18 @@ type SessionGroup = {
const RUNNING_BUCKETS = [
{ key: 'working', labelKey: 'session.item.running', colorClass: 'text-[var(--app-badge-success-text)]', pulse: true },
{ key: 'pending', labelKey: 'session.item.pending', colorClass: 'text-[var(--app-badge-warning-text)]', pulse: true },
{ key: 'idle', labelKey: 'session.item.idle', colorClass: 'text-[var(--app-hint)]', pulse: false },
] as const
/** Active sessions that warrant the optional pinned In progress section. Quiet actives stay in directory groups. */
function isPinnedInProgressSession(session: SessionSummary): boolean {
if (!session.active) {
return false
}
return session.thinking
|| (session.backgroundTaskCount ?? 0) > 0
|| (session.pendingRequestsCount ?? 0) > 0
}
export type SessionTimeRange = {
start: number | null
end: number | null
@@ -1099,10 +1108,9 @@ export function SessionList(props: {
[visibleSessions, activeMachineFilter]
)
const runningSessions = useMemo(() => {
const buckets: Record<'working' | 'pending' | 'idle', SessionSummary[]> = {
const buckets: Record<'working' | 'pending', SessionSummary[]> = {
working: [],
pending: [],
idle: []
}
if (!pinInProgressSessions) {
return buckets
@@ -1115,9 +1123,8 @@ export function SessionList(props: {
buckets.working.push(session)
} else if ((session.pendingRequestsCount ?? 0) > 0) {
buckets.pending.push(session)
} else {
buckets.idle.push(session)
}
// Quiet active sessions stay in directory groups (no Idle pin bucket).
}
const byRecent = (a: SessionSummary, b: SessionSummary) => b.updatedAt - a.updatedAt
for (const key of Object.keys(buckets) as Array<keyof typeof buckets>) {
@@ -1127,11 +1134,10 @@ export function SessionList(props: {
}, [machineFilteredSessions, pinInProgressSessions])
const runningSessionTotal = runningSessions.working.length
+ runningSessions.pending.length
+ runningSessions.idle.length
const groups = useMemo(
() => groupSessionsByDirectory(
pinInProgressSessions
? machineFilteredSessions.filter((session) => !session.active)
? machineFilteredSessions.filter((session) => !isPinnedInProgressSession(session))
: machineFilteredSessions
),
[machineFilteredSessions, pinInProgressSessions]
-10
View File
@@ -205,16 +205,6 @@ export function SessionRowSummary(props: {
<span className="text-[11px] font-medium leading-none">{t('session.item.pending')}</span>
) : null}
</span>
) : s.active ? (
<span
className="inline-flex shrink-0 items-center gap-1 text-[var(--app-hint)]"
title={t('session.item.idle')}
>
<span className="h-1.5 w-1.5 rounded-full bg-current" aria-hidden="true" />
{!inRunningSection ? (
<span className="text-[11px] font-medium leading-none">{t('session.item.idle')}</span>
) : null}
</span>
) : attention && nestedTooltips && attentionId ? (
<SessionAttentionIndicator
attention={attention}
+1 -2
View File
@@ -145,7 +145,6 @@ export default {
'session.item.pending': 'pending',
'session.item.thinking': 'thinking',
'session.item.running': 'Running',
'session.item.idle': 'Idle',
'session.item.permission': 'Permission required',
'session.item.needsInput': 'Needs input',
'session.item.background': 'Background tasks running',
@@ -735,7 +734,7 @@ export default {
'settings.display.activeSessionsOnly': 'Active sessions only',
'settings.display.activeSessionsOnly.desc': 'Hide inactive sessions in the sidebar. The session you have open stays visible.',
'settings.display.pinInProgressSessions': 'Pin in-progress sessions',
'settings.display.pinInProgressSessions.desc': 'Move active sessions into a pinned In progress section at the top of the sidebar. Off keeps them inside their project directories.',
'settings.display.pinInProgressSessions.desc': 'Move working and pending sessions into a pinned In progress section at the top of the sidebar. Quiet active sessions stay inside their project directories. Off keeps everything in directory groups.',
'settings.display.sessionListStatus': 'Session list status',
'settings.display.sessionListStatus.standard': 'Standard',
'settings.display.sessionListStatus.detailed': 'Detailed',
+1 -2
View File
@@ -145,7 +145,6 @@ export default {
'session.item.pending': '待处理',
'session.item.thinking': '思考中',
'session.item.running': '运行中',
'session.item.idle': '空闲',
'session.item.permission': '需要权限',
'session.item.needsInput': '需要输入',
'session.item.background': '后台任务运行中',
@@ -734,7 +733,7 @@ export default {
'settings.display.activeSessionsOnly': '仅显示活跃会话',
'settings.display.activeSessionsOnly.desc': '在侧边栏隐藏非活跃会话;当前打开的会话仍会保留显示。',
'settings.display.pinInProgressSessions': '置顶进行中会话',
'settings.display.pinInProgressSessions.desc': '将活跃会话移到侧边栏顶部的「进行中」分区。关闭后仍保留在各自项目目录分组中。',
'settings.display.pinInProgressSessions.desc': '将运行中和待处理会话移到侧边栏顶部的「进行中」分区;安静的活跃会话仍留在各自项目目录分组中。关闭后全部保留在目录分组中。',
'settings.display.sessionListStatus': '会话列表状态',
'settings.display.sessionListStatus.standard': '标准',
'settings.display.sessionListStatus.detailed': '详细',
+1
View File
@@ -778,6 +778,7 @@ function SessionPage() {
message: t('chat.sendError.aborted'),
code: 'abort',
scheduledAt: null,
deliveryMode: 'steer',
mutationStarted: true,
restoreSuppressed: false
}