feat: headless demo page that renders a table from a manifest URL
Summary
/headless(shortcut/h): a page with no schema of its own. It reads a manifest URL from?manifest=and renders that endpoint. It starts on/drizzle/api/schemaand has a second example,/headless/api/orders/schema, an in-memory array served as plain JSON with an inferred schema, offset paging and no chart or live mode. Inspect lists what the endpoint declares and runsrunEndpointConformanceagainst it in the browser. Anyhttps://endpoint with CORS headers can be pasted (http://for localhost only); other URL forms are rejected.data-table-remote: renderingDataTableRemotefor the first time surfaced gaps.searchParamsSerializeris optional and derived from the manifest.AGENTS.mdand the Cursor rules already showed the component without it.uuidandliveare part of the URL state, so the detail sheet opens on a table with a select column and live mode has state to flip.commandSlotandtoolbarActionsmay be functions of the resolved manifest, filter schema,refreshandfetchPreviousPage.sheetSlotreceives the same context as a second argument.extraColumnsappends columns after the manifest's;defaultColumnVisibilityis applied over the manifest's defaults.
data-table-sheet: a record value renders as JSON and a missing value as a dash, instead of[object Object]andnull.data-table-query(first commit):runEndpointConformancefailed this repo's own Drizzle endpoint onsize, becausecreateDrizzleHandlerreturns a group of rows sharing one cursor value whole (six here, probe size five). An oversized page now passes when every row carries the page'snextCursorin a timestamp column, with a note in the check's detail. The manifest has to name that timestamp column; a body that isnullor not an object is now a failed shape check instead of a throw. An endpoint that ignoressizestill fails. Docs describesizeas a target.- Web app: the shared logs schema declares
level-indicatorandstatus-codeas its wire displays (the custom cells still render on/infiniteand/drizzle), the loading skeleton no longer usesMath.random()(hydration mismatch), and the live button takes its timestamp column as a prop. Docs, README, sitemap and the home examples list include the new page.
Row actions: DataTableRemote does not draw them itself, so a headless table does not have to install the actions block. It gains extraColumns and defaultColumnVisibility, and the host places the actions block's column and bulk bar. The page does this for the logs example, as /drizzle does, and only for a manifest this site serves. The docs show the wiring.
Test plan
- Local checks pass (format, lint, typecheck, tests)
- CI passes
- Manually verified in browser (if UI change)
- Registry rebuilt (if
packages/registry/src/changed)
Browser checks were run in headless Chromium against a local dev server: filters, sort, infinite scroll, detail sheet and its URL state, command palette, live mode, Inspect on both examples, a cross-origin endpoint with CORS, six rejected or failing URLs, mobile width and dark mode. The fixed kit was also run against the production /drizzle/api at its default probe size: 9 passed.
Breaking changes
None. Behaviour changes to note:
- In
DataTableRemote, a column whose key is a reserved request parameter (uuid,live,sort,cursor,direction,size) keeps its column but loses its filter, with a console warning.uuidandliveare now part of the URL state. - The sheet's fallback for record and missing values changed as described above.
- A passing conformance check may now carry a
detail. /headless/api/ordersand its manifest sendaccess-control-allow-origin: *(public sample data).
Summary by cubic
Adds a /headless demo page that renders a table from any manifest URL and closes the gaps in DataTableRemote that real usage surfaced.
/headlessreads the manifest from?manifest=, starts on/drizzle/api/schema, and ships a second plain-JSON endpoint (/headless/api/orders) with an inferred schema and offset paging. Manifest URLs are validated (https only, http for localhost; tabs, line breaks and spaces are rejected), and the rows URL is derived from the manifest path alone so a query string on the manifest is not copied onto row requests. This site's own endpoint pasted as an absolute URL is treated as served here, so it keeps row actions and loses the "another host" note.- Inspect lists what the endpoint declares and runs
runEndpointConformancein the browser; anyhttps://URL with CORS headers can be pasted,http://only for localhost. The rows response is validated against the list contract instead of rendering an empty table. - The endpoint row is the top strip of the table's main column on the logs example, whose row actions render the way
/drizzledoes: a bulk bar on a selection and a hidden row-menu column. Actions appear only for manifests this site serves. DataTableRemotenow derives its search param serializer from the manifest, keepsuuidandlivein URL state, letscommandSlot,toolbarActions, andsheetSlotbe functions of the resolved manifest, and takesextraColumnsanddefaultColumnVisibilityso the host can append the actions block. It still does not draw row actions itself.- The detail sheet renders a record as JSON, a missing value as a dash instead of
[object Object]andnull, and[unserializable]for values JSON cannot write. runEndpointConformanceaccepts a page larger thansizewhen every row carries the page'snextCursorin a timestamp column named by the manifest;sizeis documented as a target. A body that is not an object is a failed shape check, not a throw.- The loading skeleton no longer uses
Math.random()(hydration mismatch), and the live button takes its timestamp column as a prop.
Behavior changes
uuid,live,sort,cursor,direction,size, plus any key the pagination strategy owns (offsetor a custom cursor key), are reserved URL-state keys inDataTableRemote; a filter on a column with any of them is dropped with a warning, and row selection is seeded fromuuidonly when there is no select column.readPathreads own properties only, so a primary key oftoStringorconstructorno longer looks present on every row.- The sheet's fallback for record and missing values changed as described above, and a passing conformance check may carry a
detailnote. /headless/api/ordersand its manifest sendaccess-control-allow-origin: *(public sample data).