fix(desktop): persist dropped image bytes before attach
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
import type { ComposerAttachment } from '@/store/composer'
|
||||
import { $connection } from '@/store/session'
|
||||
|
||||
import {
|
||||
@@ -7,7 +9,8 @@ import {
|
||||
type DroppedFile,
|
||||
extractDroppedFiles,
|
||||
HERMES_PATHS_MIME,
|
||||
partitionDroppedFiles
|
||||
partitionDroppedFiles,
|
||||
useComposerActions
|
||||
} from './use-composer-actions'
|
||||
|
||||
// A Finder/Explorer drop carries a native File handle; an in-app drag (project
|
||||
@@ -244,3 +247,74 @@ describe('attachmentPreviewDataUrl', () => {
|
||||
await expect(attachmentPreviewDataUrl('/home/gateway/shot.png')).resolves.toBe(REMOTE_PREVIEW)
|
||||
})
|
||||
})
|
||||
|
||||
describe('useComposerActions native image drops', () => {
|
||||
afterEach(() => {
|
||||
Reflect.deleteProperty(window, 'hermesDesktop')
|
||||
vi.unstubAllGlobals()
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('copies dropped screenshot bytes before trusting a transient macOS path', async () => {
|
||||
const transientPath =
|
||||
'/var/folders/x7/example/T/TemporaryItems/NSIRD_screencaptureui_4roSuW/Screen Shot 2026-08-11.png'
|
||||
|
||||
const durablePath = '/Users/test/Library/Application Support/Hermes/composer-images/composer_saved.png'
|
||||
const previewUrl = 'data:image/png;base64,c2NyZWVuc2hvdA=='
|
||||
|
||||
const screenshot = new File([new Uint8Array([1, 2, 3])], 'Screen Shot 2026-08-11.png', {
|
||||
type: 'image/png'
|
||||
})
|
||||
|
||||
const saveImageBuffer = vi.fn(async () => durablePath)
|
||||
|
||||
const readFileDataUrl = vi.fn(async (path: string) => {
|
||||
if (path === transientPath) {
|
||||
throw new Error('temporary screenshot path disappeared')
|
||||
}
|
||||
|
||||
return previewUrl
|
||||
})
|
||||
|
||||
const add = vi.fn<(attachment: ComposerAttachment) => void>()
|
||||
|
||||
Object.defineProperty(window, 'hermesDesktop', {
|
||||
configurable: true,
|
||||
value: {
|
||||
readFileDataUrl,
|
||||
saveImageBuffer
|
||||
}
|
||||
})
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useComposerActions({
|
||||
activeSessionId: null,
|
||||
currentCwd: '/Users/test/project',
|
||||
requestGateway: vi.fn(),
|
||||
scope: {
|
||||
add,
|
||||
remove: vi.fn(() => null),
|
||||
target: 'test-composer'
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
let attached = false
|
||||
|
||||
await act(async () => {
|
||||
attached = await result.current.attachDroppedItems([{ file: screenshot, path: transientPath }])
|
||||
})
|
||||
|
||||
expect(attached).toBe(true)
|
||||
expect(saveImageBuffer).toHaveBeenCalledOnce()
|
||||
expect(readFileDataUrl).toHaveBeenCalledWith(durablePath)
|
||||
expect(readFileDataUrl).not.toHaveBeenCalledWith(transientPath)
|
||||
expect(add).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
kind: 'image',
|
||||
path: durablePath,
|
||||
previewUrl
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -644,7 +644,14 @@ export function useComposerActions({
|
||||
const isImage = file.type.startsWith('image/') || isImagePath(file.name) || (filePath && isImagePath(filePath))
|
||||
|
||||
if (isImage) {
|
||||
if ((filePath && (await attachImagePath(filePath))) || (await attachImageBlob(file))) {
|
||||
// Finder may expose a dropped screenshot through a short-lived
|
||||
// TemporaryItems/NSIRD_screencaptureui path even when the visible
|
||||
// file has already landed on Desktop. Reading that path for the
|
||||
// preview can succeed, then image.attach fails after macOS removes
|
||||
// it before submit. Persist the File bytes into Desktop's durable
|
||||
// composer-image cache first; keep the native path as a compatibility
|
||||
// fallback for older shells that cannot save the buffer.
|
||||
if ((await attachImageBlob(file)) || (filePath && (await attachImagePath(filePath)))) {
|
||||
attached = true
|
||||
|
||||
continue
|
||||
|
||||
Reference in New Issue
Block a user