From d86096a720ebecf6e4b101fc1d405f01c5354e86 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 10 Jul 2026 17:26:32 +0000 Subject: [PATCH] Align attention card actions Co-Authored-By: Paperclip --- ui/src/components/AttentionQueueRow.test.tsx | 22 ++- ui/src/components/AttentionQueueRow.tsx | 137 ++++++++++--------- ui/src/pages/WhatNeedsMe.tsx | 6 +- 3 files changed, 94 insertions(+), 71 deletions(-) diff --git a/ui/src/components/AttentionQueueRow.test.tsx b/ui/src/components/AttentionQueueRow.test.tsx index 44105b7e070..7d2d72a978d 100644 --- a/ui/src/components/AttentionQueueRow.test.tsx +++ b/ui/src/components/AttentionQueueRow.test.tsx @@ -184,8 +184,8 @@ describe("AttentionQueueRow", () => { expect(links.some((a) => a.textContent?.includes("Hire agent: Research Analyst"))).toBe(false); }); - it("renders inline decision verbs on a collapsed inline row", () => { - const el = render( + it("renders collapsed inline decision verbs in the right-side action area with semantic variants", () => { + render( { onDismiss={noop} />, ); - expect(el.textContent).toContain("Approve"); - expect(el.textContent).toContain("Reject"); + + const header = container?.querySelector('[role="button"][aria-expanded]'); + expect(header?.textContent).not.toContain("Approve"); + expect(header?.textContent).not.toContain("Reject"); + + const decisionActions = container?.querySelector('[aria-label="Decision actions"]'); + expect(decisionActions?.textContent).toContain("Approve"); + expect(decisionActions?.textContent).toContain("Reject"); + + const buttons = Array.from(decisionActions?.querySelectorAll("button") ?? []); + expect(buttons.find((button) => button.textContent === "Approve")?.getAttribute("data-variant")).toBe( + "default", + ); + expect(buttons.find((button) => button.textContent === "Reject")?.getAttribute("data-variant")).toBe( + "destructive", + ); }); it("does not expose a toggle button for non-inline rows", () => { diff --git a/ui/src/components/AttentionQueueRow.tsx b/ui/src/components/AttentionQueueRow.tsx index 3aee44aaf98..5e35da5f58c 100644 --- a/ui/src/components/AttentionQueueRow.tsx +++ b/ui/src/components/AttentionQueueRow.tsx @@ -140,7 +140,7 @@ export function AttentionQueueRow({
0 && }
- {/* Inline decision verbs on collapsed inline rows (plan §3). */} - {expandable && !expanded && verbs.length > 0 && ( -
- {verbs.map((verb) => ( - - ))} -
- )} {/* Controls: kept as siblings (not inside the clickable header) so they never toggle expand and stay valid interactive targets. */} -
- {!inline && href && ( - +
+ {expandable && !expanded && verbs.length > 0 && ( +
+ {verbs.map((verb) => ( + + ))} +
)} - {isHidden ? ( - onRestore && ( - - ) - ) : ( - - - - - - {onSnooze && onSnooze(item, iso)} />} - onDismiss(item)}> - - Dismiss - - {href && ( - <> - - - Open source - - - )} - - - )} + ) + ) : ( + + + + + + {onSnooze && onSnooze(item, iso)} />} + onDismiss(item)}> + + Dismiss + + {href && ( + <> + + + Open source + + + )} + + + )} +
@@ -324,6 +326,13 @@ export function AttentionQueueRow({ ); } +function decisionVerbVariant(verb: AttentionItem["decisionVerbs"][number]): "default" | "outline" | "destructive" { + const text = `${verb.id} ${verb.label}`.toLowerCase(); + if (/\b(reject|decline|deny|delete|remove)\b/.test(text)) return "destructive"; + if (/\b(accept|approve|confirm|apply)\b/.test(text)) return "default"; + return "outline"; +} + /** Small pill used for the project / workspace chips. Clickable when `onClick`. */ function Chip({ icon: Icon, @@ -335,7 +344,7 @@ function Chip({ onClick?: (e: MouseEvent) => void; }) { const className = cn( - "inline-flex max-w-[12rem] items-center gap-1 rounded-sm border border-border/70 bg-muted/40 px-1.5 py-0.5 text-(length:--text-nano) text-muted-foreground", + "inline-flex max-w-(--sz-12rem) items-center gap-1 rounded-sm border border-border/70 bg-muted/40 px-1.5 py-0.5 text-(length:--text-nano) text-muted-foreground", onClick && "transition-colors hover:border-border hover:bg-accent hover:text-foreground", ); const content = ( @@ -508,7 +517,7 @@ function ApprovalResolver({ item, companyId }: { item: AttentionItem; companyId: value={note} onChange={(e) => setNote(e.target.value)} placeholder="Optional decision note…" - className="min-h-[64px] text-sm" + className="min-h-16 text-sm" />