From f896fa6442485b275e8db4d69d015720edb9a736 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Tue, 24 Mar 2026 19:56:47 +0100 Subject: [PATCH] feat: add approval queue UX with position labels, queue preview, and count badges - ChatPane: show 'Approval 1 of N' label, queued-approvals accordion preview, queue count badge in header, and hint text for queue advancement - Sidebar: show '+N queued' count next to 'Awaiting approval' badge - ActivityPanel: show total approval count in header badge Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/renderer/components/ActivityPanel.tsx | 6 +- src/renderer/components/ChatPane.tsx | 75 ++++++++++++++++++++++- src/renderer/components/Sidebar.tsx | 3 +- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/src/renderer/components/ActivityPanel.tsx b/src/renderer/components/ActivityPanel.tsx index db71435..e674fd4 100644 --- a/src/renderer/components/ActivityPanel.tsx +++ b/src/renderer/components/ActivityPanel.tsx @@ -184,6 +184,8 @@ export function ActivityPanel({ const isBusy = session.status === 'running'; const hasPendingApproval = session.pendingApproval?.status === 'pending'; + const queuedCount = (session.pendingApprovalQueue ?? []).filter((a) => a.status === 'pending').length; + const totalApprovalCount = (hasPendingApproval ? 1 : 0) + queuedCount; const toolsDisabled = isBusy || projectIsScratchpad; const accent = modeAccent[pattern.mode] ?? modeAccent.single; const hasTools = mcpServers.length > 0 || lspProfiles.length > 0; @@ -200,7 +202,9 @@ export function ActivityPanel({ {hasPendingApproval ? ( - Approval + + Approval{totalApprovalCount > 1 ? `s (${totalApprovalCount})` : ''} + ) : isBusy ? ( diff --git a/src/renderer/components/ChatPane.tsx b/src/renderer/components/ChatPane.tsx index 4fdae22..c3024e8 100644 --- a/src/renderer/components/ChatPane.tsx +++ b/src/renderer/components/ChatPane.tsx @@ -228,13 +228,18 @@ function ApprovalBanner({ approval, onResolve, isResolving, + position, + total, }: { approval: PendingApprovalRecord; onResolve: (decision: ApprovalDecision) => void; isResolving: boolean; + position?: number; + total?: number; }) { const kindLabel = approval.kind === 'final-response' ? 'Final response review' : 'Tool call approval'; const hasMessages = approval.messages && approval.messages.length > 0; + const showPosition = position !== undefined && total !== undefined && total > 1; return (
@@ -247,6 +252,11 @@ function ApprovalBanner({ {kindLabel} + {showPosition && ( + + {position} of {total} + + )}
{/* Agent / permission context */} @@ -301,11 +311,62 @@ function ApprovalBanner({ Reject + {showPosition && ( + + Next approval will appear after this one is resolved + + )} ); } +/* ── Queued approvals preview ──────────────────────────────── */ + +function QueuedApprovalsList({ approvals }: { approvals: PendingApprovalRecord[] }) { + const [expanded, setExpanded] = useState(false); + + return ( +
+ + {expanded && ( +
+ {approvals.map((approval) => { + const kindLabel = approval.kind === 'final-response' ? 'response' : 'tool'; + return ( +
+ + {approval.title} + + {kindLabel} + + {approval.agentName && ( + {approval.agentName} + )} +
+ ); + })} +
+ )} +
+ ); +} + /* ── ChatPane ──────────────────────────────────────────────── */ interface ChatPaneProps { @@ -340,6 +401,8 @@ export function ChatPane({ const isSessionBusy = session.status === 'running'; const pendingApproval = session.pendingApproval?.status === 'pending' ? session.pendingApproval : undefined; + const queuedApprovals = (session.pendingApprovalQueue ?? []).filter((a) => a.status === 'pending'); + const totalPendingCount = (pendingApproval ? 1 : 0) + queuedApprovals.length; const isScratchpad = isScratchpadProject(project); const primaryAgent = pattern.agents[0]; const selectedModel = primaryAgent ? findModel(primaryAgent.model, availableModels) : undefined; @@ -446,6 +509,11 @@ export function ChatPane({
Awaiting approval + {queuedApprovals.length > 0 && ( + + +{queuedApprovals.length} queued + + )}
)} {isSessionBusy && !pendingApproval && } @@ -579,12 +647,17 @@ export function ChatPane({
{/* Pending approval banner */} {pendingApproval && ( -
+
void handleResolveApproval(decision)} + position={totalPendingCount > 1 ? 1 : undefined} + total={totalPendingCount > 1 ? totalPendingCount : undefined} /> + {queuedApprovals.length > 0 && ( + + )}
)} diff --git a/src/renderer/components/Sidebar.tsx b/src/renderer/components/Sidebar.tsx index 49bc29e..d2cef02 100644 --- a/src/renderer/components/Sidebar.tsx +++ b/src/renderer/components/Sidebar.tsx @@ -168,6 +168,7 @@ function SessionItem({ const isRunning = session.status === 'running'; const isError = session.status === 'error'; const hasPendingApproval = session.pendingApproval?.status === 'pending'; + const queuedCount = (session.pendingApprovalQueue ?? []).filter((a) => a.status === 'pending').length; const mode = pattern?.mode ?? 'single'; const visual = modeVisuals[mode]; const ModeIcon = visual.icon; @@ -273,7 +274,7 @@ function SessionItem({ {hasPendingApproval && ( - Awaiting approval + Awaiting approval{queuedCount > 0 && ` (+${queuedCount})`} )} {isError && (