docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one - #13734

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case
Aug 31, 2026
Merged

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one#13734
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#12650

What was wrong

content/docs/ui/react-pages.mdx is the documentation home for both source-authoring
tiers — content/docs/ui/pages.mdx sends the html-tier reader here for "the html and
react source-authoring tiers in full" — but everything from the security-gate section
down is react-tier material carrying no tier marker. The naming sentence was unqualified:

Blocks are referenced by the PascalCase form of their registered type: object-form
to ObjectForm, list-view to ListView, object-chart to ObjectChart.

So an author who reads the page top to bottom and then writes a kind:'html' page copies
ListView, is rejected at save time with "is not an allowed component", and finds nothing
on the page naming the rule they broke. The html-tier section already said "only tags in
the public block manifest are accepted" and "an unknown tag is a hard error at save time"
without ever saying how those tags are spelled — that is the exact gap.

Evidence from the registration code

The html tier's whitelist is the registry's own type names, compared by exact string:

  • packages/sdui-parser/src/index.tscompile() builds
    const allowedTags = new Set(Object.keys(manifest.components)).
  • packages/sdui-parser/src/index.tsmanifestFromConfigs() keys the manifest by the
    registry config's own type: components[c.type] = { type: c.type, ... }.
  • packages/sdui-parser/src/parse.ts:61 — the check is
    !this.opts.allowedTags.has(tag). An exact Set.has; there is no case folding
    anywhere in the parser, and the parsed node keeps type: tag verbatim.
  • sdui.manifest.json (objectui's registry-inputs dump, 57 components) — zero keys
    begin with an uppercase letter. list-view present, ListView absent; object-form
    present, ObjectForm absent.
  • packages/spec/src/ui/react-blocks.tsReactBlockDef.tag is documented as "PascalCase
    name the author writes in JSX" and schemaType as "The registry/render type, e.g.
    object-form". PascalCase is a react-tier projection, not a registered name.

Compiled against that real manifest (compile(source, sdui.manifest.json)):

tag in sourceokdiagnostic
ListViewfalseListView is not an allowed component
list-viewtruetag accepted
ObjectFormfalseObjectForm is not an allowed component
object-formtruetag accepted
RecordRelatedListfalseRecordRelatedList is not an allowed component
record:related_listtag accepted (fails later on a required prop, not on the tag)
flextruetag accepted

One correction to the card's wording

"kebab-case" is the shape of the plain block names but not the whole rule: 28 of the 57
registered names carry a namespace prefix (record:, page:, element:, action:), and
four are snake_case after the colon — record:related_list, record:line_items,
record:quick_actions, record:reference_rail. Writing only "kebab-case" would have sent
an author to record:related-list, which is not registered either. The page therefore says
registered type names, written verbatim, with kebab-case named as the shape and a
namespaced example shown.

The change

Two body edits, no restructuring of either tier section:

  1. The html-tier section gains one paragraph naming the registered spelling (with the
    plain, namespaced and layout-container examples) and naming the error a borrowed
    PascalCase tag produces.
  2. The react-tier naming sentence is scoped with "On this tier" and points at the html
    spelling.

Headings, frontmatter and every table are untouched — this stays clear of epic #12243's
content/docs/** frontmatter/headings slice.

Verification

pnpm check:doc-anchors, check:doc-authoring, check:docs-single-h1,
check:doc-frontmatter, check:react-page-adapter-contract and the rest of the derived
family: 25 GREEN, 0 RED, 1 NOT MEASURED, run at 6c5a1a754 (the final commit). The
26-family list came from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
re-derived after the commit; NOT MEASURED is scripts/check-test-completeness.mjs, which
prints "PREREQUISITE NOT MET" because it grades a saved turbo run test log that no local
run produced — its own text says to record it as NOT MEASURED locally.

check:nul-bytes green (7572 files); the edited file also scanned directly for raw ASCII
control bytes, none found. The MDX itself compiles (@mdx-js/mdxcompile() on the edited
file, frontmatter stripped, output 33002 chars) — worth checking because the added text puts
tag names with angle brackets and a colon inside inline code spans.

Declared narrowing — repo-wide ESLint.pnpm lint was not run whole. Measured instead:
eslint --no-inline-config --format json content/docs/ui/react-pages.mdx reports 1 entry,
0 errors, and its single message is "File ignored because no matching configuration was
supplied" — the population comes from ESLint's own config, not my guess, so this .mdx
path is outside the linted set entirely. The diff touches that one file and no ESLint
config, and no type-aware linting is involved, so no untouched file's verdict can move.

Not addressed here

Docs-only, so no changeset — the diff publishes nothing from any package, and the
skip-changeset label carries that (recent content/docs/**-only merges land the same
way). objectstack-ai/objectui#6598, the html-tier columns binding defect from the same
recon, is out of scope: this documents naming only and papers over nothing.


Generated by Claude Code

…pe the PascalCase one
The html tier's allowed tags are the registry's own type names: `compile()` in
`@objectstack/sdui-parser` builds its whitelist as `Object.keys(manifest.components)`
and `parse.ts` matches the tag against it by exact string, with no case folding. All
57 keys in `sdui.manifest.json` are lowercase — `list-view` is there, `ListView` is
not — so a PascalCase tag borrowed from the react tier is rejected at save time with
"is not an allowed component".
The page never said so. Its PascalCase sentence was unqualified while the page is
also the documentation home for the html tier, so an author reading top to bottom
had no way to learn the naming rule before hitting the error.
Two body edits, no restructuring:
- the html-tier section now names the registered spelling, including the namespaced
and layout forms, and names the error a borrowed PascalCase tag produces;
- the react-tier sentence is scoped to that tier and points at the html spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 09:09
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 00a5e74Aug 31, 2026
35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12650-html-tier-kebab-case branch August 31, 2026 09:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one - #13734

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case
Aug 31, 2026
Merged

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one#13734
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#12650

What was wrong

content/docs/ui/react-pages.mdx is the documentation home for both source-authoring
tiers — content/docs/ui/pages.mdx sends the html-tier reader here for "the html and
react source-authoring tiers in full" — but everything from the security-gate section
down is react-tier material carrying no tier marker. The naming sentence was unqualified:

Blocks are referenced by the PascalCase form of their registered type: object-form
to ObjectForm, list-view to ListView, object-chart to ObjectChart.

So an author who reads the page top to bottom and then writes a kind:'html' page copies
ListView, is rejected at save time with "is not an allowed component", and finds nothing
on the page naming the rule they broke. The html-tier section already said "only tags in
the public block manifest are accepted" and "an unknown tag is a hard error at save time"
without ever saying how those tags are spelled — that is the exact gap.

Evidence from the registration code

The html tier's whitelist is the registry's own type names, compared by exact string:

  • packages/sdui-parser/src/index.tscompile() builds
    const allowedTags = new Set(Object.keys(manifest.components)).
  • packages/sdui-parser/src/index.tsmanifestFromConfigs() keys the manifest by the
    registry config's own type: components[c.type] = { type: c.type, ... }.
  • packages/sdui-parser/src/parse.ts:61 — the check is
    !this.opts.allowedTags.has(tag). An exact Set.has; there is no case folding
    anywhere in the parser, and the parsed node keeps type: tag verbatim.
  • sdui.manifest.json (objectui's registry-inputs dump, 57 components) — zero keys
    begin with an uppercase letter. list-view present, ListView absent; object-form
    present, ObjectForm absent.
  • packages/spec/src/ui/react-blocks.tsReactBlockDef.tag is documented as "PascalCase
    name the author writes in JSX" and schemaType as "The registry/render type, e.g.
    object-form". PascalCase is a react-tier projection, not a registered name.

Compiled against that real manifest (compile(source, sdui.manifest.json)):

tag in sourceokdiagnostic
ListViewfalseListView is not an allowed component
list-viewtruetag accepted
ObjectFormfalseObjectForm is not an allowed component
object-formtruetag accepted
RecordRelatedListfalseRecordRelatedList is not an allowed component
record:related_listtag accepted (fails later on a required prop, not on the tag)
flextruetag accepted

One correction to the card's wording

"kebab-case" is the shape of the plain block names but not the whole rule: 28 of the 57
registered names carry a namespace prefix (record:, page:, element:, action:), and
four are snake_case after the colon — record:related_list, record:line_items,
record:quick_actions, record:reference_rail. Writing only "kebab-case" would have sent
an author to record:related-list, which is not registered either. The page therefore says
registered type names, written verbatim, with kebab-case named as the shape and a
namespaced example shown.

The change

Two body edits, no restructuring of either tier section:

  1. The html-tier section gains one paragraph naming the registered spelling (with the
    plain, namespaced and layout-container examples) and naming the error a borrowed
    PascalCase tag produces.
  2. The react-tier naming sentence is scoped with "On this tier" and points at the html
    spelling.

Headings, frontmatter and every table are untouched — this stays clear of epic #12243's
content/docs/** frontmatter/headings slice.

Verification

pnpm check:doc-anchors, check:doc-authoring, check:docs-single-h1,
check:doc-frontmatter, check:react-page-adapter-contract and the rest of the derived
family: 25 GREEN, 0 RED, 1 NOT MEASURED, run at 6c5a1a754 (the final commit). The
26-family list came from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
re-derived after the commit; NOT MEASURED is scripts/check-test-completeness.mjs, which
prints "PREREQUISITE NOT MET" because it grades a saved turbo run test log that no local
run produced — its own text says to record it as NOT MEASURED locally.

check:nul-bytes green (7572 files); the edited file also scanned directly for raw ASCII
control bytes, none found. The MDX itself compiles (@mdx-js/mdxcompile() on the edited
file, frontmatter stripped, output 33002 chars) — worth checking because the added text puts
tag names with angle brackets and a colon inside inline code spans.

Declared narrowing — repo-wide ESLint.pnpm lint was not run whole. Measured instead:
eslint --no-inline-config --format json content/docs/ui/react-pages.mdx reports 1 entry,
0 errors, and its single message is "File ignored because no matching configuration was
supplied" — the population comes from ESLint's own config, not my guess, so this .mdx
path is outside the linted set entirely. The diff touches that one file and no ESLint
config, and no type-aware linting is involved, so no untouched file's verdict can move.

Not addressed here

Docs-only, so no changeset — the diff publishes nothing from any package, and the
skip-changeset label carries that (recent content/docs/**-only merges land the same
way). objectstack-ai/objectui#6598, the html-tier columns binding defect from the same
recon, is out of scope: this documents naming only and papers over nothing.


Generated by Claude Code

…pe the PascalCase one
The html tier's allowed tags are the registry's own type names: `compile()` in
`@objectstack/sdui-parser` builds its whitelist as `Object.keys(manifest.components)`
and `parse.ts` matches the tag against it by exact string, with no case folding. All
57 keys in `sdui.manifest.json` are lowercase — `list-view` is there, `ListView` is
not — so a PascalCase tag borrowed from the react tier is rejected at save time with
"is not an allowed component".
The page never said so. Its PascalCase sentence was unqualified while the page is
also the documentation home for the html tier, so an author reading top to bottom
had no way to learn the naming rule before hitting the error.
Two body edits, no restructuring:
- the html-tier section now names the registered spelling, including the namespaced
and layout forms, and names the error a borrowed PascalCase tag produces;
- the react-tier sentence is scoped to that tier and points at the html spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 09:09
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 00a5e74Aug 31, 2026
35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12650-html-tier-kebab-case branch August 31, 2026 09:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one - #13734

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case
Aug 31, 2026
Merged

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one#13734
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#12650

What was wrong

content/docs/ui/react-pages.mdx is the documentation home for both source-authoring
tiers — content/docs/ui/pages.mdx sends the html-tier reader here for "the html and
react source-authoring tiers in full" — but everything from the security-gate section
down is react-tier material carrying no tier marker. The naming sentence was unqualified:

Blocks are referenced by the PascalCase form of their registered type: object-form
to ObjectForm, list-view to ListView, object-chart to ObjectChart.

So an author who reads the page top to bottom and then writes a kind:'html' page copies
ListView, is rejected at save time with "is not an allowed component", and finds nothing
on the page naming the rule they broke. The html-tier section already said "only tags in
the public block manifest are accepted" and "an unknown tag is a hard error at save time"
without ever saying how those tags are spelled — that is the exact gap.

Evidence from the registration code

The html tier's whitelist is the registry's own type names, compared by exact string:

  • packages/sdui-parser/src/index.tscompile() builds
    const allowedTags = new Set(Object.keys(manifest.components)).
  • packages/sdui-parser/src/index.tsmanifestFromConfigs() keys the manifest by the
    registry config's own type: components[c.type] = { type: c.type, ... }.
  • packages/sdui-parser/src/parse.ts:61 — the check is
    !this.opts.allowedTags.has(tag). An exact Set.has; there is no case folding
    anywhere in the parser, and the parsed node keeps type: tag verbatim.
  • sdui.manifest.json (objectui's registry-inputs dump, 57 components) — zero keys
    begin with an uppercase letter. list-view present, ListView absent; object-form
    present, ObjectForm absent.
  • packages/spec/src/ui/react-blocks.tsReactBlockDef.tag is documented as "PascalCase
    name the author writes in JSX" and schemaType as "The registry/render type, e.g.
    object-form". PascalCase is a react-tier projection, not a registered name.

Compiled against that real manifest (compile(source, sdui.manifest.json)):

tag in sourceokdiagnostic
ListViewfalseListView is not an allowed component
list-viewtruetag accepted
ObjectFormfalseObjectForm is not an allowed component
object-formtruetag accepted
RecordRelatedListfalseRecordRelatedList is not an allowed component
record:related_listtag accepted (fails later on a required prop, not on the tag)
flextruetag accepted

One correction to the card's wording

"kebab-case" is the shape of the plain block names but not the whole rule: 28 of the 57
registered names carry a namespace prefix (record:, page:, element:, action:), and
four are snake_case after the colon — record:related_list, record:line_items,
record:quick_actions, record:reference_rail. Writing only "kebab-case" would have sent
an author to record:related-list, which is not registered either. The page therefore says
registered type names, written verbatim, with kebab-case named as the shape and a
namespaced example shown.

The change

Two body edits, no restructuring of either tier section:

  1. The html-tier section gains one paragraph naming the registered spelling (with the
    plain, namespaced and layout-container examples) and naming the error a borrowed
    PascalCase tag produces.
  2. The react-tier naming sentence is scoped with "On this tier" and points at the html
    spelling.

Headings, frontmatter and every table are untouched — this stays clear of epic #12243's
content/docs/** frontmatter/headings slice.

Verification

pnpm check:doc-anchors, check:doc-authoring, check:docs-single-h1,
check:doc-frontmatter, check:react-page-adapter-contract and the rest of the derived
family: 25 GREEN, 0 RED, 1 NOT MEASURED, run at 6c5a1a754 (the final commit). The
26-family list came from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
re-derived after the commit; NOT MEASURED is scripts/check-test-completeness.mjs, which
prints "PREREQUISITE NOT MET" because it grades a saved turbo run test log that no local
run produced — its own text says to record it as NOT MEASURED locally.

check:nul-bytes green (7572 files); the edited file also scanned directly for raw ASCII
control bytes, none found. The MDX itself compiles (@mdx-js/mdxcompile() on the edited
file, frontmatter stripped, output 33002 chars) — worth checking because the added text puts
tag names with angle brackets and a colon inside inline code spans.

Declared narrowing — repo-wide ESLint.pnpm lint was not run whole. Measured instead:
eslint --no-inline-config --format json content/docs/ui/react-pages.mdx reports 1 entry,
0 errors, and its single message is "File ignored because no matching configuration was
supplied" — the population comes from ESLint's own config, not my guess, so this .mdx
path is outside the linted set entirely. The diff touches that one file and no ESLint
config, and no type-aware linting is involved, so no untouched file's verdict can move.

Not addressed here

Docs-only, so no changeset — the diff publishes nothing from any package, and the
skip-changeset label carries that (recent content/docs/**-only merges land the same
way). objectstack-ai/objectui#6598, the html-tier columns binding defect from the same
recon, is out of scope: this documents naming only and papers over nothing.


Generated by Claude Code

…pe the PascalCase one
The html tier's allowed tags are the registry's own type names: `compile()` in
`@objectstack/sdui-parser` builds its whitelist as `Object.keys(manifest.components)`
and `parse.ts` matches the tag against it by exact string, with no case folding. All
57 keys in `sdui.manifest.json` are lowercase — `list-view` is there, `ListView` is
not — so a PascalCase tag borrowed from the react tier is rejected at save time with
"is not an allowed component".
The page never said so. Its PascalCase sentence was unqualified while the page is
also the documentation home for the html tier, so an author reading top to bottom
had no way to learn the naming rule before hitting the error.
Two body edits, no restructuring:
- the html-tier section now names the registered spelling, including the namespaced
and layout forms, and names the error a borrowed PascalCase tag produces;
- the react-tier sentence is scoped to that tier and points at the html spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 09:09
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 00a5e74Aug 31, 2026
35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12650-html-tier-kebab-case branch August 31, 2026 09:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one - #13734

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case
Aug 31, 2026
Merged

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one#13734
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#12650

What was wrong

content/docs/ui/react-pages.mdx is the documentation home for both source-authoring
tiers — content/docs/ui/pages.mdx sends the html-tier reader here for "the html and
react source-authoring tiers in full" — but everything from the security-gate section
down is react-tier material carrying no tier marker. The naming sentence was unqualified:

Blocks are referenced by the PascalCase form of their registered type: object-form
to ObjectForm, list-view to ListView, object-chart to ObjectChart.

So an author who reads the page top to bottom and then writes a kind:'html' page copies
ListView, is rejected at save time with "is not an allowed component", and finds nothing
on the page naming the rule they broke. The html-tier section already said "only tags in
the public block manifest are accepted" and "an unknown tag is a hard error at save time"
without ever saying how those tags are spelled — that is the exact gap.

Evidence from the registration code

The html tier's whitelist is the registry's own type names, compared by exact string:

  • packages/sdui-parser/src/index.tscompile() builds
    const allowedTags = new Set(Object.keys(manifest.components)).
  • packages/sdui-parser/src/index.tsmanifestFromConfigs() keys the manifest by the
    registry config's own type: components[c.type] = { type: c.type, ... }.
  • packages/sdui-parser/src/parse.ts:61 — the check is
    !this.opts.allowedTags.has(tag). An exact Set.has; there is no case folding
    anywhere in the parser, and the parsed node keeps type: tag verbatim.
  • sdui.manifest.json (objectui's registry-inputs dump, 57 components) — zero keys
    begin with an uppercase letter. list-view present, ListView absent; object-form
    present, ObjectForm absent.
  • packages/spec/src/ui/react-blocks.tsReactBlockDef.tag is documented as "PascalCase
    name the author writes in JSX" and schemaType as "The registry/render type, e.g.
    object-form". PascalCase is a react-tier projection, not a registered name.

Compiled against that real manifest (compile(source, sdui.manifest.json)):

tag in sourceokdiagnostic
ListViewfalseListView is not an allowed component
list-viewtruetag accepted
ObjectFormfalseObjectForm is not an allowed component
object-formtruetag accepted
RecordRelatedListfalseRecordRelatedList is not an allowed component
record:related_listtag accepted (fails later on a required prop, not on the tag)
flextruetag accepted

One correction to the card's wording

"kebab-case" is the shape of the plain block names but not the whole rule: 28 of the 57
registered names carry a namespace prefix (record:, page:, element:, action:), and
four are snake_case after the colon — record:related_list, record:line_items,
record:quick_actions, record:reference_rail. Writing only "kebab-case" would have sent
an author to record:related-list, which is not registered either. The page therefore says
registered type names, written verbatim, with kebab-case named as the shape and a
namespaced example shown.

The change

Two body edits, no restructuring of either tier section:

  1. The html-tier section gains one paragraph naming the registered spelling (with the
    plain, namespaced and layout-container examples) and naming the error a borrowed
    PascalCase tag produces.
  2. The react-tier naming sentence is scoped with "On this tier" and points at the html
    spelling.

Headings, frontmatter and every table are untouched — this stays clear of epic #12243's
content/docs/** frontmatter/headings slice.

Verification

pnpm check:doc-anchors, check:doc-authoring, check:docs-single-h1,
check:doc-frontmatter, check:react-page-adapter-contract and the rest of the derived
family: 25 GREEN, 0 RED, 1 NOT MEASURED, run at 6c5a1a754 (the final commit). The
26-family list came from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
re-derived after the commit; NOT MEASURED is scripts/check-test-completeness.mjs, which
prints "PREREQUISITE NOT MET" because it grades a saved turbo run test log that no local
run produced — its own text says to record it as NOT MEASURED locally.

check:nul-bytes green (7572 files); the edited file also scanned directly for raw ASCII
control bytes, none found. The MDX itself compiles (@mdx-js/mdxcompile() on the edited
file, frontmatter stripped, output 33002 chars) — worth checking because the added text puts
tag names with angle brackets and a colon inside inline code spans.

Declared narrowing — repo-wide ESLint.pnpm lint was not run whole. Measured instead:
eslint --no-inline-config --format json content/docs/ui/react-pages.mdx reports 1 entry,
0 errors, and its single message is "File ignored because no matching configuration was
supplied" — the population comes from ESLint's own config, not my guess, so this .mdx
path is outside the linted set entirely. The diff touches that one file and no ESLint
config, and no type-aware linting is involved, so no untouched file's verdict can move.

Not addressed here

Docs-only, so no changeset — the diff publishes nothing from any package, and the
skip-changeset label carries that (recent content/docs/**-only merges land the same
way). objectstack-ai/objectui#6598, the html-tier columns binding defect from the same
recon, is out of scope: this documents naming only and papers over nothing.


Generated by Claude Code

…pe the PascalCase one
The html tier's allowed tags are the registry's own type names: `compile()` in
`@objectstack/sdui-parser` builds its whitelist as `Object.keys(manifest.components)`
and `parse.ts` matches the tag against it by exact string, with no case folding. All
57 keys in `sdui.manifest.json` are lowercase — `list-view` is there, `ListView` is
not — so a PascalCase tag borrowed from the react tier is rejected at save time with
"is not an allowed component".
The page never said so. Its PascalCase sentence was unqualified while the page is
also the documentation home for the html tier, so an author reading top to bottom
had no way to learn the naming rule before hitting the error.
Two body edits, no restructuring:
- the html-tier section now names the registered spelling, including the namespaced
and layout forms, and names the error a borrowed PascalCase tag produces;
- the react-tier sentence is scoped to that tier and points at the html spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 09:09
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 00a5e74Aug 31, 2026
35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12650-html-tier-kebab-case branch August 31, 2026 09:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one - #13734

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case
Aug 31, 2026
Merged

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one#13734
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#12650

What was wrong

content/docs/ui/react-pages.mdx is the documentation home for both source-authoring
tiers — content/docs/ui/pages.mdx sends the html-tier reader here for "the html and
react source-authoring tiers in full" — but everything from the security-gate section
down is react-tier material carrying no tier marker. The naming sentence was unqualified:

Blocks are referenced by the PascalCase form of their registered type: object-form
to ObjectForm, list-view to ListView, object-chart to ObjectChart.

So an author who reads the page top to bottom and then writes a kind:'html' page copies
ListView, is rejected at save time with "is not an allowed component", and finds nothing
on the page naming the rule they broke. The html-tier section already said "only tags in
the public block manifest are accepted" and "an unknown tag is a hard error at save time"
without ever saying how those tags are spelled — that is the exact gap.

Evidence from the registration code

The html tier's whitelist is the registry's own type names, compared by exact string:

  • packages/sdui-parser/src/index.tscompile() builds
    const allowedTags = new Set(Object.keys(manifest.components)).
  • packages/sdui-parser/src/index.tsmanifestFromConfigs() keys the manifest by the
    registry config's own type: components[c.type] = { type: c.type, ... }.
  • packages/sdui-parser/src/parse.ts:61 — the check is
    !this.opts.allowedTags.has(tag). An exact Set.has; there is no case folding
    anywhere in the parser, and the parsed node keeps type: tag verbatim.
  • sdui.manifest.json (objectui's registry-inputs dump, 57 components) — zero keys
    begin with an uppercase letter. list-view present, ListView absent; object-form
    present, ObjectForm absent.
  • packages/spec/src/ui/react-blocks.tsReactBlockDef.tag is documented as "PascalCase
    name the author writes in JSX" and schemaType as "The registry/render type, e.g.
    object-form". PascalCase is a react-tier projection, not a registered name.

Compiled against that real manifest (compile(source, sdui.manifest.json)):

tag in sourceokdiagnostic
ListViewfalseListView is not an allowed component
list-viewtruetag accepted
ObjectFormfalseObjectForm is not an allowed component
object-formtruetag accepted
RecordRelatedListfalseRecordRelatedList is not an allowed component
record:related_listtag accepted (fails later on a required prop, not on the tag)
flextruetag accepted

One correction to the card's wording

"kebab-case" is the shape of the plain block names but not the whole rule: 28 of the 57
registered names carry a namespace prefix (record:, page:, element:, action:), and
four are snake_case after the colon — record:related_list, record:line_items,
record:quick_actions, record:reference_rail. Writing only "kebab-case" would have sent
an author to record:related-list, which is not registered either. The page therefore says
registered type names, written verbatim, with kebab-case named as the shape and a
namespaced example shown.

The change

Two body edits, no restructuring of either tier section:

  1. The html-tier section gains one paragraph naming the registered spelling (with the
    plain, namespaced and layout-container examples) and naming the error a borrowed
    PascalCase tag produces.
  2. The react-tier naming sentence is scoped with "On this tier" and points at the html
    spelling.

Headings, frontmatter and every table are untouched — this stays clear of epic #12243's
content/docs/** frontmatter/headings slice.

Verification

pnpm check:doc-anchors, check:doc-authoring, check:docs-single-h1,
check:doc-frontmatter, check:react-page-adapter-contract and the rest of the derived
family: 25 GREEN, 0 RED, 1 NOT MEASURED, run at 6c5a1a754 (the final commit). The
26-family list came from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
re-derived after the commit; NOT MEASURED is scripts/check-test-completeness.mjs, which
prints "PREREQUISITE NOT MET" because it grades a saved turbo run test log that no local
run produced — its own text says to record it as NOT MEASURED locally.

check:nul-bytes green (7572 files); the edited file also scanned directly for raw ASCII
control bytes, none found. The MDX itself compiles (@mdx-js/mdxcompile() on the edited
file, frontmatter stripped, output 33002 chars) — worth checking because the added text puts
tag names with angle brackets and a colon inside inline code spans.

Declared narrowing — repo-wide ESLint.pnpm lint was not run whole. Measured instead:
eslint --no-inline-config --format json content/docs/ui/react-pages.mdx reports 1 entry,
0 errors, and its single message is "File ignored because no matching configuration was
supplied" — the population comes from ESLint's own config, not my guess, so this .mdx
path is outside the linted set entirely. The diff touches that one file and no ESLint
config, and no type-aware linting is involved, so no untouched file's verdict can move.

Not addressed here

Docs-only, so no changeset — the diff publishes nothing from any package, and the
skip-changeset label carries that (recent content/docs/**-only merges land the same
way). objectstack-ai/objectui#6598, the html-tier columns binding defect from the same
recon, is out of scope: this documents naming only and papers over nothing.


Generated by Claude Code

…pe the PascalCase one
The html tier's allowed tags are the registry's own type names: `compile()` in
`@objectstack/sdui-parser` builds its whitelist as `Object.keys(manifest.components)`
and `parse.ts` matches the tag against it by exact string, with no case folding. All
57 keys in `sdui.manifest.json` are lowercase — `list-view` is there, `ListView` is
not — so a PascalCase tag borrowed from the react tier is rejected at save time with
"is not an allowed component".
The page never said so. Its PascalCase sentence was unqualified while the page is
also the documentation home for the html tier, so an author reading top to bottom
had no way to learn the naming rule before hitting the error.
Two body edits, no restructuring:
- the html-tier section now names the registered spelling, including the namespaced
and layout forms, and names the error a borrowed PascalCase tag produces;
- the react-tier sentence is scoped to that tier and points at the html spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 09:09
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 00a5e74Aug 31, 2026
35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12650-html-tier-kebab-case branch August 31, 2026 09:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one - #13734

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case
Aug 31, 2026
Merged

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one#13734
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#12650

What was wrong

content/docs/ui/react-pages.mdx is the documentation home for both source-authoring
tiers — content/docs/ui/pages.mdx sends the html-tier reader here for "the html and
react source-authoring tiers in full" — but everything from the security-gate section
down is react-tier material carrying no tier marker. The naming sentence was unqualified:

Blocks are referenced by the PascalCase form of their registered type: object-form
to ObjectForm, list-view to ListView, object-chart to ObjectChart.

So an author who reads the page top to bottom and then writes a kind:'html' page copies
ListView, is rejected at save time with "is not an allowed component", and finds nothing
on the page naming the rule they broke. The html-tier section already said "only tags in
the public block manifest are accepted" and "an unknown tag is a hard error at save time"
without ever saying how those tags are spelled — that is the exact gap.

Evidence from the registration code

The html tier's whitelist is the registry's own type names, compared by exact string:

  • packages/sdui-parser/src/index.tscompile() builds
    const allowedTags = new Set(Object.keys(manifest.components)).
  • packages/sdui-parser/src/index.tsmanifestFromConfigs() keys the manifest by the
    registry config's own type: components[c.type] = { type: c.type, ... }.
  • packages/sdui-parser/src/parse.ts:61 — the check is
    !this.opts.allowedTags.has(tag). An exact Set.has; there is no case folding
    anywhere in the parser, and the parsed node keeps type: tag verbatim.
  • sdui.manifest.json (objectui's registry-inputs dump, 57 components) — zero keys
    begin with an uppercase letter. list-view present, ListView absent; object-form
    present, ObjectForm absent.
  • packages/spec/src/ui/react-blocks.tsReactBlockDef.tag is documented as "PascalCase
    name the author writes in JSX" and schemaType as "The registry/render type, e.g.
    object-form". PascalCase is a react-tier projection, not a registered name.

Compiled against that real manifest (compile(source, sdui.manifest.json)):

tag in sourceokdiagnostic
ListViewfalseListView is not an allowed component
list-viewtruetag accepted
ObjectFormfalseObjectForm is not an allowed component
object-formtruetag accepted
RecordRelatedListfalseRecordRelatedList is not an allowed component
record:related_listtag accepted (fails later on a required prop, not on the tag)
flextruetag accepted

One correction to the card's wording

"kebab-case" is the shape of the plain block names but not the whole rule: 28 of the 57
registered names carry a namespace prefix (record:, page:, element:, action:), and
four are snake_case after the colon — record:related_list, record:line_items,
record:quick_actions, record:reference_rail. Writing only "kebab-case" would have sent
an author to record:related-list, which is not registered either. The page therefore says
registered type names, written verbatim, with kebab-case named as the shape and a
namespaced example shown.

The change

Two body edits, no restructuring of either tier section:

  1. The html-tier section gains one paragraph naming the registered spelling (with the
    plain, namespaced and layout-container examples) and naming the error a borrowed
    PascalCase tag produces.
  2. The react-tier naming sentence is scoped with "On this tier" and points at the html
    spelling.

Headings, frontmatter and every table are untouched — this stays clear of epic #12243's
content/docs/** frontmatter/headings slice.

Verification

pnpm check:doc-anchors, check:doc-authoring, check:docs-single-h1,
check:doc-frontmatter, check:react-page-adapter-contract and the rest of the derived
family: 25 GREEN, 0 RED, 1 NOT MEASURED, run at 6c5a1a754 (the final commit). The
26-family list came from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
re-derived after the commit; NOT MEASURED is scripts/check-test-completeness.mjs, which
prints "PREREQUISITE NOT MET" because it grades a saved turbo run test log that no local
run produced — its own text says to record it as NOT MEASURED locally.

check:nul-bytes green (7572 files); the edited file also scanned directly for raw ASCII
control bytes, none found. The MDX itself compiles (@mdx-js/mdxcompile() on the edited
file, frontmatter stripped, output 33002 chars) — worth checking because the added text puts
tag names with angle brackets and a colon inside inline code spans.

Declared narrowing — repo-wide ESLint.pnpm lint was not run whole. Measured instead:
eslint --no-inline-config --format json content/docs/ui/react-pages.mdx reports 1 entry,
0 errors, and its single message is "File ignored because no matching configuration was
supplied" — the population comes from ESLint's own config, not my guess, so this .mdx
path is outside the linted set entirely. The diff touches that one file and no ESLint
config, and no type-aware linting is involved, so no untouched file's verdict can move.

Not addressed here

Docs-only, so no changeset — the diff publishes nothing from any package, and the
skip-changeset label carries that (recent content/docs/**-only merges land the same
way). objectstack-ai/objectui#6598, the html-tier columns binding defect from the same
recon, is out of scope: this documents naming only and papers over nothing.


Generated by Claude Code

…pe the PascalCase one
The html tier's allowed tags are the registry's own type names: `compile()` in
`@objectstack/sdui-parser` builds its whitelist as `Object.keys(manifest.components)`
and `parse.ts` matches the tag against it by exact string, with no case folding. All
57 keys in `sdui.manifest.json` are lowercase — `list-view` is there, `ListView` is
not — so a PascalCase tag borrowed from the react tier is rejected at save time with
"is not an allowed component".
The page never said so. Its PascalCase sentence was unqualified while the page is
also the documentation home for the html tier, so an author reading top to bottom
had no way to learn the naming rule before hitting the error.
Two body edits, no restructuring:
- the html-tier section now names the registered spelling, including the namespaced
and layout forms, and names the error a borrowed PascalCase tag produces;
- the react-tier sentence is scoped to that tier and points at the html spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 09:09
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 00a5e74Aug 31, 2026
35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12650-html-tier-kebab-case branch August 31, 2026 09:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one - #13734

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case
Aug 31, 2026
Merged

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one#13734
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#12650

What was wrong

content/docs/ui/react-pages.mdx is the documentation home for both source-authoring
tiers — content/docs/ui/pages.mdx sends the html-tier reader here for "the html and
react source-authoring tiers in full" — but everything from the security-gate section
down is react-tier material carrying no tier marker. The naming sentence was unqualified:

Blocks are referenced by the PascalCase form of their registered type: object-form
to ObjectForm, list-view to ListView, object-chart to ObjectChart.

So an author who reads the page top to bottom and then writes a kind:'html' page copies
ListView, is rejected at save time with "is not an allowed component", and finds nothing
on the page naming the rule they broke. The html-tier section already said "only tags in
the public block manifest are accepted" and "an unknown tag is a hard error at save time"
without ever saying how those tags are spelled — that is the exact gap.

Evidence from the registration code

The html tier's whitelist is the registry's own type names, compared by exact string:

  • packages/sdui-parser/src/index.tscompile() builds
    const allowedTags = new Set(Object.keys(manifest.components)).
  • packages/sdui-parser/src/index.tsmanifestFromConfigs() keys the manifest by the
    registry config's own type: components[c.type] = { type: c.type, ... }.
  • packages/sdui-parser/src/parse.ts:61 — the check is
    !this.opts.allowedTags.has(tag). An exact Set.has; there is no case folding
    anywhere in the parser, and the parsed node keeps type: tag verbatim.
  • sdui.manifest.json (objectui's registry-inputs dump, 57 components) — zero keys
    begin with an uppercase letter. list-view present, ListView absent; object-form
    present, ObjectForm absent.
  • packages/spec/src/ui/react-blocks.tsReactBlockDef.tag is documented as "PascalCase
    name the author writes in JSX" and schemaType as "The registry/render type, e.g.
    object-form". PascalCase is a react-tier projection, not a registered name.

Compiled against that real manifest (compile(source, sdui.manifest.json)):

tag in sourceokdiagnostic
ListViewfalseListView is not an allowed component
list-viewtruetag accepted
ObjectFormfalseObjectForm is not an allowed component
object-formtruetag accepted
RecordRelatedListfalseRecordRelatedList is not an allowed component
record:related_listtag accepted (fails later on a required prop, not on the tag)
flextruetag accepted

One correction to the card's wording

"kebab-case" is the shape of the plain block names but not the whole rule: 28 of the 57
registered names carry a namespace prefix (record:, page:, element:, action:), and
four are snake_case after the colon — record:related_list, record:line_items,
record:quick_actions, record:reference_rail. Writing only "kebab-case" would have sent
an author to record:related-list, which is not registered either. The page therefore says
registered type names, written verbatim, with kebab-case named as the shape and a
namespaced example shown.

The change

Two body edits, no restructuring of either tier section:

  1. The html-tier section gains one paragraph naming the registered spelling (with the
    plain, namespaced and layout-container examples) and naming the error a borrowed
    PascalCase tag produces.
  2. The react-tier naming sentence is scoped with "On this tier" and points at the html
    spelling.

Headings, frontmatter and every table are untouched — this stays clear of epic #12243's
content/docs/** frontmatter/headings slice.

Verification

pnpm check:doc-anchors, check:doc-authoring, check:docs-single-h1,
check:doc-frontmatter, check:react-page-adapter-contract and the rest of the derived
family: 25 GREEN, 0 RED, 1 NOT MEASURED, run at 6c5a1a754 (the final commit). The
26-family list came from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
re-derived after the commit; NOT MEASURED is scripts/check-test-completeness.mjs, which
prints "PREREQUISITE NOT MET" because it grades a saved turbo run test log that no local
run produced — its own text says to record it as NOT MEASURED locally.

check:nul-bytes green (7572 files); the edited file also scanned directly for raw ASCII
control bytes, none found. The MDX itself compiles (@mdx-js/mdxcompile() on the edited
file, frontmatter stripped, output 33002 chars) — worth checking because the added text puts
tag names with angle brackets and a colon inside inline code spans.

Declared narrowing — repo-wide ESLint.pnpm lint was not run whole. Measured instead:
eslint --no-inline-config --format json content/docs/ui/react-pages.mdx reports 1 entry,
0 errors, and its single message is "File ignored because no matching configuration was
supplied" — the population comes from ESLint's own config, not my guess, so this .mdx
path is outside the linted set entirely. The diff touches that one file and no ESLint
config, and no type-aware linting is involved, so no untouched file's verdict can move.

Not addressed here

Docs-only, so no changeset — the diff publishes nothing from any package, and the
skip-changeset label carries that (recent content/docs/**-only merges land the same
way). objectstack-ai/objectui#6598, the html-tier columns binding defect from the same
recon, is out of scope: this documents naming only and papers over nothing.


Generated by Claude Code

…pe the PascalCase one
The html tier's allowed tags are the registry's own type names: `compile()` in
`@objectstack/sdui-parser` builds its whitelist as `Object.keys(manifest.components)`
and `parse.ts` matches the tag against it by exact string, with no case folding. All
57 keys in `sdui.manifest.json` are lowercase — `list-view` is there, `ListView` is
not — so a PascalCase tag borrowed from the react tier is rejected at save time with
"is not an allowed component".
The page never said so. Its PascalCase sentence was unqualified while the page is
also the documentation home for the html tier, so an author reading top to bottom
had no way to learn the naming rule before hitting the error.
Two body edits, no restructuring:
- the html-tier section now names the registered spelling, including the namespaced
and layout forms, and names the error a borrowed PascalCase tag produces;
- the react-tier sentence is scoped to that tier and points at the html spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 09:09
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 00a5e74Aug 31, 2026
35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12650-html-tier-kebab-case branch August 31, 2026 09:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one - #13734

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case
Aug 31, 2026
Merged

docs(react-pages): state the html tier's registered-name rule and scope the PascalCase one#13734
os-project-manager merged 1 commit into
mainfrom
claude/issue-12650-html-tier-kebab-case

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#12650

What was wrong

content/docs/ui/react-pages.mdx is the documentation home for both source-authoring
tiers — content/docs/ui/pages.mdx sends the html-tier reader here for "the html and
react source-authoring tiers in full" — but everything from the security-gate section
down is react-tier material carrying no tier marker. The naming sentence was unqualified:

Blocks are referenced by the PascalCase form of their registered type: object-form
to ObjectForm, list-view to ListView, object-chart to ObjectChart.

So an author who reads the page top to bottom and then writes a kind:'html' page copies
ListView, is rejected at save time with "is not an allowed component", and finds nothing
on the page naming the rule they broke. The html-tier section already said "only tags in
the public block manifest are accepted" and "an unknown tag is a hard error at save time"
without ever saying how those tags are spelled — that is the exact gap.

Evidence from the registration code

The html tier's whitelist is the registry's own type names, compared by exact string:

  • packages/sdui-parser/src/index.tscompile() builds
    const allowedTags = new Set(Object.keys(manifest.components)).
  • packages/sdui-parser/src/index.tsmanifestFromConfigs() keys the manifest by the
    registry config's own type: components[c.type] = { type: c.type, ... }.
  • packages/sdui-parser/src/parse.ts:61 — the check is
    !this.opts.allowedTags.has(tag). An exact Set.has; there is no case folding
    anywhere in the parser, and the parsed node keeps type: tag verbatim.
  • sdui.manifest.json (objectui's registry-inputs dump, 57 components) — zero keys
    begin with an uppercase letter. list-view present, ListView absent; object-form
    present, ObjectForm absent.
  • packages/spec/src/ui/react-blocks.tsReactBlockDef.tag is documented as "PascalCase
    name the author writes in JSX" and schemaType as "The registry/render type, e.g.
    object-form". PascalCase is a react-tier projection, not a registered name.

Compiled against that real manifest (compile(source, sdui.manifest.json)):

tag in sourceokdiagnostic
ListViewfalseListView is not an allowed component
list-viewtruetag accepted
ObjectFormfalseObjectForm is not an allowed component
object-formtruetag accepted
RecordRelatedListfalseRecordRelatedList is not an allowed component
record:related_listtag accepted (fails later on a required prop, not on the tag)
flextruetag accepted

One correction to the card's wording

"kebab-case" is the shape of the plain block names but not the whole rule: 28 of the 57
registered names carry a namespace prefix (record:, page:, element:, action:), and
four are snake_case after the colon — record:related_list, record:line_items,
record:quick_actions, record:reference_rail. Writing only "kebab-case" would have sent
an author to record:related-list, which is not registered either. The page therefore says
registered type names, written verbatim, with kebab-case named as the shape and a
namespaced example shown.

The change

Two body edits, no restructuring of either tier section:

  1. The html-tier section gains one paragraph naming the registered spelling (with the
    plain, namespaced and layout-container examples) and naming the error a borrowed
    PascalCase tag produces.
  2. The react-tier naming sentence is scoped with "On this tier" and points at the html
    spelling.

Headings, frontmatter and every table are untouched — this stays clear of epic #12243's
content/docs/** frontmatter/headings slice.

Verification

pnpm check:doc-anchors, check:doc-authoring, check:docs-single-h1,
check:doc-frontmatter, check:react-page-adapter-contract and the rest of the derived
family: 25 GREEN, 0 RED, 1 NOT MEASURED, run at 6c5a1a754 (the final commit). The
26-family list came from node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
re-derived after the commit; NOT MEASURED is scripts/check-test-completeness.mjs, which
prints "PREREQUISITE NOT MET" because it grades a saved turbo run test log that no local
run produced — its own text says to record it as NOT MEASURED locally.

check:nul-bytes green (7572 files); the edited file also scanned directly for raw ASCII
control bytes, none found. The MDX itself compiles (@mdx-js/mdxcompile() on the edited
file, frontmatter stripped, output 33002 chars) — worth checking because the added text puts
tag names with angle brackets and a colon inside inline code spans.

Declared narrowing — repo-wide ESLint.pnpm lint was not run whole. Measured instead:
eslint --no-inline-config --format json content/docs/ui/react-pages.mdx reports 1 entry,
0 errors, and its single message is "File ignored because no matching configuration was
supplied" — the population comes from ESLint's own config, not my guess, so this .mdx
path is outside the linted set entirely. The diff touches that one file and no ESLint
config, and no type-aware linting is involved, so no untouched file's verdict can move.

Not addressed here

Docs-only, so no changeset — the diff publishes nothing from any package, and the
skip-changeset label carries that (recent content/docs/**-only merges land the same
way). objectstack-ai/objectui#6598, the html-tier columns binding defect from the same
recon, is out of scope: this documents naming only and papers over nothing.


Generated by Claude Code

…pe the PascalCase one
The html tier's allowed tags are the registry's own type names: `compile()` in
`@objectstack/sdui-parser` builds its whitelist as `Object.keys(manifest.components)`
and `parse.ts` matches the tag against it by exact string, with no case folding. All
57 keys in `sdui.manifest.json` are lowercase — `list-view` is there, `ListView` is
not — so a PascalCase tag borrowed from the react tier is rejected at save time with
"is not an allowed component".
The page never said so. Its PascalCase sentence was unqualified while the page is
also the documentation home for the html tier, so an author reading top to bottom
had no way to learn the naming rule before hitting the error.
Two body edits, no restructuring:
- the html-tier section now names the registered spelling, including the namespaced
and layout forms, and names the error a borrowed PascalCase tag produces;
- the react-tier sentence is scoped to that tier and points at the html spelling.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/s documentation Improvements or additions to documentation labels Aug 31, 2026
@os-project-manager
os-project-manager marked this pull request as ready for review August 31, 2026 09:09
@os-project-manager
os-project-manager added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 00a5e74Aug 31, 2026
35 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-12650-html-tier-kebab-case branch August 31, 2026 09:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers

2 participants

@os-project-manager@claude