round 2: address manual compaction review findings

This commit is contained in:
Hypatia May
2026-07-30 18:15:12 +08:00
parent eda7c76eb1
commit 4de4d693a2
22 changed files with 531 additions and 152 deletions

View File

@@ -38,17 +38,22 @@ function request(
resultSeq?: number,
replacementSeq?: number,
): RequestView {
return {
purpose,
const base = {
startSeq,
turn: 1,
step: purpose === 'assistant' ? 1 : 0,
startedAt: startSeq,
completedAt: startSeq + 1,
status: 'complete',
status: 'complete' as const,
...(resultSeq === undefined ? {} : { resultSeq }),
...(replacementSeq === undefined ? {} : { replacementSeq }),
}
return purpose === 'assistant'
? { ...base, purpose, turn: 1, step: 1 }
: {
...base,
purpose,
turn: 1,
step: 0,
...(replacementSeq === undefined ? {} : { replacementSeq }),
}
}
describe('trajectory context branches', () => {

View File

@@ -5,7 +5,9 @@
*/
import { afterEach, describe, expect, it } from 'vitest'
import { cleanup, render, screen } from '@testing-library/react'
import type { ConversationSnapshot } from '@deepseek-ai/dsh-client-runtime/client'
import type {
ConversationSnapshot, RequestView,
} from '@deepseek-ai/dsh-client-runtime/client'
import { TrajectoryGroupHeader } from '../src/client/TrajectoryGroupHeader.tsx'
import { TrajectoryTurn } from '../src/client/TrajectoryTurn.tsx'
import { TrajectoryTurnHeader } from '../src/client/TrajectoryTurnHeader.tsx'
@@ -161,6 +163,49 @@ describe('deriveTrajectoryLayout', () => {
expect(turns[1]?.groups.flatMap(g => g.cells.map(c => c.text))).toEqual(['second', 'ok2'])
})
it('places standalone compaction chronologically in its own between-turn section', () => {
const nodes = [
{ kind: 'user', seq: 1, time: 1_000, content: [{ type: 'text', text: 'first' }], source: null },
{
kind: 'assistant', seq: 2, time: 2_000, turn: 1, step: 1,
blocks: [{ kind: 'text', text: 'before compaction' }],
},
{ kind: 'user', seq: 5, time: 5_000, content: [{ type: 'text', text: 'second' }], source: null },
{
kind: 'assistant', seq: 6, time: 6_000, turn: 2, step: 1,
blocks: [{ kind: 'text', text: 'after compaction' }],
},
] as unknown as ConversationSnapshot['nodes']
const compaction: RequestView = {
purpose: 'compaction',
startSeq: 3,
turn: null,
step: 0,
startedAt: 3_000,
completedAt: 4_000,
status: 'complete',
summary: [{ type: 'text', text: 'standalone summary' }],
}
const turns = deriveTrajectoryLayout({
codeDispatches: new Map(),
nodes,
partial: null,
runningCalls: [],
requests: [compaction],
})
expect(turns.map(turn => turn.turn)).toEqual([1, null, 2])
expect(turns[1]?.groups).toMatchObject([{
title: 'Compaction 3',
cells: [{
kind: 'compacted',
sourceSeq: 3,
text: 'standalone summary',
}],
}])
})
it('keeps usage and a meaningful summary when assistant has no text block', () => {
const nodes = [
{

View File

@@ -277,6 +277,41 @@ describe('tab switching in ConversationRoot', () => {
expect(screen.queryByRole('complementary', { name: 'Event details' })).toBeNull()
})
it('labels a standalone compaction as between-turn work in the ledger and inspector', async () => {
const nodes = [
{ kind: 'user', seq: 1, time: 1_000, content: [], source: null },
{
kind: 'assistant', seq: 2, time: 2_000, turn: 1, step: 1,
blocks: [{ kind: 'text', text: 'before' }],
},
{ kind: 'user', seq: 5, time: 5_000, content: [], source: null },
{
kind: 'assistant', seq: 6, time: 6_000, turn: 2, step: 1,
blocks: [{ kind: 'text', text: 'after' }],
},
] as unknown as ConversationSnapshot['nodes']
const compaction: RequestView = {
purpose: 'compaction',
startSeq: 3,
turn: null,
step: 0,
startedAt: 3_000,
completedAt: 4_000,
status: 'complete',
summary: [{ type: 'text', text: 'standalone summary' }],
}
const b = await bench(historySnapshot(nodes, { requests: [compaction] }))
const view = mount(b.slots, nodes)
fireEvent.click(screen.getByRole('tab', { name: 'Trajectory' }))
expect(screen.getByText('Between turns')).toBeTruthy()
expect(view.container.textContent).not.toContain('Turn null')
fireEvent.click(screen.getByRole('button', { name: 'Request #2 · Compaction' }))
expect(screen.getByText('Compaction · Between turns')).toBeTruthy()
expect(view.container.textContent).not.toContain('Turn null')
})
it('dragging the overview focuses overlapping records without filtering the ledger', async () => {
const b = await bench()
mount(b.slots)
@@ -386,6 +421,44 @@ describe('timeline projection', () => {
})
})
it('projects between-turn compaction without inventing a turn boundary', () => {
const withStandaloneCompaction = [
{
turn: 1,
groups: [{
title: 'Step 1',
cells: [{ index: 1, kind: 'message', text: 'before', timeSeconds: 0 }],
}],
},
{
turn: null,
groups: [{
title: 'Compaction 3',
cells: [{ index: 2, kind: 'compacted', text: 'summary', timeSeconds: 0 }],
}],
},
{
turn: 2,
groups: [{
title: 'Step 1',
cells: [{ index: 3, kind: 'message', text: 'after', timeSeconds: 0 }],
}],
},
] satisfies readonly TrajectoryTurnModel[]
expect(deriveTrajectoryTimeline(withStandaloneCompaction)).toMatchObject({
spans: [
{ index: 1, start: 0, end: 1 },
{ index: 2, start: 1, end: 2 },
{ index: 3, start: 2, end: 3 },
],
turnBoundaries: [
{ turn: 1, time: 0 },
{ turn: 2, time: 2 },
],
})
})
it('empty inputs produce no model and the standalone view reports its empty form', () => {
expect(deriveTrajectoryTimeline([])).toBeNull()
render(createElement(