mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
fix(mobile): stop labeling accepted messages queued (#13658)
This commit is contained in:
@@ -276,7 +276,6 @@ function AgentControls({
|
||||
|
||||
function MobileNativeChatMessageImpl({
|
||||
message,
|
||||
queued,
|
||||
toolsExpanded = false,
|
||||
fontScale = 1,
|
||||
messageIndex,
|
||||
@@ -284,7 +283,6 @@ function MobileNativeChatMessageImpl({
|
||||
onOpenFile
|
||||
}: {
|
||||
message: NativeChatMessage
|
||||
queued?: boolean
|
||||
toolsExpanded?: boolean
|
||||
/** Multiplies all chat text sizes for pinch-to-zoom (1 = no change). */
|
||||
fontScale?: number
|
||||
@@ -328,27 +326,24 @@ function MobileNativeChatMessageImpl({
|
||||
|
||||
// Copy + scroll-to-top, shown inline with the first tool call (or after the
|
||||
// prose when there are no tools).
|
||||
const controls =
|
||||
isAgent && !queued ? (
|
||||
<AgentControls
|
||||
onCopy={handleCopy}
|
||||
onScrollToTop={
|
||||
onScrollToMessage && messageIndex !== undefined
|
||||
? () => onScrollToMessage(messageIndex)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
) : null
|
||||
const controls = isAgent ? (
|
||||
<AgentControls
|
||||
onCopy={handleCopy}
|
||||
onScrollToTop={
|
||||
onScrollToMessage && messageIndex !== undefined
|
||||
? () => onScrollToMessage(messageIndex)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
) : null
|
||||
|
||||
return (
|
||||
<View style={[styles.row, isUser && styles.rowUser]}>
|
||||
{isUser && queued ? <Text style={styles.queuedTag}>Queued</Text> : null}
|
||||
<View
|
||||
style={[
|
||||
styles.content,
|
||||
isUser && styles.userBubble,
|
||||
isReasoning && styles.reasoning,
|
||||
queued && styles.queued,
|
||||
copied && styles.copied
|
||||
]}
|
||||
>
|
||||
|
||||
@@ -71,6 +71,7 @@ type Overrides = {
|
||||
onClearSendError?: () => void
|
||||
inputLockReason?: 'disconnected' | 'waiting' | null
|
||||
onSend?: (text: string) => Promise<boolean>
|
||||
pending?: Parameters<typeof MobileNativeChatView>[0]['pending']
|
||||
}
|
||||
|
||||
function assistantTurn(id: string, text: string): NativeChatMessage {
|
||||
@@ -117,6 +118,13 @@ describe('MobileNativeChatView', () => {
|
||||
return (list.props.data as { id: string }[]).map((row) => row.id)
|
||||
}
|
||||
|
||||
function renderedRow(id: string): ReturnType<typeof createElement> {
|
||||
const list = renderer!.root.find((node) => node.type === 'FlatList')
|
||||
const data = list.props.data as NativeChatMessage[]
|
||||
const index = data.findIndex((row) => row.id === id)
|
||||
return list.props.renderItem({ item: data[index], index })
|
||||
}
|
||||
|
||||
function banners(): ReactTestInstance[] {
|
||||
return renderer!.root.findAll((node) => node.props.accessibilityRole === 'alert')
|
||||
}
|
||||
@@ -187,6 +195,15 @@ describe('MobileNativeChatView', () => {
|
||||
expect(listIds()).toEqual(['a1', 'streaming'])
|
||||
})
|
||||
|
||||
it('renders an accepted optimistic image send without a queued state', async () => {
|
||||
await render({
|
||||
pending: [{ id: 'pending-1', text: 'look', images: ['file:///phone-photo.jpg'] }]
|
||||
})
|
||||
|
||||
expect(listIds()).toEqual(['pending-1'])
|
||||
expect(renderedRow('pending-1').props).not.toHaveProperty('queued')
|
||||
})
|
||||
|
||||
it('keeps a visible lock through a subscribed-end lease blip', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
|
||||
@@ -58,8 +58,7 @@ type Props = {
|
||||
loadingEarlier?: boolean
|
||||
onLoadEarlier?: () => void
|
||||
onSend: (text: string) => Promise<boolean>
|
||||
/** Optimistic queued sends (owned by the route so they survive view switches). */
|
||||
/** Optimistic user echoes, including any ridden-along image preview URIs. */
|
||||
/** Accepted user echoes awaiting transcript replacement, including image previews. */
|
||||
pending: MobileNativeChatPendingItem[]
|
||||
/** Local photo URIs retained when the authoritative transcript replaces an
|
||||
* optimistic image bubble. */
|
||||
@@ -176,9 +175,8 @@ export function MobileNativeChatView({
|
||||
[]
|
||||
)
|
||||
|
||||
const pendingIds = useMemo(() => new Set(pending.map((p) => p.id)), [pending])
|
||||
// `data` is the list source: folded transcript + synthetic streaming bubble +
|
||||
// route-owned optimistic queued messages. Memoize on the same deps so the
|
||||
// route-owned accepted echoes. Memoize on the same deps so the
|
||||
// downstream autoscroll effects/`renderItem` keep referential stability.
|
||||
const { data } = useMemo(
|
||||
() =>
|
||||
@@ -248,7 +246,6 @@ export function MobileNativeChatView({
|
||||
({ item, index }: { item: NativeChatMessage; index: number }) => (
|
||||
<MobileNativeChatMessage
|
||||
message={item}
|
||||
queued={pendingIds.has(item.id)}
|
||||
toolsExpanded={toolsExpanded}
|
||||
fontScale={fontScale}
|
||||
messageIndex={index}
|
||||
@@ -256,7 +253,7 @@ export function MobileNativeChatView({
|
||||
onOpenFile={onOpenFile}
|
||||
/>
|
||||
),
|
||||
[pendingIds, toolsExpanded, fontScale, onScrollToMessage, onOpenFile]
|
||||
[toolsExpanded, fontScale, onScrollToMessage, onOpenFile]
|
||||
)
|
||||
|
||||
const emptyState = mobileNativeChatEmptyState(status, agent ?? null, error)
|
||||
|
||||
@@ -49,15 +49,6 @@ export const styles = StyleSheet.create({
|
||||
reasoning: {
|
||||
opacity: 0.7
|
||||
},
|
||||
queued: {
|
||||
opacity: 0.55
|
||||
},
|
||||
queuedTag: {
|
||||
color: colors.textMuted,
|
||||
fontSize: 11,
|
||||
fontWeight: '600',
|
||||
marginBottom: 2
|
||||
},
|
||||
toolRun: {
|
||||
marginTop: spacing.xs
|
||||
},
|
||||
|
||||
@@ -51,7 +51,7 @@ export function foldMobileNativeChatMessages(messages: NativeChatMessage[]): Nat
|
||||
|
||||
/** Assemble the list data the chat renders: the folded transcript, then a
|
||||
* synthetic bubble for the streaming text the gate let through, then the
|
||||
* route-owned optimistic "queued" messages at the tail. */
|
||||
* route-owned accepted optimistic messages at the tail. */
|
||||
export function buildMobileNativeChatTransientData({
|
||||
folded,
|
||||
streaming,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// (src/renderer/src/components/native-chat/NativeChatComposer.tsx): slash/skill
|
||||
// sends are TUI control actions, not chat turns — they never echo as a user
|
||||
// bubble, because the transcript will never contain a matching user turn and
|
||||
// the optimistic echo would sit at "Queued" forever.
|
||||
// the optimistic echo would never reconcile.
|
||||
|
||||
import {
|
||||
getNativeChatAgentProfile,
|
||||
|
||||
@@ -181,7 +181,7 @@ export function useMobileNativeChatMessageSend(args: {
|
||||
})
|
||||
// Why (desktop parity): a slash/skill send dispatches into the agent's own
|
||||
// TUI, not the conversation — the transcript never echoes it as a user
|
||||
// turn, so an optimistic bubble would sit at "Queued" forever and the
|
||||
// turn, so an optimistic bubble would never reconcile and the
|
||||
// unconfirmed hold could never observe a landing.
|
||||
const classification = classifyMobileNativeChatSend(agent, text)
|
||||
if (outcome === 'unknown') {
|
||||
|
||||
Reference in New Issue
Block a user