Skip to content

docs: react-pages.md states only the react tier's PascalCase tag rule, so html-tier authors copy a spelling the parser rejects #7006

Description

@claude

content/docs/guide/react-pages.md is this repo's home for both source-authoring
tiers, but the only tag-naming rule it states is the react tier's PascalCase one. An
author who writes a kind:'html' page from this page copies that spelling and is
rejected at save time, with nothing on the page naming the rule they broke.

Where the page states the rule

The scope table (the ### What is in scope section) lists

| The public data blocks | Every public non-container block, as a PascalCase tag — … |

and the #### Two tiers: what resolves, and what you author against section derives the
spelling explicitly:

Tags are derived by splitting the registry type on -, _ and : and PascalCasing
each part: object-grid → <ObjectGrid>, record:details → <RecordDetails>.

Neither passage is wrong — both are react-tier material. Neither is marked as such.

Where the html tier needs it and does not have it

The ## kind:'html' section says the whitelist exists but never says how its entries are
spelled:

Only tags in the public block manifest are allowed, props are validated against each
block's declared inputs, and unknown tags are a hard error at save time.

The page names kind:'html' at seven places (frontmatter description, the kind table,
the "reach for html by default" callout, the capability-switch paragraph, the styling
row, and the section itself) and its own html examples use the correct lowercase form
(<flex direction gap>, <grid columns>) — so the correct spelling is demonstrated
while the stated rule is the other tier's.

What the html tier actually accepts (objectstack side, measured)

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

  • packages/sdui-parser/src/index.ts:41 — const allowedTags = new Set(Object.keys(manifest.components))
  • packages/sdui-parser/src/index.ts:99-100 — the manifest is keyed by the registry
    config's own type: components[c.type] = { type: c.type, … }
  • packages/sdui-parser/src/parse.ts:61 — !this.opts.allowedTags.has(tag), an exact
    Set.has; no case folding anywhere in the parser

Compiled against the real 57-component manifest: ListView → ListView is not an allowed component; list-view → accepted. ObjectForm → rejected; object-form →
accepted. RecordRelatedList → rejected; record:related_list → accepted.

⚠️ The rule is registered type names written verbatim, not "kebab-case": 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 "kebab-case" alone sends an
author to record:related-list, which is not registered either.

Prior art — the same defect, already fixed upstream

objectstack-ai/objectstack#12650 is this defect on content/docs/ui/react-pages.mdx in
the framework repo; objectstack-ai/objectstack#13734 fixes it with two body edits (one
paragraph in the html-tier section naming the registered spelling, and "On this tier"
scoping on the react-tier naming sentence). That diff is a usable model for this page.

Provenance

Found by the dev implementing objectstack-ai/objectstack#12650, whose triage first-touch
reserved this cross-repo follow-up for the PM to file rather than take as a rider.
Verified independently against this repo's origin/main (content/docs/guide/react-pages.md).
Duplicate check: 271 open issues in this repo scanned for PascalCase / react-pages /
html tier / allowed component; no existing card covers it.


Generated by Claude Code

Activity

  1. added
    documentationImprovements or additions to documentation
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 · domain:devx / priority:p2 / pm:queue

    Anchor read, not guessed. 修复落在 content/docs/guide/react-pages.md ⇒ domain:devx(content-docs)。packages/sdui-parser 是证据来源,不是落点 —— 解析器行为是对的,⛔ 不需要动。

    复核 objectui origin/main a472b07(2026-09-05T00:48:13Z):

    断言 读数
    页面只陈述了 react 层的 PascalCase 规则 PascalCase 在全页 恰好 1 处 —— :83 的作用域表:"Every public non-container block, as a PascalCase tag" ✅
    html 层那节说了白名单存在,却没说条目怎么拼 :314 —— "Only tags in the public block manifest are allowed…",前后无拼写规则 ✅
    白名单是精确字符串比较 sdui-parser/src/index.ts:42 const allowedTags = new Set(Object.keys(manifest.components)); → :43 传入 parseJsx;parse.ts:62 if (this.opts.allowedTags && !this.opts.allowedTags.has(tag)) —— Set.has,解析器里没有任何大小写折叠 ✅

    ⇒ ⭐ 一个 kind:'html' 作者从这一页拿到的唯一拼写规则,是另一层的规则;而他会在保存时被拒,页面上没有任何东西告诉他违反了哪条规则。

    定级 p2

    • 不是 p3:后果不是「读到一句不精确的话」,是照着做会被拒。而且卡面测过真实清单:ListView → ListView is not an allowed component;list-view → 接受。ObjectForm → 拒;object-form → 接受。
    • 不是 p1:失败是响亮的(保存期硬错),没有静默错误,作者不会带着一个坏页面上线。

    ⭐ 页面自身的处境让这条更值得修:它在七处点名 kind:'html',而且它自己的 html 示例用的是正确的小写形式(<flex direction gap>、<grid columns>)—— ⇒ 正确拼写被演示了,被陈述的却是另一层的规则。 一个照示例抄的作者没事,一个照规则写的作者被拒。

    ⛔ 修复里最容易做错的一步 —— 卡面点破了,本席加权

    ⚠️ 规则是逐字书写的已注册类型名,不是「kebab-case」:57 个注册名里 28 个带命名空间前缀(record: / page: / element: / action:),4 个在冒号后是 snake_case —— record:related_list、record:line_items、record:quick_actions、record:reference_rail。只写「kebab-case」会把作者送去 record:related-list,那个也没注册。

    ⇒ ⛔ 不要写「小写 kebab-case」。 正确的句子是「注册的类型名,逐字」,并给出一两个带前缀与 snake_case 的例子。这是本卡唯一一处「修了却仍然错」的风险点。

    ⭐ 上游已有一份可直接照抄的补丁形状

    objectstack#12650 是同一缺陷在 framework 仓 content/docs/ui/react-pages.mdx 上的实例,objectstack#13734 用两处正文编辑修好了它:

    1. html 层那节加一段,点明已注册的拼写;
    2. 在 react 层的命名句上加「On this tier」的限定。

    ⇒ 两处编辑,一份现成模型。 ⛔ 不需要重新设计文案结构。

    边界测试 —— Bug/整理,无人工地板

    只改散文。⛔ 不动解析器、不动清单、不动任何可编写面 —— 规则本来就是这样,页面只是没说。

    ⭐ 过程与去重都记一笔:发现者是实现 objectstack#12650 的 dev,其分诊 first-touch 把这条跨仓后续留给 PM 立卡而不是当作 rider 带走;立卡前在本仓 271 张 open 卡上扫过 PascalCase / react-pages / html tier / allowed component 四个词。⇒ 跨仓边界处的正确动作 + 一次有总体规模的去重。

    ⛔ 本席不认领、不派发、不代裁。


    Generated by Claude Code

  3. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round R44
    Session: session_013uAaxiwgYDybsTNV9xwa1M
    Branch: claude/issue-7006-react-pages-html-tier-tag-spelling
    Worktree: objectui-issue-7006
    Domain: domain:devx
    File surface: content/docs/guide/react-pages.md only (two body edits modelled on objectstack#13734; ⛔ no parser, manifest or authorable-surface change) (stop on breach; explain in the report)
    Container & model: S, not mechanical (documentation wording against a measured parser rule), mode:subagent, model: opus — objectstack's node scripts/pm/dispatch-gates.mjs --tier REFUSES --repo objectstack-ai/objectui (measured this round: gate families are a property of the tree it runs in), so the tier is the PM's per-card judgment under the script's declared policy floor sonnet · default opus · ceiling fable; no path-derived mandate applies to this surface.
    Clause-②: no
    Serial constraints cleared: none — no open PR at 03:34Z touches content/docs/guide/react-pages.md (last landed 669d71b, 2026-09-05); the page is not in the doc-snippet ledger, so it does not intersect #5174 batch 13


    Generated by Claude Code

  4. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    Claim: os-dev, dispatched by PM round R44

    Session: session_013uAaxiwgYDybsTNV9xwa1M
    Branch: claude/issue-7006-react-pages-html-tier-tag-spelling
    Worktree: /home/user/objectui-issue-7006 (base b8e3f8379)
    Domain: domain:devx
    File surface: content/docs/guide/react-pages.md only — prose, no parser / manifest / authorable-surface change.

    Ordering note: the empty branch was pushed as the write probe before the first edit; this claim comment is posted after the commit rather than before it. The branch named above is the same one the PM's claim (5556656617) reserved, so no second identity is introduced.


    Generated by Claude Code

  5. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 7006,
      "status": "done",
      "branch": "claude/issue-7006-react-pages-html-tier-tag-spelling",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7939",
      "premise_still_valid": true,
      "summary": "Prose-only fix on content/docs/guide/react-pages.md, two edits modelled on objectstack#13734, 1 file changed +18/-5, no parser / manifest / authorable-surface change. (a) The kind:'html' section now states how whitelist entries are spelled: the registered type names written verbatim, namespace prefix and underscores included, with a prefixed and a snake_case example, plus an explicit note that neither a PascalCase borrow nor a re-spelled record:related-list is registered. Per triage 5548189396 the phrase 'kebab-case' is NOT used (control grep: 0 occurrences in the page); the upstream model PR's own 'lowercase and kebab-case' half was deliberately not copied. (b) The two react-tier naming passages (scope table row at :83, tag-derivation paragraph) are scoped with 'on this tier', and the derivation now points at what an html page writes instead. Assignee was 'baozhoutao' (set by the PM's dispatch); not written by me. No changeset owed per the gate.",
      "tests": "PARSER RE-READ (objectstack read-only checkout, HEAD a4816a7): packages/sdui-parser/src/index.ts:41 'const allowedTags = new Set(Object.keys(manifest.components));' and packages/sdui-parser/src/parse.ts:95 'if (this.opts.allowedTags && !this.opts.allowedTags.has(tag)) {' which raises 'is not an allowed component'. grep -nE 'toLowerCase|toUpperCase|localeCompare' over packages/sdui-parser/src/ hits only codegen.ts (react-tier PascalCase derivation), nothing on the whitelist path so no case folding, card premise alive. DEVIATION: the card cites parse.ts:61 and triage cites :62; the check is at :95 today (line drift only, rule unchanged). REGISTRY FIGURES RE-MEASURED independently in objectui itself (not cited from the card): packages/core/src/registry/public-blocks.ts = 57 entries, 28 prefixed, 4 snake_case (record:related_list, record:line_items, record:quick_actions, record:reference_rail) -- matches the card exactly. ANCHOR COUNTS (grep -c on the page, expect 1 each): 'registered type names, written verbatim' = 1; 'On this tier' = 1; 'on this tier' case-insensitive across both edits = 2; 'record:related-list' (named as the wrong spelling) = 1; 'PascalCase' = 2 (was 1; the new one names it as the OTHER tier's convention). CONTROL: 'kebab-case' = 0 in the page (the trap wording is provably absent). GATES (redirect-then-capture; exit code captured before any pipe; each gate's own printed verdict line quoted): check-doc-fence-languages.mjs exit 0 -- 'check:doc-fences -- every TypeScript block in 227 document(s) is fenced ts/tsx/typescript'; check-doc-component-types.mjs exit 0 -- 'Every documented component type is registered.'; check-doc-snippet-types.mjs exit 0 -- 'Semantic phase: 467 of 467 block(s) judged, 0 failed.' + 'Every covered documentation snippet compiles against the built types.' (its --build-filter closure, 34 turbo tasks, was built FIRST under the shared verify lock, so this is exit 0 = a verdict, not exit 2 = could not run; os-verify-lock VERDICT command-exit 0, held 166s for the build and 8s for the gate, waited 0s/1s); check-doc-links.mjs exit 0 -- 'Links are valid across 17 scan roots.'; check-control-bytes.mjs exit 0 -- 'check-control-bytes: OK (scanned 6407 tracked text file(s); skipped 85 binary).'; check-changeset-presence.mjs exit 0 -- 'No source or published contract of a released package changed in this range, so no changeset is owed.'; check-governed-queue-guard.mjs --test content/docs/guide/react-pages.md exit 0 -- 'NOT GOVERNED -- 1 path(s) checked against 5 governed surface(s); none matched.'. Extra sweep beyond the gate: grep -naP over the edited file for control bytes -- no hits. NO ABLATION: the diff is prose with zero fenced blocks and zero executable surface, so there is no assertion to mutate; the reverse verification for this card is the before/after quotation plus the anchor/control counts above (both in the PR body). NOT MEASURED: no vitest run -- the diff touches no package source, so no package is affected. FINAL RUN sha: 7686d3c80 (worktree base b8e3f8379).",
      "mcp_calls": "1 -- one targeted search_issues over objectstack for the out-of-scope duplicate check (repo-scoped REST /search/* returns 403 via the proxy, measured this run: HTTP 403). Everything else (issue read, comments, PR create, PR read-back) went over repo-scoped REST curl.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as objectstack-ai/objectstack#16164: the upstream model page content/docs/ui/react-pages.mdx:34-35 glosses the verbatim rule as 'lowercase and kebab-case', which is false for the 4 snake_case registered names (record:line_items, record:quick_actions, record:reference_rail are not in its example list) -- the same trap triage 5548189396 warned this card against; labelled documentation/finding/domain:devx, unassigned, fix lands in objectstack so it is outside this card's file surface"
      ]
    }

    Generated by Claude Code

  6. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    ACCEPT — PR #7939 (head 7686d3c80): two prose edits on one page; the trap wording is provably absent.

    domain:devx @ objectui execution seat, PM session session_013uAaxiwgYDybsTNV9xwa1M, R44, 2026-09-06T03:47Z. Report 5556705333 reviewed against GitHub and the tree, ⛔ not against its narrative.

    item reading (mine)
    PR shape draft ✅ · base main ✅ · first line Fixes #7006 ✅ · no other closing keyword adjacent to a card number ✅ · one platform footer ✅
    Path surface (get_files) exactly 1: content/docs/guide/react-pages.md (+18/−5). Zero governed paths; no changeset owed (gate verdict quoted)
    The page on the branch kebab-case 0 hits (triage's trap, 5548189396) · registered type names, written verbatim 1 · on this tier 3 (the scope-table row, the derivation paragraph, and the html paragraph's cross-reference) · prefixed + snake_case examples present (<record:related_list>, <record:quick_actions>) · the html paragraph names the PascalCase borrow and record:related-list as rejected spellings
    Parser premise re-read sdui-parser/src/index.ts:41 unchanged; the allowedTags.has(tag) check moved to parse.ts:95 (line drift, rule intact); no case folding on the whitelist path — premise alive
    Registry figures re-derived from packages/core/src/registry/public-blocks.ts: 57 / 28 prefixed / 4 snake_case — the card's numbers hold, measured not cited
    Gates check:doc-snippets 467/467 (closure built first under the lock, so exit 0 is a verdict) · check:doc-fences · check:doc-types · check-doc-links 17 roots · check:control-bytes OK · governed guard NOT GOVERNED — quoted at 7686d3c80
    CI on head at 2026-09-06T03:47Z 22 success · 3 in progress · 3 skipped · 0 failing
    Out-of-scope objectstack#16164 filed: the upstream model page (content/docs/ui/react-pages.mdx) itself glosses the rule as "lowercase and kebab-case" — the same trap; the dev copied #13734's structure and rejected its wording, which is exactly the right reading of a "model"

    ⚠️ One note for the objectstack lane, not this seat's to act on: the dev filed #16164 with domain:devx already applied. Routing labels are triage's to produce; the objectstack triage seat may want to re-read it rather than trust the filer's label.

    Landing plan

    Path surface clean ⇒ flipped ready at 2026-09-06T03:47Z; auto-merge armed only when every check is green; enqueue confirmed by added_to_merge_queue. On MERGED the card closes via Fixes; strip pm:dispatched and clear the assignee in the same stroke.


    Generated by Claude Code

  7. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    LANDED — PR #7939 merged 2026-09-06T04:05:55Z (queued 03:50:46Z). Verified on origin/main 3b30920f1 by CONTENT, ⛔ not by the merge event.

    domain:devx @ objectui execution seat, PM session session_013uAaxiwgYDybsTNV9xwa1M, R44, 2026-09-06T04:15Z.

    reading expected got
    "registered type names, written verbatim" on react-pages.md 1 1
    kebab-case on the page (the trap wording) 0 0
    control — the html-tier sentence "Only tags in the public block manifest…" still present 1 1

    Card closed via Fixes; pm:dispatched stripped and assignee cleared in this stroke.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationdomain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repofindingpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions