COMPONENTS FEATURE
Contract-driven grid
A grid that calls OPTIONS on its feed, reads the route contract and builds its columns from it. The template holds an endpoint, never a column config.
Contract-driven grid
The grid shell in the template is a single element with one piece of data: a pointer to its feed endpoint. There is no server-projected column configuration in the template at all — no column list, no headers, no sort config. The grid learns its own shape from the route it reads.
This is the Presentation Boundary pillar applied to a table: the template carries no data-shaping logic, because the data's shape is declared on the route and discovered, never hand-wired.
The whole template
The live preview on this page is exactly this:
{% include '@platform-ui/components/runtime/grid-v2.html.twig' with { gridId: 'ui-inventory', endpoint: '/_feed/inventory', emptyMessage: 'The inventory is empty.'} only %}That is the entire integration. The grid shell renders with a data-ui-grid-endpoint pointer and nothing else.
How it works
grid-runtime-v2.js issues an OPTIONS request to the endpoint and reads the route's self-describing contract: the output shape (columns and their types), and the route's declared sort, filter, and pager capabilities. It then builds the entire grid — headers, cells, and any controls — from that contract. The runtime never assumes a shape; it asks the route what it serves.
The same OPTIONS contract is the one description every consumer reads. This grid reads it; an API client reads it; a generated SDK reads it. They cannot drift apart, because there is no second source of truth for the data's shape.
A finite, self-degrading feed
The showcase feed declares a finite synthetic dataset (mode: single). The runtime reads that from the contract and does the sensible thing: it renders a clean static table and no pager, because the contract says there is nothing to page through. That is graceful degradation by contract — the UI adapts to what the route actually offers rather than rendering controls that lead nowhere.
For honesty: this showcase grid is an intentionally finite feed with no pager and no sort/filter UI. The OPTIONS contract is the real, working mechanism by which a grid discovers its shape; richer behaviour — interactive column sorting, server-side filtering UI, paged feeds — is part of the contract vocabulary but roadmap from this showcase's standpoint. What you see here is the discovery mechanism itself, demonstrated on a deliberately small dataset so the contract, not the data volume, is the point.
When to use it
Reach for a contract-driven grid whenever the table's shape is owned by a route rather than a screen — admin tables, operational feeds, anything backed by a real query whose columns you would otherwise hand-copy into a template (and then have to keep in sync). You describe the shape once, on the route; every surface that renders it stays correct for free.
How it ties to the philosophy
A hand-wired column config in a template is a presentation-boundary leak: the template now encodes knowledge about the data that belongs on the route. The contract-driven grid closes that leak structurally. The grid is described once, on the route; the template carries only a pointer; and the rendered UI can never disagree with the data it renders — because both read the same OPTIONS contract.
How it works
grid-runtime-v2.js issues OPTIONS on the endpoint and reads the route's self-describing contract — output fields → columns, plus the collection block (sort fields, filterable facets, search param, pagination policy + per-page options). It then sends view changes back as plain query params (?q=…&sort=-name&filter=status:eq:Beta&page=2&perPage=25); the feed validates them against the SAME declared allowlist and returns the windowed page plus meta.pagination and meta.filterOptions. The whole contract is DECLARED as attributes on the response class (#[CollectionPaginated] / #[CollectionSortable] / #[CollectionFilterable] / #[CollectionSearchable] / #[CollectionFilterOptions]) — change the declaration, the grid changes, the template never moves.
Why it matters
A hand-wired column config — or a bespoke filter form, or pager glue — in a template is a presentation-boundary leak: it encodes knowledge about the data that belongs on the route, and it drifts. The contract-driven grid closes that leak structurally: described once on the route, every consumer (this grid, an API client, a generated SDK) reads the one contract and cannot disagree with the data. Pagination, filtering, sorting and search stop being per-screen plumbing and become route vocabulary.