Repository navigation
Commit 8faba89
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoAqVfFyhsxKuFcEP9YTBj25)_
Part of #25712 (4b 2g PR 1: one parser body; Tier S).
Part of objectstack#25677.
Clause-②: yes (narrowing: `validateTree` now also judges the nodes held
in a manifest entry's declared `slots`, so a caller whose manifest
carries `slots` gets diagnostics on slot-held nodes it did not get
before)
Metadata-protocol: none
## 维护者速读(草稿)
- **改了什么:**
- 按 2g 裁定(#25712 记录 6418729849,E+B),把渲染器那份 `@object-ui/sdui-parser`
多出来的东西并进 `@objectstack/sdui-parser`,包括节点槽位校验、已退役的 `body` 写法、html
层来源标记,以及它多导出的符号。
- `body-dialect.ts` 和 `provenance.ts` 从 MIT 包整体搬入,保留 MIT 声明,并随包发布
`LICENSE-MIT`。
- 两份不一致的地方按本包规则:非 enum 的 `type-mismatch` / `member-type-mismatch` 仍然一律是
error;`unconsumed-widget-option` 仍写成字面量。
- `SchemaNode` / `ValidationResult` 改名为 `ParsedSchemaNode` /
`ManifestValidationResult`,旧名保留为 `@deprecated` 别名,不删任何导出。
- **哪些原来能保存的现在会被拒:没有。** 保存门(`os validate` / `build` / `lint` 和 `os
serve` 的保存入口)读的是仓里跟踪的 `sdui.manifest.json`,也就是 `@objectstack/console`
随包带的那份。它 106 个条目里 0 个带 `slots`,所以新增的槽位遍历在保存门上无事可做。只有自己用 `slotsFor`
生成清单的调用方才会走到它,今天只有渲染器,而渲染器那份解析器本来就在遍历。
- **为什么改:** 两份解析器靠 `check:sdui-lockstep` 维持同一套语法和诊断码,其余部分各自漂移。本 PR 是 2g
的第 1 步。PR 2 把 ui 的 122 个引用改到本包并删除 ui 那份;PR 3 退役 lockstep 门。
- **风险与代价(含回滚):** 库层面的收窄,带 BREAKING 标注,按 `patch` 发布(元数据协议不变)。`parseJsx`
产出的节点多了一个 JSON 看不见的 symbol 标记。回滚 = revert 本 PR。
- **你要做的:** 无(Tier S)。
## 席位核对
(left for the dispatch seat)
## Why
Two copies of the constrained JSX parser existed:
- `@objectstack/sdui-parser` (`packages/sdui-parser`), which the save
gate runs;
- `@object-ui/sdui-parser` (`ui/packages/sdui-parser`), which the
renderer runs.
`check:sdui-lockstep` held them to one grammar region, one
diagnostic-code set and one containment predicate, and they drifted
everywhere else. Tesla's 2g ruling keeps `@objectstack/sdui-parser` as
the one package with no forwarding release, deletes the old package,
rewrites its importers and takes `@object-ui/sdui-parser` out of the
fixed group. This PR is step 1: the body becomes one, here. The plan,
module by module, is #25712 comment 6419004222.
## What changes
### Source, module by module
| Module | Winner | What this PR does |
|---|---|---|
| `input-type.ts` | n/a | Unchanged; the copies were identical. |
| `codegen.ts` | ui | The generated `.d.ts` header gains `/*
eslint-disable */` and names `@objectstack/sdui-parser`. No checked-in
output carries the header. |
| `parse.ts` | ui | `ParsedSchemaNode`. Every element is stamped with
`markHtmlTierNode` at production. The LOCKSTEP comments go; the
`forbidden-attr` rationale no longer leans on the gate. |
| `types.ts` | ui | Adds `ParsedSchemaNode`, `ManifestValidationResult`
and `ManifestComponent.slots`. Keeps `SchemaNode` and `ValidationResult`
as `@deprecated` aliases. |
| `index.ts` | ui | The renderer copy's superset of exports (below) and
`manifestFromConfigs`' `slotsFor` option. The `binding` note keeps this
copy's pointer to the single record in `types.ts`. |
| `validate.ts` | ui code, **os severity** | Adds the node-slot walk
(objectui#11170) and the `body` replacement in the unknown-prop branch.
`checkType` and `checkMemberTypes` keep this package's rule: every
mismatch is an `error`. The LOCKSTEP comments go. |
| `dashboard-widget-options.ts` | os code, ui header | The emitted
`code:` stays an inline literal, because of
`check:dispatcher-error-vocabulary`. The header takes the renderer's
census derivation, re-rooted to the `ui/` paths now in this tree, and
keeps the spec-side drift section that `check:widget-option-census`
holds. |
| `body-dialect.ts` | moved in | MIT notice kept. |
| `provenance.ts` | moved in | MIT notice kept. The registry key
`@object-ui/sdui-parser.html-tier-node` is unchanged, because it is the
cross-instance contract. |
**New exports:**
- `HTML_TIER_NODE`, `isHtmlTierNode`, `markHtmlTierNode`;
- `acceptsChildren`, `CHILD_LIST_KEY`, `SDUI_BASE_PROPS`,
`SduiBaseProp`, `SduiBasePropScope`;
- `checkRetiredBodyDialect`, `RETIRED_CHILD_LIST_KEY`;
- `NodeSlotDeclarationLike`, `assertFullyLoaded`,
`RegistryConfigLike.lazy`.
These cover every name the 2g-b importer inventory needs.
### Licence
- `packages/sdui-parser/LICENSE-MIT` is a verbatim copy of
`ui/packages/sdui-parser/LICENSE` (MIT, Copyright (c) 2024 ObjectQL),
added to `files`. `npm pack --dry-run` lists it.
`scripts/check-published-files.mjs` registers it in `EXTRA_ENTRIES` with
its reason, because the gate admits no unexplained `files` entry.
- The two moved modules open with a `/*! … */` notice that names the
licence, the copyright line and `LICENSE-MIT`. esbuild keeps it in both
`dist/index.js` and `dist/index.mjs`.
- The four ported tests that carried the renderer's MIT header keep it,
with its pointer retargeted to `LICENSE-MIT`.
- The `license` field stays `Apache-2.0`.
### Behaviour, measured
- **The save gate's verdicts do not move.**
`scripts/gen-sdui-manifest-node.mjs` generates the tracked
`sdui.manifest.json` through `manifestFromConfigs` without `slotsFor`,
so 0 of its 106 entries carry `slots`. `packages/console/build-dist.sh`
copies that file over the console's `dist/sdui.manifest.json`, and that
copy is what `resolveSduiManifest` gives `os validate`, `build`, `lint`
and `os serve`'s save door. The slot walk only works for a caller that
builds a manifest with `slotsFor`. Today that is the renderer, whose own
copy already walks the slots.
- **`body`.** The message now names `children`. A `body` child list
under a component that takes no children draws `not-a-container`, the
code the `children` spelling draws. Both stay warnings.
- The two sentences drop the renderer copy's `objectui#6771`, because
`check:doc-authoring` refuses a tracker id in author-facing text (the
module header keeps the citation). The renderer copy's text, `("body"
was retired by objectui#6771)`, becomes `("body" is its retired
spelling)`. `node-slot-walk-11170` pins the new text.
- **The tree.** `parseJsx` elements carry an enumerable, symbol-keyed
marker. `JSON.stringify` and `Object.keys` do not see it. The consumers'
suites pass unchanged (Verification).
### Tests (`packages/sdui-parser/src/__tests__`)
- **Ported whole (7):** `base-props-bind-hidden-11008`,
`base-props-one-list-11044`, `body-dialect-6771`, `input-type-union`,
`node-slot-walk-11170`, `provenance`, `tier`. Path pointers in comments
are re-rooted to `ui/`. `provenance`'s "objectui-only" sentence now says
what was true until this change.
- **ui-only cases merged into this package's twins (7):**
- `containment-declared-slot` +3: the base key, the `body` spelling, and
the `isContainer` projection. The import now comes from `../index.js`.
- `dashboard-widget-options` +3: the retired-sub-caption shapes and
their control.
- `type-attribute-collision` +1: the no-`type` regression guard with
provenance. Re-ablated: restoring `{ type: tag, ...props }` keeps all
nine green, so the header now says "nine".
- The plan's estimate was 12; the other five were already here under
different titles or quote styles. `literal-subset`'s `__proto__` case is
covered by this file's `it.each`, and `member-kind`'s two by
`member-type-mismatch`.
- **Exact twins, so not ported (4):** `compile`,
`html-tier-manifest-10735`, `inert-expression-6598`,
`kanban-quick-add-8285`.
- **New:** `deprecated-type-aliases.test.ts` (2 cases) pins both aliases
at type level. Negative control: re-pointing one fails `pnpm typecheck`
with TS2344.
- **Left for PR 2 (5),** because each imports `@object-ui/*`, React or
the spec, or reads ui sources: `injected-component-input-6950`,
`inline-whitespace.test.tsx`, `render.test.tsx`, `spec-symbol-batch7`,
`dashboard-widget-options-census`.
- **Severity.** No ported case pinned `warning` for a non-enum mismatch,
so no expectation flipped.
### What this leaves for PR 2 and PR 3
- **PR 2** rewrites importers to `@objectstack/sdui-parser`. Old names
are not for new code.
- Once ui imports this package, a non-enum mismatch is an `error` in the
Console too.
- `ui/scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts`
reads the ui copy's `dashboard-widget-options.ts` path, and the census
header here points at the census test's current path. Both move with it.
- The old `body` message text is pinned in
`ui/packages/components/src/renderers/__tests__/base-props-one-list-11044.test.tsx`
and quoted in `ui/content/docs/components/layout/semantic.mdx`.
- `scripts/sdui-manifest.sources/sdui-parser.json` records
`ui/packages/sdui-parser` as a registry source.
- **PR 3** retires `check:sdui-lockstep`. It is green on this head (9
and 9 sources agree), so the PRs can land in either order.
## Verification
Head `930df7bda`, on `main` `6b9355299`. 24 files, +1794 / −191, in two
commits. Tier S: no governed path (each checked with
`check-governed-merges --test`), under 3000 lines.
- **`@objectstack/sdui-parser`:** `vitest run`, 22 files, 349 tests
pass. `pnpm typecheck` and `pnpm build` (tsup + `check-dts-emitted`)
exit 0.
- **Consumers, built against this dist:** `packages/lint` 164 files /
7356 tests pass; `packages/metadata-protocol` 266 files / 21539 tests
pass (3 files / 19 tests skipped by their own guards).
- **`@objectstack/spec` `vitest --project repo`:**
- On `1951e860e`: 1 of 1279 failed, `visibility-alias-keys-retirement`.
It is the same failure CI's Test Core (1/8) showed on that head.
- Its packages/** walk now reaches the ported
`base-props-one-list-11044.test.ts`, whose fixture gives the SDUI tier's
deprecated-but-accepted `visibleOn` base prop a value. Under `ui/` the
walk never read it.
- `930df7bda` adds the file to the walk's structural exclusions with its
reason. That file then passes 12/12, and main's same shard is green.
- **`pnpm check:sdui-lockstep`:** OK in-tree. The grammar region is
byte-identical (214 lines), all 25 codes agree, the containment
predicate agrees, across 9 and 9 sources. PR 3 can land before or after
this one.
- **Changeset gates:**
- `check-changeset-no-major --event` with this body's declaration: level
axis ✓, patch with `yes (narrowing)` and `none`.
- `check-adr-0087-registration`: ✓, `not-required
(no-migration-prescription)`.
- **Gate sweep:** `node scripts/pm/dispatch-gates.mjs --commands`
derives 120 gates on `930df7bda`. All 120 ran and exited 0. `--ran`
reconciles 120 derived, 120 run, 0 NOT-MEASURED, 0 UNRUN.
- Four spec gates that read built declarations (`check:api-surface`,
`check:dual-source-exports`, `check:entry-nameability`,
`check:exported-any`) first refused with exit 3 on a stale local spec
`dist`. They were re-run green after `pnpm --filter @objectstack/spec
build`.
- Earlier passes:
- Pass 1 (81 gates, pre-fix head) found `check-issue-citations`,
`check:doc-authoring` and `check:published-files` red; see Behaviour and
Licence.
- Pass 2 (100 gates, `1951e860e`) was 100/100 green.
- **Push battery** (`GITHUB_ACTIONS=true GITHUB_EVENT_NAME=push node
scripts/pm/check-dispatch-gates.mjs`) on `930df7bda`: 2182 cases pass, 0
fail, EXIT 0. It was also 2182/0 on `1951e860e`.
- **CI on `930df7bda`:** 41 success, 8 skipped, 0 failed; mergeable
`clean`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01A6SEitNngAMugGbuJHWsDS
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6633075 commit 8faba89
24 files changed
Lines changed: 1794 additions & 191 deletions
File tree
- .changeset
- packages
- sdui-parser
- src
- __tests__
- spec/src/ui
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
| 52 | + | |
| 53 | + | |
53 | 54 | | |
54 | 55 | | |
55 | 56 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
0 commit comments