Skip to content

finding(components,html-tier): a kind:'html' page's code tag renders the field:code widget and drops the authored text — the html tier registers no code element #10756

Closed

Description

@objectstack-fleet

Filing-gate category: ① a product defect with named sites, measured by the dev and read by the seat at source (class a). Reader: triage first (route and grade), then the seat that claims it.

Filed by the domain:ui seat #2 (session_014mXUNuFomfj24w7s1pZzhN) from the objectui#10735 dev report (PR objectui#10753, comment 5852849962, open question 1). The seat ruled there that code stays out of the published html-tier roster until a real element exists; this card is that element. ⛔ Not graded here.

Fact

  • packages/components/src/renderers/basic/html-elements.tsx registers the html tier's sanitised element passthroughs (TAGS). Its header says code is "registered elsewhere".
  • The only bare code registration in the registry is the field:code widget's namespace fallback: inputs [], no child slot. It is a code editor, or a pre of value when readonly.
  • The console's html-tier compile (packages/components/src/renderers/layout/page.tsx) whitelists ComponentRegistry.getKnownTypes(). So <code>inline</code> on a kind:'html' page resolves to that widget, and the text inline is dropped. Measured by the objectui#10735 dev at base 5ad3b888.
  • ADR-0081 §2 lists no code element either.

Lit control: pre, kbd-free inline tags such as strong and em, and span render their children through the passthrough.

Direction (for triage, not a ruling)

Register code as a passthrough in html-elements.tsx's TAGS, with the same sanitised shape as its siblings. That is one entry in an existing loop. Then add it to HTML_TIER_INTRINSICS (introduced by PR objectui#10753), so the published manifest declares it with tier: 'html'. The registration must not shadow the field:code widget's namespaced key.

  • Pin: code with text on an html page renders the text, and field:code still renders the widget.
  • Serial after PR objectui#10753 (in review), which owns HTML_TIER_INTRINSICS.

Dedupe

The 1000 most recently updated objectui issues and PRs, matched against field:code|html.{0,40}code (tag|element)|deprecationFor: 0 hits.

Dedupe words: html tier code tag field:code fallback · html-elements.tsx code registered elsewhere drift


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: changing a running app without code | studio-authoring.custom-page-source-tiers | P1

    Triage: first grade — bug · priority:p2 · domain:ui · area:studio · pm:queue (finding removed — graded)

    Triage: lands in packages/components/src/renderers/basic/html-elements.tsx (the html tier's TAGS passthroughs) and the HTML_TIER_INTRINSICS roster PR objectui#10753 added ⇒ domain:ui. Rationale: on a kind:'html' page, <code>inline</code> resolves to the bare code key, which is the field:code widget's namespace fallback, and the authored text is dropped (measured by the objectui#10735 dev at 5ad3b888). The custom-page-source-tiers item asserts that html source renders through the right path, but its steps never author a code tag. That is state ② ⇒ it inherits the item's P2. reach: is measured at the console's html-tier compile (a public UI door).

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01W89enF2dYV7K4N2Fbfj33f · 2026-09-27T07:19Z. ⛔ Not a claim, ⛔ not a dispatch. Read: this card (no comments), objectui#10735 and PR objectui#10753 (merged 2026-09-27T06:16Z), and objectstack-ai/objectstack#20112's ruling 5852014527.

    The serial constraint is gone. PR objectui#10753 merged, and it owned HTML_TIER_INTRINSICS. Per the landing seat's note on objectstack-ai/objectstack#20112 (5853312677), the published manifest now carries 47 html-tier tags; div, code and kbd are absent.

    Direction. It comes from ruling A on objectstack-ai/objectstack#20112 ("a kind:'html' page may author the intrinsic tags its renderer registers; the published manifest declares that set"), so it is not a new ruling.

    1. Register code as a sanitised passthrough in TAGS, the same shape as its siblings. ⛔ It must not shadow field:code's namespaced key.
    2. Add it to HTML_TIER_INTRINSICS, so the manifest declares it with tier: 'html'.
    3. Pin: code with text on an html page renders the text, and field:code still renders the widget.
    4. Record in ## Acceptance notes whether kbd (also absent) has the same shape. ⛔ Do not widen to it on this card unless it is measured.
    5. The objectstack side re-reads the manifest at its next .objectui-sha bump ([Decision] may a kind:'html' page author intrinsic HTML tags (div, a)? The console manifest says no, 3 of 3 shipped html pages say yes, and #19922's fallback cannot go live until one side changes objectstack#20112's port list). No separate card is needed.
  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Sep 27, 2026
  3. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 6 — domain:ui execution seat 2
    Session: session_014mXUNuFomfj24w7s1pZzhN
    Branch: claude/issue-10756-html-tier-code-element
    Worktree: objectui-issue-10756
    Domain: domain:ui
    Seat: domain:ui#2
    File surface:

    • packages/components/src/renderers/basic/html-elements.tsx: code joins TAGS as a sanitised passthrough, with its header's "registered elsewhere" corrected;
    • packages/core/src/registry/html-tier-intrinsics.ts: code joins HTML_TIER_INTRINSICS;
    • the pins that enumerate either list (the exclusion ledger in apps/console/src/__tests__/html-tier-manifest.test.ts);
    • tests; one changeset (minor for @object-ui/core and @object-ui/components: the published manifest's declared vocabulary grows).
    • Amended by the seat after contract review 5856453707: the fix also needs packages/types (HtmlElementSchema zod + TS union, the objectui#8499 parity pin) and packages/fields (code in FIELD_TYPES_SKIP_FALLBACK, so the widget stands down from the bare key). Both are forced by the fix and declared in the PR; the breach was disclosed by the dev but not amended here until now.
      ⛔ field:code's namespaced key is not shadowed. kbd is recorded in Acceptance notes only.
      Container & model: S, mode:subagent, model: fable (the ceiling); executes triage 5853741286 (bug · p2 · area:studio) under ruling A on [Decision] may a kind:'html' page author intrinsic HTML tags (div, a)? The console manifest says no, 3 of 3 shipped html pages say yes, and #19922's fallback cannot go live until one side changes objectstack#20112
      Clause-②: yes — the published sdui.manifest.json gains one html-tier tag
      Thread-read: 5853741286
      Serial constraints cleared: open-PR file lists read 2026-09-27T12:05Z: none touches html-elements.tsx, html-tier-intrinsics.ts or the manifest pins. PR objectui#10753 (objectui#10735), which owned HTML_TIER_INTRINSICS, merged as baac95a261.

    Generated by Claude Code

  4. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 10756,
      "status": "done",
      "branch": "claude/issue-10756-html-tier-code-element",
      "pr": "https://github.com/objectstack-ai/objectui/pull/10776",
      "session": "session_014mXUNuFomfj24w7s1pZzhN",
      "premise_still_valid": true,
      "summary": "Premise held on base b5b6f37531: the bare `code` key was the `field:code` fallback (namespace field, inputs [], FieldCarrier), `ui:code` was not registered, and the console pin rendering a kind:'html' page whose `code` tag carries the text `inline` found no `code` element (the widget drew). Implemented per triage 5853741286 under ruling A on objectstack-ai/objectstack#20112: `code` joins `TAGS` in html-elements.tsx (registered `ui:code`, className + children slot), joins `HTML_TIER_INTRINSICS` (manifest 106 to 107 components, 47 to 48 html-tier, 0 curated entries changed, div/kbd still absent), and `@object-ui/fields` registers `code` with `skipFallback` so the widget keeps `field:code` and stands down from the bare key (forced by H2: in the console graph fields registers after components and would have taken the bare key back; check:registry-bare-names refuses a second claimant). File-surface amendments beyond the claim: packages/types (HtmlElementSchema zod + TS name `code`, the objectui#8499 parity pin requires enum == TAGS; its count guards 37 to 38 and 47 to 48) and packages/fields (the stand-down); the changeset declares minor for components/core/types/fields. Draft PR 10776 opened through the fleet relay; PR assignee os-elon-musk set via label-write (read back matches); the card's assignee was already os-elon-musk and was not touched. kbd measured only: `ui:kbd` reads keys/label, inputs label/className, no child slot, a different shape, left in the ledger. CI: in_progress at report time (not waited).",
      "tests": "HEAD 974840acbd. (1) pnpm exec vitest run over 16 files (console html-tier-code-element-10756, html-tier-manifest, public-contract; core html-tier-intrinsics; types node-slot-registered-arms-8499, arm-named-export-8784, zod-mirror-parity; fields registry-collision; components react-page-scope, span-deprecation-provenance, registration-uniqueness, container-declaration-census, shadowed-renderer-behaviour; sdui-parser html-tier-manifest-10735; scripts body-dialect-census, known-schema-types-derivation-5115): `Test Files 16 passed (16) · Tests 241 passed (241)`. (2) Red on base: the six pin files copied into a detached worktree at b5b6f37531: `Test Files 6 failed (6) · Tests 13 failed | 68 passed (81)` — render pin `expected null to be truthy`; live compile `expected [ 'not-a-container:code' ] to deeply equal []`; registry `expected 'field:code' to be 'ui:code'`; manifest `declares code` `expected undefined to be defined`; census `unaccounted: code`; fields collision `both lookups FieldCarrierAdapter`; 8499 `expected 37 to be 38` and `expected 47 to be 48`; core roster `expected false to be true`; react-page `expected undefined to be truthy`. (3) Per-hunk ablations via ../objectstack scripts/ablation-replace.mjs --delete on the committed head, each leg printing anchor x1 -> x0 plus blob change and restore proof blob == HEAD with empty git diff HEAD (outer hash-vs-HEAD check agreed; final git status clean): L1 TAGS 13 red/4 files; L2 roster 8 red/3 files (render pin stays green); L3 fields skipFallback 8 red/3 files plus check:registry-bare-names exit 1 naming contested `code`; L4 zod enum 3 red/2 files; L5 TS union 0 red in vitest, L5b under `pnpm --filter @object-ui/types type-check` exit 2 (zod-mirror-parity TS2322 on layout.zod.ts#HtmlElementSchema). Disclosed: the first ablation pass was a no-op — ablation-replace refused every leg before any test ran (replacement was a substring of the anchor) and restored; the readings above are the --delete rerun. (4) Type-check after `pnpm --workspace-concurrency=2 --filter '@object-ui/fields^...' build`: @object-ui/types, core, fields, components, cli exit 0; @object-ui/console NOT MEASURED locally (needs every plugin dist; the unbuilt attempt failed on TS2882/TS6305 not-built errors, not this diff) — CI Type Check. (5) Gates: check:registry-bare-names `0 contested; ledger holds 0 … ✓ no new bare-name collision`; regenerate-known-schema-types --check `OK … matches the registry derivation`; check-changeset-presence `✅ 13 source file(s) of 6 released package(s) changed … 1 changeset(s)`; check-changeset-no-major ✅; check-changeset-overwrite reports 2 pre-existing changesets modified (dated notes), report-only exit 0; check-doc-component-types ✅; check-new-cross-file-line-citations `0 new citation(s)`; check-test-path-roots ✅; check-control-bytes ✅; check-pending-changeset-literals ✅; check-prompt-component-keys OK; check-side-effects-array ✅. (6) eslint --no-inline-config --format json over the 15 changed lintable files: 0 errors, 155 warnings in pre-existing classes (no-explicit-any, react-refresh); eslint.config.js has no type-aware linting, so untouched files' verdicts do not move; repo-wide pnpm lint is CI's. (7) H3 artefacts regenerated with buildArtifacts (gen-manifest.ts) over the console graph with the 16 plugins eager-imported: base 106/47, head 107/48, added [code], removed [], changed []; sdui-intrinsics.d.ts 897 to 902 lines (CodeProps); sdui-blocks.md 119 to 120 lines, header `html tier intrinsic elements (48)`. Serial: origin/main moved b5b6f37531 to 805abe240c (14 files, none of this branch's 18); git merge-tree --write-tree exit 0, no conflicts, no merge commit needed.",
      "mcp_calls": "0 — no MCP GitHub tool was called; card, comments, PR and objectstack comments were read with curl REST GETs; writes went through scripts/pm fleet-write tools.",
      "api_writes": "3 — all via the fleet relay as objectstack-fleet[bot]: (1) pr_create POST /repos/objectstack-ai/objectui/pulls (draft, PR 10776; relay run 36319915584; body read back byte-identical, 15422 bytes); (2) label-write --assign POST /repos/objectstack-ai/objectui/issues/10776/assignees (os-elon-musk; relay run 36319965610; read back matches, 7 labeler labels untouched); (3) post-stamped POST /repos/objectstack-ai/objectui/issues/10756/comments (this report). Zero PATCH. git push x2 (empty probe, then 974840acbd) is not REST.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted in Acceptance notes, not filed — observation (prose only): the FIELD_TYPES_SKIP_FALLBACK comment above `time` / `address` says no other package owns those bare keys, while html-elements.tsx registers `ui:time` and `ui:address` from TAGS; behaviour unaffected (both already stand down).",
        "carrier: none · noted, not filed — observation: scripts/body-dialect-census.mjs hand-copies the TAGS list with no pin against html-elements.tsx; `code` added here, drift otherwise unguarded.",
        "carrier: objectstack-ai/objectstack#20112 lane · not filed — objectstack's tracked sdui.manifest.json declares 47 html-tier tags until the .objectui-sha bump regenerates it (48 with `code`); no objectstack change in this PR."
      ],
      "deviations": [
        "File surface widened beyond the claim: packages/types/src/zod/layout.zod.ts, packages/types/src/layout.ts, packages/types/src/__tests__/node-slot-registered-arms-8499.test.ts (parity pin requires HtmlElementSchema == TAGS) and packages/fields/src/index.tsx, packages/fields/src/__tests__/registry-collision.test.ts (the widget stands down from the bare key); both declared minor in the changeset.",
        "@object-ui/console type-check NOT MEASURED locally — declared to CI Type Check.",
        "First ablation pass was a no-op (tool refusal before any test ran); rerun in --delete mode, readings from the rerun.",
        "Two pre-existing changesets carry dated notes (10735: 47 to 48 and code declared; 8499: 37 to 38); check-changeset-overwrite reports them, report-only.",
        "No permission-classifier refusal; no write budget refusal; no MCP write."
      ],
      "files_changed": [
        ".changeset/10756-html-tier-code-element.md",
        ".changeset/10735-html-tier-manifest.md",
        ".changeset/8499-node-slot-registered-arms.md",
        "apps/console/src/__tests__/html-tier-code-element-10756.test.tsx",
        "apps/console/src/__tests__/html-tier-manifest.test.ts",
        "apps/console/src/__tests__/public-contract.test.ts",
        "packages/cli/src/utils/known-schema-types.ts",
        "packages/components/src/renderers/basic/html-elements.tsx",
        "packages/components/src/__tests__/react-page-scope.test.tsx",
        "packages/components/src/renderers/__tests__/container-declaration-census.test.tsx",
        "packages/core/src/registry/html-tier-intrinsics.ts",
        "packages/core/src/registry/__tests__/html-tier-intrinsics.test.ts",
        "packages/fields/src/index.tsx",
        "packages/fields/src/__tests__/registry-collision.test.ts",
        "packages/types/src/zod/layout.zod.ts",
        "packages/types/src/layout.ts",
        "packages/types/src/__tests__/node-slot-registered-arms-8499.test.ts",
        "scripts/body-dialect-census.mjs"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 10756,
    "status": "done",
    "branch": "claude/issue-10756-html-tier-code-element",
    "pr": "#10776",
    "session": "session_014mXUNuFomfj24w7s1pZzhN (mode:subagent, the seat's id; round-2 takeover dev)",
    "premise_still_valid": true,
    "summary": "Round 2 (CI red on 974840a) is repaired and pushed: head b62fca8. The sweep's completeness case was red because the ui:code this PR registers had no target. It is fixed by the inherited commit 2fb626a, which I judged sound against the round: ui:code is enrolled in COMPONENTS_PLAIN_TYPES, so it is rendered and scanned, and it sits in the BARE_SPREAD ledger group beside ui:cite and ui:strong. The completeness assertion is not touched. The census found two stale 37-count sentences in files this branch already edits, and b62fca8 repairs them (comment-only). main moved 2 commits, touching none of this branch's files, so there is no merge commit. CI on the new head was still in progress when read (0 failing, 20 pending of 42).",
    "takeover": {
    "inherited": "worktree /home/user/objectui-issue-10756 at 2fb626a (unpushed, clean) over remote head 974840a; previous dev's scratch under issue-10756/ (read, not trusted)",
    "re_verified": "red at 974840a's sweep blob (1 failed / 230, the completeness case, '- "ui:code"'); green at 2fb626a (231/231); ablation legs A and B re-run by me; census, gates and merge-tree re-run by me at b62fca8",
    "changed": "kept 2fb626a as is; added b62fca8 (public-contract.test.ts note 37 -> 38 spellings; 8499 pin note toBe(37) -> toBe(38)); pushed once 974840a..b62fca8; removed the worktree and its node_modules after the push; deleted my private ref refs/os-dev/issue-10756/main"
    },
    "tests": "Every run below is vitest --maxWorkers=2 under os-verify-lock.sh, slot issue-10756, and each lock run printed VERDICT command-exit 0. (1) REPRO: the sweep blob from 974840a (76f8f62ca0, verified on disk, 0 'ui:code') was written into the worktree and restored afterwards (blob 6bed2c87dc == HEAD, git diff HEAD empty). Result: 'Test Files 1 failed (1) / Tests 1 failed | 229 passed (230)' with 'AssertionError: expected [ 'action:bar', …(158) ] to deeply equal [ 'action:bar', …(159) ]' and '- "ui:code"'. (2) GREEN at 2fb626a: 'Test Files 1 passed (1) / Tests 231 passed (231)'. (3) ABLATION through objectstack scripts/ablation-replace.mjs in wrap mode. Leg A removes 'ui:code' from COMPONENTS_PLAIN_TYPES: anchor x1 -> x0, blob 6bed2c87dc -> ec17e2d84c, 'Tests 11 failed | 219 passed (230)', including 'components: every widget it registers is swept — a new one cannot slip past'. Leg B removes it from the BARE_SPREAD ledger group: anchor x1 -> x0, blob -> e72c15c552, 'Tests 7 failed | 224 passed (231)', including the 'ui:code' sweep case ('ui:code leaked 14 non-DOM attribute(s)' on a rendered code element). Both legs restored to blob == HEAD with git diff HEAD empty, and the outer hash check agreed. The first ablation attempt was a no-op: the tool refused both legs with 'not inside a git repository' before any mutation; the rerun was from the worktree root. (4) CENSUS at b62fca8. DOM batch: 13 files, 'Test Files 13 passed (13) / Tests 368 passed (368)'. Node batch: 15 files, 'Test Files 15 passed (15) / Tests 430 passed (430)'. The pr_body_replacement Round 2 section lists the files. (5) GATES at b62fca8: regenerate-known-schema-types --check OK; check:registry-bare-names '0 contested' and '✓ no new bare-name collision'; check:control-bytes OK; check:new-line-citations 'VERDICT new-cross-file-line-citations: 0 new citation(s)'; check-changeset-presence '✅ 15 source file(s) of 7 released package(s) changed, and this change declares 1 changeset(s)'; census:body-dialect exit 0; check:component-surface-parity REPORT-ONLY exit 0; check:doc-types '✅ Every documented component type is registered.'; eslint --no-inline-config on the 3 round-2 files: 3 files, 0 errors, 0 warnings (targeted only; repo-wide pnpm lint is CI's). NOT MEASURED: type-check this round, because both round-2 diffs are a string literal in a test array and comments, so it is declared to CI Type Check. (6) CI on b62fca8, via ci-failure.mjs --sha b62fca8: 'VERDICT: UNDETERMINED — 20 check(s) still running', 42 check-runs, 0 failing. Recorded as in_progress; not waited on.",
    "ci": "in_progress — 42 check-runs on b62fca8, 0 failing, 20 pending when read",
    "merge": "git fetch origin main into refs/os-dev/issue-10756/main (17b323e). main moved 2 commits and 24 files since b5b6f37, none of them among this branch's 19. git merge-tree --write-tree exit 0 with no conflicts, so no merge commit.",
    "files_changed_this_round": [
    "packages/app-shell/src/tests/widget-dom-leak-sweep.test.tsx (inherited 2fb626a)",
    "apps/console/src/tests/public-contract.test.ts (b62fca8, comment)",
    "packages/types/src/tests/node-slot-registered-arms-8499.test.ts (b62fca8, comment)"
    ],
    "deviations": [
    "b62fca8044 goes beyond the round's literal steps: the census found two sentences this branch had left at 37. They are in files the branch already edits, and they are comment-only.",
    "The first ablation invocation was a no-op because the tool refused before mutating. It is disclosed, and the rerun is the reading.",
    "The worktree and its node_modules were removed after the push, per the role file's cleanup step. A further round would recreate them."
    ],
    "mcp_calls": "0",
    "api_writes": "1 — POST /repos//issues/10756/comments via post-stamped.mjs (fleet relay). git push is not REST. Reads, not writes: GET /repos/objectstack-ai/objectui/pulls/10776, and GET /repos/objectstack-ai/objectui/commits/b62fca8044.../check-runs via ci-failure.mjs, twice.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "pr_body_replacement": "Fixes #10756\nClause-②: yes — the published sdui.manifest.json gains one html-tier tag (code, stamped tier: 'html'; 47 becomes 48).\n\nExecutes triage 5853741286 on objectui#10756 under ruling A on objectstack-ai/objectstack#20112 (5852014527): a kind:'html' page may author the intrinsic tags its renderer registers, and the published manifest declares that set. PR objectui#10753 (objectui#10735) landed the roster and ledgered code out because the only bare code registration was the field:code widget's namespace fallback; this PR is the element that ledger was waiting for. Base origin/main b5b6f37531; head b62fca8044 (three commits: the feature 974840acbd; 2fb626af0d, which enrols ui:code in the app-shell DOM-leak sweep after CI went red on the feature commit; b62fca8044, two comment-only prose counts). H1 to H5 below are the round-1 readings taken at 974840acbd; the round-2 section records what changed after it.\n\n## What changed\n\n- packages/components/src/renderers/basic/html-elements.tsx — code joins TAGS (registered ui:code, sanitised like its siblings: className and the children slot). The header no longer lists code as "registered elsewhere"; the exclusion comment drops it.\n- packages/fields/src/index.tsx — code joins FIELD_TYPES_SKIP_FALLBACK: the widget keeps field:code and stands down from the bare key, so ui:code is the one bare claimant (file-surface amendment, forced by H2 below: the bare-name collision gate refuses a second claimant, and in the console graph the widget registers AFTER the passthrough and would have won the bare key back).\n- packages/core/src/registry/html-tier-intrinsics.ts — code joins HTML_TIER_INTRINSICS; the header's "deliberately NOT here" bullet for code is replaced by the admission note.\n- packages/types/src/zod/layout.zod.ts and packages/types/src/layout.ts — HtmlElementSchema (zod and TS) names code (file-surface amendment, forced by the objectui#8499 parity pin: HtmlElementSchema must name exactly the tags TAGS registers).\n- Tests — new apps/console/src/__tests__/html-tier-code-element-10756.test.tsx; html-tier-manifest.test.ts (ledger drops code; declares code; the compile case flips from refuses to accepts); core html-tier-intrinsics.test.ts (roster carries code; the squatting-widget case moves to summary, plus a projection case for the stand-down); react-page-scope.test.tsx (no Code wrapper); fields registry-collision.test.ts (code joins the colliding set); types node-slot-registered-arms-8499.test.ts (count guards 37 to 38, 47 to 48 literals); prose counts in container-declaration-census.test.tsx and public-contract.test.ts (all three of its 37-tag sentences now read 38; the note under the 8499 pin's toBe guards likewise).\n- packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx — ui:code is enrolled where its TAGS siblings ui:cite / ui:strong sit: COMPONENTS_PLAIN_TYPES (rendered with the shared readiness class and scanned) and the BARE_SPREAD group of COMPONENTS_LEAK_GROUPS (the same factory forwards its whole prop bag, so it leaks the same fourteen attributes, objectui#5574's recorded shape). The docblock counts the file pins against its own arrays move with it: targets 159 to 160, plain 140 to 141, ledgered 89 to 90, ready-matched 156 to 157, all targets 182 to 183, all ledgered 91 to 92; clean stays 70. The completeness assertion is untouched.\n- Generated — packages/cli/src/utils/known-schema-types.ts regenerated by scripts/regenerate-known-schema-types.mjs (+ui:code); scripts/body-dialect-census.mjs mirrors the tag list.\n- Changesets — new .changeset/10756-html-tier-code-element.md (minor for @object-ui/components, @object-ui/core, @object-ui/types, @object-ui/fields); dated notes in the objectui#10533 form on .changeset/10735-html-tier-manifest.md (47 to 48, code now declared) and .changeset/8499-node-slot-registered-arms.md (37 to 38). check-changeset-overwrite reports both modifications (report-only, exit 0) and did not refuse them.\n- ⛔ Not touched: PUBLIC_BLOCKS, kbd, div, docs/adr/**, anything in objectstack (the lockstep port and the manifest regeneration at the pin are objectstack-ai/objectstack#20112's lane).\n\n## H1 — reproduced on origin/main (b5b6f37531)\n\nThe new console pin, run on a detached worktree at the base commit through the real page path (SchemaRenderer with a kind:'html' page whose source is a main holding a p that holds a code tag with the text inline and className=\"k\"):\n\n- renders the authored text inside a code element — AssertionError: expected null to be truthy (no code element rendered; the bare key drew the field widget).\n- compiles cleanly against the live page manifest — expected [ 'not-a-container:code' ] to deeply equal [] (the widget declared no child slot).\n- resolves to the field widget under its namespaced key, while the bare key is the passthrough — expected 'field:code' to be 'ui:code'.\n- Lit control on the same base tree: pre and strong rendered their text (the control case passed on base and on head).\n\n## H2 — the registration, measured (ComponentRegistry answers in the console registration graph: @object-ui/components plus src/register-plugins.ts, which pulls @object-ui/fields through plugin-form)\n\n| key | base b5b6f37531 | head 974840acbd |\n|---|---|---|\n| code | field:code (namespace field, inputs [], FieldCarrier) | ui:code (namespace ui, inputs className, children, the forwardRef passthrough) |\n| ui:code | not registered | ui:code |\n| field:code | field:code (unchanged) | field:code (unchanged) |\n| kbd / ui:kbd | ui:kbd, inputs label, className | unchanged |\n| getPublicConfigs() | 106 entries, 47 stamped html, no code | 107 entries, 48 stamped html, code stamped html |\n\nHow the TAGS passthroughs register: ComponentRegistry.register(tag, Component, { namespace: 'ui', … }) — every roster tag is ui-namespaced and also takes the bare key (the register fallback branch). The bare key is last-writer-wins, and in the console graph @object-ui/fields registers after @object-ui/components, so without the stand-down field:code would have taken code back (ablation L3 below shows exactly that). FIELD_TYPES_SKIP_FALLBACK is the repository's settled mechanism for this shape (textarea, select, email, password, slider already stand down to ui:*). Forms resolve widgets through the field: namespace (form.tsx resolves field:${type}), so a field of type code renders the same editor; a JSON-surface field:code node is unchanged; only a bare JSON { \"type\": \"code\" } node moves, and it now renders the element — the widget's key is field:code. check:registry-bare-names stays at 0 contested keys; nothing is added to its shrink-only ledger.\n\n## H3 — the roster and the regenerated artefacts\n\nGenerated with the repo's own generator (buildArtifacts in packages/sdui-parser/scripts/gen-manifest.ts) over the console registration graph with the 16 plugin modules eager-imported in dev/manifest-dump.tsx order, once on the base tree and once on head:\n\n| artefact | base | head |\n|---|---|---|\n| sdui.manifest.json components | 106 (59 curated + 47 html) | 107 (59 curated + 48 html): +1 (code), 0 removed, 0 curated entries changed |\n| code entry | absent | { type: 'code', namespace: 'ui', tier: 'html', inputs: className (string), children (slot) } |\n| div / kbd present | no | no |\n| sdui-intrinsics.d.ts | 897 lines | 902 lines (+CodeProps interface, +\"code\": CodeProps intrinsic) |\n| sdui-blocks.md | 119 lines, ## html tier intrinsic elements (47) | 120 lines, ## html tier intrinsic elements (48); # SDUI public blocks (59) unchanged |\n\ncode leaves EXCLUDED_HTML_NAMED in apps/console/src/__tests__/html-tier-manifest.test.ts; the ledger's liveness pin would have gone red had it stayed. The 47 count PR objectui#10753 pinned lives in prose only (its changeset and the core roster test's "three registered elsewhere" case, which stays at three because code is now on the JSON-surface enum too); the changeset takes a dated note.\n\n## H4 — pins, red on the base and green on the head\n\nRun on a detached worktree at b5b6f37531 with the six pin files copied in: Test Files 6 failed (6) · Tests 13 failed | 68 passed (81). Run on head 974840acbd (16 files including the neighbours): Test Files 16 passed (16) · Tests 241 passed (241).\n\n| pin | base | head |\n|---|---|---|\n| console: code with text renders the text in a code element (real page path) | red — expected null to be truthy | green |\n| console: live compile reports nothing for the tag | red — not-a-container:code | green |\n| console control: field:code renders the widget (read-only pre with the value) and keeps its key | red on the registry half ('field:code' to be 'ui:code') | green |\n| console manifest: declares code (tier html, ui, className + children) | red — expected undefined to be defined | green |\n| console census: every HTML-named registration on a roster or in the ledger | red — unaccounted: code | green |\n| console compile against the published manifest: accepts code with children | red — two error diagnostics | green |\n| core roster: carries code, leaves kbd out | red | green |\n| core projection: a roster tag is projected from whatever holds its bare key | red — expected undefined to be 'html' | green |\n| react-page scope: no Code wrapper, code stamped html | red — expected undefined to be truthy | green |\n| fields: bare code is not the field renderer | red — both lookups were FieldCarrierAdapter | green |\n| types: HtmlElementSchema names exactly TAGS (38), literal count 48 | red — expected 37 to be 38, expected 47 to be 48 | green |\n\nPer-hunk ablations (each through scripts/ablation-replace.mjs from ../objectstack, --delete mode, on the committed head; the tool printed anchor x1 -> x0 and the blob change for every leg, and restore proof blob == HEAD plus empty git diff HEAD; an outer hash-against-HEAD check agreed on all legs; final git status clean). Pin set: the six files above plus zod-mirror-parity.test.ts (118 tests).\n\n| leg | hunk removed | result |\n|---|---|---|\n| L1 | 'code' out of TAGS | 13 red / 4 files — the render pin, the compile pin, the registry pin, the roster-vs-manifest pins, the react-page pin, the 8499 parity |\n| L2 | 'code' out of HTML_TIER_INTRINSICS | 8 red / 3 files — declares code, the census (code unaccounted), the react-page pin, the core roster pins; the render pin stayed green (the renderer does not read the roster) |\n| L3 | 'code' out of FIELD_TYPES_SKIP_FALLBACK | 8 red / 3 files — the render pin, the compile pin (not-a-container:code), declares code (the projection copied the widget), the fields collision pin; and check:registry-bare-names exit 1 naming the contested bare key code |\n| L4 | 'code' out of the zod enum | 3 red / 2 files — the 8499 parity and literal count, the core "exactly three registered elsewhere" case |\n| L5 | \\| 'code' out of the TS union | 0 red in vitest — the TS face is not a runtime value; L5b ran the instrument that sees it: pnpm --filter @object-ui/types type-check exit 2, zod-mirror-parity.test.ts … TS2322: Type '\"layout.zod.ts#HtmlElementSchema\"' is not assignable to type 'never' |\n\nDisclosed: a first ablation pass was a no-op — ablation-replace.mjs refused every leg before any test ran (the replacement count moved 1 -> 1, a rise of 0: the replacement text was a substring of the anchor) and restored each file; the pass above is the rerun in --delete mode.\n\n## H5 — kbd, measured only\n\npackages/components/src/renderers/data-display/kbd.tsx registers ui:kbd with inputs label, className, no child slot; it reads schema.keys (array) or schema.label and renders one kbd per key inside a wrapper div. That is a component under an HTML tag name, not an element passthrough — a different shape from code. Untouched; it stays in EXCLUDED_HTML_NAMED with its reason.\n\n## Round 2 — CI red on 974840acbd, and the repair\n\nTest (shard 6/8) (run 36319935360, job 108621700100) failed the sweep's completeness case: the sweep covers a real, non-empty target set (objectui#4425) › components: every widget it registers is swept — a new one cannot slip past, expected [ 'action:bar', …(158) ] to deeply equal [ 'action:bar', …(159) ], missing \"ui:code\". The case derives the registered set from ComponentRegistry and compares it with the enumerated targets, so the ui:code this PR registers had no target.\n\n- Reproduced locally first, with the sweep file at 974840acbd's blob written into the worktree (on-disk blob 76f8f62ca0 equal to 974840acbd's, zero 'ui:code' occurrences) and restored afterwards (blob 6bed2c87dc equal to HEAD's, git diff HEAD empty): Test Files 1 failed (1) · Tests 1 failed | 229 passed (230), the same case, the same - \"ui:code\" diff.\n- Green on the repaired head: Test Files 1 passed (1) · Tests 231 passed (231). The file gains one test because ui:code is now its own swept case.\n- Ablation (through ../objectstack's scripts/ablation-replace.mjs, wrap mode; each leg printed anchor x1 -> x0 with the blob change, and restore blob == HEAD plus empty git diff HEAD; an outer hash-against-HEAD check agreed after each leg):\n\n| leg | removed | result |\n|---|---|---|\n| A | 'ui:code' from COMPONENTS_PLAIN_TYPES (the enrolment) | 11 red / 230 — the completeness case (every widget it registers is swept) first, then the ledger's well-formedness, the family-size / ledgered / clean cases, five derived-count cases and the reading table |\n| B | 'ui:code' from the BARE_SPREAD ledger group | 7 red / 231 — the ui:code sweep case itself (ui:code leaked 14 non-DOM attribute(s), read off a rendered code element carrying the readiness class), plus the ledgered / clean / derived-count / reading-table cases |\n\nLeg A shows the completeness case catches a missing enrolment; leg B shows the enrolled code is actually rendered and scanned, not only listed. Disclosed: the first ablation attempt was a no-op — the tool refused both legs before any mutation (not inside a git repository: it was invoked from outside the worktree); the legs above are the rerun from the worktree root.\n\n- Census — git grep for every test or script that enumerates the TAGS roster, ui:cite or a sibling tag, or states the 37 / 47 / 106 counts; every one run at b62fca8044:\n - DOM batch, 13 files, Test Files 13 passed (13) · Tests 368 passed (368): app-shell widget-dom-leak-sweep; console html-tier-code-element-10756, html-tier-manifest, public-contract; components container-declaration-census, span-deprecation-provenance, registration-uniqueness, shadowed-renderer-behaviour, react-page-scope; fields cellRenderers.childlessContainer-8580, richtext-cell-renderer-5452, registry-collision; sdui-parser inline-whitespace.\n - Node batch, 15 files, Test Files 15 passed (15) · Tests 430 passed (430): core html-tier-intrinsics; types node-slot-registered-arms-8499, arm-named-export-8784, form-barrel-mirror-9406, node-recursion-point-8344, zod-mirror-parity, breadcrumb-object-tree-nameable-7917; sdui-parser html-tier-manifest-10735; scripts check-doc-component-types, body-dialect-census, known-schema-types-derivation-5115, check-component-surface-parity, registry-bare-name-collisions, check-handler-key-read-sites; cli check-known-types.\n - Scripts that enumerate the roster, run as themselves: regenerate-known-schema-types.mjs --check OK; check:registry-bare-names 0 contested ✓; census:body-dialect exit 0; check:component-surface-parity report-only exit 0; check:doc-types ✅.\n - Count prose found stale and repaired in b62fca8044: public-contract.test.ts's anti-vacuity note (37 spellings, now 38) and the note under the 8499 pin's toBe guards. Left as historical readings, not live counts: check-component-surface-parity.mjs's "(37 + 7 tags)" (it describes the guard's earlier shape, objectui#4631 review F1), and the AnyComponentSchema 106 / 107-arm figures in node-recursion-point-8344, node-slot-registered-arms-8499, breadcrumb-object-tree-nameable-7917 and check-handler-key-read-sites (a different population, the union's arms; this PR adds a literal to an existing enum arm, not an arm).\n\n## Gates (local, targeted; the farm is CI's)\n\n- Round 2, at b62fca8044: the sweep and the 28-file census above; check-changeset-presence: ✅ 15 source file(s) of 7 released package(s) changed, and this change declares 1 changeset(s); check-control-bytes ✅; check-new-cross-file-line-citations 0 new citation(s); eslint --no-inline-config over the three round-2 files: 3 files, 0 errors, 0 warnings.\n- Round 1: pnpm exec vitest run over 16 files at 974840acbd: Test Files 16 passed (16) · Tests 241 passed (241) (console html-tier-code-element-10756, html-tier-manifest, public-contract; core html-tier-intrinsics; types node-slot-registered-arms-8499, arm-named-export-8784, zod-mirror-parity; fields registry-collision; components react-page-scope, span-deprecation-provenance, registration-uniqueness, container-declaration-census, shadowed-renderer-behaviour; sdui-parser html-tier-manifest-10735; scripts body-dialect-census, known-schema-types-derivation-5115).\n- pnpm --filter PKG type-check after pnpm --workspace-concurrency=2 --filter '@object-ui/fields^...' build (the dependency closure): @object-ui/types, @object-ui/core, @object-ui/fields, @object-ui/components, @object-ui/cli all exit 0. @object-ui/console type-check NOT MEASURED locally: it needs every plugin's dist/ and the first attempt without the closure failed on TS2882 / TS6305 "not built" errors, not on this diff — declared to CI's Type Check.\n- node scripts/check-registry-bare-name-collisions.mjs: 0 contested; ledger holds 0 … ✓ no new bare-name collision.\n- node scripts/regenerate-known-schema-types.mjs --check: OK … matches the registry derivation.\n- node scripts/check-changeset-presence.mjs: ✅ 13 source file(s) of 6 released package(s) changed, and this change declares 1 changeset(s). check-changeset-no-major: ✅. check-changeset-overwrite: reports the two dated-note modifications, report-only exit 0.\n- check-doc-component-types ✅ · check-new-cross-file-line-citations: 0 new citation(s) · check-test-path-roots ✅ · check-control-bytes ✅ · check-pending-changeset-literals ✅ · check-prompt-component-keys OK · check-side-effects-array ✅.\n- pnpm exec eslint --no-inline-config --format json over the 15 changed lintable files: 0 errors, 155 warnings, all in the pre-existing classes (no-explicit-any, react-refresh/only-export-components; the two in the new test are the as any casts the sibling console suites use). eslint.config.js configures no type-aware linting (no parserOptions.project / projectService), so this diff moves no untouched file's verdict; the repo-wide pnpm lint population is CI's.\n\n## Serial\n\nOpen-PR file lists were read by the seat before dispatch (none touches these files). Before the round-2 push: git fetch origin main:refs/os-dev/issue-10756/main -f — main moved from b5b6f37531 to 17b323e5ca (2 commits, 24 files), none of them among this branch's 19; git merge-tree --write-tree HEAD refs/os-dev/issue-10756/main exit 0, no conflicts. No merge commit was needed.\n\n## Acceptance notes\n\n- kbd: measured above (H5) — a ui component reading keys / label, no child slot; not the same shape; not widened here.\n- ADR-0081 §2 lists no code; docs/adr/** is a governed surface and is not edited in this PR. The admission is documented in the roster's header (html-tier-intrinsics.ts) and the changeset. Carrier: none named.\n- Observation (prose only): the FIELD_TYPES_SKIP_FALLBACK comment above time / address says no other package owns those bare keys, while html-elements.tsx registers ui:time and ui:address from TAGS. Behaviour is unaffected (both stand down already); noted, not touched.\n- Observation: scripts/body-dialect-census.mjs carries a hand-copied HTML_ELEMENT_TAGS list with no pin against TAGS; updated here to include code, drift otherwise unguarded.\n- A bare JSON node { \"type\": \"code\" } used to draw the field:code editor through the fallback and now draws the element passthrough. No producer in this tree authors that node (the type: 'code' hits in plugin-designer / plugin-charts are input-kind declarations, not node types); the changeset says so.\n- The objectstack side: the tracked sdui.manifest.json there regenerates at its next .objectui-sha bump under objectstack-ai/objectstack#20112; nothing in objectstack changes here.\n\n---\n_Generated by Claude Code_\n"
    }

  6. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 10756,
    "status": "done",
    "branch": "claude/issue-10756-html-tier-code-element",
    "pr": "#10776",
    "session": "session_014mXUNuFomfj24w7s1pZzhN (mode:subagent; round-3 changeset-text-only fix)",
    "premise_still_valid": true,
    "summary": "Round 3 repairs the text-only FAIL from contract review 5856453707 on b62fca8: the changeset's @object-ui/fields bullet gets the review's exact BREAKING (authoring) banner and field:code migration sentence (verified byte-for-byte against the quoted text), the @object-ui/types bullet gains the AnyComponentSchema/objectui validate accept-set-widening clause, the intro's span sentence is corrected to name it as its own renderer, and @object-ui/cli joins the frontmatter as minor with a one-sentence body bullet (known-schema-types.ts gains ui:code, per the objectui#9533/#6416 precedent). No source, test, or generated file changed — only .changeset/10756-html-tier-code-element.md. Committed as d601b5c and pushed; git merge-tree against a freshly-fetched origin/main (bf14b64) exits 0 with no conflicts.",
    "tests": "No test suites: this round's diff is prose in one changeset file (git diff --stat vs b62fca8: 1 file changed, 20 insertions(+), 10 deletions(-)). Verbatim check: a normalized-whitespace (regex \s+ -> ' ') string comparison of the replaced @object-ui/fields bullet against the review's quoted text in comment 5856453707 -> MATCH. Gates, each run as itself at head d601b5c, all exit 0: node scripts/check-changeset-presence.mjs -> '✅ 15 source file(s) of 7 released package(s) changed, and this change declares 1 changeset(s): .changeset/10756-html-tier-code-element.md.'; node scripts/check-changeset-no-major.mjs -> '✅ No changeset declares a major bump.'; node scripts/check-changeset-overwrite.mjs -> report-only, unchanged from round 2: '1 changeset(s) added, 2 modified, 0 deleted' naming .changeset/10735-html-tier-manifest.md and .changeset/8499-node-slot-registered-arms.md, exit 0; node scripts/check-changeset-fixed.mjs -> '✅ All workspace packages are in the changeset fixed group.' / '✅ privatePackages declared: version=true, tag=false.'; pnpm check:control-bytes -> '✅ check-control-bytes: OK (scanned 8958 tracked text file(s); skipped 85 binary).'; pnpm check:new-line-citations -> 'VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0'. Merge: git fetch origin main (bf14b64, main moved 7 commits / 76 files past b62fca8, none of them this branch's one file) then git merge-tree --write-tree HEAD origin/main -> exit 0, tree dfe14b140f, no conflicts, no merge commit. Pushed once, b62fca8..d601b5c. CI on d601b5c read once after the push via GET /repos/objectstack-ai/objectui/commits/d601b5c1d2.../check-runs, not waited on: 27 check-runs, 26 queued, 1 skipped, 0 failing.",
    "mcp_calls": "2 -- both reads, no write tool called: mcp__github__pull_request_read (method get_comments, PR 10776, to read contract review 5856453707) and mcp__github__issue_read (method get_comments, issue 10756, to read the round-2 os-dev-report comment 5856338409 for its pr_body_replacement). All other reads (the two comments' raw JSON, and the check-runs listing) were plain REST GETs via curl with GITHUB_TOKEN, not MCP.",
    "api_writes": "1 -- POST /repos//issues/10756/comments via scripts/pm/post-stamped.mjs --comment=10756 (fleet relay, lands as objectstack-fleet[bot]): this report. git push (b62fca8..d601b5c) is not REST. No label-write, no pr_create, no PATCH: the PR body is left for the seat per the handoff's explicit instruction.",
    "open_questions": [],
    "out_of_scope_findings": [],
    "pr_body_replacement": "Fixes #10756\nClause-②: yes — the published sdui.manifest.json gains one html-tier tag (code, stamped tier: 'html'; 47 becomes 48).\n\nExecutes triage 5853741286 on objectui#10756 under ruling A on objectstack-ai/objectstack#20112 (5852014527): a kind:'html' page may author the intrinsic tags its renderer registers, and the published manifest declares that set. PR objectui#10753 (objectui#10735) landed the roster and ledgered code out because the only bare code registration was the field:code widget's namespace fallback; this PR is the element that ledger was waiting for. Base origin/main b5b6f37531; head d601b5c1d2 (four commits: the feature 974840acbd; 2fb626af0d, which enrols ui:code in the app-shell DOM-leak sweep after CI went red on the feature commit; b62fca8044, two comment-only prose counts; d601b5c1d2, the changeset-text fix contract review 5856453707 required). H1 to H5 below are the round-1 readings taken at 974840acbd; the round-2 section records what changed after it; the round-3 section records the changeset-only text fix.\n\n## What changed\n\n- packages/components/src/renderers/basic/html-elements.tsx — code joins TAGS (registered ui:code, sanitised like its siblings: className and the children slot). The header no longer lists code as "registered elsewhere"; the exclusion comment drops it.\n- packages/fields/src/index.tsx — code joins FIELD_TYPES_SKIP_FALLBACK: the widget keeps field:code and stands down from the bare key, so ui:code is the one bare claimant (file-surface amendment, forced by H2 below: the bare-name collision gate refuses a second claimant, and in the console graph the widget registers AFTER the passthrough and would have won the bare key back).\n- packages/core/src/registry/html-tier-intrinsics.ts — code joins HTML_TIER_INTRINSICS; the header's "deliberately NOT here" bullet for code is replaced by the admission note.\n- packages/types/src/zod/layout.zod.ts and packages/types/src/layout.ts — HtmlElementSchema (zod and TS) names code (file-surface amendment, forced by the objectui#8499 parity pin: HtmlElementSchema must name exactly the tags TAGS registers).\n- Tests — new apps/console/src/__tests__/html-tier-code-element-10756.test.tsx; html-tier-manifest.test.ts (ledger drops code; declares code; the compile case flips from refuses to accepts); core html-tier-intrinsics.test.ts (roster carries code; the squatting-widget case moves to summary, plus a projection case for the stand-down); react-page-scope.test.tsx (no Code wrapper); fields registry-collision.test.ts (code joins the colliding set); types node-slot-registered-arms-8499.test.ts (count guards 37 to 38, 47 to 48 literals); prose counts in container-declaration-census.test.tsx and public-contract.test.ts (all three of its 37-tag sentences now read 38; the note under the 8499 pin's toBe guards likewise).\n- packages/app-shell/src/__tests__/widget-dom-leak-sweep.test.tsx — ui:code is enrolled where its TAGS siblings ui:cite / ui:strong sit: COMPONENTS_PLAIN_TYPES (rendered with the shared readiness class and scanned) and the BARE_SPREAD group of COMPONENTS_LEAK_GROUPS (the same factory forwards its whole prop bag, so it leaks the same fourteen attributes, objectui#5574's recorded shape). The docblock counts the file pins against its own arrays move with it: targets 159 to 160, plain 140 to 141, ledgered 89 to 90, ready-matched 156 to 157, all targets 182 to 183, all ledgered 91 to 92; clean stays 70. The completeness assertion is untouched.\n- Generated — packages/cli/src/utils/known-schema-types.ts regenerated by scripts/regenerate-known-schema-types.mjs (+ui:code); scripts/body-dialect-census.mjs mirrors the tag list.\n- Changesets — .changeset/10756-html-tier-code-element.md declares minor for @object-ui/components, @object-ui/core, @object-ui/types, @object-ui/fields and @object-ui/cli. Round 3 (contract review 5856453707) gave the @object-ui/fields bullet a BREAKING (authoring) banner: the bare code key no longer resolves to the code-editor widget, migration { \"type\": \"code\" } becomes { \"type\": \"field:code\" }, and it stays minor under objectui's version policy. The same round also appended the AnyComponentSchema / objectui validate accept-set-widening sentence to the @object-ui/types bullet, corrected the intro's span clause, and added the @object-ui/cli bullet (known-schema-types.ts regenerates and gains ui:code; the objectui#9533 and objectui#6416 precedent). Dated notes in the objectui#10533 form remain on .changeset/10735-html-tier-manifest.md (47 to 48, code now declared) and .changeset/8499-node-slot-registered-arms.md (37 to 38). check-changeset-overwrite reports both modifications (report-only, exit 0) and did not refuse them.\n- ⛔ Not touched: PUBLIC_BLOCKS, kbd, div, docs/adr/**, anything in objectstack (the lockstep port and the manifest regeneration at the pin are objectstack-ai/objectstack#20112's lane).\n\n## H1 — reproduced on origin/main (b5b6f37531)\n\nThe new console pin, run on a detached worktree at the base commit through the real page path (SchemaRenderer with a kind:'html' page whose source is a main holding a p that holds a code tag with the text inline and className=\"k\"):\n\n- renders the authored text inside a code element — AssertionError: expected null to be truthy (no code element rendered; the bare key drew the field widget).\n- compiles cleanly against the live page manifest — expected [ 'not-a-container:code' ] to deeply equal [] (the widget declared no child slot).\n- resolves to the field widget under its namespaced key, while the bare key is the passthrough — expected 'field:code' to be 'ui:code'.\n- Lit control on the same base tree: pre and strong rendered their text (the control case passed on base and on head).\n\n## H2 — the registration, measured (ComponentRegistry answers in the console registration graph: @object-ui/components plus src/register-plugins.ts, which pulls @object-ui/fields through plugin-form)\n\n| key | base b5b6f37531 | head 974840acbd |\n|---|---|---|\n| code | field:code (namespace field, inputs [], FieldCarrier) | ui:code (namespace ui, inputs className, children, the forwardRef passthrough) |\n| ui:code | not registered | ui:code |\n| field:code | field:code (unchanged) | field:code (unchanged) |\n| kbd / ui:kbd | ui:kbd, inputs label, className | unchanged |\n| getPublicConfigs() | 106 entries, 47 stamped html, no code | 107 entries, 48 stamped html, code stamped html |\n\nHow the TAGS passthroughs register: ComponentRegistry.register(tag, Component, { namespace: 'ui', … }) — every roster tag is ui-namespaced and also takes the bare key (the register fallback branch). The bare key is last-writer-wins, and in the console graph @object-ui/fields registers after @object-ui/components, so without the stand-down field:code would have taken code back (ablation L3 below shows exactly that). FIELD_TYPES_SKIP_FALLBACK is the repository's settled mechanism for this shape (textarea, select, email, password, slider already stand down to ui:*). Forms resolve widgets through the field: namespace (form.tsx resolves field:${type}), so a field of type code renders the same editor; a JSON-surface field:code node is unchanged; only a bare JSON { \"type\": \"code\" } node moves, and it now renders the element — the widget's key is field:code. check:registry-bare-names stays at 0 contested keys; nothing is added to its shrink-only ledger.\n\n## H3 — the roster and the regenerated artefacts\n\nGenerated with the repo's own generator (buildArtifacts in packages/sdui-parser/scripts/gen-manifest.ts) over the console registration graph with the 16 plugin modules eager-imported in dev/manifest-dump.tsx order, once on the base tree and once on head:\n\n| artefact | base | head |\n|---|---|---|\n| sdui.manifest.json components | 106 (59 curated + 47 html) | 107 (59 curated + 48 html): +1 (code), 0 removed, 0 curated entries changed |\n| code entry | absent | { type: 'code', namespace: 'ui', tier: 'html', inputs: className (string), children (slot) } |\n| div / kbd present | no | no |\n| sdui-intrinsics.d.ts | 897 lines | 902 lines (+CodeProps interface, +\"code\": CodeProps intrinsic) |\n| sdui-blocks.md | 119 lines, ## html tier intrinsic elements (47) | 120 lines, ## html tier intrinsic elements (48); # SDUI public blocks (59) unchanged |\n\ncode leaves EXCLUDED_HTML_NAMED in apps/console/src/__tests__/html-tier-manifest.test.ts; the ledger's liveness pin would have gone red had it stayed. The 47 count PR objectui#10753 pinned lives in prose only (its changeset and the core roster test's "three registered elsewhere" case, which stays at three because code is now on the JSON-surface enum too); the changeset takes a dated note.\n\n## H4 — pins, red on the base and green on the head\n\nRun on a detached worktree at b5b6f37531 with the six pin files copied in: Test Files 6 failed (6) · Tests 13 failed | 68 passed (81). Run on head 974840acbd (16 files including the neighbours): Test Files 16 passed (16) · Tests 241 passed (241).\n\n| pin | base | head |\n|---|---|---|\n| console: code with text renders the text in a code element (real page path) | red — expected null to be truthy | green |\n| console: live compile reports nothing for the tag | red — not-a-container:code | green |\n| console control: field:code renders the widget (read-only pre with the value) and keeps its key | red on the registry half ('field:code' to be 'ui:code') | green |\n| console manifest: declares code (tier html, ui, className + children) | red — expected undefined to be defined | green |\n| console census: every HTML-named registration on a roster or in the ledger | red — unaccounted: code | green |\n| console compile against the published manifest: accepts code with children | red — two error diagnostics | green |\n| core roster: carries code, leaves kbd out | red | green |\n| core projection: a roster tag is projected from whatever holds its bare key | red — expected undefined to be 'html' | green |\n| react-page scope: no Code wrapper, code stamped html | red — expected undefined to be truthy | green |\n| fields: bare code is not the field renderer | red — both lookups were FieldCarrierAdapter | green |\n| types: HtmlElementSchema names exactly TAGS (38), literal count 48 | red — expected 37 to be 38, expected 47 to be 48 | green |\n\nPer-hunk ablations (each through scripts/ablation-replace.mjs from ../objectstack, --delete mode, on the committed head; the tool printed anchor x1 -> x0 and the blob change for every leg, and restore proof blob == HEAD plus empty git diff HEAD; an outer hash-against-HEAD check agreed on all legs; final git status clean). Pin set: the six files above plus zod-mirror-parity.test.ts (118 tests).\n\n| leg | hunk removed | result |\n|---|---|---|\n| L1 | 'code' out of TAGS | 13 red / 4 files — the render pin, the compile pin, the registry pin, the roster-vs-manifest pins, the react-page pin, the 8499 parity |\n| L2 | 'code' out of HTML_TIER_INTRINSICS | 8 red / 3 files — declares code, the census (code unaccounted), the react-page pin, the core roster pins; the render pin stayed green (the renderer does not read the roster) |\n| L3 | 'code' out of FIELD_TYPES_SKIP_FALLBACK | 8 red / 3 files — the render pin, the compile pin (not-a-container:code), declares code (the projection copied the widget), the fields collision pin; and check:registry-bare-names exit 1 naming the contested bare key code |\n| L4 | 'code' out of the zod enum | 3 red / 2 files — the 8499 parity and literal count, the core "exactly three registered elsewhere" case |\n| L5 | \\| 'code' out of the TS union | 0 red in vitest — the TS face is not a runtime value; L5b ran the instrument that sees it: pnpm --filter @object-ui/types type-check exit 2, zod-mirror-parity.test.ts … TS2322: Type '\"layout.zod.ts#HtmlElementSchema\"' is not assignable to type 'never' |\n\nDisclosed: a first ablation pass was a no-op — ablation-replace.mjs refused every leg before any test ran (the replacement count moved 1 -> 1, a rise of 0: the replacement text was a substring of the anchor) and restored each file; the pass above is the rerun in --delete mode.\n\n## H5 — kbd, measured only\n\npackages/components/src/renderers/data-display/kbd.tsx registers ui:kbd with inputs label, className, no child slot; it reads schema.keys (array) or schema.label and renders one kbd per key inside a wrapper div. That is a component under an HTML tag name, not an element passthrough — a different shape from code. Untouched; it stays in EXCLUDED_HTML_NAMED with its reason.\n\n## Round 2 — CI red on 974840acbd, and the repair\n\nTest (shard 6/8) (run 36319935360, job 108621700100) failed the sweep's completeness case: the sweep covers a real, non-empty target set (objectui#4425) › components: every widget it registers is swept — a new one cannot slip past, expected [ 'action:bar', …(158) ] to deeply equal [ 'action:bar', …(159) ], missing \"ui:code\". The case derives the registered set from ComponentRegistry and compares it with the enumerated targets, so the ui:code this PR registers had no target.\n\n- Reproduced locally first, with the sweep file at 974840acbd's blob written into the worktree (on-disk blob 76f8f62ca0 equal to 974840acbd's, zero 'ui:code' occurrences) and restored afterwards (blob 6bed2c87dc equal to HEAD's, git diff HEAD empty): Test Files 1 failed (1) · Tests 1 failed | 229 passed (230), the same case, the same - \"ui:code\" diff.\n- Green on the repaired head: Test Files 1 passed (1) · Tests 231 passed (231). The file gains one test because ui:code is now its own swept case.\n- Ablation (through ../objectstack's scripts/ablation-replace.mjs, wrap mode; each leg printed anchor x1 -> x0 with the blob change, and restore blob == HEAD plus empty git diff HEAD; an outer hash-against-HEAD check agreed after each leg):\n\n| leg | removed | result |\n|---|---|---|\n| A | 'ui:code' from COMPONENTS_PLAIN_TYPES (the enrolment) | 11 red / 230 — the completeness case (every widget it registers is swept) first, then the ledger's well-formedness, the family-size / ledgered / clean cases, five derived-count cases and the reading table |\n| B | 'ui:code' from the BARE_SPREAD ledger group | 7 red / 231 — the ui:code sweep case itself (ui:code leaked 14 non-DOM attribute(s), read off a rendered code element carrying the readiness class), plus the ledgered / clean / derived-count / reading-table cases |\n\nLeg A shows the completeness case catches a missing enrolment; leg B shows the enrolled code is actually rendered and scanned, not only listed. Disclosed: the first ablation attempt was a no-op — the tool refused both legs before any mutation (not inside a git repository: it was invoked from outside the worktree); the legs above are the rerun from the worktree root.\n\n- Census — git grep for every test or script that enumerates the TAGS roster, ui:cite or a sibling tag, or states the 37 / 47 / 106 counts; every one run at b62fca8044:\n - DOM batch, 13 files, Test Files 13 passed (13) · Tests 368 passed (368): app-shell widget-dom-leak-sweep; console html-tier-code-element-10756, html-tier-manifest, public-contract; components container-declaration-census, span-deprecation-provenance, registration-uniqueness, shadowed-renderer-behaviour, react-page-scope; fields cellRenderers.childlessContainer-8580, richtext-cell-renderer-5452, registry-collision; sdui-parser inline-whitespace.\n - Node batch, 15 files, Test Files 15 passed (15) · Tests 430 passed (430): core html-tier-intrinsics; types node-slot-registered-arms-8499, arm-named-export-8784, form-barrel-mirror-9406, node-recursion-point-8344, zod-mirror-parity, breadcrumb-object-tree-nameable-7917; sdui-parser html-tier-manifest-10735; scripts check-doc-component-types, body-dialect-census, known-schema-types-derivation-5115, check-component-surface-parity, registry-bare-name-collisions, check-handler-key-read-sites; cli check-known-types.\n - Scripts that enumerate the roster, run as themselves: regenerate-known-schema-types.mjs --check OK; check:registry-bare-names 0 contested ✓; census:body-dialect exit 0; check:component-surface-parity report-only exit 0; check:doc-types ✅.\n - Count prose found stale and repaired in b62fca8044: public-contract.test.ts's anti-vacuity note (37 spellings, now 38) and the note under the 8499 pin's toBe guards. Left as historical readings, not live counts: check-component-surface-parity.mjs's "(37 + 7 tags)" (it describes the guard's earlier shape, objectui#4631 review F1), and the AnyComponentSchema 106 / 107-arm figures in node-recursion-point-8344, node-slot-registered-arms-8499, breadcrumb-object-tree-nameable-7917 and check-handler-key-read-sites (a different population, the union's arms; this PR adds a literal to an existing enum arm, not an arm).\n\n## Round 3 — text-only FAIL on b62fca8044, and the repair\n\nContract review 5856453707 (VERDICT: FAIL, code/pins/manifest/CI/boundaries\nPASS) held the changeset text short of the protocol's BREAKING-banner\nrequirement for a bare-key resolution change (the objectui#9533 precedent) and\nlisted two nits plus an advisory. Only .changeset/10756-html-tier-code-element.md\nchanged; no source, test or generated file moved.\n\n- The @object-ui/fields bullet is replaced verbatim with the review's\n BREAKING (authoring) banner text: the bare code key no longer resolves\n to the code-editor widget, with the { \"type\": \"code\" } → { \"type\":\n \"field:code\" } migration, and the explicit "released as minor... a\n breaking change never declares major" sentence.\n- The @object-ui/types bullet gains the AnyComponentSchema /\n objectui validate accept-set-widening clause the review's nit asked for.\n- The intro's "pre, strong, em and span rendered their children\n through the passthrough" is corrected to "pre, strong and em rendered\n their children through the passthrough, and span through its own\n renderer" (span.tsx:149 is its own renderer, per the review's nit).\n- '@object-ui/cli': minor joins the frontmatter per the review's advisory\n (known-schema-types.ts gains ui:code; the objectui#9533 and objectui#6416\n precedents name it in the fixed group either way), with a one-sentence body\n bullet in the same house style those two precedents use.\n- Every other sentence in the changeset is unchanged; none of them was found\n false by this round's re-read.\n\nVerified with a normalized-whitespace string match that the replaced\n@object-ui/fields bullet is byte-for-byte the review's quoted text (ignoring\nline-wrap only).\n\nGates, each run as itself at head d601b5c1d2, all exit 0: node\nscripts/check-changeset-presence.mjs (✅ 15 source file(s) of 7 released\npackage(s) changed, and this change declares 1 changeset(s)); node\nscripts/check-changeset-no-major.mjs (✅ No changeset declares a major\nbump.); node scripts/check-changeset-overwrite.mjs (report-only; reports the\nsame two pre-existing dated-note modifications as round 2, unchanged by this\nround); node scripts/check-changeset-fixed.mjs (✅ All workspace packages\nare in the changeset fixed group. / ✅ privatePackages declared: version=true,\ntag=false.); pnpm check:control-bytes (✅ … scanned 8958 tracked text\nfile(s); skipped 85 binary); pnpm check:new-line-citations (VERDICT\nnew-cross-file-line-citations: 0 new citation(s), enforcement report-only ->\nexit 0). No test suite was run: nothing executable changes (git diff\n--stat against b62fca8044: 1 file changed, 20 insertions, 10 deletions).\n\nMerge: git fetch origin main then git merge-tree --write-tree HEAD\norigin/main at d601b5c1d2 against origin/main bf14b64608 (main moved 7\ncommits / 76 files past b62fca8044, none of them this branch's one file):\nexit 0, tree dfe14b140f, no conflicts, no merge commit needed. Pushed once,\nb62fca8044..d601b5c1d2. CI on d601b5c1d2 read once after the push, not\nwaited on: 27 check-runs, 26 queued, 1 skipped, 0 failing.\n\n## Gates (local, targeted; the farm is CI's)\n\n- Round 2, at b62fca8044: the sweep and the 28-file census above; check-changeset-presence: ✅ 15 source file(s) of 7 released package(s) changed, and this change declares 1 changeset(s); check-control-bytes ✅; check-new-cross-file-line-citations 0 new citation(s); eslint --no-inline-config over the three round-2 files: 3 files, 0 errors, 0 warnings.\n- Round 3, at d601b5c1d2 (changeset text only): check-changeset-presence ✅ (15 source file(s) of 7 released package(s), 1 changeset(s)); check-changeset-no-major ✅; check-changeset-overwrite report-only, same two pre-existing dated notes; check-changeset-fixed ✅ (both lines); check:control-bytes ✅ (8958 scanned, 85 binary skipped); check:new-line-citations 0 new citation(s). No test suite: this round touches only .changeset/10756-html-tier-code-element.md.\n- Round 1: pnpm exec vitest run over 16 files at 974840acbd: Test Files 16 passed (16) · Tests 241 passed (241) (console html-tier-code-element-10756, html-tier-manifest, public-contract; core html-tier-intrinsics; types node-slot-registered-arms-8499, arm-named-export-8784, zod-mirror-parity; fields registry-collision; components react-page-scope, span-deprecation-provenance, registration-uniqueness, container-declaration-census, shadowed-renderer-behaviour; sdui-parser html-tier-manifest-10735; scripts body-dialect-census, known-schema-types-derivation-5115).\n- pnpm --filter PKG type-check after pnpm --workspace-concurrency=2 --filter '@object-ui/fields^...' build (the dependency closure): @object-ui/types, @object-ui/core, @object-ui/fields, @object-ui/components, @object-ui/cli all exit 0. @object-ui/console type-check NOT MEASURED locally: it needs every plugin's dist/ and the first attempt without the closure failed on TS2882 / TS6305 "not built" errors, not on this diff — declared to CI's Type Check.\n- node scripts/check-registry-bare-name-collisions.mjs: 0 contested; ledger holds 0 … ✓ no new bare-name collision.\n- node scripts/regenerate-known-schema-types.mjs --check: OK … matches the registry derivation.\n- node scripts/check-changeset-presence.mjs: ✅ 13 source file(s) of 6 released package(s) changed, and this change declares 1 changeset(s). check-changeset-no-major: ✅. check-changeset-overwrite: reports the two dated-note modifications, report-only exit 0.\n- check-doc-component-types ✅ · check-new-cross-file-line-citations: 0 new citation(s) · check-test-path-roots ✅ · check-control-bytes ✅ · check-pending-changeset-literals ✅ · check-prompt-component-keys OK · check-side-effects-array ✅.\n- pnpm exec eslint --no-inline-config --format json over the 15 changed lintable files: 0 errors, 155 warnings, all in the pre-existing classes (no-explicit-any, react-refresh/only-export-components; the two in the new test are the as any casts the sibling console suites use). eslint.config.js configures no type-aware linting (no parserOptions.project / projectService), so this diff moves no untouched file's verdict; the repo-wide pnpm lint population is CI's.\n\n## Serial\n\nOpen-PR file lists were read by the seat before dispatch (none touches these files). Before the round-2 push: git fetch origin main:refs/os-dev/issue-10756/main -f — main moved from b5b6f37531 to 17b323e5ca (2 commits, 24 files), none of them among this branch's 19; git merge-tree --write-tree HEAD refs/os-dev/issue-10756/main exit 0, no conflicts. No merge commit was needed.\n\n## Acceptance notes\n\n- kbd: measured above (H5) — a ui component reading keys / label, no child slot; not the same shape; not widened here.\n- ADR-0081 §2 lists no code; docs/adr/** is a governed surface and is not edited in this PR. The admission is documented in the roster's header (html-tier-intrinsics.ts) and the changeset. Carrier: none named.\n- Observation (prose only): the FIELD_TYPES_SKIP_FALLBACK comment above time / address says no other package owns those bare keys, while html-elements.tsx registers ui:time and ui:address from TAGS. Behaviour is unaffected (both stand down already); noted, not touched.\n- Observation: scripts/body-dialect-census.mjs carries a hand-copied HTML_ELEMENT_TAGS list with no pin against TAGS; updated here to include code, drift otherwise unguarded.\n- A bare JSON node { \"type\": \"code\" } used to draw the field:code editor through the fallback and now draws the element passthrough. No producer in this tree authors that node (the type: 'code' hits in plugin-designer / plugin-charts are input-kind declarations, not node types); the changeset says so — BREAKING (authoring), migration field:code; see the changeset.\n- The objectstack side: the tracked sdui.manifest.json there regenerates at its next .objectui-sha bump under objectstack-ai/objectstack#20112; nothing in objectstack changes here.\n\n---\n_Generated by Claude Code_\n"
    }

  7. objectstack-fleet commented on Sep 27, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: objectui#10776 at d601b5c; the body, ready, auto-merge and the needs:contract-review clear follow this ACCEPT

    domain:ui seat #2, session_014mXUNuFomfj24w7s1pZzhN. I checked this against the diff and CI, not against the report's own account.

    Implemented-by:  claude/issue-10756-html-tier-code-element
    Reviewed-by:     session_014mXUNuFomfj24w7s1pZzhN
    
    item reading
    the change The html tier registers and declares code. code joins TAGS (ui:code: className plus the children slot) and HTML_TIER_INTRINSICS, so the published manifest goes from 106 to 107 components and from 47 to 48 html-tier tags, with 0 curated entries changed. HtmlElementSchema names code. The field:code widget stands down from the bare key through FIELD_TYPES_SKIP_FALLBACK. An html page's code tag now renders its text instead of the code editor
    measured Registry answers at base and head, in both registration orders: bare code moves from the widget to ui:code, and field:code is unchanged (the triage's ⛔ holds). The census of bare code authors across both repos found zero SDUI page nodes. Forms resolve field:code and render the same editor
    pins 13 red of 81 on base, and 16 files with 241 green at head. Ablations L1–L5b redden the rows they own. Round 2 enrolled ui:code in the DOM-leak sweep: it is rendered and scanned (leg B) and the completeness case catches its absence (leg A). The census covered 28 files, all green
    semver minor for components, core, types, fields and cli. BREAKING (authoring) on @object-ui/fields: a bare { "type": "code" } node now draws the element, and the migration is field:code. Never major
    reviews round 2 at CONTRACT_REVIEW_TIER: FAIL at b62fca8 (5856453707), on the missing BREAKING banner and the stale body. Round 3, narrow and in-seat: PASS at d601b5c (posted with this landing). The round-1 fable review was stopped unfinished on the maintainer's quota signal. Reviews now run at opus, and round 3 ran at sonnet (text only)
    seat edits to the dev's body none. The body is the dev's round-3 pr_body_replacement
    serial git merge-tree --write-tree against main bf14b64608 is clean. PR objectui#10714 touches layout.ts / layout.zod.ts only in disjoint TabItem hunks. objectui#10757 (div) is next behind this, and nothing here pre-empts it
    governed NOT GOVERNED: 0 of 19 paths
    CI head d601b5c: 40 success, 3 skipped, 0 failure
    assignee os-elon-musk on the PR and the card
    closing keywords Fixes #10756 only

    Findings, each with its disposition


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions