Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

Commit 2eeebe3

Browse files
committed
fix(types,console): retire the page kind jsx from PageNodeSchema.kind and move the console sample to html (objectui#12464)
@objectstack/spec 18 refuses the page kind jsx (objectstack-ai/objectstack#23638). PageNodeSchema.kind drops 'jsx'; the console's sdui-jsx-preview sample authors kind 'html'. The page renderer keeps its 'jsx' arm for stored rows until the resolved spec carries the retirement; it reads kind as a widened wire value so that arm still compiles against the narrowed type. Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude <noreply@anthropic.com>
1 parent d9d9d98 commit 2eeebe3

7 files changed

Lines changed: 59 additions & 15 deletions

File tree

‎apps/console/src/__tests__/sdui-preview-page-source-query-params.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ describe('ADR-0080 preview harnesses — page-source query params', () => {
125125
expect(pages.length).toBeGreaterThanOrEqual(4);
126126
expect(pages.map((p) => `${p.file}:${p.name}:${p.kind}`)).toEqual(
127127
expect.arrayContaining([
128-
'sdui-jsx-preview.tsx:command_center:jsx',
128+
'sdui-jsx-preview.tsx:command_center:html',
129129
'sdui-tiers-preview.tsx:release_notes:html',
130130
'sdui-tiers-preview.tsx:pipeline_react:react',
131131
'sdui-workbench-preview.tsx:crm_workbench:react',

‎apps/console/src/sdui-jsx-preview.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* ADR-0080 browser preview: a kind:'jsx' page rendered through the real
1+
/* ADR-0080 browser preview: a kind:'html' page rendered through the real
22
* PageRenderer (compile -> SchemaRenderer).
33
*
44
* ADR-0080 EXCEPTION — Tailwind in page source
@@ -12,7 +12,7 @@
1212
*
1313
* It renders fully styled HERE only because this harness file is itself inside
1414
* the scanned `src` — precisely the "works only by coincidence" failure mode
15-
* ADR-0065 names. This file is a renderer-PLUMBING preview (does the jsx tier
15+
* ADR-0065 names. This file is a renderer-PLUMBING preview (does the html tier
1616
* compile and mount the registered blocks), not an authoring example: DO NOT
1717
* copy its source string into a page. The authoring example is
1818
* `sdui-tiers-preview.tsx`, whose sources carry no className at all and style
@@ -84,7 +84,7 @@ const source = `
8484
</grid>
8585
</flex>`;
8686

87-
const page = { type: 'home', kind: 'jsx', name: 'command_center', label: 'Command Center', source };
87+
const page = { type: 'home', kind: 'html', name: 'command_center', label: 'Command Center', source };
8888

8989
createRoot(document.getElementById('root')!).render(
9090
<React.StrictMode>

‎content/docs/guide/react-pages.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ that are awkward to express as nested JSON:
1414
| `"html"` | Constrained JSX/HTML | **No** — parsed into a schema tree | Untrusted OK |
1515
| `"react"` | Real React (hooks, handlers, arbitrary JS) | **Yes** — in the main React tree | Trusted only |
1616

17-
Both set `source` and leave `regions` unused. `"jsx"` is a deprecated alias for
18-
`"html"` and is still accepted.
17+
Both set `source` and leave `regions` unused. The former alias `"jsx"` was retired
18+
in `@objectstack/spec` 18 and is refused; author `"html"`.
1919

2020
> Page `kind` also carries the record-page override values `"full"` (default)
2121
> and `"slotted"` — a different axis, covered in [Slotted Pages](./slotted-pages.md).

‎packages/components/src/renderers/layout/page.tsx‎

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -662,17 +662,22 @@ export const PageRenderer: React.FC<{
662662

663663
// Select the layout variant based on template or page type
664664
const layoutElement = useMemo(() => {
665-
// `PageSchema['kind']` now spells the source-authored values too, matching
666-
// @objectstack/spec — no cast needed to read what the renderer dispatches on.
667-
const kind = schema.kind;
665+
// Read as the stored WIRE value, not as the authoring type. `PageNodeSchema['kind']`
666+
// follows @objectstack/spec 18, which retired `'jsx'` (objectui#12464); a
667+
// server still on a spec release built before that retirement can serve a
668+
// stored `jsx` page that spec 18's rehydration would have turned into
669+
// `html`. Widening (not a cast) keeps the `'jsx'` arm below compiling until
670+
// the resolved spec carries the retirement and that arm is dropped.
671+
const kind: string | undefined = schema.kind;
668672
// `kind:'react'` — TRUSTED execution tier: real React, run (not parsed) by
669673
// @object-ui/react-runtime, gated behind the host CAP_REACT_PAGES flag.
670674
if (kind === 'react') {
671675
return <ReactKindPage schema={schema} />;
672676
}
673677
// `kind:'html'` (formerly 'jsx') — author-written constrained JSX compiled
674-
// (parsed, never executed) to a SchemaNode tree and rendered. The legacy
675-
// 'jsx' value is still accepted as a deprecated alias.
678+
// (parsed, never executed) to a SchemaNode tree and rendered. A stored
679+
// 'jsx' page is still rendered as html until the resolved spec carries its
680+
// retirement; it can no longer be authored against the types.
676681
// Styling on this tier is the blocks' own structured props
677682
// (`<flex direction gap>`, `<grid columns>`) plus a JSON `style` object —
678683
// NOT Tailwind utility classes. `source` is runtime metadata, so the build's

‎packages/types/src/__tests__/authoring-nodes-11364.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ describe('objectui#11364: the spec-declared nodes have a TypeScript authoring fa
178178

179179
it('a stored page document is typed under its page kind', () => {
180180
const home: PageDocumentNode = { type: 'home', kind: 'html', name: 'landing', label: 'Landing', source: '<Page />' };
181-
// @ts-expect-error `kind` is the spec's override mode (full | slotted | html | react | jsx)
181+
// @ts-expect-error `kind` is the spec's override mode (full | slotted | html | react)
182182
const badKind: PageDocumentNode = { type: 'home', kind: 'markdown', name: 'landing', label: 'Landing' };
183183
// @ts-expect-error `type` is the page kind the registry dispatches on, so the node must carry it
184184
const untyped: PageDocumentNode = { name: 'landing', label: 'Landing' };
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/**
2+
* ObjectUI
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
/**
10+
* objectui#12464: `@objectstack/spec` 18 retired the page kind `jsx`
11+
* (objectstack-ai/objectstack#23638), naming `html` as its replacement, so the
12+
* SDUI page node no longer declares it.
13+
*
14+
* The assertions are TYPE-level: they are judged by this package's
15+
* `type-check` (its `tsc -p tsconfig.test.json` leg), never by vitest, which
16+
* strips types. A `@ts-expect-error` that stops being needed is TS2578, so the
17+
* directive below fails in both directions.
18+
*
19+
* `Equal` (not `Extends`) on the whole union: re-adding `'jsx'` widens it, and
20+
* only an exact comparison fails on a widening.
21+
*/
22+
import { describe, expect, it } from 'vitest';
23+
import type { PageNodeSchema } from '../layout';
24+
25+
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
26+
type Assert<T extends true> = T;
27+
28+
type _KindIsTheSpecVocabulary = Assert<
29+
Equal<NonNullable<PageNodeSchema['kind']>, 'full' | 'slotted' | 'html' | 'react'>
30+
>;
31+
32+
describe('PageNodeSchema.kind no longer declares the retired page kind jsx (objectui#12464)', () => {
33+
it('refuses kind jsx and accepts its replacement html', () => {
34+
const html: PageNodeSchema = { type: 'page', kind: 'html', source: '<Page />' };
35+
// @ts-expect-error `jsx` was retired from the page kind in @objectstack/spec 18; author `html`
36+
const jsx: PageNodeSchema = { type: 'page', kind: 'jsx', source: '<Page />' };
37+
expect([html.kind, jsx.kind]).toEqual(['html', 'jsx']);
38+
});
39+
});

‎packages/types/src/layout.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,8 +1773,8 @@ export interface PageNodeSchema extends BaseSchema, Omit<SpecPage, (typeof PAGE_
17731773
* - `"html"`: constrained JSX, PARSED into a SchemaNode tree and
17741774
* rendered. Never executed — safe for untrusted authors. Styled with
17751775
* the blocks' own structured props (`<flex direction gap>`,
1776-
* `<grid columns>`) plus a JSON `style` object. `"jsx"` is a
1777-
* deprecated alias, still accepted.
1776+
* `<grid columns>`) plus a JSON `style` object. Its former alias
1777+
* `"jsx"` was retired in `@objectstack/spec` 18 and is refused here.
17781778
* - `"react"`: real React, transpiled and EVALUATED in the main tree.
17791779
* No sandbox; gated behind the `react-pages` host capability. Styled
17801780
* with inline `style` objects.
@@ -1792,7 +1792,7 @@ export interface PageNodeSchema extends BaseSchema, Omit<SpecPage, (typeof PAGE_
17921792
*
17931793
* @default 'full'
17941794
*/
1795-
kind?: 'full' | 'slotted' | 'html' | 'jsx' | 'react';
1795+
kind?: 'full' | 'slotted' | 'html' | 'react';
17961796
/**
17971797
* Slotted override map. Each slot accepts a single SchemaNode or an
17981798
* array (arrays are flattened into the slot position). Slots not

0 commit comments

Comments
 (0)