mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 16:03:26 +00:00
fix(mobile): hold the launch-draft prefill until the transcript settles
session.tabs delivers launchDraft before the transcript read resolves, so the seed effect could run against an empty in-flight message list and miss the user-turn decline. Launching from an issue, submitting the prefill in the TUI, and never opening desktop chat (nothing else clears the host seed) then prefilled the mobile composer with the already-sent issue link — a send tapped before it retracted duplicated it to the agent. Thread the session's loading state through and skip the seed while the read is in flight. idle/waiting-session still seed: no session means no user turns.
This commit is contained in:
@@ -144,7 +144,8 @@ export function useMobileNativeChatController(args: {
|
||||
tabId: activeSessionTabId,
|
||||
sessionId: activeChatSessionId,
|
||||
messages: nativeChatSession.messages,
|
||||
launchDraft: activeChatResolution ? (activeSessionTab?.launchDraft ?? null) : null
|
||||
launchDraft: activeChatResolution ? (activeSessionTab?.launchDraft ?? null) : null,
|
||||
transcriptLoading: nativeChatSession.status === 'loading'
|
||||
})
|
||||
|
||||
const nativeChatStatus = activeChatResolution ? activeSessionTab?.agentStatus : null
|
||||
|
||||
@@ -44,12 +44,14 @@ describe('useMobileNativeChatDrafts', () => {
|
||||
tabId,
|
||||
sessionId = `session-${tabId}`,
|
||||
messages = [],
|
||||
launchDraft = null
|
||||
launchDraft = null,
|
||||
transcriptLoading = false
|
||||
}: {
|
||||
tabId: string
|
||||
sessionId?: string | null
|
||||
messages?: NativeChatMessage[]
|
||||
launchDraft?: string | null
|
||||
transcriptLoading?: boolean
|
||||
}): null {
|
||||
state = useMobileNativeChatDrafts({
|
||||
hostId: 'host',
|
||||
@@ -57,7 +59,8 @@ describe('useMobileNativeChatDrafts', () => {
|
||||
tabId,
|
||||
sessionId,
|
||||
messages,
|
||||
launchDraft
|
||||
launchDraft,
|
||||
transcriptLoading
|
||||
})
|
||||
return null
|
||||
}
|
||||
@@ -793,6 +796,46 @@ describe('useMobileNativeChatDrafts', () => {
|
||||
expect(state?.composerText).toBe('issue link plus my notes')
|
||||
})
|
||||
|
||||
it('holds the launch draft until the transcript read settles', async () => {
|
||||
// `session.tabs` carries launchDraft before the transcript loads. Seeding on
|
||||
// the empty in-flight list would prefill an already-submitted issue link, and
|
||||
// a send tapped before it retracts duplicates it to the agent.
|
||||
await mount('a')
|
||||
await act(async () =>
|
||||
renderer?.update(
|
||||
createElement(Harness, { tabId: 'a', launchDraft: 'issue link', transcriptLoading: true })
|
||||
)
|
||||
)
|
||||
expect(state?.composerText).toBe('')
|
||||
|
||||
// The settled transcript already holds the submitted turn — decline for good.
|
||||
await act(async () =>
|
||||
renderer?.update(
|
||||
createElement(Harness, {
|
||||
tabId: 'a',
|
||||
launchDraft: 'issue link',
|
||||
messages: [userTextMessage('m1', 'already sent from the TUI')]
|
||||
})
|
||||
)
|
||||
)
|
||||
expect(state?.composerText).toBe('')
|
||||
})
|
||||
|
||||
it('seeds once the transcript settles empty', async () => {
|
||||
await mount('a')
|
||||
await act(async () =>
|
||||
renderer?.update(
|
||||
createElement(Harness, { tabId: 'a', launchDraft: 'issue link', transcriptLoading: true })
|
||||
)
|
||||
)
|
||||
expect(state?.composerText).toBe('')
|
||||
|
||||
await act(async () =>
|
||||
renderer?.update(createElement(Harness, { tabId: 'a', launchDraft: 'issue link' }))
|
||||
)
|
||||
expect(state?.composerText).toBe('issue link')
|
||||
})
|
||||
|
||||
it('clears an untouched prefill when the host stops publishing the launch draft', async () => {
|
||||
await mount('a')
|
||||
await act(async () =>
|
||||
|
||||
@@ -43,6 +43,9 @@ export function useMobileNativeChatDrafts(args: {
|
||||
messages: readonly NativeChatMessage[]
|
||||
/** Host-provided launch context still parked as an unsent TUI-input draft. */
|
||||
launchDraft?: string | null
|
||||
/** The transcript read is still in flight, so `messages` is not yet the
|
||||
* session's real history and cannot be trusted to decline the seed. */
|
||||
transcriptLoading?: boolean
|
||||
}): {
|
||||
composerText: string
|
||||
setComposerText: Dispatch<SetStateAction<string>>
|
||||
@@ -59,7 +62,7 @@ export function useMobileNativeChatDrafts(args: {
|
||||
onUnconfirmed: () => void
|
||||
) => void
|
||||
} {
|
||||
const { hostId, worktreeId, tabId, sessionId, messages, launchDraft } = args
|
||||
const { hostId, worktreeId, tabId, sessionId, messages, launchDraft, transcriptLoading } = args
|
||||
const draftKey = mobileNativeChatScopeKey(hostId, worktreeId, tabId)
|
||||
const pendingKey = draftKey && sessionId ? `${draftKey}\0${sessionId}` : null
|
||||
const [drafts, setDrafts] = useState<Record<string, string>>({})
|
||||
@@ -85,6 +88,12 @@ export function useMobileNativeChatDrafts(args: {
|
||||
if (!draftKey || !launchDraft?.trim() || seededLaunchDraftByKeyRef.current.has(draftKey)) {
|
||||
return
|
||||
}
|
||||
// Why: `session.tabs` carries launchDraft before the transcript read settles,
|
||||
// and an empty in-flight list would let the decline below miss an
|
||||
// already-submitted prefill — long enough for a send to duplicate it.
|
||||
if (transcriptLoading) {
|
||||
return
|
||||
}
|
||||
// A user turn already in the transcript means the one-line TUI prefill was
|
||||
// submitted or deliberately cleared; decline instead of resurrecting it.
|
||||
if (messages.some((message) => normalizedUserText(message) !== null)) {
|
||||
@@ -95,7 +104,7 @@ export function useMobileNativeChatDrafts(args: {
|
||||
setDrafts((previous) =>
|
||||
(previous[draftKey] ?? '') === '' ? { ...previous, [draftKey]: launchDraft } : previous
|
||||
)
|
||||
}, [draftKey, launchDraft, messages])
|
||||
}, [draftKey, launchDraft, messages, transcriptLoading])
|
||||
|
||||
// Drop an untouched adopted copy once the prefill is resolved elsewhere — a
|
||||
// user turn landed (sent or cleared TUI-side) or the host stopped publishing
|
||||
|
||||
Reference in New Issue
Block a user