Repository navigation
feat(spec)!: retire the page kind jsx — refused naming html, its D2 conversion leaves the load path (F5-jsx of #23543) - #23638
Conversation
…onversion leaves the load path (WIP) Claude-Session: https://claude.ai/code/session_01DdfPiKZUYAduc1ERcEvDso Co-authored-by: Claude <noreply@anthropic.com>
…t for the jsx retirement Claude-Session: https://claude.ai/code/session_01DdfPiKZUYAduc1ERcEvDso Co-authored-by: Claude <noreply@anthropic.com>
…piled kind Claude-Session: https://claude.ai/code/session_01DdfPiKZUYAduc1ERcEvDso Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdfPiKZUYAduc1ERcEvDso Co-authored-by: Claude <noreply@anthropic.com>
…tire-jsx-page-kind
📓 Docs Drift CheckThis PR changes 5 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 149 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 19a19bd1898e24742059dea73e169fa3fabb9cf0 && git checkout 19a19bd1898e24742059dea73e169fa3fabb9cf0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 23e689dc7d1de961a168997685295ddf009d5c2c 2e1b65d066242d602f855ba62d5e4f247a09871b && git checkout -B drift-repro 23e689dc7d1de961a168997685295ddf009d5c2c && git merge --no-ff 2e1b65d066242d602f855ba62d5e4f247a09871b
node scripts/docs-audit/affected-docs.mjs --json 23e689dc7d1de961a168997685295ddf009d5c2c
|
席位核对 — ACCEPT ·
|
Contract reviewServed-tier: Family F5-jsx of ruling 6182001882 (items 5 and 7) on card #23543, PR #23638, read at 2026-10-15T08:32Z. Inputs: the card body and its thirteen comments, the PR body and its 43-file list, the net diff against Gate verdicts on this head (second reading): 44 check-runs, 33 Governance: none of the 43 paths is a governed surface ( ① Derived judgmentsAccept-set changes (each narrows what an author may write):
Public-surface changes: no new export ( ② Semver level
③ Boundary flagsDev
Dev
Dev
Residual text (not in the report), to ride the FAIL remedy push:
Report corrections, not flags: the dev report says 44 files; the PR has 43 (its own enumeration lists 43). The report read 24 check-runs with 22 in progress; at this record's second reading there are 44, with 3 in progress, named above. Premise re-read on the head: no Remedy for the FAIL (one push; the record is re-rendered on the new head): add Implemented-by: VERDICT: FAIL |
…the jsx-retirement prose names the doors that parse a page Claude-Session: https://claude.ai/code/session_01DdfPiKZUYAduc1ERcEvDso Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Family F5-jsx of ruling 6182001882 (items 5 and 7) on card #23543, PR #23638, second record, read at 2026-10-15T08:50Z. Inputs: the card body and its fourteen comments (the ruling, the F5-jsx claim 6185261661, both dev reports 6187521557 and 6188089228), the PR body and its 43-file list, the PR's three comments (the drift check, the seat review 6187645640 and the earlier record 6187937404 on Gate verdicts on this head (fourth reading): 39 check-runs, 27 Governance: none of the 43 paths is a governed surface ( ① Derived judgmentsAccept-set changes, each a narrowing of what an author may write:
Public-surface changes: no new export ( ② Semver level
③ Boundary flagsDev
Dev
Dev
Residual text from the earlier record (items 8, 9 and 10): all three answered on this head by ① judgment 10 (b), (c) and (d); nothing further is owed. No new residue was found on the head beyond the objectui-dependent note in item 5. Report corrections, not flags: the first dev report says 44 files; the PR has 43. The patch-round report's gate readings are the dev's own and are not inputs here; the check-runs above are. Premise re-read on the head: no Landing terms are the seat's (6187645640 item 8): this record, then every check green — the seven Implemented-by: VERDICT: PASS |
… 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>
…; the console sample moves to html (objectui#12464) (objectstack-ai#12485) Fixes objectstack-ai#12464 Clause-②: yes — the published `@object-ui/types` `PageNodeSchema.kind` narrows (drops `'jsx'`), levelled `minor` (objectui levels a breaking change `minor`, never `major`) The objectui layer of objectstack-ai/objectstack#23543 family F5-jsx, which landed in `@objectstack/spec` as objectstack-ai/objectstack#23638 (`c5dd7fce30`): spec 18 refuses the page kind `jsx` and names `html`. ## What changed - **`@object-ui/types`, TS face.** `PageNodeSchema.kind` is `'full' | 'slotted' | 'html' | 'react'`; its docblock no longer calls `jsx` an accepted alias. The `@ts-expect-error` comment in `authoring-nodes-11364.test.ts` lists the four kinds. - **`@object-ui/types`, zod face (premise correction).** The card measured "no `'jsx'` page kind in `packages/types/src/zod/`". That holds for the literal text, but the zod `PageNodeSchema` takes `kind` from the spec's `PageSchema` by reference, so on the pinned `18.0.0-next.1` it still admitted `jsx`. With the TS face narrowed, the zod face was wider than the TS face, and `zod-mirror-parity.test.ts` failed `types` type-check with two TS2322 (`"layout.zod.ts#PageNodeSchema"` and `"kind"` not assignable to `never`, from `WiderLedgerMismatch` and `WiderLedgerKeyDrift`). The zod face now picks its enum out of the spec's by reference, `stripImportedDefaults(SpecPageSchema).shape.kind.unwrap().extract(['full', 'slotted', 'html', 'react'])`, the way `PAGE_KIND_NODE_TYPE` already does for `type`. Against spec `main` this is the spec's own enum. Against `next.1` it refuses `jsx` at `kind`. A kind the spec drops fails to compile here. A ledger entry was not used because it cannot be correct on both spec versions: it would be stale on spec `main`. - **Console sample.** `apps/console/src/sdui-jsx-preview.tsx` authors `kind: 'html'`; its header comment names the html tier. The extraction pin in `sdui-preview-page-source-query-params.test.ts` reads `sdui-jsx-preview.tsx:command_center:html`. - **`@object-ui/components` page renderer: the `'jsx'` arm stays (half 2 deferred, below).** Narrowing the type made the arm a compile error: `TS2367: This comparison appears to be unintentional because the types '"full" | "slotted" | undefined' and '"jsx"' have no overlap` (the components declaration build). The renderer now reads `kind` as the stored wire value, `const kind: string | undefined = schema.kind;`. That is a widening annotation, not a cast. Its comment says why: a server on a spec built before the retirement can serve a stored `jsx` row. No `jsx` to `html` mapping is added anywhere. Rendering is unchanged. - **Docs.** `content/docs/guide/react-pages.md` no longer says `"jsx"` "is still accepted"; it says the alias was retired in spec 18 and to author `"html"`. - **Changeset** `.changeset/12464-retire-page-kind-jsx.md`: `@object-ui/types: minor`, `@object-ui/console: patch` (the console is published: no `private` field). `@object-ui/components` gets no entry because its behaviour does not change. ## Half 2 (the renderer's `'jsx'` arm): deferred The pin reading on this tree: - `packages/types` and `packages/components` both resolve `@objectstack/spec` `18.0.0-next.1` from the lockfile and the installed store. Its `dist/ui/index.mjs` declares `kind: z.enum(["full", "slotted", "html", "react", "jsx"])` and `COMPILED_PAGE_KINDS = ["html", "jsx"]`. - `npm view @objectstack/spec@next version` answers `18.0.0-next.1` (dist-tags `next: 18.0.0-next.1`, `latest: 17.7.0`). The published manifest carries no `gitHead`. The resolved spec therefore does not carry `c5dd7fce30`, and the arm stays as the triage requires. The PM splits it into its own card. **Pin window:** until a `next` built from `c5dd7fce30` or a descendant is resolved, a stored `jsx` page served by a `next.1` runtime still renders as `html` through that arm. ## Verification (head `4cc23c7f7`) - Closure build `turbo run build --filter='@object-ui/console^...' --concurrency=2`: 34/34 tasks. - `type-check` for `@object-ui/types`, `@object-ui/components`, `@object-ui/app-shell` and `@object-ui/console`: exit 0 each. - vitest, root form: - `packages/types/`: 383 files, 10335 tests passed. - 19 targeted files (the page renderer suites, the console preview and html-tier suites, `public-contract`, the app-shell `pageRequiresStamp` double): 220 tests passed. - New pin `packages/types/src/__tests__/page-kind-jsx-retired-12464.test.ts`: `Equal` on the whole `kind` union, a `@ts-expect-error` on `kind: 'jsx'`, and a zod `safeParse` refused at path `kind` with `html` as the control. - **Reverse verification** (after commit, with a trap restore): `'jsx'` was re-added to the TS union and to the `.extract` list. Then `tsc -p tsconfig.test.json` reported `TS2344` (the `Equal` pin) and `TS2578` (the unused `@ts-expect-error`), and vitest reported `1 failed | 1 passed` (the zod pin). Restore proof: `git diff HEAD` was empty, and both files' hashes matched their HEAD blobs. - eslint over the 7 changed source files: 0 errors and 10 warnings, every warning pre-existing (`no-explicit-any` and `exhaustive-deps` on untouched lines). The population is the config's `**/*.{ts,tsx}` block, and the file count comes from `--format json`. The config enables no type-aware linting (no `projectService` or `project`), so these edits cannot change the verdict on any untouched file. - Exit 0 for each of `check:phantom-deps`, `self-import`, `unreferenced-sources`, `doc-example-readers`, `handler-key-reads`, `side-effects-array`, `element-data-source-declaration`, `spec-symbols`, `action-forward-parity`, `designer-field-key-parity`, `icon-record-names`, `i18n-keys`, `i18n-drift`, `i18n-designer-parity`, `control-bytes` and `new-line-citations`. Also exit 0: `check-doc-snippet-types.mjs` (792 of 792 blocks judged, after its scoped build), `check-changeset-fixed.mjs` and `check-changeset-presence.mjs`. The guard `check-governed-queue-guard.mjs --test` reports NOT GOVERNED. - NOT MEASURED, Spec Main Shape Gate locally. Reason: it needs a spec tarball built from objectstack `main`, which was not built in this container. The check runs on this PR. By construction, on spec `main` the TS `kind` equals the spec's four-value enum, so the TS2430 at `PageNodeSchema` no longer applies, and the zod `.extract` is the identity there. - NOT MEASURED, `check:spec-floors`. It exits 1 with `no-artifact` because it needs a whole-workspace build, and it is the known red on `main`. ## Acceptance notes (noted, not filed) - `packages/app-shell/src/views/metadata-admin/ResourceEditPage.pageRequiresStamp-11357.test.tsx`: the server double's `compiledRequires` reads `body.kind !== 'html' && body.kind !== 'jsx'`, mirroring `next.1`'s `COMPILED_PAGE_KINDS`. It is test-only and outside this card's file surface. Carrier: the half-2 card. - `packages/types/src/zod/layout.zod.ts`: the comment on the re-attached spec checks names an `html` / `react` / `jsx` page, and `html` / `jsx` for `checkPageRequiresKind`. That describes `next.1`'s checks. Carrier: the half-2 card. - `packages/components/src/renderers/layout/page.tsx`: the comment above `getJsxManifest` still reads "a `kind:'jsx'` page carries a constrained JSX `source`". Carrier: the half-2 card. - The sample keeps its file name `sdui-jsx-preview.tsx` (with `sdui-jsx-preview.html`, and the names in two console tests) and its `page as any`. A rename touches files outside this card's surface. Carrier: none. --- _Generated by [Claude Code](https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #23543
Clause-②: yes (narrowing)
This PR lands family F5-jsx of ruling
6182001882(item 5) only. F3, F2, F4 and F5-http stay on #23543, one PR each, in the ruled order. #23543 remains open.What
Ruling F5-jsx A:
'jsx'retires fromPageSchema.kindthroughenumWithRetiredValuesnaminghtml, together with theCOMPILED_PAGE_KINDSbranch; the entry retires.PageSchema.kindis nowenumWithRetiredValues(['full', 'slotted', 'html', 'react'], PAGE_KIND_RETIRED_GUIDANCE)(packages/spec/src/ui/page.zod.ts). An authoredkind: 'jsx'failstsc(it leftz.input) and, at parse, gets the prescription in place of zod's anonymous enum error: "Pagekind: 'jsx'was removed in @objectstack/spec 18 (ADR-0087 D2) … Usekind: 'html'." AtdefineStackthat isSTACK_SCHEMA_INVALID/ 422 with one issue atpages.0.kind. The guidance carries noos migrate metasentence on purpose: the rename belongs to protocol 11, belowMIGRATION_SUPPORT_FLOOR, so the tool lists no edit for it (theflow-node-crud-filter-aliasprecedent).COMPILED_PAGE_KINDSis['html'].checkPageSourceCompleteness's source kinds arehtml/react. ThecheckPageRequiresKindrefusal no longer namesjsx.page-kind-jsx-to-htmlgetsretiredFromLoadPath: trueandretiredAfter: '17.7.0'(the stamp PR feat(spec)!: retire seven overdue ADR-0087 D2 conversions from the load path at 18; record why eight stay #23523 and PR feat(spec)!: retire the datasource driver spelling mongo — refused naming mongodb, its D2 conversion leaves the load path (F1 of #23543) #23600 used). Its docblock's "stays on the load path — a recorded verdict" paragraph is rewritten as the retirement record.18.page-kind-jsx-retired(semantic, step 18) states the hand edit. It carries noconversionIds: no migration step replays a protocol-11 conversion, andmigrations.test.tsrefuses an id no step replays.conversions.test.tsmoves the entry to the retired list. A newdescribe('page-kind-jsx-to-html')pins three claims: a storedjsxrow still converges tohtmland the converted row parses; the authoring funnel no longer rewrites an authoredjsx; anddefineStackrefuses it withSTACK_SCHEMA_INVALID/ 422 atpages.0.kind, message first sentence asserted, withhtmland a typo (htm) as controls. The removal-version pin'sJOINgains the row.Mechanism assumptions, measured
applyConversionsToStoredItem('page', …)runs withincludeRetired: true. The stored-row readers aremetadata-protocolconvertStoredItemDetailed(protocol.ts) andmetadatadatabase-loader.ts, so asys_metadatapage row written asjsxreachesPageSchemaashtml. This is pinned inconversions.test.ts. The metadata save door does not replay page conversions (itsapplyConversionsToFlowfallback is for flows only), so a page saved withkind: 'jsx'is refused 422. The D3 entry and the changeset both say so. No credential-redactor-style reader judges a raw page body by kind, so F1's "keep it on a contract face" situation does not arise here.'jsx'readers. Re-taken on this tree withgit grep(non-testpackages/*/src). Each branch is now unreachable because the parse refuses the kind, so each is deleted; none keeps consumer-side tolerance:metadata-protocol/src/runtime-authoring-gate.ts: the localCOMPILED_PAGE_KINDSis['html'];cli/src/utils/sdui-manifest.ts:checkedPageKeysreadshtmlonly;cli/src/utils/i18n-extract.ts: comments only;lint/src/page-walk.ts:SOURCE_AUTHORED_KINDSishtml/react;lint/src/validate-jsx-pages.ts: readshtmlonly;lint/src/validate-page-source-styling.ts: readshtml/react;lint/src/authoring-rules.ts,rule-explanations.ts,validate-translation-references.ts: comment and explanation text;spec/src/ui/page.form.ts: thesourcefield'svisibleWhenand help text. The generateden.metadata-forms.generated.tswas regenerated withnode scripts/check-i18n-bundles.mjs --write, and the three translated help-text values were edited by hand to drop the alias.registry.ts's docblock is rewritten as the retirement record (as above).content/docs/references/ui/page.mdx(check:generated --fix) anden.metadata-forms.generated.ts.check:generatedreported no change toapi-surface/,export-origins/,authorable-surface/or the strictness ledger: an enum-value narrowing is byte-invisible to them.jsxwas the "live conversion" subject of several suites (assumption 5's question). Each now drivesflow-node-http-callout-rename, the live entry that retires last in the ruled order:lint-conversion-notices.e2e,build-json-failure-conversions.e2eandvalidate-json-failure-conversions.e2e;stack-conversions-record.test.ts(the "two conversions" case) andconversions.test.ts(copy-on-write,normalizeStackInput);artifact-forward-conversion.test.tsFIRING CONTROL 1, which drivespage-component-visibility-to-visibleWhen.Fixture triage (pin sweep, whole repo:
kind: 'jsx'and the conversion id)html(they used the alias): lintvalidate-jsx-pages.test.ts,page-walk-conversion-parity.test.tsandvalidate-action-name-refs.test.ts; clisdui-manifest.test.ts(two fixtures).object-refinement-check-exports.test.ts(twojsxrows) andpage-print.test.ts(one row);page-requires-compiled-kinds.test.ts, whose vocabulary pin now reads four kinds and one compiled kind;page-walk.test.ts,validate-page-source-styling.test.tsandvalidate-jsx-pages.test.ts, where thejsxrows now assert the rule does not read the retired kind.protocol.batch-verb-driver-text.test.tsseeds storedjsxrows. That is still a non-canonical stored row that the stored pass rewrites, so only its comment changed.format.conversion-notice.test.tsformats a synthetic notice.content/docs/ui/pages.mdxandreact-pages.mdx;liveness/page.json(therequiresrow's evidence);18.page-requires-non-compiled-kind-refusedand18.ui-html-page-div-refused, and the step-18 rationale paragraph forpage-requires-non-compiled-kind-refused(registry.ts.template).Changeset
.changeset/23543-f5-jsx-retire-page-kind-jsx.md:@objectstack/specminor;metadata-protocol,cliandlintpatch.Clause-②: yes (narrowing). It names the refused spelling, the prescription (html), and how stored rows keep loading.registered page-kind-jsx-retired.Verification
Head before the
mainmerge:5bf25840. Mergedorigin/mainf7cd580bthroughscripts/pm/os-regen-merge.sh(no conflicts;check:generatedthen read "All 14 generated artifacts are up to date").831a9fa8: spec (src/conversions,src/migrations,src/ui,src/shared,stack-conversions-record) 195 files / 6977 passed; metadata-core 18 / 719 passed; metadata-protocol 236 files / 20550 passed, 19 skipped (closure rebuilt first). The readings below were taken on5bf25840, before the merge;main's incoming commits touched none of this diff's files.@objectstack/spec: full suite 740 files, 21785 passed (the one failing file,page-requires-compiled-kinds.test.ts, was then fixed and re-run green with the conversion, pin and migration suites: 8 files / 567 passed);typecheckexit 0;check:generatedall 14 up to date.@objectstack/lint: 139 files / 6771 passed; typecheck exit 0.@objectstack/metadata-protocol: 237 files / 28429 passed, 19 skipped; typecheck exit 0.@objectstack/metadata-core: 18 files / 701 passed; typecheck exit 0.@objectstack/cli: unit tier 290 files / 4323 passed; typecheck exit 0. The three edited nightly-tier e2e suites (OS_TEST_TIERS=nightly): 3 files / 35 passed.pnpm check:i18n: OK, all bundles in sync.dispatch-gates.mjs --commandsderived 118 commands. Each was run one by one, with its exit code captured before any pipe.--ran(with exit codes): "118 derived famil(ies) accounted for — 116 run, 2 NOT-MEASURED".check-plugin-teardown-shape --self-test(shallow clone) andcheck:dual-build-cjs-loads(full build absent).check:platform-checklistexits 1 on symbol anchors inmetadata-protocol/src/protocol.tsandservice-storage(countable,canEdit,anonymousFormIntake*,envWideRawViewRows). This diff touches neither file; it is the samemain-tree red F1's report recorded.check-adr-0087-registration,check:spec-changes,check:upgrade-guide,check:docs,check:liveness,check:doc-authoring,check:nul-bytes,check:i18n-stale-fill.scripts/ablation-replace.mjs). The kind enum was spelled back as a barez.enum([...four kinds]), which keeps the narrowing and drops the prescription.4ceaa5dffafc→35fbaad1835c.defineStackrefusal case inconversions.test.tsand thepage-kind-jsx-to-htmlrow ofretired-key-removal-version.pin.test.ts(direction: red).git diff HEADempty. Spec tests resolvesrc, so no dist leg applies.Acceptance notes
major, Changesets pre mode onnext, and the two gate edits (the maintainer's ruling on #12030), after #12030 lands objectui#12334). The ruling sized objectui's half as "one line" (the console sampleapps/console/src/sdui-jsx-preview.tsx:87). At the pinned.objectui-shab9814b334there are three more readers:packages/types/src/layout.ts:1795declaresPageNodeSchema.kind?: 'full' | 'slotted' | 'html' | 'jsx' | 'react'on an interface that extends the spec'sPagewith some keys omitted, withkindnot inPAGE_SPEC_EXCLUDED. Once objectui builds against this spec, that override no longer narrows the spec'skind(an "incorrectly extends" error, by reading; not compiled here).packages/components/src/renderers/layout/page.tsx:611keeps akind === 'jsx'branch.typeszod mirror (SpecPageFieldsfromSpecPageSchema.shape) will refusejsxat runtime.scripts/build-console.shinjects this tree's spec only at the vite bundle step, and objectui's owntscresolves the spec from objectui's lockfile.skills/objectstack-ui/rules/pages.md:138still says "The legacy valuekind:'jsx'is a deprecated alias forkind:'html'." That file is a governed (Tier H) surface, so it is left for a separate skills-only PR rather than making this one Tier H.flow-node-http-callout-rename, F4's two visibility entries, F3's two lifts andretry-policy-converged. The suites re-pointed here rideflow-node-http-callout-rename, which retires last (F5-http). That PR inherits the "no live conversion left" question F1's report recorded.Generated by Claude Code