Repository navigation
feat: headless demo page that renders a table from a manifest URL - #123
Conversation
runEndpointConformance failed any endpoint that returned more rows than `size`, including this repo's own Drizzle endpoint: createDrizzleHandler returns a group of rows sharing one cursor value whole, because the next page is read with a strict `<` and splitting the group would drop the rest of it. The demo data writes six rows per timestamp and the kit probes with five. A page larger than `size` now passes when every row carries the page's `nextCursor` in a timestamp column, with a note in the check's detail. An endpoint that ignores `size` returns rows spread along the cursor and still fails, as does an oversized page with no cursor. Docs and the contract comment now describe `size` as a target.
/headless has no schema of its own. It takes a manifest URL from `?manifest=`, starts on /drizzle/api/schema, and offers a second endpoint, /headless/api/orders, served as plain JSON with an inferred schema and offset paging. Inspect shows what the endpoint declares and runs runEndpointConformance against it in the browser. Any https endpoint with CORS headers can be pasted; other URL forms are rejected. Rendering DataTableRemote for the first time surfaced gaps in the block: - searchParamsSerializer is optional and derived from the manifest - `uuid` and `live` are part of the URL state, so the detail sheet opens on a table with a select column and live mode has state to flip - commandSlot and toolbarActions may be functions of the resolved manifest, filter schema, refresh and fetchPreviousPage - the sheet shows JSON for a record and a dash for a missing value instead of "[object Object]" and "null" The shared logs schema declares `level-indicator` and `status-code` as its wire displays, the loading skeleton no longer uses Math.random() (hydration mismatch), and the live button takes its timestamp column as a prop. The docs say row actions are not drawn by DataTableRemote yet.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
1 issue found across 32 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/registry/src/components/data-table/data-table-remote/data-table-remote.tsx">
<violation number="1" location="packages/registry/src/components/data-table/data-table-remote/data-table-remote.tsx:504">
P2: `defaultRowSelection` is only applied on mount, so URL-driven row changes leave the old row selected after closing or navigating. Synchronize row selection with `search.uuid` (or avoid seeding it in multi-select mode); the sheet already reads the current UUID directly.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| defaultColumnFilters={defaultColumnFilters} | ||
| defaultColumnSorting={sort ? [sort] : undefined} | ||
| // Restores the open row from a shared link. | ||
| defaultRowSelection={ |
There was a problem hiding this comment.
P2: defaultRowSelection is only applied on mount, so URL-driven row changes leave the old row selected after closing or navigating. Synchronize row selection with search.uuid (or avoid seeding it in multi-select mode); the sheet already reads the current UUID directly.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/registry/src/components/data-table/data-table-remote/data-table-remote.tsx, line 504:
<comment>`defaultRowSelection` is only applied on mount, so URL-driven row changes leave the old row selected after closing or navigating. Synchronize row selection with `search.uuid` (or avoid seeding it in multi-select mode); the sheet already reads the current UUID directly.</comment>
<file context>
@@ -330,6 +500,10 @@ function RemoteTableQuery({
defaultColumnFilters={defaultColumnFilters}
defaultColumnSorting={sort ? [sort] : undefined}
+ // Restores the open row from a shared link.
+ defaultRowSelection={
+ typeof search.uuid === "string" ? { [search.uuid]: true } : undefined
+ }
</file context>
Page: - reject tabs, line breaks and spaces in a manifest URL. Browsers strip them before parsing, so `/<tab>/host` was fetched as `//host` while being treated as a path on this site - derive the rows URL from the manifest path alone, so a manifest URL with a query string no longer sends row requests to `/schema?v=2?...` - validate the rows response against the list contract and report a wrong shape instead of rendering an empty table - only the request started last may set the rows error message - revive string dates inside a SuperJSON envelope too - a conformance run that throws says so data-table-remote: - drop the filter of a column whose key is a reserved request parameter (`uuid`, `live`, `sort`, `cursor`, `direction`, `size`) and warn - seed the row selection from `uuid` only without a select column; with one it ticked the row's checkbox Conformance kit: - a body that is `null`, a string or an array is reported as a failed shape check instead of throwing - the tied-group exemption needs a timestamp column named by the manifest; there is no guessing from the rows - a dotted cursor key carried literally on the row is found Also: "[unserializable]" instead of "[object Object]" for a value JSON cannot write, the contract comment names the timestamp case, and three tests assert what they claim.
There was a problem hiding this comment.
All reported issues were addressed across 18 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
The endpoint row is now the top strip of the table's main column: the same box as the "Filters" header beside it, with a bottom border that continues that header's. The logs example draws row actions the way /drizzle does: the bulk bar on a selection, and the row menu as a column that ships hidden. Actions are drawn only for a manifest this site serves, because a root-relative action href resolves against the page's origin whichever host the manifest came from. DataTableRemote stays independent of the actions block. It gains two general props the host uses to place that block's pieces: - extraColumns: columns appended after the manifest's - defaultColumnVisibility: visibility defaults applied over the manifest's The docs show the wiring.
There was a problem hiding this comment.
1 issue found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/registry/src/components/data-table/data-table-remote/data-table-remote.tsx">
<violation number="1" location="packages/registry/src/components/data-table/data-table-remote/data-table-remote.tsx:381">
P2: `defaultColumnVisibility` is only used to initialize the persisted visibility state, so a newly added extra column can ignore its default and render visible. Merge defaults for previously unseen column IDs when columns or defaults change, or remount/reset visibility when the extra-column set changes.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| () => ({ | ||
| ...getDefaultColumnVisibility(definition), | ||
| ...manifest.defaults?.columnVisibility, | ||
| ...visibilityOverrides, |
There was a problem hiding this comment.
P2: defaultColumnVisibility is only used to initialize the persisted visibility state, so a newly added extra column can ignore its default and render visible. Merge defaults for previously unseen column IDs when columns or defaults change, or remount/reset visibility when the extra-column set changes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/registry/src/components/data-table/data-table-remote/data-table-remote.tsx, line 381:
<comment>`defaultColumnVisibility` is only used to initialize the persisted visibility state, so a newly added extra column can ignore its default and render visible. Merge defaults for previously unseen column IDs when columns or defaults change, or remount/reset visibility when the extra-column set changes.</comment>
<file context>
@@ -358,8 +378,9 @@ function RemoteTable({
() => ({
...getDefaultColumnVisibility(definition),
...manifest.defaults?.columnVisibility,
+ ...visibilityOverrides,
}),
- [definition, manifest.defaults?.columnVisibility],
</file context>
- data-table-remote: a filter under one of the pagination strategy's own keys (`offset`, or a custom cursor key) is dropped like the other reserved request parameters - conformance kit: `readPath` reads own properties only, so a primary key of `toString` or `constructor` no longer looks present on every row - headless page: this site's own endpoint pasted as an absolute URL is treated as served here, so it keeps its row actions and loses the "another host" note - docs: the actions example carries its imports, and the sentence above it says the actions block is needed only when actions are drawn
There was a problem hiding this comment.
1 issue found across 8 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/web/src/app/headless/client.tsx">
<violation number="1" location="apps/web/src/app/headless/client.tsx:241">
P1: This trusts the requested origin rather than the manifest response's final origin. A same-origin URL that redirects to another host can supply root-relative action hrefs, which the page will post to this site's routes after `servedHere` enables them. Track and validate the final manifest response origin, or keep actions disabled unless that response is confirmed same-origin.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| if (externalHost === null) return true; | ||
| if (typeof window === "undefined") return false; | ||
| try { | ||
| return new URL(url).origin === window.location.origin; |
There was a problem hiding this comment.
P1: This trusts the requested origin rather than the manifest response's final origin. A same-origin URL that redirects to another host can supply root-relative action hrefs, which the page will post to this site's routes after servedHere enables them. Track and validate the final manifest response origin, or keep actions disabled unless that response is confirmed same-origin.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/web/src/app/headless/client.tsx, line 241:
<comment>This trusts the requested origin rather than the manifest response's final origin. A same-origin URL that redirects to another host can supply root-relative action hrefs, which the page will post to this site's routes after `servedHere` enables them. Track and validate the final manifest response origin, or keep actions disabled unless that response is confirmed same-origin.</comment>
<file context>
@@ -229,14 +229,27 @@ function RemoteTable({
+ if (externalHost === null) return true;
+ if (typeof window === "undefined") return false;
+ try {
+ return new URL(url).origin === window.location.origin;
+ } catch {
+ return false;
</file context>
The serializer is optional, so the examples no longer pass it. Document the reserved column keys, the conditions under which the demo draws actions, and the new props in the skill and component catalog. List /headless in the docs example lists, the install recipe's reading list and the home page.
There was a problem hiding this comment.
2 issues found across 8 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="skills/data-table-filters/SKILL.md">
<violation number="1" location="skills/data-table-filters/SKILL.md:355">
P2: The actions bullet omits `getRowId` on `DataTableActionsProvider`, which is a required prop. Add `getRowId={(r) => r.uuid}` to the wrap instruction so the recipe typechecks.</violation>
<violation number="2" location="skills/data-table-filters/SKILL.md:355">
P2: `extraColumns={[createActionsColumn()]}` creates a new array on every render, despite the parenthetical saying it is stable. Define the actions column array at module scope and pass that constant, or the remote table rebuilds its columns on every render.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| - **Verify an endpoint** with `runEndpointConformance({ url, manifest })` and `formatConformanceReport`. | ||
| - **The query string is derived from the manifest.** `searchParamsSerializer` is optional; pass it only when the app already has one. A filterable column keyed `cursor`, `direction`, `size`, `sort`, `uuid`, `live` (or the pagination strategy's own key, e.g. `offset`) loses its filter with a console warning. | ||
| - **Slots can read the resolved manifest.** `commandSlot` and `toolbarActions` take a node or a function of `{ manifest, filterSchema, tableId, refresh, fetchPreviousPage? }`; `sheetSlot` receives the same context as its second argument. | ||
| - **Actions are the host's to draw.** The block does not depend on `data-table-actions`. Wrap it in `DataTableActionsProvider` with `manifest.actions`, pass `extraColumns={[createActionsColumn()]}` (a stable array) and put `DataTableActionsBar` in `floatingBarSlot`. Do this only for a manifest you serve yourself. |
There was a problem hiding this comment.
P2: The actions bullet omits getRowId on DataTableActionsProvider, which is a required prop. Add getRowId={(r) => r.uuid} to the wrap instruction so the recipe typechecks.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At skills/data-table-filters/SKILL.md, line 355:
<comment>The actions bullet omits `getRowId` on `DataTableActionsProvider`, which is a required prop. Add `getRowId={(r) => r.uuid}` to the wrap instruction so the recipe typechecks.</comment>
<file context>
@@ -351,7 +350,10 @@ Key points:
-- **Verify an endpoint** with `runEndpointConformance({ url, manifest })` and `formatConformanceReport`.
+- **The query string is derived from the manifest.** `searchParamsSerializer` is optional; pass it only when the app already has one. A filterable column keyed `cursor`, `direction`, `size`, `sort`, `uuid`, `live` (or the pagination strategy's own key, e.g. `offset`) loses its filter with a console warning.
+- **Slots can read the resolved manifest.** `commandSlot` and `toolbarActions` take a node or a function of `{ manifest, filterSchema, tableId, refresh, fetchPreviousPage? }`; `sheetSlot` receives the same context as its second argument.
+- **Actions are the host's to draw.** The block does not depend on `data-table-actions`. Wrap it in `DataTableActionsProvider` with `manifest.actions`, pass `extraColumns={[createActionsColumn()]}` (a stable array) and put `DataTableActionsBar` in `floatingBarSlot`. Do this only for a manifest you serve yourself.
+- **Verify an endpoint** with `runEndpointConformance({ url, manifest })` and `formatConformanceReport`. `size` is a target: a group of rows sharing one timestamp cursor value may exceed it.
</file context>
| - **Actions are the host's to draw.** The block does not depend on `data-table-actions`. Wrap it in `DataTableActionsProvider` with `manifest.actions`, pass `extraColumns={[createActionsColumn()]}` (a stable array) and put `DataTableActionsBar` in `floatingBarSlot`. Do this only for a manifest you serve yourself. | |
| - **Actions are the host's to draw.** The block does not depend on `data-table-actions`. Wrap it in `DataTableActionsProvider` with `manifest.actions` and `getRowId={(r) => r.uuid}`, pass `extraColumns={[createActionsColumn()]}` (a stable array) and put `DataTableActionsBar` in `floatingBarSlot`. Do this only for a manifest you serve yourself. |
| - **Verify an endpoint** with `runEndpointConformance({ url, manifest })` and `formatConformanceReport`. | ||
| - **The query string is derived from the manifest.** `searchParamsSerializer` is optional; pass it only when the app already has one. A filterable column keyed `cursor`, `direction`, `size`, `sort`, `uuid`, `live` (or the pagination strategy's own key, e.g. `offset`) loses its filter with a console warning. | ||
| - **Slots can read the resolved manifest.** `commandSlot` and `toolbarActions` take a node or a function of `{ manifest, filterSchema, tableId, refresh, fetchPreviousPage? }`; `sheetSlot` receives the same context as its second argument. | ||
| - **Actions are the host's to draw.** The block does not depend on `data-table-actions`. Wrap it in `DataTableActionsProvider` with `manifest.actions`, pass `extraColumns={[createActionsColumn()]}` (a stable array) and put `DataTableActionsBar` in `floatingBarSlot`. Do this only for a manifest you serve yourself. |
There was a problem hiding this comment.
P2: extraColumns={[createActionsColumn()]} creates a new array on every render, despite the parenthetical saying it is stable. Define the actions column array at module scope and pass that constant, or the remote table rebuilds its columns on every render.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At skills/data-table-filters/SKILL.md, line 355:
<comment>`extraColumns={[createActionsColumn()]}` creates a new array on every render, despite the parenthetical saying it is stable. Define the actions column array at module scope and pass that constant, or the remote table rebuilds its columns on every render.</comment>
<file context>
@@ -351,7 +350,10 @@ Key points:
-- **Verify an endpoint** with `runEndpointConformance({ url, manifest })` and `formatConformanceReport`.
+- **The query string is derived from the manifest.** `searchParamsSerializer` is optional; pass it only when the app already has one. A filterable column keyed `cursor`, `direction`, `size`, `sort`, `uuid`, `live` (or the pagination strategy's own key, e.g. `offset`) loses its filter with a console warning.
+- **Slots can read the resolved manifest.** `commandSlot` and `toolbarActions` take a node or a function of `{ manifest, filterSchema, tableId, refresh, fetchPreviousPage? }`; `sheetSlot` receives the same context as its second argument.
+- **Actions are the host's to draw.** The block does not depend on `data-table-actions`. Wrap it in `DataTableActionsProvider` with `manifest.actions`, pass `extraColumns={[createActionsColumn()]}` (a stable array) and put `DataTableActionsBar` in `floatingBarSlot`. Do this only for a manifest you serve yourself.
+- **Verify an endpoint** with `runEndpointConformance({ url, manifest })` and `formatConformanceReport`. `size` is a target: a group of rows sharing one timestamp cursor value may exceed it.
</file context>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/web/src/content/docs/15-headless.mdx">
<violation number="1" location="apps/web/src/content/docs/15-headless.mdx:464">
P3: This unconditionally requires CORS, but same-origin endpoints such as the two examples do not need CORS headers. Qualify the requirement to cross-origin endpoints so users do not add unnecessary CORS configuration.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| | `/drizzle/api/schema` | The [/drizzle](/drizzle) logs table: Postgres, facets, counts, chart, live mode | | ||
| | `/headless/api/orders/schema` | An in-memory array as plain JSON: inferred schema, offset paging, no chart | | ||
|
|
||
| To try your own endpoint, paste its manifest URL. It needs CORS headers. |
There was a problem hiding this comment.
P3: This unconditionally requires CORS, but same-origin endpoints such as the two examples do not need CORS headers. Qualify the requirement to cross-origin endpoints so users do not add unnecessary CORS configuration.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/web/src/content/docs/15-headless.mdx, line 464:
<comment>This unconditionally requires CORS, but same-origin endpoints such as the two examples do not need CORS headers. Qualify the requirement to cross-origin endpoints so users do not add unnecessary CORS configuration.</comment>
<file context>
@@ -453,13 +453,19 @@ contract itself.
+| `/drizzle/api/schema` | The [/drizzle](/drizzle) logs table: Postgres, facets, counts, chart, live mode |
+| `/headless/api/orders/schema` | An in-memory array as plain JSON: inferred schema, offset paging, no chart |
+
+To try your own endpoint, paste its manifest URL. It needs CORS headers.
+
+**Inspect** lists what the endpoint declares and runs
</file context>
| To try your own endpoint, paste its manifest URL. It needs CORS headers. | |
| To try your own endpoint, paste its manifest URL. A cross-origin endpoint needs CORS headers. |
SWC drops the leading space of a multi-line JSX text that contains an HTML entity, so "Headless tables" ran into "rendered". Use a literal apostrophe instead of '.
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/web/src/content/docs/14-actions.mdx">
<violation number="1" location="apps/web/src/content/docs/14-actions.mdx:363">
P3: `/headless` does not always render these two actions from the manifest alone: it requires a same-site manifest that advertises actions, and the public `/drizzle` manifest omits them by default. Qualify this sentence with those conditions.</violation>
</file>
Reply with feedback, questions, or to request a fix.
View guided diff | Turn on auto-fix | Re-trigger cubic
| When actions are off, the list endpoint does not advertise them, the UI | ||
| renders no buttons, and `POST /drizzle/api/actions/*` answers 403. | ||
|
|
||
| [/headless](/headless) renders the same two actions from the manifest alone. |
There was a problem hiding this comment.
P3: /headless does not always render these two actions from the manifest alone: it requires a same-site manifest that advertises actions, and the public /drizzle manifest omits them by default. Qualify this sentence with those conditions.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At apps/web/src/content/docs/14-actions.mdx, line 363:
<comment>`/headless` does not always render these two actions from the manifest alone: it requires a same-site manifest that advertises actions, and the public `/drizzle` manifest omits them by default. Qualify this sentence with those conditions.</comment>
<file context>
@@ -341,12 +341,24 @@ control — see [Headless Tables](/docs/headless#trust-boundary).
+When actions are off, the list endpoint does not advertise them, the UI
+renders no buttons, and `POST /drizzle/api/actions/*` answers 403.
+
+[/headless](/headless) renders the same two actions from the manifest alone.
+See [Headless Tables](/docs/headless#capabilities) for the wiring.
</file context>
| [/headless](/headless) renders the same two actions from the manifest alone. | |
| [/headless](/headless) renders these actions when a same-site manifest advertises them. |
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.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:
DataTableRemotedoes not draw them itself, so a headless table does not have to install the actions block. It gainsextraColumnsanddefaultColumnVisibility, and the host places the actions block's column and bulk bar. The page does this for the logs example, as/drizzledoes, and only for a manifest this site serves. The docs show the wiring.Test plan
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/apiat its default probe size: 9 passed.Breaking changes
None. Behaviour changes to note:
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.detail./headless/api/ordersand its manifest sendaccess-control-allow-origin: *(public sample data).Summary by cubic
Adds a
/headlessdemo page that renders a table from any manifest URL and closes the gaps inDataTableRemotethat 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.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./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.[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.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.detailnote./headless/api/ordersand its manifest sendaccess-control-allow-origin: *(public sample data).Written for commit 9606feb. Summary will update on new commits.