From f238b90d9d5f86f056965e1d77465c4c909d8052 Mon Sep 17 00:00:00 2001 From: Jose Antonio Alatorre Date: Fri, 16 Oct 2026 22:29:52 +0200 Subject: [PATCH] feat(command-center-sdk): let EntitySummary show an icon before its title EntitySummary takes an optional titleLead, rendered before the summary title in a new cc-entity-summary__heading row, so a detail page can show the entity's icon or avatar at the size it chooses (Command Center's Agent page shows the Agent's animated icon there). A lead that renders nothing takes no space; without titleLead the title renders exactly as before. Compatibility axes: additive optional prop on EntitySummaryProps and the stylesheet classes cc-entity-summary__heading and cc-entity-summary__title-lead. No contract ID, JSON Schema, fixture, iframe protocol, theme, or storage change. Verification (Node 22.22.0): npm run check; sdk:test (347 vitest, one new, plus 137 node tests); sdk:build; packed-consumer smoke test. Co-Authored-By: Claude Opus 5.5 --- command-center-sdk/CHANGELOG.md | 7 +++++ command-center-sdk/docs/resources.md | 1 + .../src/views/EntitySummary.client.test.tsx | 28 +++++++++++++++++++ .../src/views/EntitySummary.tsx | 15 +++++++++- command-center-sdk/src/views/README.md | 3 +- command-center-sdk/styles.css | 16 +++++++++++ 6 files changed, 68 insertions(+), 2 deletions(-) diff --git a/command-center-sdk/CHANGELOG.md b/command-center-sdk/CHANGELOG.md index 0db7606e..3f7178c2 100644 --- a/command-center-sdk/CHANGELOG.md +++ b/command-center-sdk/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## Unreleased + +- **`EntitySummary` takes a `titleLead`.** A detail page can show the entity's icon or avatar + before the summary title, at the size the host chooses. The lead and the title sit in a new + `cc-entity-summary__heading` row, and a lead that renders nothing takes no space. Without + `titleLead` the title renders exactly as before. + ## 0.5.17 Compatibility axes: the packaged skill `general/maintain-command-center-code-repository`. No npm diff --git a/command-center-sdk/docs/resources.md b/command-center-sdk/docs/resources.md index f2324060..ce18f3df 100644 --- a/command-center-sdk/docs/resources.md +++ b/command-center-sdk/docs/resources.md @@ -285,6 +285,7 @@ export function ServiceDetail({ service }: { service: Service }) { headerActions={} summary={ } summary={{ entity: { id: service.uid, type: "service", title: service.name }, badges: [{ key: "status", label: service.status }], diff --git a/command-center-sdk/src/views/EntitySummary.client.test.tsx b/command-center-sdk/src/views/EntitySummary.client.test.tsx index c718dfa6..15772318 100644 --- a/command-center-sdk/src/views/EntitySummary.client.test.tsx +++ b/command-center-sdk/src/views/EntitySummary.client.test.tsx @@ -55,4 +55,32 @@ describe("EntitySummary field information", () => { await act(async () => toggle.click()); expect(container.querySelector(".cc-entity-summary__info-note")).toBeNull(); }); + + it("puts the title lead before the title and leaves the title alone without one", async () => { + const container = document.createElement("div"); + document.body.append(container); + const root = createRoot(container); + roots.push(root); + const summary = { + entity: { id: "agent-1", type: "agent", title: "Tutorial Helper" }, + badges: [], + inline_fields: [], + highlight_fields: [], + stats: [], + }; + + await act(async () => { + root.render(} />); + }); + const heading = container.querySelector(".cc-entity-summary__heading")!; + expect(heading.firstElementChild?.className).toBe("cc-entity-summary__title-lead"); + expect(heading.querySelector("[data-testid='lead']")).not.toBeNull(); + expect(heading.querySelector("h2")?.textContent).toBe("Tutorial Helper"); + + await act(async () => { + root.render(); + }); + expect(container.querySelector(".cc-entity-summary__heading")).toBeNull(); + expect(container.querySelector(".cc-entity-summary__identity > h2")?.textContent).toBe("Tutorial Helper"); + }); }); diff --git a/command-center-sdk/src/views/EntitySummary.tsx b/command-center-sdk/src/views/EntitySummary.tsx index e1a20178..a9e63b94 100644 --- a/command-center-sdk/src/views/EntitySummary.tsx +++ b/command-center-sdk/src/views/EntitySummary.tsx @@ -24,6 +24,11 @@ export interface EntitySummaryProps { onLinkSelect?: (url: string, item: EntitySummaryItem) => void; onRemoveLabel?: (label: string) => Promise | void; onLabelMutationError?: (error: unknown) => void; + /** + * Shown before the title, usually the entity's icon or avatar. The host sizes it; a lead that + * renders nothing takes no space. + */ + titleLead?: ReactNode; } function getValue(value: unknown): string { @@ -128,6 +133,7 @@ export function EntitySummary({ onRemoveLabel, renderFieldLead, summary, + titleLead, }: EntitySummaryProps) { const summaryLabels = summary.label_management?.labels ?? summary.labels ?? []; const [labels, setLabels] = useState(summaryLabels); @@ -194,7 +200,14 @@ export function EntitySummary({
-

{summary.entity.title}

+ {titleLead ? ( +
+ {titleLead} +

{summary.entity.title}

+
+ ) : ( +

{summary.entity.title}

+ )}
{summary.inline_fields.map((field) => { const value = getValue(field.value); diff --git a/command-center-sdk/src/views/README.md b/command-center-sdk/src/views/README.md index 6a073f3f..0a70c1fc 100644 --- a/command-center-sdk/src/views/README.md +++ b/command-center-sdk/src/views/README.md @@ -138,4 +138,5 @@ fitting rules are pure functions in `detail-tab-layout.ts`, and its More menu is in action mode. Hosts resolve tabs once with `resolveResourceDetailTabs` from `/resource`, which is the only place `isVisible` and function-valued `disabled` run, and switch the body on that result. `EntitySummary` and `CollapsedEntitySummary` render normalized backend-neutral summary data; hosts contribute icons, link handling, editing, label mutations, and notifications through -callbacks. No view in this module knows a consumer endpoint or application-owned search parameter. +callbacks. `EntitySummary`'s `titleLead` puts a host node, such as the entity's icon, before the +title; the host sizes it, and a lead that renders nothing takes no space. No view in this module knows a consumer endpoint or application-owned search parameter. diff --git a/command-center-sdk/styles.css b/command-center-sdk/styles.css index ff7c6d90..a152fafa 100644 --- a/command-center-sdk/styles.css +++ b/command-center-sdk/styles.css @@ -921,6 +921,22 @@ h3), min-width: 0; } +.cc-entity-summary__heading { + align-items: center; + display: flex; + gap: 0.6rem; + min-width: 0; +} + +.cc-entity-summary__title-lead { + display: inline-flex; + flex-shrink: 0; +} + +.cc-entity-summary__title-lead:empty { + display: none; +} + .cc-entity-summary h2 { font-size: var(--font-size-markdown-h1); font-weight: var(--font-weight-semibold);