fix(tui): restore resumed transcript lineage

This commit is contained in:
Brooklyn Nicholson 2026-04-26 15:16:12 -05:00
parent 350ee1bf23
commit d4dde6b5f2
11 changed files with 537 additions and 49 deletions

View file

@ -1,7 +1,26 @@
import { describe, expect, it } from 'vitest'
import { toTranscriptMessages } from '../domain/messages.js'
import { upsert } from '../lib/messages.js'
describe('toTranscriptMessages', () => {
it('preserves assistant tool-call rows so resume does not drop prior turns', () => {
const rows = [
{ role: 'user', text: 'first prompt' },
{ role: 'tool', context: 'repo', name: 'search_files', text: 'ignored raw result' },
{ role: 'assistant', text: 'first answer' },
{ role: 'user', text: 'second prompt' }
]
expect(toTranscriptMessages(rows).map(msg => [msg.role, msg.text])).toEqual([
['user', 'first prompt'],
['assistant', 'first answer'],
['user', 'second prompt']
])
expect(toTranscriptMessages(rows)[1]?.tools?.[0]).toContain('Search Files')
})
})
describe('upsert', () => {
it('appends when last role differs', () => {
expect(upsert([{ role: 'user', text: 'hi' }], 'assistant', 'hello')).toHaveLength(2)

View file

@ -1,14 +1,18 @@
import { describe, expect, it } from 'vitest'
import {
boundedLiveRenderText,
buildToolTrailLine,
edgePreview,
estimateRows,
estimateTokensRough,
fmtK,
isToolTrailResultLine,
lastCotTrailIndex,
parseToolTrailResultLine,
pasteTokenLabel,
sameToolTrailGroup
sameToolTrailGroup,
splitToolDuration
} from '../lib/text.js'
describe('isToolTrailResultLine', () => {
@ -19,6 +23,16 @@ describe('isToolTrailResultLine', () => {
})
})
describe('buildToolTrailLine', () => {
it('puts completion duration inline before the result marker', () => {
const line = buildToolTrailLine('read_file', 'x', false, '', 0.94)
expect(line).toBe('Read File("x") (0.9s) ✓')
expect(parseToolTrailResultLine(line)).toEqual({ call: 'Read File("x") (0.9s)', detail: '', mark: '✓' })
expect(splitToolDuration('Read File("x") (0.9s)')).toEqual({ label: 'Read File("x")', duration: ' (0.9s)' })
})
})
describe('lastCotTrailIndex', () => {
it('finds last non-result line', () => {
expect(lastCotTrailIndex(['a ✓', 'thinking…'])).toBe(1)
@ -68,6 +82,28 @@ describe('estimateTokensRough', () => {
})
})
describe('boundedLiveRenderText', () => {
it('preserves short live text verbatim', () => {
expect(boundedLiveRenderText('one\ntwo', { maxChars: 100, maxLines: 10 })).toBe('one\ntwo')
})
it('keeps the live tail by character budget', () => {
const out = boundedLiveRenderText('abcdefghij', { maxChars: 4, maxLines: 10 })
expect(out).toContain('ghij')
expect(out).toContain('omitted')
expect(out).not.toContain('abcdef')
})
it('keeps the live tail by line budget', () => {
const out = boundedLiveRenderText(['a', 'b', 'c', 'd'].join('\n'), { maxChars: 100, maxLines: 2 })
expect(out).toContain('c\nd')
expect(out).toContain('omitted 2 lines')
expect(out).not.toContain('a\nb')
})
})
describe('edgePreview', () => {
it('keeps both ends for long text', () => {
expect(edgePreview('Vampire Bondage ropes slipped from her neck, still stained with blood', 8, 18)).toBe(