diff --git a/packages/core/session/src/surface.ts b/packages/core/session/src/surface.ts index 60c3633a1b..112faf0bd4 100644 --- a/packages/core/session/src/surface.ts +++ b/packages/core/session/src/surface.ts @@ -85,7 +85,6 @@ export interface SurfaceFoldResult { interface SurfaceFoldState { nodes: SurfaceNode[] nodeBySeq: Map - replacements: SurfaceFoldReplacement[] replaceGeneration: number } @@ -94,13 +93,15 @@ function createFoldState(replaceGeneration = 0): SurfaceFoldState { return { nodes: [], nodeBySeq: new Map(), - replacements: [], replaceGeneration, } } -/** Apply one event to a surface fold state. */ -function applySurfaceEvent(state: SurfaceFoldState, event: SessionEvent): void { +/** Apply one event and return replacement metadata only when one occurred. */ +function applySurfaceEvent( + state: SurfaceFoldState, + event: SessionEvent, +): SurfaceFoldReplacement | undefined { if (!isSurfaceEvent(event)) return if (event.surfaceOp === 'append') { @@ -112,13 +113,12 @@ function applySurfaceEvent(state: SurfaceFoldState, event: SessionEvent): void { return } - const shadowedSeqs = replaceSurface(state, event.seq, event.surfaceOp) - state.replacements.push({ + return { seq: event.seq, start: event.surfaceOp.start, end: event.surfaceOp.end, - shadowedSeqs, - }) + shadowedSeqs: replaceSurface(state, event.seq, event.surfaceOp), + } } /** Apply one positional replacement and return the nodes it removed. */ @@ -170,13 +170,14 @@ function replaceSurface( */ export function foldSurface(events: readonly SessionEvent[]): SurfaceFoldResult { const state = createFoldState() - for (const event of events) applySurfaceEvent(state, event) + const replacements: SurfaceFoldReplacement[] = [] + for (const event of events) { + const replacement = applySurfaceEvent(state, event) + if (replacement !== undefined) replacements.push(replacement) + } return { nodes: state.nodes.map(node => ({ ...node })), - replacements: state.replacements.map(replacement => ({ - ...replacement, - shadowedSeqs: [...replacement.shadowedSeqs], - })), + replacements, } } diff --git a/packages/core/session/tests/surface.spec.ts b/packages/core/session/tests/surface.spec.ts index e127b4a4b3..071bf1af86 100644 --- a/packages/core/session/tests/surface.spec.ts +++ b/packages/core/session/tests/surface.spec.ts @@ -33,6 +33,19 @@ describe('SurfaceManager', () => { expect(foldSurface(s.events).replacements[0]!.shadowedSeqs).toEqual([0]) }) + it('does not retain fold-only replacement history in incremental state', () => { + const s = new Session(SessionId('incremental-state')) + s.append('user/message', { content: [{ type: 'text', text: 'a' }], source: { kind: 'user' } }, { surfaceOp: 'append' }) + s.append('assistant/message', { turn: 1, step: 1, content: [{ type: 'text', text: 'b' }] }, { surfaceOp: { op: 'replace', start: 0, end: 0 } }) + + expect(s.surface.nodes).toEqual([{ seq: 1, prev: null, next: null }]) + const manager = s.surface as unknown as { _state: object } + expect(Object.hasOwn(manager._state, 'replacements')).toBe(false) + expect(foldSurface(s.events).replacements).toEqual([ + { seq: 1, start: 0, end: 0, shadowedSeqs: [0] }, + ]) + }) + it('foldSurface reports the same invalid replacement failures as the incremental manager', () => { const s = new Session(SessionId('shared-fold-invalid')) s.append('user/message', { content: [{ type: 'text', text: 'a' }], source: { kind: 'user' } }, { surfaceOp: 'append' })