diff --git a/mobile/src/session/MobileNativeChatQueuedMessages.tsx b/mobile/src/session/MobileNativeChatQueuedMessages.tsx
index 15196a3198b..64a8a88b01a 100644
--- a/mobile/src/session/MobileNativeChatQueuedMessages.tsx
+++ b/mobile/src/session/MobileNativeChatQueuedMessages.tsx
@@ -1,5 +1,5 @@
import { useRef, useState } from 'react'
-import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
+import { Pressable, StyleSheet, Text, View } from 'react-native'
import {
AlertCircle,
ChevronDown,
@@ -17,6 +17,7 @@ import { colors, radii, spacing, typography } from '../theme/mobile-theme'
import type { MobileQueuedMessageCard } from './mobile-structured-queued-message-cards'
import type { MobileQueuePause } from './mobile-structured-queued-message-feed'
import type { MobileQueuedMessageEdit } from './use-mobile-structured-queued-message-controls'
+import { MobileQueuedCardText } from './MobileQueuedCardText'
/** The header's in-flight keys beside the cards' message ids, which never contain NUL. */
const RESUME_KEY = '\u0000resume'
@@ -99,6 +100,13 @@ export function MobileNativeChatQueuedMessages({
{pause ? 'Paused' : 'Queued'}
+
+ ·
+
{attentionCaption ? `${cards.length} · ${attentionCaption}` : cards.length}
@@ -273,61 +281,6 @@ export function MobileNativeChatQueuedMessages({
)
}
-/** Two lines, opening on tap when it clips: a card can hold another agent's message the person
- * never read, and Steer or Delete must not be a blind choice. Opened, a long one scrolls in a
- * capped box. */
-function MobileQueuedCardText({ text }: { text: string }): React.JSX.Element {
- const [expanded, setExpanded] = useState(false)
- const [clipped, setClipped] = useState(false)
- // The whole text laid out unseen at the card's width: more than two lines is what clips.
- const measure = (
- setClipped(event.nativeEvent.lines.length > 2)}
- >
- {text}
-
- )
- if (!clipped && !expanded) {
- return (
-
- {measure}
-
- {text}
-
-
- )
- }
- const toggle = (
- setExpanded(!expanded)}
- >
-
- {text}
-
- {expanded ? 'Show less' : 'Show more'}
-
- )
- // The press sits inside the scroll, so a drag scrolls and only a tap folds it.
- return expanded ? (
-
- {toggle}
-
- ) : (
-
- {measure}
- {toggle}
-
- )
-}
-
// Every action touches as a 44pt target (platform floor) inside its row: Android drops touches
// outside the parent, so the row is at least that tall and nothing overhangs it.
const MIN_TOUCH_TARGET = 44
@@ -378,19 +331,6 @@ const styles = StyleSheet.create({
paddingVertical: spacing.sm,
gap: 2
},
- body: {
- color: colors.textPrimary,
- fontSize: typography.bodySize
- },
- expandedBody: {
- maxHeight: 240
- },
- measure: {
- position: 'absolute',
- left: 0,
- right: 0,
- opacity: 0
- },
caption: {
color: colors.textMuted,
fontSize: typography.metaSize
diff --git a/mobile/src/session/MobileQueuedCardText.tsx b/mobile/src/session/MobileQueuedCardText.tsx
new file mode 100644
index 00000000000..fe690bcad67
--- /dev/null
+++ b/mobile/src/session/MobileQueuedCardText.tsx
@@ -0,0 +1,78 @@
+import { useState } from 'react'
+import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
+import { colors, typography } from '../theme/mobile-theme'
+
+/** Two lines, opening on tap when it clips: a card can hold another agent's message the person
+ * never read, and Steer or Delete must not be a blind choice. Opened, a long one scrolls in a
+ * capped box. */
+export function MobileQueuedCardText({ text }: { text: string }): React.JSX.Element {
+ const [expanded, setExpanded] = useState(false)
+ const [clipped, setClipped] = useState(false)
+ // The whole text laid out unseen at the card's width: more than two lines is what clips.
+ const measure = (
+ setClipped(event.nativeEvent.lines.length > 2)}
+ >
+ {text}
+
+ )
+ if (!clipped && !expanded) {
+ return (
+
+ {measure}
+
+ {text}
+
+
+ )
+ }
+ const toggle = (
+ setExpanded(!expanded)}
+ >
+
+ {text}
+
+ {expanded ? 'Show less' : 'Show more'}
+
+ )
+ // The press sits inside the scroll, so a drag scrolls and only a tap folds it.
+ return expanded ? (
+
+ {toggle}
+
+ ) : (
+
+ {measure}
+ {toggle}
+
+ )
+}
+
+const styles = StyleSheet.create({
+ body: {
+ color: colors.textPrimary,
+ fontSize: typography.bodySize
+ },
+ expandedBody: {
+ maxHeight: 240
+ },
+ measure: {
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ opacity: 0
+ },
+ caption: {
+ color: colors.textMuted,
+ fontSize: typography.metaSize
+ }
+})
diff --git a/src/renderer/src/components/native-chat/NativeChatQueuedMessageList.test.tsx b/src/renderer/src/components/native-chat/NativeChatQueuedMessageList.test.tsx
index d579520efb7..91044ff3b54 100644
--- a/src/renderer/src/components/native-chat/NativeChatQueuedMessageList.test.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatQueuedMessageList.test.tsx
@@ -504,7 +504,7 @@ describe('NativeChatQueuedMessageList', () => {
it('a paused queue holds every card, in order, under one header with Clear all and Resume', () => {
renderHeldQueue([waitingDraft('a', 1), waitingDraft('b', 2)], { reason: 'stopped' })
const header = screen.getByRole('list').previousElementSibling!
- expect(header.textContent).toBe('Paused2Clear allResume')
+ expect(header.textContent).toBe('Paused·2Clear allResume')
expect(screen.getAllByRole('button', { name: 'Steer' })).toHaveLength(2)
})
@@ -518,7 +518,7 @@ describe('NativeChatQueuedMessageList', () => {
it('a queue that drains on its own (or after a restart) reads Queued, with no Resume; no cards, nothing', () => {
renderHeldQueue([waitingDraft('waiting', 1)], null)
- expect(screen.getByRole('list').previousElementSibling?.textContent).toBe('Queued1')
+ expect(screen.getByRole('list').previousElementSibling?.textContent).toBe('Queued·1')
expect(screen.queryByRole('button', { name: 'Resume' })).toBeNull()
expect(screen.queryByRole('button', { name: 'Clear all' })).toBeNull()
cleanup()
@@ -538,7 +538,7 @@ describe('NativeChatQueuedMessageList', () => {
stopped
)
expect(screen.queryByRole('button', { name: 'Resume' })).toBeNull()
- expect(container.textContent).toContain('Queued3')
+ expect(container.textContent).toContain('Queued·3')
})
it('Resume shows it is pending and hands focus back', async () => {
@@ -622,7 +622,7 @@ describe('NativeChatQueuedMessageList', () => {
)
// The header is the box's first row, outside the list; the list is its second.
expect(box.children).toHaveLength(2)
- expect(box.firstElementChild?.textContent).toContain('Paused3')
+ expect(box.firstElementChild?.textContent).toContain('Paused·3')
expect(box.lastElementChild).toBe(list)
expect(list.className.split(' ')).toContain('divide-y')
const rows = within(list).getAllByRole('listitem')
diff --git a/src/renderer/src/components/native-chat/NativeChatQueuedMessageList.tsx b/src/renderer/src/components/native-chat/NativeChatQueuedMessageList.tsx
index d19cbfa8363..42f2b658082 100644
--- a/src/renderer/src/components/native-chat/NativeChatQueuedMessageList.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatQueuedMessageList.tsx
@@ -198,6 +198,7 @@ function NativeChatQueueHeader({
? translate('components.native-chat.queuedMessages.paused', 'Paused')
: translate('components.native-chat.queuedMessages.queued', 'Queued')}
+ ·
{count}