feat(web): merge compact status and summary cards

This commit is contained in:
Yichen Jiang
2026-08-08 14:11:17 +08:00
parent cc407a2672
commit 1db327ea6c
47 changed files with 725 additions and 121 deletions

View File

@@ -36,7 +36,10 @@ describe('compaction checkpoint recognition', () => {
it('recognizes a checkpoint carrying the seam-canonical source', () => {
const adapter = new TranscriptAdapter()
adapter.reset([canonicalCheckpoint(1)])
expect(adapter.nodes()).toEqual([{ kind: 'compaction', seq: 1, time: 1_700_000_000_001, summary: null }])
expect(adapter.nodes()).toEqual([{
kind: 'compaction', seq: 1, time: 1_700_000_000_001, summary: null,
summaryEventSeq: null, shadowedItemCount: null, shadowedTokenCount: null,
}])
})
it("agrees with the seam's own predicate on the source it recognizes", () => {

View File

@@ -92,8 +92,19 @@ export const ev = {
at(seq, { type: 'command/run', data: { commandId, name, args, source: { kind: 'user' } } }),
commandRunWithoutInput: (seq: number, commandId: string, name: string): SessionEvent =>
at(seq, { type: 'command/run', data: { commandId, name, source: { kind: 'user' } } }),
commandDone: (seq: number, commandId: string, kind: 'success' | 'error' = 'success', text?: string): SessionEvent =>
at(seq, { type: 'command/done', data: { commandId, kind, ...text === undefined ? {} : { text } } }),
commandDone: (
seq: number,
commandId: string,
kind: 'success' | 'error' = 'success',
text?: string,
sourceEventSeq?: number,
): SessionEvent =>
at(seq, { type: 'command/done', data: {
commandId,
kind,
...text === undefined ? {} : { text },
...sourceEventSeq === undefined ? {} : { sourceEventSeq },
} }),
/** A compaction's log-only `compact/summary` provenance record. */
compactSummary: (seq: number, summary: string, start: number, end: number): SessionEvent =>
at(seq, { type: 'compact/summary', data: {

View File

@@ -223,8 +223,14 @@ describe('TranscriptAdapter', () => {
checkpoint(5, 4, { start: 2, end: 3, sourceEventSeqs: [4, 2, 3] }),
])
expect(adapter.nodes().filter(n => n.kind === 'compaction')).toEqual([
{ kind: 'compaction', seq: 2, time: 1_700_000_000_002, summary: 'first' },
{ kind: 'compaction', seq: 5, time: 1_700_000_000_005, summary: 'second' },
{
kind: 'compaction', seq: 2, time: 1_700_000_000_002, summary: 'first',
summaryEventSeq: 1, shadowedItemCount: 2, shadowedTokenCount: 100,
},
{
kind: 'compaction', seq: 5, time: 1_700_000_000_005, summary: 'second',
summaryEventSeq: 4, shadowedItemCount: 2, shadowedTokenCount: 100,
},
])
})
@@ -296,7 +302,7 @@ describe('TranscriptAdapter', () => {
...(summary === undefined ? [] : [summary]),
checkpoint(2, 1, { start: 0, end: 0, sourceEventSeqs: [1, 0] }),
])
expect(adapter.nodes()).toEqual([
expect(adapter.nodes()).toMatchObject([
{ kind: 'compaction', seq: 2, time: 1_700_000_000_002, summary: null },
])
})
@@ -310,7 +316,10 @@ describe('TranscriptAdapter', () => {
checkpoint(2, 1, { start: 0, end: 0, sourceEventSeqs: [1, 0] }),
])
expect(adapter.nodes()).toEqual([
{ kind: 'compaction', seq: 2, time: 1_700_000_000_002, summary: '可用摘要' },
{
kind: 'compaction', seq: 2, time: 1_700_000_000_002, summary: '可用摘要',
summaryEventSeq: 1, shadowedItemCount: 2, shadowedTokenCount: 100,
},
])
})
@@ -324,7 +333,10 @@ describe('TranscriptAdapter', () => {
source: { kind: 'plugin', plugin: 'compact' },
}),
})])
expect(adapter.nodes()).toEqual([{ kind: 'compaction', seq: 2, time: 1_700_000_000_002, summary: null }])
expect(adapter.nodes()).toEqual([{
kind: 'compaction', seq: 2, time: 1_700_000_000_002, summary: null,
summaryEventSeq: null, shadowedItemCount: null, shadowedTokenCount: null,
}])
})
it('skips a non-summary provenance seq before reaching the real one', () => {
@@ -468,20 +480,22 @@ describe('TranscriptAdapter', () => {
expect(adapter.nodes().map(n => n.kind)).toEqual(['user', 'command'])
})
it('renders the /compact row alongside the marker its own command produced', () => {
// The row that reports the compaction is a command node; dropping command
// folding would delete it together with every other slash-command row.
it('preserves the domain-event link for the UI to fold a /compact row into its marker', () => {
const adapter = new TranscriptAdapter()
adapter.reset([
ev.user(0, '压缩前的问题'),
ev.commandRun(1, 'cmd-compact', 'compact'),
compactSummary(2, [{ type: 'text', text: '手动压缩摘要' }]),
checkpoint(3, 2, { start: 0, end: 0, sourceEventSeqs: [2, 0] }),
ev.commandDone(4, 'cmd-compact', 'success', '已压缩'),
ev.commandDone(4, 'cmd-compact', 'success', '已压缩', 2),
])
const nodes = adapter.nodes()
expect(nodes.map(n => [n.kind, n.seq])).toEqual([['user', 0], ['command', 1], ['compaction', 3]])
expect(nodes[1]).toMatchObject({ name: 'compact', outcome: { kind: 'success', text: '已压缩' } })
expect(nodes[1]).toMatchObject({
name: 'compact',
outcome: { kind: 'success', text: '已压缩', sourceEventSeq: 2 },
})
expect(nodes[2]).toMatchObject({ kind: 'compaction', summaryEventSeq: 2 })
})
})