From 2eeebe321d53c6ca36b68b4e1306e8e1ec1a2f64 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 11:36:47 +0000 Subject: [PATCH 1/3] 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 --- ...i-preview-page-source-query-params.test.ts | 2 +- apps/console/src/sdui-jsx-preview.tsx | 6 +-- content/docs/guide/react-pages.md | 4 +- .../components/src/renderers/layout/page.tsx | 15 ++++--- .../__tests__/authoring-nodes-11364.test.ts | 2 +- .../page-kind-jsx-retired-12464.test.ts | 39 +++++++++++++++++++ packages/types/src/layout.ts | 6 +-- 7 files changed, 59 insertions(+), 15 deletions(-) create mode 100644 packages/types/src/__tests__/page-kind-jsx-retired-12464.test.ts diff --git a/apps/console/src/__tests__/sdui-preview-page-source-query-params.test.ts b/apps/console/src/__tests__/sdui-preview-page-source-query-params.test.ts index 13260e0c47..dee10b1c4d 100644 --- a/apps/console/src/__tests__/sdui-preview-page-source-query-params.test.ts +++ b/apps/console/src/__tests__/sdui-preview-page-source-query-params.test.ts @@ -125,7 +125,7 @@ describe('ADR-0080 preview harnesses — page-source query params', () => { expect(pages.length).toBeGreaterThanOrEqual(4); expect(pages.map((p) => `${p.file}:${p.name}:${p.kind}`)).toEqual( expect.arrayContaining([ - 'sdui-jsx-preview.tsx:command_center:jsx', + 'sdui-jsx-preview.tsx:command_center:html', 'sdui-tiers-preview.tsx:release_notes:html', 'sdui-tiers-preview.tsx:pipeline_react:react', 'sdui-workbench-preview.tsx:crm_workbench:react', diff --git a/apps/console/src/sdui-jsx-preview.tsx b/apps/console/src/sdui-jsx-preview.tsx index d4fc0fc613..38c1cfe012 100644 --- a/apps/console/src/sdui-jsx-preview.tsx +++ b/apps/console/src/sdui-jsx-preview.tsx @@ -1,4 +1,4 @@ -/* ADR-0080 browser preview: a kind:'jsx' page rendered through the real +/* ADR-0080 browser preview: a kind:'html' page rendered through the real * PageRenderer (compile -> SchemaRenderer). * * ADR-0080 EXCEPTION — Tailwind in page source @@ -12,7 +12,7 @@ * * It renders fully styled HERE only because this harness file is itself inside * the scanned `src` — precisely the "works only by coincidence" failure mode - * ADR-0065 names. This file is a renderer-PLUMBING preview (does the jsx tier + * ADR-0065 names. This file is a renderer-PLUMBING preview (does the html tier * compile and mount the registered blocks), not an authoring example: DO NOT * copy its source string into a page. The authoring example is * `sdui-tiers-preview.tsx`, whose sources carry no className at all and style @@ -84,7 +84,7 @@ const source = ` `; -const page = { type: 'home', kind: 'jsx', name: 'command_center', label: 'Command Center', source }; +const page = { type: 'home', kind: 'html', name: 'command_center', label: 'Command Center', source }; createRoot(document.getElementById('root')!).render( diff --git a/content/docs/guide/react-pages.md b/content/docs/guide/react-pages.md index 500723920a..69b8717fc7 100644 --- a/content/docs/guide/react-pages.md +++ b/content/docs/guide/react-pages.md @@ -14,8 +14,8 @@ that are awkward to express as nested JSON: | `"html"` | Constrained JSX/HTML | **No** — parsed into a schema tree | Untrusted OK | | `"react"` | Real React (hooks, handlers, arbitrary JS) | **Yes** — in the main React tree | Trusted only | -Both set `source` and leave `regions` unused. `"jsx"` is a deprecated alias for -`"html"` and is still accepted. +Both set `source` and leave `regions` unused. The former alias `"jsx"` was retired +in `@objectstack/spec` 18 and is refused; author `"html"`. > Page `kind` also carries the record-page override values `"full"` (default) > and `"slotted"` — a different axis, covered in [Slotted Pages](./slotted-pages.md). diff --git a/packages/components/src/renderers/layout/page.tsx b/packages/components/src/renderers/layout/page.tsx index 4ae92f7f91..521dc573d4 100644 --- a/packages/components/src/renderers/layout/page.tsx +++ b/packages/components/src/renderers/layout/page.tsx @@ -662,17 +662,22 @@ export const PageRenderer: React.FC<{ // Select the layout variant based on template or page type const layoutElement = useMemo(() => { - // `PageSchema['kind']` now spells the source-authored values too, matching - // @objectstack/spec — no cast needed to read what the renderer dispatches on. - const kind = schema.kind; + // Read as the stored WIRE value, not as the authoring type. `PageNodeSchema['kind']` + // follows @objectstack/spec 18, which retired `'jsx'` (objectui#12464); a + // server still on a spec release built before that retirement can serve a + // stored `jsx` page that spec 18's rehydration would have turned into + // `html`. Widening (not a cast) keeps the `'jsx'` arm below compiling until + // the resolved spec carries the retirement and that arm is dropped. + const kind: string | undefined = schema.kind; // `kind:'react'` — TRUSTED execution tier: real React, run (not parsed) by // @object-ui/react-runtime, gated behind the host CAP_REACT_PAGES flag. if (kind === 'react') { return ; } // `kind:'html'` (formerly 'jsx') — author-written constrained JSX compiled - // (parsed, never executed) to a SchemaNode tree and rendered. The legacy - // 'jsx' value is still accepted as a deprecated alias. + // (parsed, never executed) to a SchemaNode tree and rendered. A stored + // 'jsx' page is still rendered as html until the resolved spec carries its + // retirement; it can no longer be authored against the types. // Styling on this tier is the blocks' own structured props // (``, ``) plus a JSON `style` object — // NOT Tailwind utility classes. `source` is runtime metadata, so the build's diff --git a/packages/types/src/__tests__/authoring-nodes-11364.test.ts b/packages/types/src/__tests__/authoring-nodes-11364.test.ts index 66fa215a03..8f63479ee1 100644 --- a/packages/types/src/__tests__/authoring-nodes-11364.test.ts +++ b/packages/types/src/__tests__/authoring-nodes-11364.test.ts @@ -178,7 +178,7 @@ describe('objectui#11364: the spec-declared nodes have a TypeScript authoring fa it('a stored page document is typed under its page kind', () => { const home: PageDocumentNode = { type: 'home', kind: 'html', name: 'landing', label: 'Landing', source: '' }; - // @ts-expect-error `kind` is the spec's override mode (full | slotted | html | react | jsx) + // @ts-expect-error `kind` is the spec's override mode (full | slotted | html | react) const badKind: PageDocumentNode = { type: 'home', kind: 'markdown', name: 'landing', label: 'Landing' }; // @ts-expect-error `type` is the page kind the registry dispatches on, so the node must carry it const untyped: PageDocumentNode = { name: 'landing', label: 'Landing' }; diff --git a/packages/types/src/__tests__/page-kind-jsx-retired-12464.test.ts b/packages/types/src/__tests__/page-kind-jsx-retired-12464.test.ts new file mode 100644 index 0000000000..eca6a0176b --- /dev/null +++ b/packages/types/src/__tests__/page-kind-jsx-retired-12464.test.ts @@ -0,0 +1,39 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * objectui#12464: `@objectstack/spec` 18 retired the page kind `jsx` + * (objectstack-ai/objectstack#23638), naming `html` as its replacement, so the + * SDUI page node no longer declares it. + * + * The assertions are TYPE-level: they are judged by this package's + * `type-check` (its `tsc -p tsconfig.test.json` leg), never by vitest, which + * strips types. A `@ts-expect-error` that stops being needed is TS2578, so the + * directive below fails in both directions. + * + * `Equal` (not `Extends`) on the whole union: re-adding `'jsx'` widens it, and + * only an exact comparison fails on a widening. + */ +import { describe, expect, it } from 'vitest'; +import type { PageNodeSchema } from '../layout'; + +type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true : false; +type Assert = T; + +type _KindIsTheSpecVocabulary = Assert< + Equal, 'full' | 'slotted' | 'html' | 'react'> +>; + +describe('PageNodeSchema.kind no longer declares the retired page kind jsx (objectui#12464)', () => { + it('refuses kind jsx and accepts its replacement html', () => { + const html: PageNodeSchema = { type: 'page', kind: 'html', source: '' }; + // @ts-expect-error `jsx` was retired from the page kind in @objectstack/spec 18; author `html` + const jsx: PageNodeSchema = { type: 'page', kind: 'jsx', source: '' }; + expect([html.kind, jsx.kind]).toEqual(['html', 'jsx']); + }); +}); diff --git a/packages/types/src/layout.ts b/packages/types/src/layout.ts index 403e6cbe5f..89b76bcc5e 100644 --- a/packages/types/src/layout.ts +++ b/packages/types/src/layout.ts @@ -1773,8 +1773,8 @@ export interface PageNodeSchema extends BaseSchema, Omit`, - * ``) plus a JSON `style` object. `"jsx"` is a - * deprecated alias, still accepted. + * ``) plus a JSON `style` object. Its former alias + * `"jsx"` was retired in `@objectstack/spec` 18 and is refused here. * - `"react"`: real React, transpiled and EVALUATED in the main tree. * No sandbox; gated behind the `react-pages` host capability. Styled * with inline `style` objects. @@ -1792,7 +1792,7 @@ export interface PageNodeSchema extends BaseSchema, Omit Date: Thu, 15 Oct 2026 11:53:05 +0000 Subject: [PATCH 2/3] fix(types): the zod face of PageNodeSchema refuses the retired page kind jsx too (objectui#12464) The zod mirror took `kind` from the spec's PageSchema by reference, so on the pinned 18.0.0-next.1 it still admitted 'jsx' and was wider than the narrowed TS face (zod-mirror-parity WiderLedger TS2322). It now picks its enum out of the spec's by reference with `.extract`, through the stripImportedDefaults boundary, identical to the spec's enum once the resolved spec carries the retirement. Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude --- .../__tests__/page-kind-jsx-retired-12464.test.ts | 12 ++++++++++++ packages/types/src/zod/layout.zod.ts | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/packages/types/src/__tests__/page-kind-jsx-retired-12464.test.ts b/packages/types/src/__tests__/page-kind-jsx-retired-12464.test.ts index eca6a0176b..95a8ce45ac 100644 --- a/packages/types/src/__tests__/page-kind-jsx-retired-12464.test.ts +++ b/packages/types/src/__tests__/page-kind-jsx-retired-12464.test.ts @@ -18,9 +18,14 @@ * * `Equal` (not `Extends`) on the whole union: re-adding `'jsx'` widens it, and * only an exact comparison fails on a widening. + * + * The zod face (`zod/layout.zod.ts` `PageNodeSchema`) refuses the same value + * at `kind`: it picks its enum out of the spec's by reference, so it does not + * wait for the resolved spec to carry the retirement. */ import { describe, expect, it } from 'vitest'; import type { PageNodeSchema } from '../layout'; +import { PageNodeSchema as PageNodeZod } from '../zod/layout.zod'; type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true : false; type Assert = T; @@ -36,4 +41,11 @@ describe('PageNodeSchema.kind no longer declares the retired page kind jsx (obje const jsx: PageNodeSchema = { type: 'page', kind: 'jsx', source: '' }; expect([html.kind, jsx.kind]).toEqual(['html', 'jsx']); }); + + it('the zod face refuses kind jsx at kind and accepts html', () => { + const jsx = PageNodeZod.safeParse({ type: 'page', kind: 'jsx', source: '' }); + expect(jsx.success).toBe(false); + expect(jsx.error?.issues.map((i) => i.path.join('.'))).toEqual(['kind']); + expect(PageNodeZod.safeParse({ type: 'page', kind: 'html', source: '' }).success).toBe(true); + }); }); diff --git a/packages/types/src/zod/layout.zod.ts b/packages/types/src/zod/layout.zod.ts index 1712973942..4abfe12444 100644 --- a/packages/types/src/zod/layout.zod.ts +++ b/packages/types/src/zod/layout.zod.ts @@ -1293,6 +1293,17 @@ export const PageNodeSchema = BaseSchema.extend(SpecPageFields.shape).extend({ icon: z.string().optional().describe('Page icon (Lucide icon name)'), description: z.string().optional().describe('Page description'), pageType: PageTypeSchema.optional().describe('Page type (record, home, app, utility)'), + // objectui#12464: the page kind `jsx` retired in @objectstack/spec 18 + // (objectstack-ai/objectstack#23638), naming `html`. Picked out of the spec's + // own enum by reference (`.extract`), the way `PAGE_KIND_NODE_TYPE` is below: + // identical to the spec's enum once the resolved spec carries the + // retirement, it refuses `jsx` before that, and a kind the spec drops fails to + // compile here. Mirrors the TS face's `PageNodeSchema.kind`. + kind: stripImportedDefaults(SpecPageSchema) + .shape.kind.unwrap() + .extract(['full', 'slotted', 'html', 'react']) + .optional() + .describe('Page override mode (full | slotted | html | react)'), object: z.string().optional().describe('Bound object name (for record pages)'), template: z.string().optional().describe('Layout template name'), variables: z.array(PageVariableSchema).optional().describe('Local page state variables'), From 4cc23c7f77360ea0504e8fd9e3715eb810bfd2e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 15 Oct 2026 12:08:01 +0000 Subject: [PATCH 3/3] chore(changeset): declare the PageNodeSchema.kind narrowing (objectui#12464) Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB Co-authored-by: Claude --- .changeset/12464-retire-page-kind-jsx.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .changeset/12464-retire-page-kind-jsx.md diff --git a/.changeset/12464-retire-page-kind-jsx.md b/.changeset/12464-retire-page-kind-jsx.md new file mode 100644 index 0000000000..f22b77832c --- /dev/null +++ b/.changeset/12464-retire-page-kind-jsx.md @@ -0,0 +1,12 @@ +--- +'@object-ui/types': minor +'@object-ui/console': patch +--- + +The page kind `jsx` is retired from the page node, following `@objectstack/spec` 18 (objectstack-ai/objectstack#23638), which refuses it at parse and names `html` as its replacement (objectui#12464). + +**Breaking (levelled `minor`, per this repo's version policy): `PageNodeSchema.kind` narrows.** The exported `PageNodeSchema` type's `kind` is now `'full' | 'slotted' | 'html' | 'react'`; a page node literal typed with it no longer compiles with `kind: 'jsx'`. Author `kind: 'html'` instead: the page's `source` is compiled and rendered exactly as before. The zod face (`PageNodeSchema` from the package's zod entry) refuses `kind: 'jsx'` at `kind` the same way, so `objectui validate` and `objectui check` report it. + +Unchanged: `@object-ui/components`' page renderer still renders a stored `kind: 'jsx'` page as `html`. A server on a spec release built before the retirement can still serve such a row, so that branch is dropped only once the resolved `@objectstack/spec` carries the retirement. + +The console's `sdui-jsx-preview` sample page now authors `kind: 'html'`, and the React Pages guide no longer presents `jsx` as an accepted alias.