diff --git a/mobile/src/session/MobileAgentWorkingIndicator.tsx b/mobile/src/session/MobileAgentWorkingIndicator.tsx
deleted file mode 100644
index cb766b0be51..00000000000
--- a/mobile/src/session/MobileAgentWorkingIndicator.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import { useEffect, useRef } from 'react'
-import { Animated, StyleSheet, Text, View } from 'react-native'
-import { colors, spacing, typography } from '../theme/mobile-theme'
-
-/** Animated three-dot "agent is working" row, shown while the active agent is
- * still producing a reply. Pure presentation — visibility is the caller's call. */
-export function MobileAgentWorkingIndicator(): React.JSX.Element {
- const dots = [
- useRef(new Animated.Value(0.3)).current,
- useRef(new Animated.Value(0.3)).current,
- useRef(new Animated.Value(0.3)).current
- ]
-
- useEffect(() => {
- const animations = dots.map((dot, i) =>
- Animated.loop(
- Animated.sequence([
- Animated.delay(i * 160),
- Animated.timing(dot, { toValue: 1, duration: 320, useNativeDriver: true }),
- Animated.timing(dot, { toValue: 0.3, duration: 320, useNativeDriver: true })
- ])
- )
- )
- animations.forEach((a) => a.start())
- return () => animations.forEach((a) => a.stop())
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [])
-
- return (
-
- Agent is working
-
- {dots.map((dot, i) => (
-
- ))}
-
-
- )
-}
-
-const styles = StyleSheet.create({
- row: {
- flexDirection: 'row',
- alignItems: 'center',
- gap: spacing.sm,
- paddingHorizontal: spacing.md,
- paddingVertical: spacing.sm
- },
- label: {
- color: colors.textMuted,
- fontSize: typography.metaSize,
- fontStyle: 'italic'
- },
- dots: {
- flexDirection: 'row',
- gap: 4
- },
- dot: {
- width: 5,
- height: 5,
- borderRadius: 2.5,
- backgroundColor: colors.textSecondary
- }
-})
diff --git a/mobile/src/session/MobileNativeChatFloatingControls.tsx b/mobile/src/session/MobileNativeChatFloatingControls.tsx
index b7f8430bcb3..2ca716de860 100644
--- a/mobile/src/session/MobileNativeChatFloatingControls.tsx
+++ b/mobile/src/session/MobileNativeChatFloatingControls.tsx
@@ -2,31 +2,23 @@ import { Pressable, Text, View } from 'react-native'
import { ArrowDown, ChevronsDownUp, ChevronsUpDown } from 'lucide-react-native'
import { colors } from '../theme/mobile-theme'
import { styles } from './mobile-native-chat-view-styles'
-import { MobileAgentWorkingIndicator } from './MobileAgentWorkingIndicator'
/** Floats over the transcript's bottom-right so the Tools toggle and jump-to-latest take no row. */
export function MobileNativeChatFloatingControls({
toolCallCount,
toolsExpanded,
- bridgeWorking,
onToggleTools,
showJumpToTail,
onJumpToTail
}: {
toolCallCount: number
toolsExpanded: boolean
- bridgeWorking: boolean
onToggleTools: () => void
showJumpToTail: boolean
onJumpToTail: () => void
}): React.JSX.Element {
return (
- {bridgeWorking ? (
-
-
-
- ) : null}
{toolCallCount > 0 ? (
({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
vi.mock('./MobileNativeChatComposer', async () => {
const { composerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture')
return composerStub()
diff --git a/mobile/src/session/MobileNativeChatView.failure-banner.test.ts b/mobile/src/session/MobileNativeChatView.failure-banner.test.ts
index 9c056b83934..859ff1a2178 100644
--- a/mobile/src/session/MobileNativeChatView.failure-banner.test.ts
+++ b/mobile/src/session/MobileNativeChatView.failure-banner.test.ts
@@ -35,9 +35,6 @@ vi.mock('./MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
vi.mock('./MobileNativeChatComposer', async () => {
const { composerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture')
return composerStub()
diff --git a/mobile/src/session/MobileNativeChatView.history-paging.test.ts b/mobile/src/session/MobileNativeChatView.history-paging.test.ts
index 116464c23d6..02d061e5d2d 100644
--- a/mobile/src/session/MobileNativeChatView.history-paging.test.ts
+++ b/mobile/src/session/MobileNativeChatView.history-paging.test.ts
@@ -36,9 +36,6 @@ vi.mock('./MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
vi.mock('./MobileNativeChatComposer', async () => {
const { composerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture')
return composerStub()
diff --git a/mobile/src/session/MobileNativeChatView.prompt-presentation.test.ts b/mobile/src/session/MobileNativeChatView.prompt-presentation.test.ts
index cfd1157ab27..14efe3a7dc6 100644
--- a/mobile/src/session/MobileNativeChatView.prompt-presentation.test.ts
+++ b/mobile/src/session/MobileNativeChatView.prompt-presentation.test.ts
@@ -61,9 +61,6 @@ vi.mock('./MobileNativeChatMessage', () => ({ MobileNativeChatMessage: 'ChatMess
vi.mock('./MobileNativeChatLiveLine', () => ({ MobileNativeChatLiveLine: 'LiveStatus' }))
vi.mock('./MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
vi.mock('./MobileNativeChatSessionOptionPickers', () => ({
MobileNativeChatSessionOptionPickers: 'SessionOptions'
}))
diff --git a/mobile/src/session/MobileNativeChatView.rows.test.ts b/mobile/src/session/MobileNativeChatView.rows.test.ts
index 789df91ca68..a9402249c3e 100644
--- a/mobile/src/session/MobileNativeChatView.rows.test.ts
+++ b/mobile/src/session/MobileNativeChatView.rows.test.ts
@@ -31,9 +31,6 @@ vi.mock('./MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
vi.mock('./MobileNativeChatComposer', async () => {
const { composerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture')
return composerStub()
diff --git a/mobile/src/session/MobileNativeChatView.stop.test.ts b/mobile/src/session/MobileNativeChatView.stop.test.ts
index fc24ceb812b..bd5a4a6e6d3 100644
--- a/mobile/src/session/MobileNativeChatView.stop.test.ts
+++ b/mobile/src/session/MobileNativeChatView.stop.test.ts
@@ -30,9 +30,6 @@ vi.mock('./MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
vi.mock('./MobileNativeChatComposer', async () => {
const { composerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture')
return composerStub()
diff --git a/mobile/src/session/MobileNativeChatView.tail-follow.test.ts b/mobile/src/session/MobileNativeChatView.tail-follow.test.ts
index 701d506feff..8d78eed4f9a 100644
--- a/mobile/src/session/MobileNativeChatView.tail-follow.test.ts
+++ b/mobile/src/session/MobileNativeChatView.tail-follow.test.ts
@@ -36,9 +36,6 @@ vi.mock('./MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
vi.mock('./MobileNativeChatComposer', async () => {
const { composerStub } = await import('./MobileNativeChatView.render-stubs.test-fixture')
return composerStub()
diff --git a/mobile/src/session/MobileNativeChatView.tsx b/mobile/src/session/MobileNativeChatView.tsx
index 66f2bdda156..2a3e1365e8b 100644
--- a/mobile/src/session/MobileNativeChatView.tsx
+++ b/mobile/src/session/MobileNativeChatView.tsx
@@ -284,8 +284,7 @@ export function MobileNativeChatView({
)
// Per-turn status rows: one live indicator while the turn runs, then a settled
- // "Worked for N" row. The structured lane owns them; the bridge lane keeps its
- // three-dot indicator.
+ // "Worked for N" row. The structured lane owns them; the bridge lane gets the live line only.
const turns = useMobileNativeChatTurnDisclosure({
messages: data,
enabled: structuredActivityUi,
@@ -297,7 +296,7 @@ export function MobileNativeChatView({
activityText: turnIndicator?.activityText ?? null,
// The display status, decided once in the session hook.
stopping: turnIndicator?.stopping === true,
- lineYields: structuredActivityUi && (ask != null || permission != null || question != null),
+ lineYields: ask != null || permission != null || question != null,
scopeKey: sendSurfaceId
})
@@ -323,7 +322,6 @@ export function MobileNativeChatView({
onOpenFile={onOpenFile}
/>
) : null
- const bridgeWorking = agentWorking === true && !structuredActivityUi
const toolCallCount = useMemo(() => mobileNativeChatToolCallCount(folded), [folded])
const toggleTools = useCallback(() => setToolsExpanded((v) => !v), [])
@@ -363,9 +361,7 @@ export function MobileNativeChatView({
keyExtractor={(item) => item.id}
renderItem={history.renderListRow}
contentContainerStyle={
- toolCallCount > 0 || bridgeWorking
- ? styles.listContentUnderFloatingControls
- : styles.listContent
+ toolCallCount > 0 ? styles.listContentUnderFloatingControls : styles.listContent
}
// Let link/file taps land while the composer keyboard is up
// instead of being swallowed by the dismiss gesture.
@@ -392,7 +388,6 @@ export function MobileNativeChatView({
({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionSheetModal' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
// Stand-in composer: exposes the view's `handleSend` through a pressable, which is
// the only composer behaviour these banner tests exercise.
@@ -192,11 +189,6 @@ describe('MobileNativeChatView', () => {
: null
}
- /** The bridge cue stays outside the scrolling transcript. */
- function workingIndicators(): unknown[] {
- return renderer!.root.findAll((node) => String(node.type) === 'WorkingIndicator')
- }
-
it('puts the clock bar under the prompt, activity at the tail, and drops the three dots', async () => {
const folded = [userTurn('u1', 'go'), assistantTurn('a1', 'still working')]
await render({ messages: folded, folded, structuredActivityUi: true, agentWorking: true })
@@ -207,7 +199,6 @@ describe('MobileNativeChatView', () => {
expect(footerProps()).toEqual({ thinking: false, activityText: null, stopping: false })
expect(listIds().at(-1)).toBe('a1')
expect(props.activeTurnIsWorking).toBe(true)
- expect(workingIndicators()).toHaveLength(0)
})
it.each([
@@ -318,22 +309,16 @@ describe('MobileNativeChatView', () => {
expect(footerProps()).toMatchObject({ activityText: 'Running pnpm test' })
})
- it('keeps the bridge lane on the three-dot indicator with no turn status', async () => {
+ it('gives the bridge lane the same live Working line, with no turn status', async () => {
const folded = [userTurn('u1', 'go')]
await render({ messages: folded, folded, agentWorking: true })
const props = rowProps('u1')
expect(props.structuredActivityUi).toBe(false)
expect(props.turnStatus).toBeNull()
expect(props.activeTurnIsWorking).toBe(false)
- expect(footerProps()).toBeNull()
- expect(workingIndicators()).toHaveLength(1)
- expect(
- renderer!.root.find((node) => String(node.type) === 'WorkingIndicator').parent?.props
- .pointerEvents
- ).toBe('none')
+ expect(footerProps()).toEqual({ thinking: false, activityText: null, stopping: false })
const list = renderer!.root.find((node) => String(node.type) === 'FlatList')
- expect(list.props.ListFooterComponent).toBeNull()
- expect(list.props.contentContainerStyle).toBe(styles.listContentUnderFloatingControls)
+ expect(list.props.contentContainerStyle).toBe(styles.listContent)
})
it('settles the finished turn to a tappable duration', async () => {
diff --git a/mobile/src/session/MobileNativeChatView.waiting-rows.test.ts b/mobile/src/session/MobileNativeChatView.waiting-rows.test.ts
index 9a433253b99..443b0e0a259 100644
--- a/mobile/src/session/MobileNativeChatView.waiting-rows.test.ts
+++ b/mobile/src/session/MobileNativeChatView.waiting-rows.test.ts
@@ -45,9 +45,6 @@ vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionShee
vi.mock('./MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
let renderer: ReactTestRenderer | null = null
afterEach(() => {
diff --git a/mobile/src/session/__mocks__/mobile-prompt-controller.ts b/mobile/src/session/__mocks__/mobile-prompt-controller.ts
index 9af112119fd..19fa304dea6 100644
--- a/mobile/src/session/__mocks__/mobile-prompt-controller.ts
+++ b/mobile/src/session/__mocks__/mobile-prompt-controller.ts
@@ -71,9 +71,6 @@ vi.mock('../MobileNativeChatVisual', () => ({ useMobileNativeChatVisualRenderer:
vi.mock('../../platform/use-reduced-motion', () => ({ useReducedMotionEnabled: () => null }))
vi.mock('../MobileNativeChatLiveLine', () => ({ MobileNativeChatLiveLine: 'LiveStatus' }))
vi.mock('../MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
-vi.mock('../MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
vi.mock('../MobileNativeChatSessionOptionPickers', () => ({
MobileNativeChatSessionOptionPickers: 'SessionOptions'
}))
diff --git a/mobile/src/session/mobile-native-chat-merged-snapshot-parity-hooks.test.tsx b/mobile/src/session/mobile-native-chat-merged-snapshot-parity-hooks.test.tsx
index 53182603f66..84eb4112b81 100644
--- a/mobile/src/session/mobile-native-chat-merged-snapshot-parity-hooks.test.tsx
+++ b/mobile/src/session/mobile-native-chat-merged-snapshot-parity-hooks.test.tsx
@@ -74,9 +74,6 @@ vi.mock('../components/ActionSheetModal', () => ({ ActionSheetModal: 'ActionShee
vi.mock('./MobileNativeChatAsk', () => ({ MobileNativeChatAsk: 'ChatAsk' }))
vi.mock('./MobileNativeChatPermission', () => ({ MobileNativeChatPermission: 'ChatPermission' }))
vi.mock('./MobileNativeChatQuestion', () => ({ MobileNativeChatQuestion: 'ChatQuestion' }))
-vi.mock('./MobileAgentWorkingIndicator', () => ({
- MobileAgentWorkingIndicator: 'WorkingIndicator'
-}))
const STOP_ROW = `stopped-before-start:orca:${NEVER_OPENED}`
diff --git a/mobile/src/session/mobile-native-chat-view-styles.ts b/mobile/src/session/mobile-native-chat-view-styles.ts
index e5758b3a810..8d35da5d00e 100644
--- a/mobile/src/session/mobile-native-chat-view-styles.ts
+++ b/mobile/src/session/mobile-native-chat-view-styles.ts
@@ -43,9 +43,6 @@ export const styles = StyleSheet.create({
fontSize: typography.metaSize,
fontWeight: '600'
},
- workingPill: {
- paddingHorizontal: 0
- },
pressed: {
opacity: 0.6
},
diff --git a/mobile/src/session/use-mobile-native-chat-turn-disclosure.ts b/mobile/src/session/use-mobile-native-chat-turn-disclosure.ts
index 495a223b9b4..7a68e168c1c 100644
--- a/mobile/src/session/use-mobile-native-chat-turn-disclosure.ts
+++ b/mobile/src/session/use-mobile-native-chat-turn-disclosure.ts
@@ -81,8 +81,8 @@ function useScopedOpenKeys(scopeKey: string): [ReadonlySet, (key: string
}
/** Owns the transcript's per-turn status rows and their disclosure state, and
- * resolves what one list row needs. Bridge-lane chats pass `enabled: false` and
- * keep their single three-dot working indicator instead. */
+ * resolves what one list row needs. Bridge-lane chats pass `enabled: false`: no turn
+ * rows, but the same live "Working…" line while the agent works. */
export function useMobileNativeChatTurnDisclosure({
messages,
enabled,
@@ -188,7 +188,7 @@ export function useMobileNativeChatTurnDisclosure({
const line = useMemo(
() =>
nativeChatLiveLine({
- draws: enabled && isWorking && !lineYields && active !== null,
+ draws: isWorking && !lineYields && (!enabled || active !== null),
thinking: active?.thinking === true,
stopping,
activityText: activeActivityText,