mirror of
https://github.com/stablyai/orca.git
synced 2026-10-27 00:02:48 +00:00
fix(mobile): stop the chat pre-clear from wiping a just-pasted image
The text write set clearInputFirst unconditionally. On the image path that Ctrl+U lands AFTER pasteMobileNativeChatImagePaths already pasted the image, so the agent receives the text alone while acceptSend still renders the thumbnail on the sent bubble — silent image loss. Desktop's image path clears exactly once, before the paste, and never again; mobile now matches: pre-clear only when nothing was deliberately pasted first. The image paste already leads with its own Ctrl+U, so a launch-draft prefill parked on the input line still cannot glue onto the message. Pinned at both levels: the controller test drives the real send hook and asserts clearInputFirst per branch, and the send module asserts the wire text carries no leading \x15. The image-attachments test injects its own baseSend, so it structurally could not observe this.
This commit is contained in:
@@ -194,6 +194,27 @@ describe('sendMobileNativeChatMessage', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('sends the text verbatim when clearInputFirst is not set', async () => {
|
||||
// An image send pastes the image (behind its own leading Ctrl+U) before this
|
||||
// text write; a clear byte here would kill the pasted image off the input line.
|
||||
const client = clientWithResponse({
|
||||
id: 'request',
|
||||
ok: true,
|
||||
result: { send: { accepted: true } },
|
||||
_meta: { runtimeId: 'runtime' }
|
||||
})
|
||||
|
||||
await sendMobileNativeChatMessage({
|
||||
client,
|
||||
terminal: 'term',
|
||||
text: 'what is this',
|
||||
clearInputFirst: false
|
||||
})
|
||||
const sent = vi.mocked(client.sendRequest).mock.calls[0]?.[1] as { text: string }
|
||||
expect(sent.text).toBe('what is this')
|
||||
expect(sent.text.startsWith('\x15')).toBe(false)
|
||||
})
|
||||
|
||||
it('sends a single non-submitting Escape for prompt cancellation', async () => {
|
||||
const client = clientWithResponse({
|
||||
id: 'request',
|
||||
|
||||
@@ -213,6 +213,27 @@ describe('useMobileNativeChatController handleNativeChatSend', () => {
|
||||
expect(restoreRejectedDraft).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('pre-clears the input line for a text-only send but never for an image send', async () => {
|
||||
// The image path pastes the image behind its OWN leading Ctrl+U and then calls
|
||||
// this send; a second clear here wipes the image off the input line and the
|
||||
// agent receives text alone while the echo bubble still shows the thumbnail.
|
||||
sendWithOutcome.mockResolvedValue('accepted')
|
||||
|
||||
await act(async () => {
|
||||
await controller!.handleNativeChatSend('answer')
|
||||
})
|
||||
expect(sendWithOutcome).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ text: 'answer', clearInputFirst: true })
|
||||
)
|
||||
|
||||
await act(async () => {
|
||||
await controller!.handleNativeChatSend('look', ['file:///a.jpg'])
|
||||
})
|
||||
expect(sendWithOutcome).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({ text: 'look', clearInputFirst: false })
|
||||
)
|
||||
})
|
||||
|
||||
it('holds an unknown-outcome send without posting the optimistic echo', async () => {
|
||||
sendWithOutcome.mockResolvedValue('unknown')
|
||||
let accepted = false
|
||||
|
||||
@@ -99,10 +99,13 @@ export function useMobileNativeChatMessageSend(args: {
|
||||
client,
|
||||
terminal: handle,
|
||||
text,
|
||||
// Why: desktop-parity pre-clear. The stale-input heal above only fires for
|
||||
// terminals a mobile image paste marked, so a desktop launch-draft prefill
|
||||
// sitting on the input line would otherwise glue onto this message.
|
||||
clearInputFirst: true,
|
||||
// Why: pre-clear only when nothing was deliberately pasted first. The heal
|
||||
// above fires only for terminals a mobile image paste marked, so a desktop
|
||||
// launch-draft prefill parked on the input line would otherwise glue onto
|
||||
// this message. An image send already led its own paste with Ctrl+U, and a
|
||||
// second one here would wipe the image it just pasted (desktop's image path
|
||||
// likewise clears once, before the paste, and never again).
|
||||
clearInputFirst: !images?.length,
|
||||
deadline,
|
||||
...(deviceTokenRef.current
|
||||
? { mobileClient: { id: deviceTokenRef.current, type: 'mobile' } }
|
||||
|
||||
Reference in New Issue
Block a user