fix(desktop): key the completed-unread dot on the focused session, not the selected one

This commit is contained in:
Jefftree
2026-07-29 11:36:16 -04:00
committed by Teknium
parent 77be513de1
commit a5b50437e4
2 changed files with 99 additions and 1 deletions

View File

@@ -182,7 +182,9 @@ function handleTransition(previous: ClientSessionState | null, next: ClientSessi
} else if (!next.busy && wasWorking) {
markSettled(storedId)
if (storedId !== $selectedStoredSessionId.get()) {
// FOCUSED, not selected: a session finishing in the tile the user is
// watching is already seen, and a tile is never the primary selection.
if (storedId !== $focusedStoredSessionId.get()) {
const cur = $unreadFinishedSessionIds.get()
if (!cur.includes(storedId)) {
@@ -912,6 +914,18 @@ export const $focusedSessionState = computed([$focusedRuntimeId, $sessionStates]
export const selectionHomesToWorkspace = (selected: null | string, tiles: readonly SessionTile[]): boolean =>
!(selected && tiles.some(t => t.storedSessionId === selected))
// Bringing a finished session to the front clears its green dot. Keyed on the
// FOCUSED session, not the selected one: a tile is never $selectedStoredSessionId,
// and a tile tab click goes through activateTreePane rather than focusOpenSession,
// so this is the one hook that catches every way a tile reaches the front.
$focusedStoredSessionId.listen(focused => {
const cur = $unreadFinishedSessionIds.get()
if (focused && cur.includes(focused)) {
$unreadFinishedSessionIds.set(cur.filter(id => id !== focused))
}
})
// Cold-start restore is the one selection change that is NOT a navigation: the
// route already pointed at the primary session before the window loaded, and
// homing on it would front the workspace tab over the PERSISTED active tab —

View File

@@ -0,0 +1,84 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
// The completed-unread dot is keyed on the FOCUSED session, not the selected
// one. A tile is never $selectedStoredSessionId, so keying either half on the
// selection left a tiled session's dot green with no way to clear it.
describe('completed-unread dot follows the focused session', () => {
beforeEach(() => {
vi.resetModules()
})
afterEach(() => {
vi.resetModules()
})
async function setup() {
const tree = await import('@/components/pane-shell/tree/store')
const model = await import('@/components/pane-shell/tree/model')
const { registry } = await import('@/contrib/registry')
const { createClientSessionState } = await import('@/lib/chat-runtime')
const session = await import('./session')
const states = await import('./session-states')
for (const id of ['workspace', 'session-tile:tiled']) {
registry.register({
area: 'panes',
data: id === 'workspace' ? { placement: 'main', uncloseable: true } : { placement: 'main' },
id,
render: () => null,
title: id
})
}
// The workspace holds the primary chat, a second zone holds the tile.
tree.declareDefaultTree(
model.split('row', [
model.group(['workspace'], { active: 'workspace', id: 'grp-main' }),
model.group(['session-tile:tiled'], { active: 'session-tile:tiled', id: 'grp-tile' })
])
)
session.$unreadFinishedSessionIds.set([])
session.$selectedStoredSessionId.set('primary')
const finishTurn = (storedSessionId: string) => {
const working = { ...createClientSessionState(null), busy: true, storedSessionId }
states.publishSessionState(`rt-${storedSessionId}`, working)
states.publishSessionState(`rt-${storedSessionId}`, { ...working, busy: false })
}
return { finishTurn, session, tree }
}
it('clears the dot when an already-open tile is fronted', async () => {
const { finishTurn, session, tree } = await setup()
tree.noteActiveTreeGroup('grp-main')
finishTurn('tiled')
expect(session.$unreadFinishedSessionIds.get()).toEqual(['tiled'])
// Fronting the tile is what a tab click does. Before the fix nothing on
// this path cleared the marker, so the dot stayed green.
tree.noteActiveTreeGroup('grp-tile')
expect(session.$unreadFinishedSessionIds.get()).toEqual([])
})
it('never marks a tile that finishes while it is the focused one', async () => {
const { finishTurn, session, tree } = await setup()
tree.noteActiveTreeGroup('grp-tile')
finishTurn('tiled')
expect(session.$unreadFinishedSessionIds.get()).toEqual([])
})
it('marks the primary session when a tile has focus', async () => {
const { finishTurn, session, tree } = await setup()
tree.noteActiveTreeGroup('grp-tile')
finishTurn('primary')
expect(session.$unreadFinishedSessionIds.get()).toEqual(['primary'])
})
})