fix(sync): deduplicate overlapping delta after coalesced part.updated (#916)

* fix: hide archived section and empty folders when no sessions remain

- Only push archived group in useSessionGrouping when there are archived
  sessions, preventing an empty archived section from rendering
- Hide empty folders in archived bucket via shouldKeepFolder check in
  SessionGroupSection (folders with no sessions and no content in
  children are filtered out)
- Always filter folders through shouldKeepFolder, not just during search

* perf: memoize archived folder filtering

* fix(sync): deduplicate overlapping delta after coalesced part.updated

When message.part.updated coalesces in the event pipeline and a
message.part.delta for the same part arrives in the same flush window,
the reducer appends the delta verbatim to the already-complete field
value, producing duplicated text in tool output and assistant messages.

Add targeted overlap reconciliation: only when a part.updated replaces
an existing part with overlapping string content, mark the next delta
for that field as dedupe-eligible. Normal streaming deltas remain
untouched (pure append).

Covers four cases:
- Full overlap: delta already present -> no-op
- Partial overlap: only non-overlapping suffix appended
- No overlap: unchanged append behavior
- Legitimate repeated output (ha + ha -> haha): preserved

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
jwcrystal
2026-04-15 10:12:30 +03:00
committed by GitHub
co-authored by Bohdan Triapitsyn
parent 9908f2dc9c
commit ea5c19e934
2 changed files with 199 additions and 2 deletions
@@ -0,0 +1,154 @@
import { describe, expect, it } from 'bun:test'
import { applyDirectoryEvent } from '../event-reducer'
import { INITIAL_STATE } from '../types'
describe('applyDirectoryEvent', () => {
it('does not duplicate overlapping delta text after a newer part.updated replaces an older one', () => {
const state = structuredClone(INITIAL_STATE)
const messageID = 'msg-1'
const partID = 'part-1'
applyDirectoryEvent(state, {
type: 'message.part.updated',
properties: {
part: {
id: partID,
type: 'text',
messageID,
text: 'Fix typo in ToolOutputDialog — ',
},
},
})
applyDirectoryEvent(state, {
type: 'message.part.updated',
properties: {
part: {
id: partID,
type: 'text',
messageID,
text: 'Fix typo in ToolOutputDialog — toolFailedToReadDiagram vs toolFailedReadDiagram • Let me fix it.',
},
},
})
applyDirectoryEvent(state, {
type: 'message.part.delta',
properties: {
messageID,
partID,
field: 'text',
delta: 'toolFailedToReadDiagram vs toolFailedReadDiagram • Let me fix it.',
},
})
expect(state.part[messageID]).toHaveLength(1)
expect(state.part[messageID]?.[0]?.text).toBe(
'Fix typo in ToolOutputDialog — toolFailedToReadDiagram vs toolFailedReadDiagram • Let me fix it.',
)
})
it('appends only the non-overlapping suffix of a streaming delta', () => {
const state = structuredClone(INITIAL_STATE)
const messageID = 'msg-2'
const partID = 'part-2'
applyDirectoryEvent(state, {
type: 'message.part.updated',
properties: {
part: {
id: partID,
type: 'text',
messageID,
text: 'toolFailedToReadDiagram vs toolFailedRead',
},
},
})
applyDirectoryEvent(state, {
type: 'message.part.updated',
properties: {
part: {
id: partID,
type: 'text',
messageID,
text: 'toolFailedToReadDiagram vs toolFailedReadDiagra',
},
},
})
applyDirectoryEvent(state, {
type: 'message.part.delta',
properties: {
messageID,
partID,
field: 'text',
delta: 'Diagram • Let me fix it.',
},
})
expect(state.part[messageID]?.[0]?.text).toBe(
'toolFailedToReadDiagram vs toolFailedReadDiagram • Let me fix it.',
)
})
it('appends a non-overlapping delta unchanged', () => {
const state = structuredClone(INITIAL_STATE)
const messageID = 'msg-3'
const partID = 'part-3'
applyDirectoryEvent(state, {
type: 'message.part.updated',
properties: {
part: {
id: partID,
type: 'text',
messageID,
text: 'PR comment done — ',
},
},
})
applyDirectoryEvent(state, {
type: 'message.part.delta',
properties: {
messageID,
partID,
field: 'text',
delta: 'Let me fix it.',
},
})
expect(state.part[messageID]?.[0]?.text).toBe('PR comment done — Let me fix it.')
})
it('preserves legitimate repeated output when no updated-to-delta dedupe window is active', () => {
const state = structuredClone(INITIAL_STATE)
const messageID = 'msg-4'
const partID = 'part-4'
applyDirectoryEvent(state, {
type: 'message.part.updated',
properties: {
part: {
id: partID,
type: 'text',
messageID,
text: 'ha',
},
},
})
applyDirectoryEvent(state, {
type: 'message.part.delta',
properties: {
messageID,
partID,
field: 'text',
delta: 'ha',
},
})
expect(state.part[messageID]?.[0]?.text).toBe('haha')
})
})
+45 -2
View File
@@ -16,6 +16,39 @@ import { stripSessionDiffSnapshots } from "./sanitize"
import { syncDebug } from "./debug"
const SKIP_PARTS = new Set(["patch", "step-start", "step-finish"])
const DELTA_OVERLAP_FIELDS = ["text", "output"] as const
type DedupeMetadata = {
__dedupeNextDeltaFields?: string[]
}
function appendNonOverlappingDelta(existingValue: string | undefined, delta: string) {
if (!existingValue || delta.length === 0) return (existingValue ?? "") + delta
if (existingValue.endsWith(delta)) return existingValue
const maxOverlap = Math.min(existingValue.length, delta.length)
for (let overlap = maxOverlap; overlap > 0; overlap--) {
if (existingValue.endsWith(delta.slice(0, overlap))) {
return existingValue + delta.slice(overlap)
}
}
return existingValue + delta
}
function getUpdatedDeltaFields(previous: Part, next: Part) {
const dedupeFields: string[] = []
for (const field of DELTA_OVERLAP_FIELDS) {
const previousValue = (previous as Record<string, unknown>)[field]
const nextValue = (next as Record<string, unknown>)[field]
if (typeof previousValue !== "string" || typeof nextValue !== "string") continue
if (previousValue.length === 0 || nextValue.length === 0) continue
if (nextValue === previousValue || nextValue.startsWith(previousValue) || previousValue.startsWith(nextValue)) {
dedupeFields.push(field)
}
}
return dedupeFields
}
// ---------------------------------------------------------------------------
// Global events
@@ -205,7 +238,11 @@ export function applyDirectoryEvent(
const next = [...parts]
const result = Binary.search(next, part.id, (p) => p.id)
if (result.found) {
next[result.index] = part
const previous = next[result.index]
const dedupeFields = getUpdatedDeltaFields(previous, part)
next[result.index] = dedupeFields.length > 0
? { ...part, __dedupeNextDeltaFields: dedupeFields } as unknown as Part
: part
} else {
// Replace optimistic part (no sessionID) with server part of same type.
// Gate: only scan if the first part lacks sessionID (optimistic parts are
@@ -262,9 +299,15 @@ export function applyDirectoryEvent(
}
const existing = parts[result.index] as Record<string, unknown>
const existingValue = existing[props.field] as string | undefined
const dedupeFields = (existing as DedupeMetadata).__dedupeNextDeltaFields ?? []
const shouldDedupe = dedupeFields.includes(props.field)
// Create new Part object + new array so React detects the change
const next = [...parts]
next[result.index] = { ...existing, [props.field]: (existingValue ?? "") + props.delta } as Part
next[result.index] = {
...existing,
[props.field]: shouldDedupe ? appendNonOverlappingDelta(existingValue, props.delta) : (existingValue ?? "") + props.delta,
__dedupeNextDeltaFields: dedupeFields.filter((field) => field !== props.field),
} as unknown as Part
draft.part[props.messageID] = next
return true
}