PR: #456: feat(agents): client_card — 2nd component shape proves catalog genericity [Story-1798] by @ankitmaheshwari-com
View PR →
Story-1798

2nd-component-shape proof: client_card (catalog genericity)

Story-1795 shipped the FIRST typed catalog components — matter_table and matter_card — but both bind the SAME DTO (MatterListItem). That left an open question: is the render substrate genuinely shape-generic, or is it secretly matter-coupled? This change answers it by registering a NON-matter shape, client_card, bound to a DIFFERENT generated DTO (ClientListItem) and dispatched by the SAME ComponentResultView seam. App-side validation only — it proves the catalog/dispatch generalizes, not a new live brain/broker path (RFD-AGT-004 §7.6).

What changed

A non-matter shape, bound to a different gen DTO

ClientCard mirrors MatterCard exactly (labelled region, one card per row, <dl> label/value pairs) but binds ClientListItem from src/gen — no hand-written types. Rows come ONLY from the validated frame (no fetch, no row invention).

Catalog entry under a snake id

client_card is declared on clientsCard with a first-class dtoBinding {dto: "ClientListItem", fields}; pnpm gen:catalog flattens it into COMPONENT_CATALOG. check:catalog asserts every bound field ⊆ the real ClientListItem field-set.

The registry is now shape-generic, not matter-typed

The dispatch map's value type became the structural RenderCatalogProps (rows: Record<string, unknown>[] + fields). Each component is authored against its own bound DTO and erased to that shape in the registry — that erasure IS the genericity proof. The old MatterListItem cast in ComponentResultView is gone; validated rows pass straight through.

Fields from the catalog, never hardcoded

resolveRenderComponent("client_card") reads the fields straight off the generated catalog and passes them in; the card renders one label/value pair per field. Change the catalog field-set and the card follows.

Regression guard intact

An unknown component_id still arrives as fallbackText and renders as text — never blank. The #1794 text fallback is unchanged; the mounted test re-asserts it alongside the new client_card case.

No new framework (ponytail)

matter_table/matter_card were left untouched; ClientCard reuses the existing card pattern, tokens, and the shared formatCell. The only shared addition is one structural props interface + one client field-label key helper — no generic "EntityCard" mini-framework.

Where it lives

Verify

pnpm lint          # clean (1 pre-existing avatar.tsx warning, unrelated)
pnpm test          # 895 passed
pnpm build         # green (tsc + route + catalog codegen)
pnpm check:catalog # OK — 22 L1 surfaces, catalog fresh
git diff --check   # clean
# NOTE: do NOT run pnpm codegen — import contracts from src/gen.

Reversible calls (stated for review): the client view fields are name, clientNumberFormatted, clientType, status, createdAt (the display subset of ClientListItem); labels are a new Agents.clientField.* key set; cell values render as their bound DTO string (enum / ISO date as-is) — no per-field formatter framework. Each card's aria-label is the client name (matter_card uses title || matterRef).

Non-goal (RFD-AGT-004): NO brain selection or broker binding for clients (a future live-path ticket), NO new framework. Reuses the matter_card pattern — a genericity proof, kept minimal.

Files changed

12 file(s), +226 / -19 lines

Path Role + -
messages/en.jsonConfig+8-0
messages/hi.jsonConfig+8-0
src/components/agents/agent-chat-panel.render.test.tsxComponent+36-0
src/components/agents/agent-chat-panel.tsxComponent+3-8
src/components/agents/render-catalog/client-card.stories.tsxComponent+21-0
src/components/agents/render-catalog/client-card.tsxComponent+42-0
src/components/agents/render-catalog/fields.tsComponent+17-0
src/components/agents/render-catalog/registry.tsComponent+13-10
src/components/agents/render-catalog/render-catalog.fixtures.tsComponent+40-1
src/lib/surface-registry/page-card/cards/clients.card.tsLibrary+14-0
src/lib/surface-registry/page-card/catalog.tsLibrary+5-0
src/lib/surface-registry/page-card/generated-component-catalog.tsLibrary+19-0