diff --git a/ui/src/components/EntityRow.test.tsx b/ui/src/components/EntityRow.test.tsx index d4f7eb9b27e..5845a9d33e8 100644 --- a/ui/src/components/EntityRow.test.tsx +++ b/ui/src/components/EntityRow.test.tsx @@ -59,4 +59,37 @@ describe("EntityRow", () => { const markup = renderToStaticMarkup(); expect(markup).toContain("min-w-0 flex-1"); }); + + it("gives the title a min-width floor and lets meta shrink under titlePriority (PAP-12988)", () => { + const markup = renderToStaticMarkup( + chips} + />, + ); + + // The name keeps a usable floor instead of collapsing to zero... + expect(markup).toContain("min-w-(--sz-7rem)"); + // ...and the meta cluster is the item that yields (shrinks), not the title. + expect(markup).toContain("min-w-0 shrink"); + expect(markup).not.toContain('class="flex items-center gap-2 shrink-0"'); + }); + + it("stacks a secondaryRow on its own line beneath the main row (PAP-12988)", () => { + const markup = renderToStaticMarkup( + chips-inline} + secondaryRow={chips-stacked} + />, + ); + + // The secondary content renders, and the shell switches from a single flex + // row to a stacked block layout so the cluster gets its own full-width line. + expect(markup).toContain("secondary-cell"); + expect(markup).toContain("chips-stacked"); + expect(markup).not.toMatch(/^
{leading &&
{leading}
} -
+
{identifier && ( @@ -78,23 +106,46 @@ export function EntityRow({

)}
- {meta &&
{meta}
} + {meta && ( +
+ {meta} +
+ )} {meta &&
} {trailing &&
{trailing}
} ); + // With a secondaryRow, wrap the main line in its own flex row and stack the + // secondary content beneath it (indented to align under the title, past the + // leading capsule). Without it, `content` is rendered directly (unchanged). + const body = secondaryRow ? ( + <> +
{content}
+
{secondaryRow}
+ + ) : ( + content + ); + if (to) { return ( - - {content} + + {body} ); } return ( -
- {content} +
+ {body}
); } diff --git a/ui/src/components/SidebarAgents.tsx b/ui/src/components/SidebarAgents.tsx index cea2fdaeb80..4def4ef4080 100644 --- a/ui/src/components/SidebarAgents.tsx +++ b/ui/src/components/SidebarAgents.tsx @@ -151,17 +151,10 @@ function SidebarAgentItem({ : isPaused && hasInvalidOrgChain ? "Invalid org chain" : pauseResumeLabel; - const trailingLabelParts: string[] = []; - if (builtInStatus) { - trailingLabelParts.push( - builtInStatus === "pending_approval" - ? "Built-in, pending approval" - : builtInStatus === "needs_setup" - ? "Built-in, needs setup" - : "Built-in", - ); - } - if (hasInvalidOrgChain) trailingLabelParts.push("Invalid reporting chain"); + const trailingLabel = [ + builtInStatus ? `Built-in agent ${builtInStatus.replace(/_/g, " ")}` : null, + hasInvalidOrgChain ? "Invalid reporting chain" : null, + ].filter(Boolean).join(", ") || undefined; // C11 (DECISION-SHEET.md): the row itself is a SidebarNavItem, so agent rows // share the nav-row chrome (type, active state, rail tooltip, live dot). @@ -180,7 +173,7 @@ function SidebarAgentItem({ )} trailing={ builtInStatus || hasInvalidOrgChain ? ( - + {builtInStatus ? ( <> @@ -193,7 +186,7 @@ function SidebarAgentItem({ ) : undefined } - trailingLabel={trailingLabelParts.length > 0 ? trailingLabelParts.join(", ") : undefined} + trailingLabel={trailingLabel} liveAccessory={ agent.pauseReason === "budget" ? : undefined } diff --git a/ui/src/pages/Agents.tsx b/ui/src/pages/Agents.tsx index 8b6b18c7cee..f166f0807ef 100644 --- a/ui/src/pages/Agents.tsx +++ b/ui/src/pages/Agents.tsx @@ -314,6 +314,32 @@ export function Agents() { const agentJoinLeavePending = agentPending && membershipMutation.variables?.starred === undefined; const agentStarred = isStarred(membershipsQuery.data, "agent", agent.id); const builtInState = builtInByAgentId.get(agent.id); + // Provenance badge + lifecycle chip + inline `Set up`. Rendered inline in + // `meta` at xl (where there's room and the meta columns align) and on a + // dedicated full-width line beneath the name below xl, so the chips never + // starve the name — the row's primary identifier — at narrow widths. + const builtInCluster = builtInState ? ( + <> + + + {builtInState.status === "needs_setup" && ( + { + e.preventDefault(); + e.stopPropagation(); + }} + > + + + )} + + ) : null; return ( )} + secondaryRow={ + builtInCluster ? ( +
+ {builtInCluster} +
+ ) : undefined + } meta={
- {builtInState && ( -
- {/* Provenance sits immediately after the name, then the - lifecycle chip; the real agent status stays in `trailing`. */} - - - {builtInState.status === "needs_setup" && ( - { - e.preventDefault(); - e.stopPropagation(); - }} - > - - - )} + {builtInCluster && ( +
+ {builtInCluster}
)}
@@ -624,10 +638,11 @@ function OrgTreeNode({ ) : ( )} - {/* min-w-0 + truncate so deep indentation on narrow screens shortens - the name with an ellipsis instead of overflowing the row. */} -
-
+
+ {/* Name floor + `truncate` keeps the primary identifier readable; the + cluster wraps to a second line under pressure instead of starving + the name at narrow widths. */} +
{node.name} {roleLabels[node.role] ?? node.role} diff --git a/ui/storybook/stories/built-in-agents.stories.tsx b/ui/storybook/stories/built-in-agents.stories.tsx index b623bde2c14..04a52ce5d27 100644 --- a/ui/storybook/stories/built-in-agents.stories.tsx +++ b/ui/storybook/stories/built-in-agents.stories.tsx @@ -66,6 +66,42 @@ const notProvisionedState: BuiltInAgentState = { pauseReason: null, }; +/** + * Mirrors Agents.tsx renderAgentRow: the built-in cluster sits inline in `meta` + * at xl and drops to a full-width `secondaryRow` beneath the name below xl, with + * `titlePriority` giving the name a floor so it never collapses (PAP-12988). + */ +function RosterRow({ + name, + lifecycle, + status, +}: { + name: string; + lifecycle?: "needs_setup" | "pending_approval"; + status: string; +}) { + const cluster = ( + <> + + {lifecycle && } + {lifecycle === "needs_setup" && ( + + )} + + ); + return ( + {cluster}
} + meta={
{cluster}
} + trailing={} + /> + ); +} + function SectionLabel({ children }: { children: React.ReactNode }) { return (

@@ -89,40 +125,15 @@ export const SurfaceGallery: Story = {

Board 1 — Roster rows
- - - - -
- } - trailing={} - /> - - -
- } - trailing={} - /> - - - -
- } - trailing={} - /> + + +
+

+ Resize below the xl breakpoint to see the badge/action + cluster drop to a second line so the agent name never collapses + (PAP-12988). +