003-components: matter_table + matter_card behind the render-substrate
Story-1794 shipped the render-substrate but rendered every valid frame
as a text/JSON summary — no typed catalog
.tsx existed yet. This change fills in the FIRST real ones:
matter_table and matter_card, each bound to the
MatterListItem DTO, registered under their snake catalog ids
(#1791), and dispatched by ComponentResultView. A
render.data frame for those ids now paints a real table /
card; an unknown id still falls back to text — the #1794 regression
guard (RFD-AGT-004 §D8/§7.6).
What changed
MatterTable + MatterCard take
{rows: MatterListItem[], fields}. Rows come ONLY from
the validated frame (no fetch, no row invention); types are reused
from src/gen — no hand types.
resolveRenderComponent reads each component's
fields straight off the generated
COMPONENT_CATALOG (#1791) and passes them in. The table
renders one <th scope="col"> per field; change the
catalog field-set and the columns follow.
ComponentResultView resolves the
component_id to its .tsx only when
fallbackText is null (valid, known rows). Unknown ids /
schema-rejected rows arrive as fallbackText and render
as text exactly as before — never blank.
Table = a real <table> with an
sr-only caption and scoped column headers
(screen-reader navigable). Card = a labelled role="group"
region, each row a <section> with a
<dl> of label/value pairs.
Dropped the unused render_marker field from
WireRenderFrame (the app never consumed it; extra wire
fields are ignored). Fixed the stale
RENDER_CATALOG_VERSION comment that claimed the version
is carried to the brain via DockChatContext — it is not;
D11 is enforced client-side by validateRenderRows.
Reuses the existing Table primitive
(src/components/ui/table.tsx) and design tokens. Cell
values render generically (String(value), em-dash for
nullish) — no per-field formatter framework, no second non-matter
shape (that is the 2nd-shape-proof ticket).
Where it lives
src/components/agents/render-catalog/matter-table.tsx— NEW.MatterTableover theTableprimitive; columns = the passed view fields.src/components/agents/render-catalog/matter-card.tsx— NEW.MatterCard; labelled region, one card per row,<dl>label/value pairs.src/components/agents/render-catalog/registry.ts— NEW.resolveRenderComponent(component_id)→{Component, fields}(fields from the generated catalog) or null → text fallback.src/components/agents/render-catalog/fields.ts— NEW.fieldLabelKey(i18n header key) + genericformatCell.src/components/agents/render-catalog/*.stories.tsx+render-catalog.fixtures.ts— NEW. Storybook coverage (incl. narrow-columns proof + missing-optional).src/components/agents/agent-chat-panel.tsx—ComponentResultViewnow dispatches to the typed component before the text fallback.src/lib/agents/atlas-rail.ts— nit 1: dropped unusedrender_marker.src/lib/surface-registry/page-card/render-catalog.ts— nit 2: corrected theRENDER_CATALOG_VERSIONcomment.messages/{en,hi}.json—matterTableCaption/matterCardRegionLabel/matterField.*(both locales).
Verify
pnpm lint # clean (1 pre-existing avatar.tsx warning, unrelated) pnpm test # 894 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.
- table-from-frame
agent-chat-panel.render.test.tsxdrives a realrender.dataframe →reduceFrame→mergeLiveRender→ panel; asserts a real<table>with 6 scoped headers from the catalog view fields + bound row data on screen. - card-from-frame a
matter_cardframe renders a labelledrole="group"region with the field labels + row data. - unknown → text an unknown
component_idstill paints the visible text/JSON fallback (regression guard intact); also re-verified in the mountedagent-dock-mount.render.test.tsx. - HR-23 the persisted reference still carries intent only (no rows) — unchanged.
Reversible calls (stated for review): column headers are
localized via a new Agents.matterField.* key set; cell
values render as their bound DTO string (status enum / ISO date shown
as-is) — richer per-field formatting is intentionally deferred (no
formatter framework). Card uses <dl> + a
display:contents grid wrapper for label/value alignment.
Non-goal (RFD-AGT-004): no second non-matter shape, no producer/emission changes, no new framework.
Files changed
14 file(s), +352 / -20 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +12 | -1 |
| messages/hi.json | Config | +12 | -1 |
| src/components/agents/agent-chat-panel.render.test.tsx | Component | +31 | -3 |
| src/components/agents/agent-chat-panel.tsx | Component | +27 | -7 |
| src/components/agents/agent-dock-mount.render.test.tsx | Component | +3 | -2 |
| src/components/agents/render-catalog/fields.ts | Component | +21 | -0 |
| src/components/agents/render-catalog/matter-card.stories.tsx | Component | +21 | -0 |
| src/components/agents/render-catalog/matter-card.tsx | Component | +42 | -0 |
| src/components/agents/render-catalog/matter-table.stories.tsx | Component | +25 | -0 |
| src/components/agents/render-catalog/matter-table.tsx | Component | +62 | -0 |
| src/components/agents/render-catalog/registry.ts | Component | +35 | -0 |
| src/components/agents/render-catalog/render-catalog.fixtures.ts | Component | +54 | -0 |
| src/lib/agents/atlas-rail.ts | Library | +2 | -2 |
| src/lib/surface-registry/page-card/render-catalog.ts | Library | +5 | -4 |