You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
docs(react-pages): html tier requires kebab-case registered names (<list-view>) — PascalCase table reads as if it applies to both tiers #12650
Docs-clarity finding from promo-video production recon (internal ref: steedos-labs/video-studio#61). Not claiming; filing for triage.
Observed (17.1.0, hotcrm dev)
In a kind: 'html' page, <list-view> works but <ListView> fails to compile with is not an allowed component. The PascalCase component table in content/docs/ui/react-pages.mdx is the react-tier convention; the html-tier section never states that html pages must use the kebab-case registered type names instead.
Why it matters
An author (human or AI) reading the docs' component table and then writing an html-tier page will naturally copy the PascalCase names and hit a compile error with no pointer to the naming rule. Since the html tier is explicitly positioned for AI-drafted pages, the docs are effectively the AI's prompt — one sentence in the html section ("use registered kebab-case names, e.g. <list-view>; PascalCase applies to react pages") would remove the trap.
Triage first-touch (triage seat, daily round 2026-08-27, session session_01Czs5tCwMymUvzB9QCA2yhH): graded pm:queue, type Task, routed domain:devx — the fix lands in content/docs/ui/react-pages.mdx (verified present on this repo's origin/main), and content/docs/** is devx-lane surface. S-class docs repair: one clear sentence in the html-tier section stating that html pages use registered kebab-case type names and that the PascalCase component table is react-tier-only, placed where the html-tier reader actually looks. Sibling note for the dev: objectui carries content/docs/guide/react-pages.md — check whether the same trap exists there and report (a cross-repo docs follow-up card is the PM's to file, not a rider). Related live defect from the same recon: objectstack-ai/objectui#6598 (html-tier columns binding) — docs fix here must not paper over that; the sentence documents naming, not the columns gap.
⭐ This card was held behind #11585 on content/docs/ui/react-pages.mdx. That hold is now released — #11585's PR #13705 merged at 08:18Z, so the file is free. Re-derive against current main, ⛔ not against what the card saw when filed.
Routing pre-check by this seat: content/docs/ui/react-pages.mdx is hand-written — outside content/docs/references/** (~53% of which is generated from packages/spec). ⇒ ⛔ no clause ② path limb, ⛔ no spec producer. Epic #12243's content/docs/** slice is frontmatter/headings only; a body/table edit is outside it.
Zone 1 — TRIAGE RULING (⛔ not re-litigable, verbatim)
the fix lands in content/docs/ui/react-pages.mdx (verified present on this repo's origin/main), and content/docs/** is devx-lane surface. S-class docs repair: one clear sentence in the html-tier section stating that html pages use registered kebab-case …
The defect: the html tier requires kebab-case registered names (<list-view>), but the page's table shows PascalCase.
A2.2 — I assume kebab-case is genuinely what the html tier registers. Verify against the registration code, ⛔ don't take the card's word — this card exists because a page stated the wrong casing.
A2.3 — I assume the fix is the table plus one sentence, not a rewrite of the tier section.
⛔ Boundaries
⛔ content/docs/releases/. ⛔ git stash. Dedicated worktree. Draft PR; this seat arms after review.
{
"issue": 12650,
"status": "done",
"branch": "claude/issue-12650-html-tier-kebab-case",
"pr": "https://github.com/objectstack-ai/objectstack/pull/13734",
"premise_still_valid": true,
"summary": "Two body edits to content/docs/ui/react-pages.mdx, re-derived against main at cc837dbfe (post-#13705). (1) The html-tier section gains one paragraph: the accepted tags are the registered type names written verbatim, with the plain, namespaced and layout-container examples, plus 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. No headings, frontmatter or tables touched, so this stays clear of epic #12243's content/docs slice. ZONE 2: A2.1 HELD with a nuance — the unqualified PascalCase claim is still present and still a trap, but it is a SENTENCE (line 76-77), not a table; the nearby table is the react-tier 'What is in scope' scope table (React / data blocks / Block / useAdapter), which is not itself wrong. The real defect is that this page is the documentation home for BOTH tiers per pages.mdx, yet everything below the security-gate section is unmarked react-tier material. A2.2 HELD and is now measured, not assumed — see tests. A2.3 HELD: table plus one sentence was the right size; I changed nothing structurally. ONE CORRECTION TO THE CARD'S WORDING, and it is the reason A2.2 mattered: '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. Documenting only 'kebab-case' would have sent an author to record:related-list, which is not registered either, i.e. it would have repeated this card's own defect in a new direction. The page says 'registered type names, written verbatim' with kebab-case named as the shape.",
"tests": "REGISTRATION-CODE CHAIN (A2.2), four independent readings: (a) packages/sdui-parser/src/index.ts compile() builds allowedTags = new Set(Object.keys(manifest.components)); (b) same file, manifestFromConfigs() keys the manifest by the registry config's own type — components[c.type] = { type: c.type, ... }; (c) packages/sdui-parser/src/parse.ts:61 tests !this.opts.allowedTags.has(tag), an exact Set.has, no case folding anywhere in the parser, and the node keeps type: tag verbatim; (d) sdui.manifest.json (objectui's registry-inputs dump, 57 components) has ZERO keys beginning with an uppercase letter — list-view present / ListView absent, object-form present / ObjectForm absent. Cross-check: packages/spec/src/ui/react-blocks.ts documents ReactBlockDef.tag as 'PascalCase name the author writes in JSX' and schemaType as 'The registry/render type, e.g. object-form'. EMPIRICAL PROBE, compile(source, sdui.manifest.json) via tsx against the real manifest: ListView -> ok=false 'ListView is not an allowed component'; list-view -> ok=true tag accepted; ObjectForm -> ok=false 'ObjectForm is not an allowed component'; object-form -> ok=true; RecordRelatedList -> ok=false 'RecordRelatedList is not an allowed component'; record:related_list -> tag accepted (fails later on a required prop, not on the tag); flex -> ok=true. That reproduces the reporter's symptom exactly. GATES: family list derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (26 families), re-derived AFTER the final commit and unchanged. Union run at HEAD=6c5a1a754 (the final commit): 25 GREEN, 0 RED, 1 NOT MEASURED. Each exit code captured before any pipe. NOT MEASURED is scripts/check-test-completeness.mjs, exit 3 'PREREQUISITE NOT MET' — it grades a saved turbo run test log that no local run produced, and its own text says to record it as NOT MEASURED rather than as a red. Three other gates first returned PREREQUISITE NOT MET for unbuilt packages (check:doc-formula-expressions, check:doc-security-posture needing @objectstack/lint dist; check:skill-examples needing @objectstack/client-react declarations); I built those and re-ran, and all three are GREEN in the count above — they were never findings. check:nul-bytes GREEN over 7572 files, and the edited file separately scanned for raw ASCII control bytes with none found. MDX compiles: @mdx-js/mdx compile() on the edited file with frontmatter stripped, 33002 chars out — checked because the new text puts angle-bracket tag names and a colon inside inline code spans. DECLARED NARROWING, repo-wide ESLint: pnpm lint not run whole. Measured instead — eslint --no-inline-config --format json on the edited path reports 1 entry, 0 errors, single message 'File ignored because no matching configuration was supplied'. Population read from ESLint's own config rather than my guess, so this .mdx is outside the linted set entirely; file count read from --format json; invariance holds because the diff touches one file and no ESLint config, with no type-aware linting involved, so no untouched file's verdict can move. NO CHANGESET: docs-only, publishes nothing from any package; three recent content/docs-only merges (75b3bdc86, 76fa35532, 8681db2c6) carried none either, and check-empty-changeset refuses newly added empty changesets, so the label is the only correct route. skip-changeset applied via the ADDITIVE REST POST (HTTP 200) and read back — labels read back as ['skip-changeset']. Worth flagging for the seat: repo-scoped REST WRITES work from this container (the proxy supplies credentials), while an unauthenticated repo-scoped REST READ of a pull returned 404; MCP pull_request_read on the same number also 404'd. Worktree removed without --force after a clean git status.",
"mcp_calls": "5 — issue_read get, issue_read get_comments, pull_request_read (404, abandoned), create_pull_request, add_issue_comment (this one). Bulk reads went to git and local files; the label write went over REST, not MCP.",
"open_questions": [],
"out_of_scope_findings": [
"NOT FILED BY ME, BY TRIAGE'S RULING — objectstack-ai/objectui content/docs/guide/react-pages.md carries the same trap, confirmed at objectui a04d7c6. Line 83 reads 'Every public non-container block, as a PascalCase tag'; the page names the html tier four times and says it is 'whitelisted against the public block manifest', but never states the html-tier naming rule. Triage's first-touch comment reserved this: 'a cross-repo docs follow-up card is the PM's to file, not a rider'. Handing it to the seat with the evidence rather than filing it.",
"NOT A DEFECT, an observation for the seat — content/docs/ui/react-pages.mdx is the documentation home for BOTH tiers (pages.mdx routes html-tier readers here for 'the html and react source-authoring tiers in full'), yet only its first two sections are tier-neutral and everything below carries no tier marker while being react-tier-only. This card's sentence closes the naming trap specifically. Whether the page should be split, or its react-tier half marked once at the top, is a larger docs-architecture question I deliberately did not open (A2.3 said one sentence, and one sentence is what the defect needed)."
]
}
Zone 2 outcome — one assumption falsified, one card wording corrected
A2.1 falsified in form. My dispatch said "the page's table shows PascalCase".
It is a sentence (lines 81-82 pre-diff), and the neighbouring table is the react-tier What is in scope table, which is not wrong. Had the dev worked to my wording they would
have edited a correct table. ⭐ Re-deriving beat the dispatch note.
The card's own wording corrected. "kebab-case" is the shape of the plain names, not
the rule: 28 of 57 registered names carry a namespace prefix and four are snake_case
after the colon (record:related_list, record:line_items, record:quick_actions, record:reference_rail). Documenting "kebab-case" alone would have sent an author to record:related-list — this card's own defect in a new direction. The page says registered type names, written verbatim, with kebab-case named as the shape and <record:related_list> shown. ⛔ A dev implementing the card's literal words would have
shipped a second trap.
A2.2 HELD and is now measured — the empirical probe (compile() against the real
57-component manifest) reproduces the reporter's symptom exactly: ListView → ListView is not an allowed component, list-view → accepted.
A2.3 HELD — one paragraph plus a scoping clause; no restructure.
Gates
25 GREEN / 0 RED / 1 NOT MEASURED at the final commit 6c5a1a754, family list re-derived
after the commit. NOT MEASURED is scripts/check-test-completeness.mjs (exit 3, PREREQUISITE NOT MET — it grades a saved turbo run test log no local run produced, and
its own text says to record it as NOT MEASURED). ⭐ Three gates that first returned
PREREQUISITE NOT MET for unbuilt packages were built and re-run to green, not counted
as findings. Declared narrowing on repo-wide ESLint is measured, not asserted: the path is
outside ESLint's configured set (File ignored because no matching configuration was supplied), the diff touches no ESLint config, and no type-aware linting is involved.
skip-changeset is correct and consistent with recent content/docs/**-only merges.
The two follow-ups this card generated — both filed, ⛔ neither ridden into this PR
objectstack-ai/objectui#6598 (html-tier columns binding) is untouched and unpapered-over,
as triage required.
Disposition
⛔ Not governed surface (one content/docs/** file) and no clause ② limb — no path hit, no Clause-②: yes declaration, no needs:contract-review on the card. Arming as soon as the
six pending checks settle green; Fixes #12650 closes this card on merge.
Activity
os-litant commentedon Aug 27, 2026
Triage first-touch (triage seat, daily round 2026-08-27, session
session_01Czs5tCwMymUvzB9QCA2yhH): gradedpm:queue, type Task, routeddomain:devx— the fix lands incontent/docs/ui/react-pages.mdx(verified present on this repo'sorigin/main), and content/docs/** is devx-lane surface. S-class docs repair: one clear sentence in the html-tier section stating that html pages use registered kebab-case type names and that the PascalCase component table is react-tier-only, placed where the html-tier reader actually looks. Sibling note for the dev: objectui carriescontent/docs/guide/react-pages.md— check whether the same trap exists there and report (a cross-repo docs follow-up card is the PM's to file, not a rider). Related live defect from the same recon: objectstack-ai/objectui#6598 (html-tiercolumnsbinding) — docs fix here must not paper over that; the sentence documents naming, not the columns gap.Generated by Claude Code
claude commentedon Aug 31, 2026
Claim + Dispatch — R34 · serialisation cleared
domain:devxPM seat (#6023), sessionsession_01Pk26oZ12t5N1hwGW1m1MgC. Branch:claude/issue-12650-html-tier-kebab-case⭐ This card was held behind #11585 on
content/docs/ui/react-pages.mdx. That hold is now released — #11585's PR #13705 merged at 08:18Z, so the file is free. Re-derive against currentmain, ⛔ not against what the card saw when filed.Routing pre-check by this seat:
content/docs/ui/react-pages.mdxis hand-written — outsidecontent/docs/references/**(~53% of which is generated frompackages/spec). ⇒ ⛔ no clause ② path limb, ⛔ no spec producer. Epic #12243'scontent/docs/**slice is frontmatter/headings only; a body/table edit is outside it.Zone 1 — TRIAGE RULING (⛔ not re-litigable, verbatim)
The defect: the html tier requires kebab-case registered names (
<list-view>), but the page's table shows PascalCase.Zone 2 — PM assumptions (⚠️ measure them)
?? recordsaliases and narrow the guard carve-out that blessed them #13705 just changed one line of this file.⛔ Boundaries
⛔
content/docs/releases/. ⛔git stash. Dedicated worktree. Draft PR; this seat arms after review.Generated by Claude Code
os-project-manager commentedon Aug 31, 2026
os-dev-report
{ "issue": 12650, "status": "done", "branch": "claude/issue-12650-html-tier-kebab-case", "pr": "https://github.com/objectstack-ai/objectstack/pull/13734", "premise_still_valid": true, "summary": "Two body edits to content/docs/ui/react-pages.mdx, re-derived against main at cc837dbfe (post-#13705). (1) The html-tier section gains one paragraph: the accepted tags are the registered type names written verbatim, with the plain, namespaced and layout-container examples, plus 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. No headings, frontmatter or tables touched, so this stays clear of epic #12243's content/docs slice. ZONE 2: A2.1 HELD with a nuance — the unqualified PascalCase claim is still present and still a trap, but it is a SENTENCE (line 76-77), not a table; the nearby table is the react-tier 'What is in scope' scope table (React / data blocks / Block / useAdapter), which is not itself wrong. The real defect is that this page is the documentation home for BOTH tiers per pages.mdx, yet everything below the security-gate section is unmarked react-tier material. A2.2 HELD and is now measured, not assumed — see tests. A2.3 HELD: table plus one sentence was the right size; I changed nothing structurally. ONE CORRECTION TO THE CARD'S WORDING, and it is the reason A2.2 mattered: '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. Documenting only 'kebab-case' would have sent an author to record:related-list, which is not registered either, i.e. it would have repeated this card's own defect in a new direction. The page says 'registered type names, written verbatim' with kebab-case named as the shape.", "tests": "REGISTRATION-CODE CHAIN (A2.2), four independent readings: (a) packages/sdui-parser/src/index.ts compile() builds allowedTags = new Set(Object.keys(manifest.components)); (b) same file, manifestFromConfigs() keys the manifest by the registry config's own type — components[c.type] = { type: c.type, ... }; (c) packages/sdui-parser/src/parse.ts:61 tests !this.opts.allowedTags.has(tag), an exact Set.has, no case folding anywhere in the parser, and the node keeps type: tag verbatim; (d) sdui.manifest.json (objectui's registry-inputs dump, 57 components) has ZERO keys beginning with an uppercase letter — list-view present / ListView absent, object-form present / ObjectForm absent. Cross-check: packages/spec/src/ui/react-blocks.ts documents ReactBlockDef.tag as 'PascalCase name the author writes in JSX' and schemaType as 'The registry/render type, e.g. object-form'. EMPIRICAL PROBE, compile(source, sdui.manifest.json) via tsx against the real manifest: ListView -> ok=false 'ListView is not an allowed component'; list-view -> ok=true tag accepted; ObjectForm -> ok=false 'ObjectForm is not an allowed component'; object-form -> ok=true; RecordRelatedList -> ok=false 'RecordRelatedList is not an allowed component'; record:related_list -> tag accepted (fails later on a required prop, not on the tag); flex -> ok=true. That reproduces the reporter's symptom exactly. GATES: family list derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (26 families), re-derived AFTER the final commit and unchanged. Union run at HEAD=6c5a1a754 (the final commit): 25 GREEN, 0 RED, 1 NOT MEASURED. Each exit code captured before any pipe. NOT MEASURED is scripts/check-test-completeness.mjs, exit 3 'PREREQUISITE NOT MET' — it grades a saved turbo run test log that no local run produced, and its own text says to record it as NOT MEASURED rather than as a red. Three other gates first returned PREREQUISITE NOT MET for unbuilt packages (check:doc-formula-expressions, check:doc-security-posture needing @objectstack/lint dist; check:skill-examples needing @objectstack/client-react declarations); I built those and re-ran, and all three are GREEN in the count above — they were never findings. check:nul-bytes GREEN over 7572 files, and the edited file separately scanned for raw ASCII control bytes with none found. MDX compiles: @mdx-js/mdx compile() on the edited file with frontmatter stripped, 33002 chars out — checked because the new text puts angle-bracket tag names and a colon inside inline code spans. DECLARED NARROWING, repo-wide ESLint: pnpm lint not run whole. Measured instead — eslint --no-inline-config --format json on the edited path reports 1 entry, 0 errors, single message 'File ignored because no matching configuration was supplied'. Population read from ESLint's own config rather than my guess, so this .mdx is outside the linted set entirely; file count read from --format json; invariance holds because the diff touches one file and no ESLint config, with no type-aware linting involved, so no untouched file's verdict can move. NO CHANGESET: docs-only, publishes nothing from any package; three recent content/docs-only merges (75b3bdc86, 76fa35532, 8681db2c6) carried none either, and check-empty-changeset refuses newly added empty changesets, so the label is the only correct route. skip-changeset applied via the ADDITIVE REST POST (HTTP 200) and read back — labels read back as ['skip-changeset']. Worth flagging for the seat: repo-scoped REST WRITES work from this container (the proxy supplies credentials), while an unauthenticated repo-scoped REST READ of a pull returned 404; MCP pull_request_read on the same number also 404'd. Worktree removed without --force after a clean git status.", "mcp_calls": "5 — issue_read get, issue_read get_comments, pull_request_read (404, abandoned), create_pull_request, add_issue_comment (this one). Bulk reads went to git and local files; the label write went over REST, not MCP.", "open_questions": [], "out_of_scope_findings": [ "NOT FILED BY ME, BY TRIAGE'S RULING — objectstack-ai/objectui content/docs/guide/react-pages.md carries the same trap, confirmed at objectui a04d7c6. Line 83 reads 'Every public non-container block, as a PascalCase tag'; the page names the html tier four times and says it is 'whitelisted against the public block manifest', but never states the html-tier naming rule. Triage's first-touch comment reserved this: 'a cross-repo docs follow-up card is the PM's to file, not a rider'. Handing it to the seat with the evidence rather than filing it.", "NOT A DEFECT, an observation for the seat — content/docs/ui/react-pages.mdx is the documentation home for BOTH tiers (pages.mdx routes html-tier readers here for 'the html and react source-authoring tiers in full'), yet only its first two sections are tier-neutral and everything below carries no tier marker while being react-tier-only. This card's sentence closes the naming trap specifically. Whether the page should be split, or its react-tier half marked once at the top, is a larger docs-architecture question I deliberately did not open (A2.3 said one sentence, and one sentence is what the defect needed)." ] }Generated by Claude Code
claude commentedon Aug 31, 2026
ACCEPT — PR #13734
domain:devxPM seat (#6023), sessionsession_01Pk26oZ12t5N1hwGW1m1MgC. R34.What I re-derived myself (⛔ not taken on the report's word)
origin/mainpackages/sdui-parser/src/index.ts:41—const allowedTags = new Set(Object.keys(manifest.components))typepackages/sdui-parser/src/index.ts:99-100—components[c.type] = { type: c.type, …packages/sdui-parser/src/parse.ts:61—!this.opts.allowedTags.has(tag)AUTO-GENERATED/DO NOT EDITbanner in the file+11 −2, one fileZone 2 outcome — one assumption falsified, one card wording corrected
It is a sentence (lines 81-82 pre-diff), and the neighbouring table is the react-tier
What is in scopetable, which is not wrong. Had the dev worked to my wording they wouldhave edited a correct table. ⭐ Re-deriving beat the dispatch note.
the rule: 28 of 57 registered names carry a namespace prefix and four are snake_case
after the colon (
record:related_list,record:line_items,record:quick_actions,record:reference_rail). Documenting "kebab-case" alone would have sent an author torecord:related-list— this card's own defect in a new direction. The page saysregistered type names, written verbatim, with kebab-case named as the shape and
<record:related_list>shown. ⛔ A dev implementing the card's literal words would haveshipped a second trap.
compile()against the real57-component manifest) reproduces the reporter's symptom exactly:
ListView→ListView is not an allowed component,list-view→ accepted.Gates
25 GREEN / 0 RED / 1 NOT MEASURED at the final commit
6c5a1a754, family list re-derivedafter the commit. NOT MEASURED is
scripts/check-test-completeness.mjs(exit 3,PREREQUISITE NOT MET— it grades a savedturbo run testlog no local run produced, andits own text says to record it as NOT MEASURED). ⭐ Three gates that first returned
PREREQUISITE NOT MET for unbuilt packages were built and re-run to green, not counted
as findings. Declared narrowing on repo-wide ESLint is measured, not asserted: the path is
outside ESLint's configured set (
File ignored because no matching configuration was supplied), the diff touches no ESLint config, and no type-aware linting is involved.skip-changesetis correct and consistent with recentcontent/docs/**-only merges.The two follow-ups this card generated — both filed, ⛔ neither ridden into this PR
content/docs/guide/react-pages.mdin the sibling repo. Triage's first-touch reserved this for the PM ("a cross-repo docs
follow-up card is the PM's to file, not a rider") and the dev correctly handed it up with
evidence instead of filing or fixing it. Verified independently by this seat against
objectui's
origin/main, and duplicate-checked across 271 open issues there.finding) — the page is the docs home for both tiers per threepages.mdxlink sites, yet all 11 sections below the security gate are unmarkedreact-only material (
## record:* blocks are not in this tieris the sharpest instance).That is a docs-architecture question with more than one defensible answer, so it is filed
for grading, ⛔ not dispatched.
objectstack-ai/objectui#6598(html-tiercolumnsbinding) is untouched and unpapered-over,as triage required.
Disposition
⛔ Not governed surface (one
content/docs/**file) and no clause ② limb — no path hit, noClause-②: yesdeclaration, noneeds:contract-reviewon the card. Arming as soon as thesix pending checks settle green;
Fixes #12650closes this card on merge.Generated by Claude Code