Files
hapi/web/src/components/AssistantChat/HappyComposer.parkSnapshot.test.ts
T
2d904de76e fix(web): migrate chat-path attachments on scratchlist park (#1227)
* fix(web): migrate chat-path attachments on scratchlist park (#1226)

When an image is attached via the normal upload adapter before scratchlist
mode is enabled, toggling mode swapped adapters and send() dropped the
metadata — park stored text-only and cleared chips. Migrate pending
chat-path files into hub scratchlist storage on send, and fail closed if
non-hub paths still reach the park wrapper.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): defer chat-path cleanup until scratchlist park succeeds

send() migrates before scratchlist.add; deleting the original upload
there left retries pointing at a missing chat blob when park failed.
Stamp migratedFromPath and clean up only after the park attempt result.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): park scratchlist before composer.send clears chips

assistant-ui empties text/attachments before onNew, so return false from
park could not restore retryable state and rejected cleanup deleted the
migrated hub blob. Intercept park from a live snapshot; clear only after
accept; releaseWithoutDelete so clearAttachments keeps parked hubs.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): lock composer while scratchlist park is in flight

Disable input/send and hide chip remove during migrate+add so mid-flight
edits are not wiped on success and hub blobs are not deleted out from
under the accepted entry.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): do not clear composer if park snapshot changed mid-flight

Compare post-await composer state to the pre-park snapshot before
clearing; disable DragDropZone and scratchlist promote while parking
so parent paths cannot add chips the clear would silently drop.

Addresses Codex Major on #1227 (preserve post-snapshot composer changes).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): defer migrated chat-path cleanup until park snapshot clears

Return ScratchlistParkResult.beforeClear from onParkScratchlist so
finalizeMigratedScratchlistParkCleanup runs only after HappyComposer
confirms the composer was unchanged mid-flight.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): flush rich mentions before scratchlist park snapshot

Park snapshots composer.text after flushSerializedText so session
@-mention chips serialize to markdown links before scratchlist add.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): validate park snapshot before scratchlist add

Split prepare/commit/abort so mid-flight composer edits abort orphan
hub blobs instead of parking a duplicate. Reuse restored hub paths in
prepareScratchlistParkAttachments so remounted chips do not re-upload.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): ignore scratchlist toggle hotkey while park is in flight

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 10:39:01 +08:00

35 lines
1.1 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import { composerParkSnapshotUnchanged } from './HappyComposer'
describe('composerParkSnapshotUnchanged', () => {
it('returns true when text and attachment ids match', () => {
const snapshot = {
text: 'hello',
attachments: [{ id: 'a1' }, { id: 'a2' }],
}
expect(composerParkSnapshotUnchanged(snapshot, {
text: 'hello',
attachments: [{ id: 'a1' }, { id: 'a2' }],
})).toBe(true)
})
it('returns false when text or attachments changed during park', () => {
const snapshot = {
text: 'hello',
attachments: [{ id: 'a1' }],
}
expect(composerParkSnapshotUnchanged(snapshot, {
text: 'hello world',
attachments: [{ id: 'a1' }],
})).toBe(false)
expect(composerParkSnapshotUnchanged(snapshot, {
text: 'hello',
attachments: [{ id: 'a1' }, { id: 'a2' }],
})).toBe(false)
expect(composerParkSnapshotUnchanged(snapshot, {
text: 'hello',
attachments: [{ id: 'a2' }],
})).toBe(false)
})
})