fix(desktop): keep pinned + recent sessions visible across compression
Long-running sessions auto-compress: the gateway ends the original session and surfaces the live continuation under a new id (list_sessions_rich projects the root forward to its tip). Two symptoms fell out of the id rotation: - A pinned session "vanished" — the pin is stored as the pre-compression root id, but the sidebar only matched on the live id, so it was filtered out. Pins now resolve on the durable lineage-root id (`_lineage_root_id`, already surfaced by the projection): the sidebar indexes sessions by both ids, pin/ unpin and reorder operate on the durable id, and `sessionPinId()` is shared with the Cmd+P toggle. Existing pins keep working with no migration. - A freshly-continued session was missing from the list until you ungrouped + "load 50 more" — the list paginated by original start time, so an old-but- active conversation sat past the first page. The desktop now requests `order=recent` (GET /api/sessions gains an `order` param backed by the existing recency CTE), surfacing live continuations on the first page.
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { SessionInfo } from '@/types/hermes'
|
||||
|
||||
import { sessionPinId } from './session'
|
||||
|
||||
const session = (over: Partial<SessionInfo>): SessionInfo => ({
|
||||
archived: false,
|
||||
cwd: null,
|
||||
ended_at: null,
|
||||
id: 'live',
|
||||
input_tokens: 0,
|
||||
is_active: false,
|
||||
last_active: 0,
|
||||
message_count: 0,
|
||||
model: null,
|
||||
output_tokens: 0,
|
||||
preview: null,
|
||||
source: null,
|
||||
started_at: 0,
|
||||
title: null,
|
||||
tool_call_count: 0,
|
||||
...over
|
||||
})
|
||||
|
||||
describe('sessionPinId', () => {
|
||||
it('uses the live id when there is no compression lineage', () => {
|
||||
expect(sessionPinId(session({ id: 'abc' }))).toBe('abc')
|
||||
})
|
||||
|
||||
it('uses the lineage root so a pin survives compression', () => {
|
||||
// After auto-compression the entry surfaces under a fresh tip id but keeps
|
||||
// the original root — pinning on the root keeps the pin stable.
|
||||
expect(sessionPinId(session({ id: 'tip', _lineage_root_id: 'root' }))).toBe('root')
|
||||
})
|
||||
})
|
||||
@@ -16,6 +16,12 @@ function updateAtom<T>(store: AppAtom<T>, next: Updater<T>) {
|
||||
store.set(typeof next === 'function' ? (next as (current: T) => T)(store.get()) : next)
|
||||
}
|
||||
|
||||
/** Durable id for pinning. Auto-compression rotates a conversation's session
|
||||
* id (root -> continuation tip), so pins keyed on the live id evaporate. The
|
||||
* lineage root is stable across every compression, so we pin on that. */
|
||||
export const sessionPinId = (session: Pick<SessionInfo, '_lineage_root_id' | 'id'>): string =>
|
||||
session._lineage_root_id ?? session.id
|
||||
|
||||
export const $connection = atom<HermesConnection | null>(null)
|
||||
export const $gatewayState = atom('idle')
|
||||
export const $sessions = atom<SessionInfo[]>([])
|
||||
|
||||
Reference in New Issue
Block a user