diff --git a/packages/junior-dashboard/e2e/conversations.spec.ts b/packages/junior-dashboard/e2e/conversations.spec.ts
index c8a0fcf129..3d82f8b42c 100644
--- a/packages/junior-dashboard/e2e/conversations.spec.ts
+++ b/packages/junior-dashboard/e2e/conversations.spec.ts
@@ -428,11 +428,11 @@ test("opens and closes a conversation in the mobile workspace", async ({
const pending = page.getByLabel("Pending messages");
await expect(pending).toBeVisible();
- // Cancel bar owns the total count on mobile; expand uses a distinct label.
- await expect(pending.getByText("5 queued messages")).toHaveCount(1);
- const expand = pending.getByRole("button", { name: "Show queued messages" });
+ // Mobile collapsed control owns the total count (no separate cancel bar).
+ const expand = pending.getByRole("button", { name: "5 queued messages" });
await expect(expand).toBeVisible();
await expect(expand).toHaveAttribute("aria-expanded", "false");
+ await expect(pending.getByText("5 queued messages")).toHaveCount(1);
await expect(
pending.getByText("Also check the canary traffic from the last deploy."),
).toBeHidden();
diff --git a/packages/junior-dashboard/src/client/conversations/ConversationPage.tsx b/packages/junior-dashboard/src/client/conversations/ConversationPage.tsx
index 6b1e25379a..a33c6ee7db 100644
--- a/packages/junior-dashboard/src/client/conversations/ConversationPage.tsx
+++ b/packages/junior-dashboard/src/client/conversations/ConversationPage.tsx
@@ -288,28 +288,27 @@ function ConversationReplyFooter(props: {
onPinRequestRef.current();
}, []);
const onSubmitStart = useCallback(() => {
- cancelPendingMessagesRef.current.reset();
+ // Keep an in-flight remove intact so optimistic cache rollback stays coherent.
+ if (!cancelPendingMessagesRef.current.isPending) {
+ cancelPendingMessagesRef.current.reset();
+ }
onPinRequestRef.current();
}, []);
const cancellableMessageIds = props.pendingMessages
.filter((message) => message.clientStatus === undefined)
.map((message) => message.inboundMessageId);
- const cancellableMessageIdsRef = useRef(cancellableMessageIds);
- cancellableMessageIdsRef.current = cancellableMessageIds;
- const hasSendingOutboxMessage = props.pendingMessages.some(
- (message) => message.clientStatus === "sending",
- );
const pendingGeneratedAtRef = useRef(props.pendingGeneratedAt);
pendingGeneratedAtRef.current = props.pendingGeneratedAt;
- const onCancelQueue = useCallback(() => {
- const inboundMessageIds = cancellableMessageIdsRef.current;
+ const onCancelMessage = useCallback((message: ConversationMailboxMessage) => {
const receivedBefore = pendingGeneratedAtRef.current;
- if (!receivedBefore || inboundMessageIds.length === 0) return;
+ if (!receivedBefore) return;
cancelPendingMessagesRef.current.mutate({
- inboundMessageIds,
+ inboundMessageIds: [message.inboundMessageId],
receivedBefore,
});
}, []);
+ const cancelTargetInboundMessageId =
+ cancelPendingMessages.variables?.inboundMessageIds[0];
const cancelError = Boolean(
cancelPendingMessages.error &&
cancelPendingMessages.variables?.inboundMessageIds.some((id) =>
@@ -336,15 +335,15 @@ function ConversationReplyFooter(props: {
+ Could not remove. Try again. +
+ ) : null} ); } function ExpandQueuedMessagesButton(props: { - /** When the cancel bar already shows the total count, avoid repeating it on mobile. */ - countShownInCancelBar: boolean; expanded: boolean; hiddenCount: number; onClick(): void; @@ -166,10 +211,7 @@ function ExpandQueuedMessagesButton(props: { ? `${props.hiddenCount} more queued messages` : totalLabel; // Mobile collapses previews and uses the total count as the expand control. - // When cancel already owns that count, keep a distinct expand action label. - const mobileCollapsedLabel = props.countShownInCancelBar - ? "Show queued messages" - : totalLabel; + const mobileCollapsedLabel = totalLabel; const label = props.expanded ? "Show fewer queued messages" : moreLabel; return ( @@ -195,9 +237,10 @@ function ExpandQueuedMessagesButton(props: { export function PendingMailboxStack(props: { cancelError?: boolean; cancelPending?: boolean; + cancelTargetInboundMessageId?: string; conversation: ConversationTranscript; messages: readonly ConversationMailboxMessage[]; - onCancelQueue?: () => void; + onCancelMessage?: (message: ConversationMailboxMessage) => void; onRetry?(message: ConversationMailboxMessage): void; }): ReactNode { const [expanded, setExpanded] = useState(false); @@ -219,50 +262,34 @@ export function PendingMailboxStack(props: { const visibleRows = showCollapsed ? previewRows : rows; const hiddenCount = Math.max(0, rows.length - COLLAPSED_PENDING_ROW_COUNT); const toggleExpanded = () => setExpanded((value) => !value); - const cancellableCount = rows.filter( - (message) => message.clientStatus === undefined, - ).length; - const hasSendingRow = rows.some( - (message) => message.clientStatus === "sending", - ); - const showCancel = - cancellableCount > 0 && !hasSendingRow && Boolean(props.onCancelQueue); - const countLabel = - rows.length === 1 ? "1 queued message" : `${rows.length} queued messages`; return (