Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

feat(components,types)!: retire the raw-HTML html node type (objectui#13449) - #13475

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-13449-retire-html-renderer
Oct 25, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-13449-retire-html-renderer

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #13449

Clause-②: no (narrowing)
Metadata-protocol: narrowed

What this does

Retires the raw-HTML html node type, as the triage grade routed it (comment 6385669346: "Route: retire, as recommended"). Its renderer in @object-ui/components wrote the authored html string into a div through dangerouslySetInnerHTML with no sanitiser, and @objectstack/spec declares no html component. It is removed, not sanitised; no deprecation alias. Breaking for a page that still names it, so the changeset is minor with a FROM / TO migration (this repository's version policy forbids major).

  • @object-ui/components: the html.tsx registration (ui:html plus the bare key) and its import in renderers/basic/index.ts are gone.
  • @object-ui/core: html leaves PUBLIC_BLOCKS (it sat there as the curated tier's "escape hatch"), so getPublicConfigs() and the published sdui.manifest.json no longer carry it.
  • @object-ui/types: HtmlSchema leaves the package entry, DataDisplaySchema and the SchemaRegistry map (so ComponentType no longer offers 'html'). The zod arm becomes RetiredHtmlNodeSchema, a retiredNodeType('html', …) refusal inside DataDisplaySchema, not exported: a type: "html" node is refused BY NAME at type, with the remedy in the message. This is the shape the maintainer ruled for the bare kanban key (objectui#8802) and that objectui#12954 reused for cloud:plan-status.
  • @object-ui/cli: known-schema-types.ts regenerated (node scripts/regenerate-known-schema-types.mjs; its --check answers OK), so objectui check no longer counts html / ui:html.
  • Docs: content/docs/components/basic/html.mdx (it said "Render raw HTML content safely", which was false), its meta.json row and its index link are gone; guide/react-pages.md says there is no raw-HTML block on either page tier. The schema-catalog example components-basic-html/basic-html.json is gone and the catalog index regenerated (scripts/regenerate-catalog-index.py; --check OK).
  • @object-ui/fields: html stays in FIELD_TYPES_SKIP_FALLBACK, moved from the "bare-name collision" group to a retired-key note. No behaviour change: the field editor still stands down from the bare key, so a stale node draws the unknown-component panel, never a rich-text editor.

The html FIELD type (HtmlFieldMetadata, drawn by field:html, value displayed through the rehype-sanitize path objectui#13431 landed) is a different layer and is untouched.

Reach, measured before and after (not a source-grep claim)

A temporary probe rendered both doors through the real SchemaRenderer and the full console registration graph (@object-ui/components plus apps/console/src/register-plugins), with a payload of an img element carrying an onerror attribute plus a bold element:

door base a3c0da9 this branch
JSON node { "type": "html", "html": PAYLOAD } injected: the img with its onerror attribute is in the DOM unknown-component panel (OBJUI-001), payload shown escaped as text
kind: 'html' page whose source writes an html tag carrying an html attribute compile reports ZERO diagnostics; the img with its onerror attribute is in the DOM compile refuses the tag (forbidden-tag, unknown-component); the page draws its compile report
getPublicConfigs() names html yes no

The second row is worse than the card stated: the tier guide/react-pages.md documents as "Untrusted OK", parsed and never executed, could inject raw markup, because the page compiler whitelists every registered type. The probe was replaced by a committed pin (below) and deleted.

Dispatch hypotheses, answered

  • H1 holds: sink, import, HtmlSchema and the zod literal sat where the claim read them at a3c0da9.
  • H2: content/docs/fields/rich-text.mdx names type: 'html' twice, and both are the FIELD type (HtmlFieldMetadata in the ts block; "HTML Mode" of the rich-text widget in prose). Both stay. check:doc-types had passed the code-block one only because the retired node registered the same bare key, so it now carries a field-vocabulary exemption in scripts/check-doc-component-types.mjs, worded like the richtext row beside it.
  • H3: apps/console/dist is not tracked, so nothing in-tree to regenerate for the manifest; the two tracked generated files that named the type are regenerated by their own generators (above). objectstack's tracked sdui.manifest.json still names html: it is generated from objectui's pinned build and its staleness gate forces a regenerate on the next .objectui-sha bump (see Acceptance notes).
  • H4 holds: no product producer. git grep for a quoted html over every tracked file, read hit by hit: apart from what this PR removes (the docs page, the catalog example, the data-display-examples.json entry, tests), every hit is the field type, a kind: 'html' page, a report export format, a code-editor language, or an HTML tag name.

File surface beyond the claim's list

The claim named renderers/basic/**, the two types files, the docs and tests naming the component, and the changeset. The dispatch's suggested route asked for every other list naming html as a component type; those are: packages/core/src/registry/public-blocks.ts, packages/types/src/{registry.ts,index.ts,zod/index.zod.ts,zod/README.md}, packages/types/examples/data-display-examples.json, packages/cli/src/utils/known-schema-types.ts (generated), packages/fields/src/index.tsx (regroup and comment only), the catalog example and its generated index, scripts/check-doc-component-types.mjs (one exemption), content/docs/guide/react-pages.md, and count / list pins in apps/console, packages/app-shell, packages/cli, packages/fields, packages/types. No governed surface: node scripts/check-governed-queue-guard.mjs --test over all 31 paths answers "NOT GOVERNED — 31 path(s) checked against 6 governed surface(s); none matched."

Pins (each red on the pre-fix implementation, see Ablation)

  • apps/console/src/__tests__/html-node-retired-13449.test.tsx — the kind: 'html' page refuses the tag and renders no authored markup; the JSON node renders no authored markup; neither registry key survives while field:html does; CONTROL: the same page without the tag renders its p text.
  • packages/types/src/__tests__/html-node-retired-13449.test.ts — compile-time: no html member in DataDisplaySchema, no 'html' in ComponentType, with non-vacuity siblings; runtime: a custom issue at type naming the retired type and both replacements; CONTROLS: a sibling node still parses, an unknown literal takes the generic path without the retirement text, and a form with an html FIELD still validates.
  • packages/cli/src/__tests__/registered-types-validate-ratchet-10859.test.ts — the strict face (objectui validate) refuses html by name; the namespaced refused-at-type pin falls 307 to 306 (ui:html left the registry).
  • packages/components/src/__tests__/basic-renderers.test.tsx, packages/fields/src/__tests__/registry-collision.test.ts — the old "renders HTML" / "bare html is the display widget" assertions became absence pins with lit controls.
  • Shifted counts and lists: zod-mirror-parity (pair moved to EXCLUSIONS, 205 to 204), content-channel-family-d-9256 (row left, 67 to 66), arm-named-export-8784 (ABSENT_BY_DECISION row), public-contract and html-tier-manifest in apps/console, widget-dom-leak-sweep in app-shell (target and ledger counts re-derived without ui:html).

Ablation (one-shot, not committed)

With the fix committed at bfd3722, the nine implementation files were put back from a3c0da9 (git checkout a3c0da9 -- …, which restores html.tsx too), landing proved on disk by count (register('html' 1, the import './html'; line 1, the PUBLIC_BLOCKS 'html', row 1, export const HtmlSchema = BaseSchema.extend 1, RetiredHtmlNodeSchema 0). Tests resolve @object-ui/* through the root vitest resolve.alias to src, so no rebuild is in the path. The five pin files: Test Files 5 failed (5), Tests 11 failed | 65 passed (76): every new assertion red, every control green. Restore leg from HEAD inside a trap: git diff HEAD 0 bytes, git status --porcelain 0 lines, html.tsx absent, every restored path's hash equals its HEAD blob.

Local verification

Union run at 3c6faea (the head before the last commit), each exit captured to a file before reading, under the shared verify lock:

  • vitest run packages/types/ packages/core/ packages/sdui-parser/ packages/cli/ examples/schema-catalog/: Test Files 697 passed (697), Tests 17792 passed | 27 skipped (17819).
  • vitest run packages/components/ packages/fields/: Test Files 705 passed | 2 skipped (707), Tests 8517 passed | 31 skipped (8548).
  • vitest run apps/console/: Test Files 243 passed (243), Tests 2460 passed (2460).
  • 48 reader files outside those packages (every test that names PUBLIC_BLOCKS, getPublicConfigs, KNOWN_SCHEMA_TYPES, FIELD_TYPES_SKIP_FALLBACK, the examples file, the catalog, react-pages.md, ui:html, or the edited doc-types gate, plus unit-registry-absence-collision): Test Files 48 passed (48), Tests 1543 passed | 16 skipped (1559).
  • type-check: core, components, fields, cli exit 0; types exit 2 on one vacuous compile-time pin in the new test file ('kbd' is not a SchemaRegistry key), fixed in bfd3722.

Re-run at the final head bfd3722 (it changes only that one test file): type-check of types, core, components, fields, cli all exit 0; the changed file Test Files 1 passed (1), Tests 6 passed (6). Gates at bfd3722, all exit 0: check:doc-types, check:registry-bare-names, check:handler-key-reads, check:doc-example-ids, check:docs-route-closure, check:prompt-keys, check:entry-guard, check:action-forward-parity, check:spec-symbols, check:new-line-citations (VERDICT 0 new citations), check:control-bytes, check:doc-fences, check:unreferenced-sources, check:side-effects-array, check:doc-example-readers, check:icon-record-names, check:changeset-claims, check:pending-changeset-literals, check:test-path-roots, check:doc-json, check-changeset-presence ("22 source file(s) of 7 released package(s) changed, and this change declares 1 changeset(s)"), check-changeset-no-major, regenerate-catalog-index.py --check, regenerate-known-schema-types.mjs --check.

NOT MEASURED locally, left to CI: check:sdui-registration-pins (exit 2, needs a console build), check:readme-exports, check:doc-snippets, check:doc-examples (exit 2 / collapsed population, need the wider package build), and the apps/console and packages/app-shell type-checks (need the whole build closure).

Acceptance notes

  • objectstack's repo-root sdui.manifest.json names html ("description": "Raw HTML content"). It is generated from objectui's pinned build by scripts/gen-sdui-manifest-node.mjs, and check-sdui-manifest's staleness check forces the regenerate on the next .objectui-sha bump, which drops the entry. Carrier: that pin bump. Until then objectstack's server-side validateJsxPages still admits the tag, while the console's own compile already refuses it.
  • objectstack's ADR-0080 table names renderers/basic/html.tsx as the "today unsafe" escape hatch the constrained JSX tier "replaces"; this PR completes that replacement. The ADR is a dated decision record and is left as written.
  • docs/audits/2026-08-zod-to-json-schema-fidelity.md still lists HtmlSchema: a dated audit, left as written.

Session: https://claude.ai/code/session_01432GBvPzfZ6CsLanp9xzkZ


Generated by Claude Code

…ui#13449)

The `html` renderer wrote `schema.html` into the page through
dangerouslySetInnerHTML with no sanitiser, and @objectstack/spec declares
no html component. Remove the registration, `HtmlSchema` and its zod arm
(kept as a by-name refusal via retiredNodeType), the PUBLIC_BLOCKS entry,
the docs page and the catalog example; regenerate the CLI's known types.

Claude-Session: https://claude.ai/code/session_01432GBvPzfZ6CsLanp9xzkZ
Co-authored-by: Claude <noreply@anthropic.com>
…13449)

Lower the CLI's namespaced refused-at-type pin by `ui:html`, pin the
strict face's by-name refusal of `html`, and re-derive the DOM-leak
sweep's docblock counts without the `ui:html` target.

Claude-Session: https://claude.ai/code/session_01432GBvPzfZ6CsLanp9xzkZ
Co-authored-by: Claude <noreply@anthropic.com>
…ral (objectui#13449)

Add the breaking (minor-marked) changeset with the FROM/TO migration, and
declare the `html` FIELD type taught by content/docs/fields/rich-text.mdx
as a field-vocabulary exemption in check:doc-types: it passed only because
the retired node type registered the same bare key.

Claude-Session: https://claude.ai/code/session_01432GBvPzfZ6CsLanp9xzkZ
Co-authored-by: Claude <noreply@anthropic.com>
…gistry map declares (objectui#13449)

Claude-Session: https://claude.ai/code/session_01432GBvPzfZ6CsLanp9xzkZ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 85 chunks) 3006.2 KB 3496.0 KB
Main entry chunk (gzip) 80.3 KB 350 KB
Entry file index-BT62FVUG.js —
Status PASS —

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

Package Size Gzipped
app-shell (buildIdentity.js) 0.80KB 0.49KB
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.41KB 7.90KB
app-shell (runtime-config.js) 24.52KB 8.48KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 8.84KB 3.61KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 614.45KB 150.43KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 253.04KB 70.68KB
fields (index.js) 295.90KB 75.88KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.17KB 11.96KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (ServerReadScope.js) 4.03KB 1.82KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.99KB 0.43KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.91KB
plugin-calendar (index.js) 55.41KB 16.19KB
plugin-charts (index.js) 88.30KB 24.33KB
plugin-chatbot (core.js) 5.22KB 2.18KB
plugin-chatbot (index.js) 237.93KB 56.26KB
plugin-dashboard (index.js) 147.15KB 39.95KB
plugin-designer (index.js) 232.83KB 50.01KB
plugin-detail (index.js) 267.21KB 71.14KB
plugin-editor (index.js) 2.21KB 1.04KB
plugin-form (index.js) 185.46KB 48.48KB
plugin-gantt (index.js) 178.19KB 45.06KB
plugin-grid (index.js) 278.30KB 77.57KB
plugin-kanban (index.js) 64.27KB 19.81KB
plugin-list (index.js) 149.60KB 39.26KB
plugin-map (index.js) 27.11KB 8.99KB
plugin-markdown (index.js) 13.79KB 4.79KB
plugin-report (index.js) 46.42KB 13.06KB
plugin-timeline (index.js) 39.30KB 11.87KB
plugin-tree (index.js) 15.25KB 5.38KB
plugin-view (index.js) 94.26KB 24.27KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 129.13KB 42.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (product-blocks.js) 0.20KB 0.19KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: bfd37223da3589b7e724670f97fb4c09499a9841
Local-runs: none
Scope: apps/console/src/tests/html-node-retired-13449.test.tsx, apps/console/src/tests/html-tier-manifest.test.ts, apps/console/src/tests/public-contract.test.ts, examples/schema-catalog/src/index.ts, examples/schema-catalog/src/schemas/components-basic-html/basic-html.json, packages/app-shell/src/tests/widget-dom-leak-sweep.test.tsx, packages/cli/src/tests/registered-types-validate-ratchet-10859.test.ts, packages/cli/src/utils/known-schema-types.ts, packages/components/src/tests/basic-renderers.test.tsx, packages/components/src/renderers/basic/html.tsx, packages/components/src/renderers/basic/index.ts, packages/core/src/registry/public-blocks.ts, packages/fields/src/tests/registry-collision.test.ts, packages/fields/src/index.tsx, packages/types/examples/data-display-examples.json, packages/types/src/tests/arm-named-export-8784.test.ts, packages/types/src/tests/content-channel-family-d-9256.test.ts, packages/types/src/tests/html-node-retired-13449.test.ts, packages/types/src/tests/zod-mirror-parity.test.ts, packages/types/src/data-display.ts, packages/types/src/index.ts, packages/types/src/registry.ts, packages/types/src/zod/README.md, packages/types/src/zod/data-display.zod.ts, packages/types/src/zod/index.zod.ts, scripts/check-doc-component-types.mjs + the five prose faces (.changeset/13449-retire-html-renderer.md, content/docs/components/basic/html.mdx, content/docs/components/basic/meta.json, content/docs/components/index.md, content/docs/guide/react-pages.md), packages/types/src/zod/tombstone.zod.ts (retiredNodeType), packages/types/src/zod/complex.zod.ts (RetiredKanbanNodeSchema), packages/types/src/field-types.ts (HtmlFieldMetadata), packages/types/package.json (exports), packages/components/src/renderers/layout/page.tsx (getJsxManifest), packages/sdui-parser/src/parse.ts (forbidden-tag), packages/sdui-parser/src/validate.ts (unknown-component), packages/core/src/registry/Registry.ts (getPublicConfigs), packages/react/src/SchemaRenderer.tsx (the OBJUI-001 panel), content/docs/fields/rich-text.mdx, examples/schema-catalog/src/schemas/fields-rich-text/html-editor.json, scripts/regenerate-known-schema-types.mjs, scripts/check-governed-queue-guard.mjs (GOVERNED_SURFACES), the whole head tree grepped for HtmlSchema, ui:html and a node-typed html literal; in objectstack-ai/objectstack: sdui.manifest.json, scripts/check-sdui-manifest.mjs, packages/cli/src/utils/sdui-manifest.ts, docs/adr/0080 and .objectui-sha.

Inputs: card objectui#13449 (body and all three comments, the triage grade 6385669346, the claim 6386906709 and the os-dev-report 6387990912), PR objectui#13475 (body, 31-file list, net diff against main at the head's merge base a3c0da9), and the 36 check-runs on the head. Not the dispatch order. The implementer was a mode:subagent dev of the session named in Reviewed-by: below and this reviewer is another subagent of that same session with no shared context, so the independence pair reads SELF-REVIEW by session and isolated by context.

① Derived judgments

  • Accept set, zod face — RIGHT, narrowing only, refusal loud and named. DataDisplaySchema (and through it AnyComponentSchema, safeValidateSchema, StrictAnyComponentSchema and objectui validate) stops accepting a type: "html" node. The literal is now claimed by RetiredHtmlNodeSchema, a retiredNodeType('html', …) arm whose .check pushes one custom issue at type unconditionally and whose message names the two remedies (an html / richtext FIELD, a kind: 'html' page). Nothing refused before is accepted now. The arm is the shape of RetiredKanbanNodeSchema (objectui#8802) and the cloud:plan-status arm (objectui#12954); it has no TypeScript twin, is exported by its module only, and @object-ui/types's exports map reaches ./zod through index.zod.ts, which does not re-export it — so nothing published parses against it. Pinned with a firing control (kbd still parses) and a separator control (an unknown literal takes the generic path without the retirement text).
  • Public surface, @object-ui/types — RIGHT, nothing dangles. The HtmlSchema interface leaves data-display.ts, so BOTH published entries that carried it lose the export: . (index.ts) and the ./data-display subpath (the changeset says "from the package entry"; the subpath goes with it and FROM → TO "there is no such export" still covers it). The SchemaRegistry row 'html' leaves, so ComponentType narrows (compile-time pins with non-vacuity siblings). The zod HtmlSchema leaves ./zod. Head tree: no importer of HtmlSchema remains; zod-mirror-parity moves 205 → 204 with the refusal arm in EXCLUSIONS, not MIRRORS; arm-named-export-8784 carries an ABSENT_BY_DECISION row quoting the barrel's own sentence.
  • Registry — RIGHT. html.tsx is deleted and its import './html'; line is gone, so neither ui:html nor the bare html key is registered. @object-ui/fields keeps html in FIELD_TYPES_SKIP_FALLBACK, so the bare key resolves to NOTHING (the OBJUI-001 panel), never to the rich-text editor, and field:html stays the widget's only key. Pinned with lit controls (text / ui:text registered; field:html registered) in components, fields and the console graph. No alias, no fallback.
  • Curated tier — RIGHT. PUBLIC_BLOCKS loses html, so getPublicConfigs(), both console manifest producers and KNOWN_SCHEMA_TYPES (regenerated by its own generator; objectui check) stop naming it. public-contract's EXPECTED_COVERED and html-tier-manifest's curated pin read the live manifest, not the roster, so a stray registration would surface.
  • The kind: 'html' page tier — RIGHT, a measured second door. getJsxManifest() whitelists ComponentRegistry.getKnownTypes(), so at the base an html tag carrying an html attribute compiled with no diagnostic and reached the sink; at the head the parser's forbidden-tag and the validator's unknown-component refuse it. The console pin drives the real compile and SchemaRenderer over the full registration graph with a payload (an img element carrying an onerror attribute) and a control page that renders its text. content/docs/guide/react-pages.md now says there is no raw-HTML block on either tier.
  • Doc gate — RIGHT, not a tolerance shim. The check-doc-component-types.mjs exemption for content/docs/fields/rich-text.mdx names the FIELD literal (HtmlFieldMetadata), the gate's designed carrier with a written reason, and the gate's own "no longer matches the tree" leg guards its staleness. Both type: 'html' on that page are the field type; the catalog's fields-rich-text/html-editor document is the field type and the types test's suite 4 pins that it still validates.
  • Shifted counts — RIGHT, two-way pins kept. widget-dom-leak-sweep 150/82/133/147/168 → 149/81/132/146/167 with componentsClean 68 unchanged (149 − 81 = 68); content-channel-family-d 67 → 66 rows and 66 × 2 − 3 cases; NAMESPACED_REFUSED_AT_TYPE 307 → 306 with ui:html named as the one leaver and the bare count unmoved (the bare key was armed, its arm became a refusal).
  • Dangling check — RIGHT. At the head HtmlSchema survives only in the changeset, retirement comments and a dated audit; ui:html only in tests and comments; no README and nothing under skills/** names the node; the docs page, its meta.json row and its index link leave together; the catalog example and its generated index leave together; data-display-examples.json's html entry leaves with no reader expecting it. Not governed: the 31 paths hit none of objectui's GOVERNED_SURFACES rows.
  • Gate verdicts on this head. Every check-run is success — Repo Gates, Lint, Type Check, Test shards 1..16/16, Test (dist pins), Build & E2E, Build Docs, Governed Surface Queue Guard, Line Citation Gate, Changeset Bump Policy / Declaration / Claim Re-read / Fixed Group Check / Overwrite Report, Bundle Analysis, Live E2E; only the path-gated coverage shards and dependabot are skipped. No red on the head.

② Semver level

  • Level and packages — RIGHT. minor on @object-ui/components, @object-ui/types, @object-ui/core, @object-ui/cli: exactly the four whose published behaviour moves. @object-ui/fields moved a comment and regrouped one Set member with no behaviour change, is not named, and the fixed group bumps it regardless. Metadata-protocol: narrowed (a node type removed) takes minor; no major, per this repository's version policy (Changeset Bump Policy green). The body states the breaking semantics, carries FROM → TO for each of the three surfaces (node, public block, schema) and the one-line fix, and separates the untouched html FIELD type. The title carries !.
  • The declaration line — WRONG on both carriers. The PR body and the changeset each spell the line as Clause-②: no with NO direction arm. The fleet's one reader of that line (clause2-line.mjs, CLAUSE2_ARMS) documents a bare no as declaring NO DIRECTION; the arm exists because a breaking narrowing hidden under a bare no is the measured gap the maintainer ruled closed on objectstack#16421 — and this diff is that case exactly: a published accept set and three exports narrowed. The recogniser (reviewFace), run over this PR's 31 paths, refuses to read the narrowing face off the committed changeset and says why — the changeset's line decides, and only the (narrowing) arm selects the narrowing face; without it the face is declaration, whose tier is TIER_DEFAULT. So as committed, the PR declares itself owed a default-tier review, and the CONTRACT_REVIEW_TIER review it actually owes stands on the dispatch's hand-mandate alone. The changeset also publishes verbatim into CHANGELOG.md, where a published paragraph is never re-addressed. objectui's own precedent for this exact shape — the cloud:plan-status retirement (objectui#12954) this PR cites, the filterableFields retirement, the DashboardWidgetSchema['type'] narrowing — all read Clause-②: no (narrowing) in packages/types/CHANGELOG.md.
    • Origin, named: the claim (comment 6386906709) spelled the bare no while its own prose reads "narrows … (BREAKING, ! changeset)", and os-dev.md binds the dev to copy the claim's two lines into the PR body verbatim and never to invent — so the dev's copy is faithful and the misdeclaration is the claim's.
    • Fix, one token on two carriers plus the claim: the dispatch seat amends the claim's line on the card to Clause-②: no (narrowing) (the dev may not diverge from the claim on its own), then the PR body's line and the changeset's line take the same spelling. That is a new head; this record is of bfd3722 only.

③ Boundary flags

Each flag in the os-dev-report (comment 6387990912), answered:

  1. Full vitest union taken at 3c6faea, not the final head — answered by the head's own check-runs: Test shards 1..16/16, Test (dist pins), Build & E2E and Type Check are success on bfd3722. Closed.
  2. File surface beyond the claim's list — every extra path is a list that named html as a node type (roster, registry map, barrel, two generated snapshots, the catalog, count pins, one doc-gate exemption); the claim said "stop on breach; explain in the report" and the report explains; no governed row is hit. Accepted.
  3. Attribution — all five branch commits end with the model-free pair (Claude-Session: and Co-authored-by: Claude); the PR body carries the session-URL footer; a comment carries the bare form, as below. Correct under the precedence both AGENTS.md files state. Accepted.
  4. First lock call queued without OS_VERIFY_LOCK_SLOT — a process note with no artefact. Accepted.
  5. Temporary probe test — absent from the head tree (no zz-probe path); its reading is carried by the committed console pin. Accepted.
  6. Worktree removed after the PR opened — process. Accepted.
  7. Out-of-scope (carrier: objectstack's next .objectui-sha bump) — objectstack's repo-root sdui.manifest.json still names html ("Raw HTML content"): confirmed at objectstack's current tree. check-sdui-manifest.mjs check 3 (STALENESS: the record's objectuiSha must equal .objectui-sha) reds the bump PR until the manifest is regenerated from the new pin, which drops the entry. Until then objectstack's validateJsxPages reads that components map as its tag set and admits the tag, AND the pinned console build (.objectui-sha at 2958f17e…) still RENDERS the node — the shipped console keeps the sink until the bump. ESCALATED to the PM: the pin bump is the carrier of this p1 fix for objectstack's shipped console and should follow this landing promptly; its manifest regenerate is forced by that gate. Not a defect of this PR.
  8. Out-of-scope (no carrier) — ADR-0080 names renderers/basic/html.tsx as the "today unsafe" escape hatch the constrained-JSX tier "replaces", in two rows; this PR completes that replacement, and a dated decision record is left as written. docs/audits/2026-08-zod-to-json-schema-fidelity.md lists HtmlSchema: dated, left. Accepted.
  9. Reviewer's note — the os-dev-report cites rich-text.mdx by two line addresses in a card comment (Commandment [WIP] Update documentation for project #11's card clause); nothing enforces it there, and the PR body and the diff are clean (Line Citation Gate: 0 new citations). Note only.

Implemented-by: claude/issue-13449-retire-html-renderer
Reviewed-by: session_01432GBvPzfZ6CsLanp9xzkZ

VERDICT: FAIL — one finding, in ②: a breaking narrowing declared without its (narrowing) arm on both carriers (and on the claim it was copied from). The code, the pins, the semver level and the migration are right; the remedy is one token on three lines, after which the record is re-rendered on the new head.


Generated by Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 85 chunks) 3033.5 KB 3496.0 KB
Main entry chunk (gzip) 80.3 KB 350 KB
Entry file index-abWd99hm.js —
Status PASS —

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

Package Size Gzipped
app-shell (buildIdentity.js) 0.80KB 0.49KB
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 21.41KB 7.90KB
app-shell (runtime-config.js) 24.52KB 8.48KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 8.84KB 3.61KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 614.45KB 150.43KB
core (index.js) 10.51KB 4.16KB
create-plugin (index.js) 27.92KB 9.50KB
data-objectstack (index.js) 253.04KB 70.68KB
fields (index.js) 295.90KB 75.88KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.52KB 2.39KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 41.17KB 11.96KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
permissions (ServerReadScope.js) 4.03KB 1.82KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.99KB 0.43KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.01KB 3.91KB
plugin-calendar (index.js) 55.31KB 16.16KB
plugin-charts (index.js) 88.30KB 24.33KB
plugin-chatbot (core.js) 5.22KB 2.18KB
plugin-chatbot (index.js) 237.93KB 56.26KB
plugin-dashboard (index.js) 147.15KB 39.95KB
plugin-designer (index.js) 232.83KB 50.01KB
plugin-detail (index.js) 268.92KB 71.56KB
plugin-editor (index.js) 2.21KB 1.04KB
plugin-form (index.js) 185.46KB 48.48KB
plugin-gantt (index.js) 178.19KB 45.06KB
plugin-grid (index.js) 277.62KB 77.39KB
plugin-kanban (index.js) 64.27KB 19.81KB
plugin-list (index.js) 149.51KB 39.15KB
plugin-map (index.js) 27.54KB 9.10KB
plugin-markdown (index.js) 13.79KB 4.79KB
plugin-report (index.js) 46.42KB 13.06KB
plugin-timeline (index.js) 39.74KB 12.00KB
plugin-tree (index.js) 15.25KB 5.38KB
plugin-view (index.js) 93.37KB 24.00KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 129.13KB 42.23KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.71KB 2.11KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.78KB 2.70KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (node-slots.js) 7.18KB 2.34KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (product-blocks.js) 0.20KB 0.19KB
types (record-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 0841978a548e74c745e103544717496f1ddb7273
Local-runs: none
Scope: apps/console/src/tests/html-node-retired-13449.test.tsx, apps/console/src/tests/html-tier-manifest.test.ts, apps/console/src/tests/public-contract.test.ts, examples/schema-catalog/src/index.ts, examples/schema-catalog/src/schemas/components-basic-html/basic-html.json, packages/app-shell/src/tests/widget-dom-leak-sweep.test.tsx, packages/cli/src/tests/registered-types-validate-ratchet-10859.test.ts, packages/cli/src/utils/known-schema-types.ts, packages/components/src/tests/basic-renderers.test.tsx, packages/components/src/renderers/basic/html.tsx, packages/components/src/renderers/basic/index.ts, packages/core/src/registry/public-blocks.ts, packages/fields/src/tests/registry-collision.test.ts, packages/fields/src/index.tsx, packages/types/examples/data-display-examples.json, packages/types/src/tests/arm-named-export-8784.test.ts, packages/types/src/tests/content-channel-family-d-9256.test.ts, packages/types/src/tests/html-node-retired-13449.test.ts, packages/types/src/tests/zod-mirror-parity.test.ts, packages/types/src/data-display.ts, packages/types/src/index.ts, packages/types/src/registry.ts, packages/types/src/zod/README.md, packages/types/src/zod/data-display.zod.ts, packages/types/src/zod/index.zod.ts, scripts/check-doc-component-types.mjs + the five prose faces (.changeset/13449-retire-html-renderer.md, content/docs/components/basic/html.mdx, content/docs/components/basic/meta.json, content/docs/components/index.md, content/docs/guide/react-pages.md) and every cited file the record on the previous head (comment 6393057967) lists, re-read where the delta touches them: the changeset, the PR body and the claim comment 6386906709 as the three carriers of the Clause-②: line; scripts/pm/clause2-line.mjs (readClause2Line, readMetadataProtocolLine) and scripts/pm/record-recognisers.mjs (reviewFace) in objectstack-ai/objectstack as the fleet's readers of those carriers.

Inputs: card objectui#13449 (body and all four comments: the triage grade 6385669346, the amended claim 6386906709, the os-dev-reports 6387990912 and 6393077333), PR objectui#13475 (body, 31-file list, net diff against main at the head's merge base a3c0da9), and the 35 check-runs on the head. Not the dispatch order. Same independence pair as before: the implementer was a mode:subagent dev of the session named in Reviewed-by: and this reviewer is another subagent of that session with no shared context — SELF-REVIEW by session, isolated by context.

The delta since the previous record

0841978 is a fast-forward of bfd3722 by exactly one commit, fix(changeset): declare the Clause-② arm as no (narrowing), whose diff is one line in .changeset/13449-retire-html-renderer.md: Clause-②: no → Clause-②: no (narrowing). Verified: bfd3722 is an ancestor of 0841978; git diff bfd3722 0841978 --stat reads 1 file, +1 −1; the net diff against the merge base a3c0da9 is byte-identical to the previous head's outside that file, with the same 31-file list and the same +487 −227; the PR's base sha is unchanged. The commit carries the model-free trailer pair. So every judgment in ① and ③ of comment 6393057967 is carried here BY REFERENCE: the code, the pins, the docs and the generated snapshots they judged are the same bytes.

① Derived judgments

Unchanged from comment 6393057967, carried by reference — the diff outside the changeset is byte-identical. In one line each: the zod face narrows only, through the unexported retiredNodeType('html', …) arm that refuses by name at type with its remedies; HtmlSchema leaves ., ./data-display and ./zod with no dangling importer; neither html nor ui:html is registered and the bare key resolves to the OBJUI-001 panel while field:html stays; PUBLIC_BLOCKS, getPublicConfigs(), the manifest producers and KNOWN_SCHEMA_TYPES drop it; the kind: 'html' second door is closed by forbidden-tag / unknown-component and pinned through the real compiler and renderer; the doc-gate exemption names a FIELD literal; shifted counts are arithmetically consistent with two-way pins; no governed row is hit. All RIGHT.

② Semver level

  • Level and packages — RIGHT, as before: minor on @object-ui/components, @object-ui/types, @object-ui/core, @object-ui/cli; Metadata-protocol: narrowed takes minor; no major; FROM → TO per surface and the one-line fix; the untouched html FIELD type separated; ! in the title.
  • The declaration line — now RIGHT on all three carriers. The changeset at 0841978, the PR body as it stands, and the claim comment 6386906709 each carry the line-initial Clause-②: no (narrowing) and Metadata-protocol: narrowed. Read through the fleet's one reader, readClause2Line answers declared, value no, arm narrowing on all three, and readMetadataProtocolLine answers narrowed. reviewFace over the PR's 31 paths with the committed changeset and the PR body handed in now answers the narrowing face at CONTRACT_REVIEW_TIER; the control with the previous spelling answers the declaration face at TIER_DEFAULT. The tier this record is served at now stands on the PR's own declaration, not on the dispatch's hand-mandate. The claim carries an amendment note naming the finding's source. Closed.

③ Boundary flags

  • Flags 1–9 of comment 6393057967: carried by reference, unchanged — the diff they were answered on is byte-identical. The escalation stands: objectstack's .objectui-sha bump is the carrier of this p1 fix for the shipped console (its tracked sdui.manifest.json still names html and the pinned console build still renders the node until then; check-sdui-manifest check 3 forces the manifest regenerate on the bump).
  • The patch round's os-dev-report (comment 6393077333): no deviation and no out-of-scope finding declared; it states "no code touched; no tests run" and that only readClause2Line was called as a module — a read of a declaration line, not a run of the PR. Consistent with the one-line delta measured above. Accepted.
  • Check-runs on 0841978, read at the time of writing (35): success — Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check, Changeset Overwrite Report, Governed Surface Queue Guard, Line Citation Gate, Test (dist pins), Build & E2E, Live E2E (informational), label; skipped (path-gated) — Test (coverage), the coverage shards, dependabot; in_progress — Type Check, Lint, Repo Gates, Test shards 1..16/16, Build Docs, Bundle Analysis. The in-progress set is the farm re-running on a head whose only delta is a prose line in a changeset the farm's path decision skips; on bfd3722 every one of them was success. An in-progress check is not a FAIL; the landing seat re-reads CI on this head before it arms anything, and a red there is a new fact this record does not pre-judge.

Implemented-by: claude/issue-13449-retire-html-renderer
Reviewed-by: session_01432GBvPzfZ6CsLanp9xzkZ

VERDICT: PASS — the one finding of the previous record is closed on all three carriers; everything else is the same bytes and the same judgments. Landing waits only on the in-progress check-runs named above turning success.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT — domain:ui seat 1 · session_01432GBvPzfZ6CsLanp9xzkZ · 2026-10-25T03:59Z · head 0841978a548e74c745e103544717496f1ddb7273

  • Shape: draft PR into main. Line 1 is Fixes #13449; no closing keyword sits beside any other open card. Clause-②: no (narrowing) and Metadata-protocol: narrowed are line-initial in the body, in the changeset, and on the amended claim 6386906709. The title carries !. Not governed (check-governed-merges --pr: 0 of 31 paths). +487 −227, under 3000. No content/docs/releases/ change.
  • Contract review of record: PASS at CONTRACT_REVIEW_TIER on this head, comment 6393106376. It supersedes the FAIL 6393057967 on bfd3722, whose one finding (the missing (narrowing) arm, which originated in the seat's own claim) is closed on all three carriers. That FAIL was the seat's error, not the dev's: the dev copied the claim faithfully.
  • Gates: check-runs on this head, read 2026-10-25T03:59Z: 33 success and 3 skipped (coverage lanes and dependabot), 0 red. That includes Type Check, Lint, Repo Gates, all 16 test shards, Build & E2E and Build Docs.
  • Prose read sentence by sentence against the diff: each of the changeset's three FROM → TO bullets (SDUI node html / ui:html unregistered; html dropped from PUBLIC_BLOCKS; HtmlSchema removed with an unexported refusal arm); its "the html FIELD type … stays" paragraph; the one-line fix; and the content/docs/guide/react-pages.md paragraph plus the components/basic/meta.json row removal. All true to the code, as the review's ① confirms.
  • Dogfood: n/a. objectui has no @objectstack/dogfood package. The breaking ! is covered by the full CI farm on this head (16 shards, Build & E2E) and the dev's ablation (11 new assertions red with the renderer restored, every control green).
  • Deviations accepted: a file surface wider than the claim's list (every list naming html as a node type, the two regenerated snapshots, and one doc-gate exemption for the FIELD literal), as the dispatch asked. The vitest union ran at 3c6faea; CI on this head is the verdict.
  • Out of scope: objectstack's shipped console keeps rendering the sink until objectstack bumps .objectui-sha (its tracked sdui.manifest.json still names html, and check-sdui-manifest forces the regenerate on that bump). That bump is objectstack's own single card, per the lane rule, not a rider here; it goes in this seat's round report as the follow-up that carries this p1 fix to the shipped console. ADR-0080 and the dated zod-fidelity audit stay as written. Nothing filed from this PR.
  • Landing: pr_ready + automerge_enable through the relay. Then follow to MERGED; the card closes through Fixes.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 25, 2026 04:00
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 25, 2026 04:00
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit 610d9ef Oct 25, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-13449-retire-html-renderer branch October 25, 2026 04:15
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security(components): the ui 'html' renderer injects schema.html unsanitised — the spec declares no html component; retire the type

2 participants