Repository navigation
feat(core,sdui-parser,components): the published manifest declares the html tier's intrinsic elements, marked tier html — div stays out (objectui#10735) - #10753
Conversation
… the manifest, marked tier html The published sdui.manifest.json is getPublicConfigs() serialised, and that read was the curated PUBLIC_BLOCKS vocabulary alone, so the objectstack gate that whitelists a kind:'html' page's tags from it refused every plain HTML tag the renderer accepts. Add a second roster, HTML_TIER_INTRINSICS (the html-elements.tsx TAGS, span, table, label and the seven sectioning tags), which getPublicConfigs() appends stamped tier:'html'; manifestFromConfigs carries exactly that stamp into a new optional ManifestComponent.tier, so a whitelist reader takes the tags by key unchanged while a reader that means the curated vocabulary filters on the stamp: generateBlockList sections the html tier under its own count and the kind:'react' JSX scope skips stamped entries. div stays undeclared (deprecated in favour of box); code (the field:code widget's bare fallback) and kbd are ledgered with reasons. PUBLIC_BLOCKS and html-elements.tsx are untouched; curated entries serialise byte-identically. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
… LayoutSchema arms HtmlElementSchema and SemanticElementSchema are deliberately not exported from the zod barrel, so both pins derive the two enum arms from LayoutSchema by shape. The label probe asserts what the registration declares: text is required and there is no child slot, so <label>Name</label> draws missing-required-prop and not-a-container while <label text="Name" /> is clean. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsSilent widening of the JSON surface — none, measured. Consumers of objectstack reader. Lockstep The 47, recounted from the registry (console graph,
Types. Pins. Head: 19 files / 217 tests green (the dev's 14 files + Regenerated artefacts (repo's ② Semver level
FAIL item (wording, published text). The PR rewrites the react-page skip line to ③ Boundary flags
Implemented-by: VERDICT: FAIL — one wording defect only: the stale verbatim quote of the react-page skip line in |
The scope builder's skip line gained a third arm (cfg.tier === 'html'), and four files still quoted the old two-arm line verbatim: the react-pages guide, two passages in public-contract.test.ts, the container census pin and layout-containers-declare-containment. Each quote now resolves. The pending 6740 changeset keeps its own quote as the reading of that change under a dated note; the 10735 changeset counts 200 ledgered findings, not rows. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: ① Derived judgmentsThe round-2 delta Stale-quote grep at head, whole tree ( Frontmatter md5, re-measured: The 6740 dated note sits directly under the paragraph it dates, reads
The 12 bot-listed dispositions, checked against head (the bot comment was rewritten in place at 05:43:58Z for the round-2 run and lists 12;
Body vs live, sentence by sentence where changed. Lead: head ② Semver levelCarried from round 1, untouched by the delta: ③ Boundary flags
Implemented-by: VERDICT: PASS |
…ding it hides (objectui#10746) (objectstack-ai#10762) Fixes objectstack-ai#10746 Clause-②: no — a Studio inspector's patch on a type switch; no declared surface moves ## What changed `packages/app-shell/src/views/metadata-admin/inspectors/ReportDefaultInspector.tsx`: the Report type picker's commit is now `commitType`. When the picked type is `joined`, the patch that commits `type: 'joined'` also carries `dataset`, `values`, `rows`, `columns`, `chart` and `order` as `undefined`-valued keys — only those the draft actually holds (`draft[key] !== undefined`). Any other type commits `{ type }` alone, as before. The list lives in `JOINED_CONTAINER_CLEARED_KEYS` with the reasoning beside it; `blocks`, `runtimeFilter` and `drilldown` are never named. `order` is a sixth key, beyond the card's five, under the role file's bounded in-place exemption (same defect class, mechanical, same file, same gate family, no other claim on the file): the spec's own `reportForm` hides `order` in the same "Dataset binding" section (`visibleWhen: "data.type != 'joined'"`), and the INSTALLED spec already refuses a container `order` on a joined report ("a `joined` report orders per block — move `order` onto `blocks[]`"), so the same invisible-key save refusal exists today for `order`. Evidence in H3 below. New pin file `ReportDefaultInspector.joinedClearsBinding-10746.test.tsx` (8 tests); changeset `.changeset/10746-joined-report-clears-binding.md` (`'@object-ui/app-shell': patch`). ## H1 — reproduction on `origin/main` (`1422a920ed`) The pin file run against the untouched component: `Tests 4 failed | 4 passed (8)`. The defect pin quotes the only `onPatch` argument the type picker commits today: `AssertionError: expected { type: 'joined' } to strictly equal { type: 'joined', …(6) }` — the commit is `{ type: 'joined' }` and nothing else. The 4 pins green on base are the boundary/control pins, which pin what must NOT change. ## H2 — the clear mechanism `onPatch` is a shallow patch in all three hosts: `ResourceEditPage` applies it as `handleDraftChange((d) => ({ ...d, ...patch }))`, `ReportConfigPanel.handlePatch` as `{ ...draftRef.current, ...patch }`, and `StudioDesignSurface.onPatch` as `setDraft((d) => ({ ...d, ...patch }))`; each saves through `client.save`, which is `JSON.stringify`. None offers a delete sentinel, so the one way to drop a key is an `undefined`-valued key — the spelling this inspector's own `commitChart` (`chart: next.type ? next : undefined`) and the siblings (`ActionDefaultInspector`, `ObjectDefaultInspector`, `DatasetDefaultInspector`'s "Clear all", pinned by its objectui#9372 suite) already use. Measured in the second pin: after the spread the key is an OWN property holding `undefined` (`Object.hasOwn` true, value `undefined` — not `null`, not an empty string), and `JSON.parse(JSON.stringify(committed))` — the shape `client.save` puts on the wire and the spec parses — has no such property. The spec's refinement itself skips `undefined` (`if (value === undefined || …) continue;` in the joined arm), so even the in-memory draft would not be refused. - Reach: The clear holds for the patch and for the save that follows it. The metadata-admin editor rebuilds its draft as the served draft spread over `layered.effective` (on load, after each save and after publish), and `effective` is the published layer, so a report whose PUBLISHED version was bound gets those keys back in the draft after the first draft save until it is published, and a report already saved `joined` with stale keys is not repaired on load. Both are the host's draft-over-baseline merge, objectui#10765. ## H3 — the spec check Installed `@objectstack/spec` is 17.4.0 (`packages/app-shell/node_modules/@objectstack/spec`). It PREDATES objectstack-ai/objectstack#20160: `grep 'selects per block'` over its `dist/` gives 0 hits; the control `grep 'orders per block'` gives 3. One-off probe with the installed `ReportSchema.safeParse` (not committed): - joined + one block: success - joined + stale `dataset` / `values` / `rows` / `columns` / `chart`: **success** — 17.4.0 accepts them - joined + stale `order`: refused, `custom` at `['order']`, "a `joined` report orders per block — move `order` onto `blocks[]`." - joined + `blocks: []`: refused at `['blocks']` - joined + `runtimeFilter` + `drilldown: false`: success - joined + `dataset: undefined` as an own key: success So the pins' parse leg measures the `order` half with the installed spec (before the fix: refused at `['order']`; after: parses), and for the four selection keys the pins assert ABSENCE and cite the rule read at objectstack `origin/main` `packages/spec/src/ui/report.zod.ts`: `JOINED_CONTAINER_SELECTION_KEYS = ['dataset', 'rows', 'columns', 'values']`, one `custom` issue per present key at `path: [key]`, message "a `joined` report selects per block — move `KEY` onto `blocks[]`, or delete it; on the container it selects nothing." (`KEY` stands for the key's own name.) No checkout on this box holds a built spec `dist` carrying that refusal, and building one was outside this card. ## H4 — edges - `joined` → non-joined: nothing is restored; the patch is `{ type }` alone and the author re-binds (pinned). - non-joined → non-joined: the binding is kept; the patch is `{ type }` alone (control, pinned). - `joined` with `blocks[]` present: untouched, same array reference, in both directions (pinned). - Undo: there is no undo stack behind the metadata draft. `ResourceEditPage`'s `Undo2` button is `doDiscardDraft` (ADR-0034: discard the whole pending draft), not a per-edit undo, and `handleDraftChange` keeps no history. One undo cannot restore the keys; switching the type back does not either. - `runtimeFilter` and `drilldown` survive the switch (pinned): the joined branch reads both. - Partially bound report: the patch names only the keys present (pinned), so `ReportConfigPanel.onFieldChange` sees no phantom clears, and an unbound report's switch stays `{ type: 'joined' }` (pinned). ## Pins and ablations Head `3f53c15776`, pin file plus the two existing `ReportDefaultInspector` suites: `Test Files 3 passed (3) · Tests 37 passed (37)`. Red on base (`1422a920ed`, component untouched, pin file present): `4 failed | 4 passed` — the defect pin, the own-key/serialised pin, the parse leg, the partially-bound pin. The other 4 are controls and boundaries, green on base by construction. Per-hunk ablations on the committed head, each through objectstack's `scripts/ablation-replace.mjs` (anchor must hit exactly once; the blob change is verified on disk; restore proven `blob == HEAD` `a9cb7e9c` and `git diff HEAD` empty), the prediction written before each run, observed direction = predicted: - M1, drop the `joined` guard (`if (nextType === 'joined')` becomes `if (typeof nextType === 'string')`): predicted CONTROL F red; observed `1 failed | 7 passed` (CONTROL: non-joined → non-joined keeps the binding). - M2, drop the presence filter (`if (draft[key] !== undefined) patch[key] = undefined;` becomes unconditional): predicted D and E red; observed `2 failed | 6 passed` (names only present keys; unbound one-key patch). The first attempt was a NO-OP the tool refused — the replacement text was a substring of the anchor, so its on-disk count could not rise (`1 to 1, a rise of 0`) — and it restored; the leg was re-run with a distinct replacement. - M3, add `blocks` to the cleared list: predicted C and G red; observed `2 failed | 6 passed` (parse leg: the joined report loses its blocks; CONTROL: blocks untouched). - M4, drop `order` from the list: predicted A, B and C red; observed `3 failed | 5 passed`. Assertion spelling: every patch is read with `toStrictEqual`, because `toHaveBeenCalledWith` / `toEqual` treat an `undefined`-valued key as absent and would read the defect and the fix alike. Round 2 (`d4945789f6`, the contract-review wording round: the changeset's reach sentence and the pin file's three-host comment; no source line changed — the diff's only non-changeset lines are the comment lines shown by `git diff -U0`, and `ReportDefaultInspector.tsx` is untouched): pin file re-run `Tests 8 passed (8)`. ## Gates (local, derived by hand from `package.json` and the workflows — objectui has no `dispatch-gates.mjs`) - ① closure build `turbo run build --filter='@object-ui/app-shell^...' --concurrency=2`: `Tasks: 28 successful, 28 total`, 2m12s, under the verify lock. - ② `pnpm --filter @object-ui/app-shell type-check` (`tsc --noEmit && tsc -p tsconfig.test.json`): exit 0; `tsc -p tsconfig.test.json --listFiles` lists the pin file (1 hit among 4673 files). - ② vitest, the three files above: 37 passed; round 2 pin file: 8 passed. - `node scripts/check-changeset-presence.mjs`: ✅ (round 2: 2 source files of 1 released package changed, 1 changeset declared). `node scripts/check-changeset-no-major.mjs`: ✅. `pnpm check:changeset-claims`: ✅. `pnpm check:pending-changeset-literals`: ✅. - `pnpm check:control-bytes`: ✅ OK (round 2: 8942 tracked text files). The role file's control-byte grep over the changed files: no match. - `pnpm check:new-line-citations`: `0 new citation(s)` (both rounds). - `pnpm check:test-path-roots`: ✅ OK. `pnpm check:vi-mock-specifiers`: ✅ OK. - eslint with the package config on the two touched `.tsx` files: exit 0. - NOT MEASURED locally, CI-owned: `pnpm lint` (repo-wide), the 8 test shards, `Build & E2E`, `check:i18n-*` (no `t()` key was added or changed). Round 1 CI on `3f53c157`: 40 success, 3 skipped, 0 failed per the contract review. ## Serial Round 2: `origin/main` at `25c7d584e4` (+5 commits over BASE `1422a920ed`: objectstack-ai#10752, objectstack-ai#10753, objectstack-ai#10708, objectstack-ai#10751, objectstack-ai#10761); none touches the three files; `git merge-tree --write-tree` exits 0 (clean, tree `738f2bf534`). No merge commit was needed. ## Acceptance notes - `chart` is cleared per the triage direction and objectstack-ai/objectstack#20161 (inert on a joined container); the installed spec does not refuse it, so its clearing is pinned by absence only. - Deviation: the worktree-creating `git fetch origin main` ran against the shared checkout (the role-file recipe) rather than inside the worktree as the order asked; `origin/main` did not move (`1422a920ed` before and after), only the shared checkout's `FETCH_HEAD` was touched. Every later fetch used a private ref inside the worktree. - Observation, not filed: `ReportConfigPanel` hosts this inspector too, merges with the same spread and emits `onFieldChange(key, undefined, next)` once per cleared key — the documented "field changed" signal, correct for a live preview. - The draft-over-baseline merge that re-introduces a published binding after the first draft save is objectui#10765 (filed by the seat), not addressed here. --- _Generated by [Claude Code](https://claude.ai/code/session_014mXUNuFomfj24w7s1pZzhN)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #10735
Clause-②: yes — the published manifest's declared vocabulary grows.
Executes the maintainer's ruling A on objectstack#20112 (batch #225 item 1, 「其他同意」): the published
sdui.manifest.jsondeclares the html tier's registered intrinsic set exactly as the registry declares it, with each tag'sinputsand child slot;divstays undeclared. Baseorigin/main5ad3b88; head 6417e8d (round 2: wording only, one commit on top of 5bd6270).What changed
packages/core/src/registry/html-tier-intrinsics.ts(new):HTML_TIER_INTRINSICS— the 37 tags ofhtml-elements.tsx'sTAGS, plusspan,table,labeland the seven sectioning tags ofsemantic.tsx(47). Exported from@object-ui/corebesidePUBLIC_BLOCKS.packages/core/src/registry/Registry.ts:getPublicConfigs()appends the roster's registered tags LAST, each stampedtier: 'html'in the projection (never written onto the registration);PublicComponentConfig.tieradmits'html'.PUBLIC_BLOCKSis untouched.packages/sdui-parser:ManifestComponent.tier?: 'html';manifestFromConfigswrites exactly'html'or omits the key (every curated entry serialises byte-identically);generateBlockListsections the html tier under its own count;RegistryConfigLike.tieradmits'html'.gen-manifest.tsheader updated, behaviour unchanged.packages/components/src/renderers/layout/react-page.tsx: thekind:'react'scope builder skipstier === 'html'entries, so noP/A/Imgwrapper is injected (file-surface amendment, forced by the card's ⛔ on silent widening — see H2).react-page-scope.test.tsxandcontainer-declaration-census.test.tsxupdated;public-contract.test.tsprose amended (two ledger passages that said curating a sectioning tag would newly widen the manifest).content/docs/guide/react-pages.md(the html tier is not injected on the react tier),content/docs/guide/plugin-development.md(the component-leveltier: 'html'marker)..changeset/10735-html-tier-manifest.md:minorfor@object-ui/core,@object-ui/sdui-parser,@object-ui/components.if (!tag || cfg.isContainer || cfg.tier === 'html') continue;— incontent/docs/guide/react-pages.md("skips every container and every html-tier entry"; frontmatter lines 1–4 md5 unchanged, 9c5f94a7…), in bothpublic-contract.test.tspassages, in thecontainer-declaration-censuspin, and inlayout-containers-declare-containment.test.tsx(comment-only, previously untouched).git grep -Fof the old line at head: 2 hits, both inside.changeset/6740-flex-is-container.md— its original paragraph and the dated note that marks it. The 10735 changeset now says "200 ledgered findings" (10 ledger rows whose counts sum to 200)..changeset/6740-flex-is-container.md(pre-existing,@object-ui/components: patch): reconciled in the objectui#10533 dated-note form, scoped "at this change" —⚠️ **Dated note, 2026-09-27 — the skip line quoted above has since gained a third arm — objectui#10735.**— frontmatter byte-identical (md5 ce888ebd… before and after).check-changeset-overwriteREPORTS the modification (report-only, exit 0) and did not refuse it.html-elements.tsx,div.tsx,PUBLIC_BLOCKS, the registration-uniqueness test.H1 — the census (measured from
ComponentRegistry.getKnownTypes()after@object-ui/components+apps/console/src/register-plugins.tsload, in the console vitest project)Instrument for "is an HTML tag": the keys of
JSX.IntrinsicElementsin@types/react(178 names, HTML and SVG; lit control:div,p,svgin,object-gridout). Registry: 485 known keys, 208 bare. Intersection: 64 bare registrations carry an element name.html-elements.tsxTAGS— 37, all registered: h1 h2 h3 h4 h5 h6 p a blockquote pre strong em b i u small mark sub sup del ins abbr ul ol li dl dt dd figure figcaption img hr br time address cite q.span(basic/span.tsx; inputs className, children),table(complex/table.tsx; caption, footer, columns, data, className — no child slot),label(form/label.tsx; text required, className — no child slot), andaside main header nav footer section article(layout/semantic.tsx; className, children;isContainer).div— deprecated on the JSON surface in favour ofbox(objectui#3965, PR objectui#6878); kept out by the ruling.code,summary,object— bare namespace fallbacks offield:code/field:summary/field:object(inputs[], no child slot);view—plugin-view's bare key (SVG name coincidence).codeas "registered elsewhere"; measured, the onlycoderegistration is the field widget (a code editor /preblock readingvalue). Declaring it would teach a tag whose children the runtime drops, so it is NOT declared — see Acceptance notes and the report's open question.kbd— auicomponent readingkeys/label, not named by the ruling; not declared (additive later on a named need; retracting is a narrowing).form input textarea select switch— JSON-surface form components; the tier excludes form controls by design (the exclusion list inhtml-elements.tsx).progress,dialog—uicomponents under tag names, not passthroughs, not named.button text image html— already curated inPUBLIC_BLOCKS; they reach the manifest from there, unstamped.The (c) list is not prose:
apps/console/src/__tests__/html-tier-manifest.test.tsholds every HTML-named bare registration to one ofPUBLIC_BLOCKS,HTML_TIER_INTRINSICSor its exclusion ledgerEXCLUDED_HTML_NAMED(13 entries, each with a reason, each pinned live), so a registration that newly squats on a tag name lands there by absence.H2 — the fork, the consumer map, and the marker decision
Premise measured true:
getPublicConfigs()throughmanifestFromConfigsis the sole source of the published manifest — objectui'sapps/console/dev/manifest-dump.tsx/scripts/dump-public-manifest.mjs, and objectstack'sscripts/gen-sdui-manifest-node.mjs(which imports the 16 registry modules from objectui's built tree at.objectui-shaand calls exactlyComponentRegistry.getPublicConfigs()then its lockstep copy'smanifestFromConfigs). The manifest is ONE flatcomponentsmap with no tier marker;compile()derivesallowedTags = Object.keys(manifest.components). So listing the html tags inPUBLIC_BLOCKSwould have widened the curated JSON-surface vocabulary silently — the fork the card names.Marker chosen: a per-entry
tier: 'html'onManifestComponent, carried from a projection-only stamp ingetPublicConfigs(). Why not a second registry read or a second manifest section: the objectstack generator callsgetPublicConfigs()and nothing else, andcompile()reads onlycomponents— either alternative needs an objectstack-side change before the gate accepts a single html tag, and the second-section shape would leave the gate refusing the tags until its lockstep copy was ported. With the per-entry marker the objectstack gate accepts the tags with NO reader change: it ignores the extra key.Consumers, and what each does with the marker:
packages/lintvalidateJsxPages→compile()validateTreereadsinputs/namespacetierscripts/check-sdui-manifest.mjscomponents[k].type === k, count, shascripts/gen-sdui-manifest-node.mjsgetPublicConfigs()+ lockstepmanifestFromConfigspackages/sdui-parser/src/{index,types,codegen}.tsneeds this port for the marker to reach the tracked file — until then the tags are declared there WITHOUTtier, which the gate treats identicallyreact-page.tsxbuildComponentScopegetPublicConfigs()tier === 'html'(elseP/A/Imgwrappers would be injected)generateBlockListgenerateDtsJSX.IntrinsicElements(the html-tier authoring surface)page.tsxhtml-tier compilegetKnownTypes(), not the manifestgetPublicConfigs()container-declaration-censusreads the curated contract as the unstamped entries;public-contractcoverage lists filter byPUBLIC_BLOCKSand are unchangedReverse verification that consumers read the REBUILT
.d.ts: a scratch console file assigningtier: 'json'to aManifestComponentand to aPublicComponentConfig—tsc --noEmitexit 2,TS2322: Type '"json"' is not assignable to type '"html"'and… to type '"html" | "public" | "internal" | undefined'; the'html'assignments compiled. Probe removed;git statusclean.H3 — the regenerated artefacts
Generated with the repo's own generator (
buildArtifactsinpackages/sdui-parser/scripts/gen-manifest.ts) over the console registration graph with the 16 plugin modules eager-imported inmanifest-dump.tsxorder, once on the base tree and once on head:sdui.manifest.jsoncomponentsdiv/code/kbdpresenttier: 'html'/ withoutsdui-intrinsics.d.tsintrinsic entriessdui-blocks.md# SDUI public blocks (59)unchanged +## html tier intrinsic elements (47)Sample head entries:
a→ inputs className, href, target, rel, title, children (slot);br→ className only (void, no slot);label→ text (required), className (no slot). The base run's manifest differs from objectstack's trackedsdui.manifest.jsononly because the pin f8a9d0f predates this base (cmpdiffers at byte 1183) — the regeneration at the next pin bump is thedomain:clilane's step.Pins — red on base, green on head
Two mutation legs (implementation files checked out at base, tests at head; trap-guarded restores to
HEAD, proven bygit diff HEADempty and blob-hash equality — "RESTORE PROVEN" printed twice):Registry.ts, sdui-parserindex.ts/types.ts/codegen.ts,react-page.tsxat base:Test Files 5 failed (5),Tests 17 failed | 61 passed (78)— every behavioural pin red (the roster-only pins stay green by design).react-page.tsxalone at base:Tests 1 failed | 8 passed (9)—a PascalCased html-tier tag is an unknown identifier on this tierred (anImgwrapper was injected).Test Files 9 passed (9),Tests 114 passed (114)over the five pin files plusregistration-uniqueness.test.tsx(unchanged, green),public-tier.test.ts,tier.test.ts,public-contract.test.ts.Round 2 (head 6417e8d): the three re-quoted test files plus
react-page-scopeandhtml-tier-manifestre-run —public-contract,container-declaration-census,layout-containers-declare-containment,react-page-scope,html-tier-manifest—Test Files 5 passed (5),Tests 89 passed (89).Broader affected suites at head 5bd6270: console
html-tier-manifest,public-contract,public-block-binding-reach,record-block-record-reach; componentsreact-page-scope,container-declaration-census,container-declaration-ratchet,layout-containers-declare-containment,registration-uniqueness; plugin-treeregistration.publicTier-10064—Test Files 10 passed,Tests 137 passed(plus the scratch generator run).Gates (exit captured before any pipe; verdict lines quoted)
turbo run build --filter="@object-ui/console^..." --concurrency=2under the verify lock —Tasks: 34 successful, 34 total,VERDICT command-exit 0.pnpm run type-check, script echoed):@object-ui/coreexit 0,@object-ui/sdui-parserexit 0,@object-ui/componentsexit 0,@object-ui/consoleexit 0.--listFilescounts the new / edited test files inside their programs (1, 1, 2, 1).turbo run lintover the four packages —Tasks: 5 successful, 5 total, 0 errors; eslint--format jsonover the 14 touched files: 0 errors, 22 warnings, all pre-existingno-explicit-anylines not introduced here.check:control-bytes—✅ check-control-bytes: OK (scanned 8928 tracked text file(s)).check:new-line-citations—VERDICT new-cross-file-line-citations: 0 new citation(s).check-changeset-presence— round 2:✅ 15 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s);changeset:check—✅ No changeset declares a major bumpand✅ All workspace packages are in the changeset fixed group.check:control-bytes✅ OK (scanned 8928 tracked text file(s));check:new-line-citationsVERDICT new-cross-file-line-citations: 0 new citation(s);check-changeset-claimsexit 0 (2 bodies judged, no negation; the re-read list is unchanged);check-changeset-overwriteexit 0 report-only —1 changeset(s) added, 1 modified, 0 deleted, the modified one being.changeset/6740-flex-is-container.md(the dated note above);check-doc-links—Links are valid across 17 scan roots.check:unreferenced-sourcesOK;check:pending-changeset-literals✅ No test source names a pending changeset;check:test-path-rootsOK;check:doc-types✅ Every documented component type is registered;check:doc-fencesOK;check:registry-bare-names✓ no new bare-name collision;check:esm-specifiers(specifier leg) OK;check:component-surface-parityreport-only exit 0.check:changeset-claims(report-only): 13 pending changesets name a file this change touches; each re-read — the "forwards exactly seven keys per input" claims stay true (this adds a component-level key, no input key), and "none of the 8 is in the curated public contract" stays true (the seven now enter the manifest as html-tier entries, not curated).check:readme-exports(exit 1 = 8 self-imports unjudgeable becausepackages/cliandpackages/plugin-aiare outside the built closure; 538 judged, 0 fabricated),check:doc-snippets(exit 2 PRECONDITION NOT MET, same unbuilt packages; the two edited guides gained prose only),check:eager-closureandcheck:sdui-registration-pins(need a console build), repo-widepnpm lint(per-package eslint via turbo covered the four touched packages exactly as CI runs them;eslint.config.jsdeclares no type-awareproject, so this diff moves no untouched file's verdict).Serial
Claim read: no open PR touches
gen-manifest.ts,public-blocks.tsorhtml-elements.tsx. Round 1:origin/mainfetched into the private refrefs/issue-10735/main(01700dc, four commits past base),git merge-tree --write-tree HEAD refs/issue-10735/mainexit 0 (tree 368f01d8). Round 2, before the push of 6417e8d: main re-fetched at 704e05b (nine commits past base: #10741, #10574, #10736, #10745, #10749, #10734, #10733, #10744, #10752),merge-tree --write-treeexit 0 (tree 0d9bcb93); the set of files main moved since base is disjoint from this branch's files (comm -12empty), so no merge commit was needed.Acceptance notes
divon the html tier is now split across two faces. The published contract refuses it (forbidden-tag), while the renderer's declaration still readsdeprecated: { surfaces: ['json'] }(objectui#4000) and the console's html-tier compile (page.tsx,getKnownTypes()) still accepts and renders it. The ruling's item 3 parks the renderer-side retirement in these notes: the reconciliation is a separate card (declare thehtmlsurface ondiv's deprecation withboxas the replacement, and decide whether the console compile should read the same roster).boxnamed as the replacement" has no mechanism today. The gate's refusal is the parser's genericforbidden-tagtext (the tag is not an allowed component); nothing in the manifest or the parser namesboxfor an absentdiv.Registry.deprecationFor('div', 'json').replacementnames it, but the gate never reaches the registry. Not implemented here (a new contract shape); raised in the report's open questions.code: thehtml-elements.tsxheader and ADR-0081 §2 both saycodeis "registered elsewhere"; the only registration isfield:code's bare fallback. On an html page todaycoderesolves to that widget and drops its text — a reproducible runtime defect independent of this card; reported as an out-of-scope finding.labelandtableare declared as registered, so the HTML-shapedlabelwith text children drawsmissing-required-prop(text) andnot-a-container, and atableauthored with element children drawsnot-a-container; the registered spellings (text="…",columns/data) are clean. Pinned.domain:clilane: regeneratesdui.manifest.jsonat the pin; portpackages/sdui-parser/src/{index,types,codegen}.tsto the lockstep copy so the marker reaches the tracked file; the ledger's 200a/divrows go stale (thearows) or stay refused (thedivrows, to be rewritten toboxas the ruling says).semantic.tsx's comment ("none of these seven tags is in the curatedPUBLIC_BLOCKScontract, so there is no injected identifier") remains literally true and its conclusion holds on two grounds now; not edited (no registration change).Pending changesets re-read (round 2, head 6417e8d)
The
changeset-claim-re-readbot lists 12 pending changesets naming files this diff touches (13 in round 1,6740now being one this change modifies). Each re-read paragraph by paragraph against head:5905-componentinput-inputtype-tombstone.md— still true:packages/sdui-parser/src/index.tsstill forwards exactly seven keys per input (name,type,of,required,enum,binding,description); this change'stierwrite sits at component level, besidetype/namespace/isContainer, and adds no per-input key.5905-componentinput-retire-constraint-keys.md— still true, same seven-keys-per-input claim, same reason.7493-componentinput-retire-label-defaultvalue-advanced.md— still true, same seven-keys-per-input claim;min/max/step/placeholder/label/defaultValue/advancedstill reach no manifest.6067-component-meta-derive-from-canonical.md— still true: it describes the pre-6067 shape ofRegistry.tsin the past tense; at headComponentMetais stillCanonicalComponentMeta & RegistryComponentMetaExtras, andRegistryComponentMetaExtras.tieris unchanged ('public' | 'internal'). The'html'value lives only on thePublicComponentConfigprojection.componentinput-reexport-4972.md— still true:Registry.tsstill re-exportsComponentInputfrom@object-ui/types(export type { ComponentInput } from '@object-ui/types').default-children-retired-5051.md— still true: none of the threeComponentMetadeclarations offersdefaultChildren; this change adds noComponentMetakey.6764-container-declaration-census.md— still true as written:react-page.tsxdrops containers from the react scope and readsgetPublicConfigs(), and none of the 8 is in the CURATED public contract. The seven sectioning tags now ride that read as html-tier entries, stampedtier: 'html'and skipped, which leaves the claim's operative words ("curated public contract") intact; not corrected.9280-record-highlights-entry-icon-retired.md— still true:gen-manifest.tsstill serialises registryinputsintosdui.manifest.jsonandsdui-intrinsics.d.ts(the header now also names the html tier's stamped entries; behaviour unchanged).components-react-page-published-dts-text-5666.md— still true:react-pages.md's injected-scope note and §Styling still say plain HTML with inlinestyle, not Tailwind; the guide's edited sentence quotes the skip line and adds "and every html-tier entry", nothing about styling.page-source-tailwind-framing-5461.md— still true:react-page.tsxkeeps both styling comments (the header note and thebuildComponentScopenote); this change adds a comment inside the loop and removes none.page-source-tailwind-prose-retraction-5469.md— still true:react-pages.md§Styling wording is unchanged.palette-canonical-discussion-5495.md— still true:public-blocks.tsstill recordsrecord:chatteras the same block "under a Salesforce-familiar name"; this change adds a header paragraph and touches no roster entry.6740-flex-is-container.md— CORRECTED (prose-only): its paragraph quotes the react-page skip line asif (!tag || cfg.isContainer) continue;, which this PR changed; a dated note in the objectui#10533 form (⚠️ **Dated note, 2026-09-27 — the skip line quoted above has since gained a third arm — objectui#10735.**) records the current line and keeps the entry as the reading of that change. Frontmatter byte-identical, md5 ce888ebde4729564f67bca69a7928743 before and after.check-changeset-overwritereports the modification (report-only, exit 0).Generated by Claude Code