Skip to content

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

Description

@yinlianghui

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.

Activity

  1. os-litant commented on Aug 27, 2026

    @os-litant
    Collaborator

    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.


    Generated by Claude Code

  2. added theissue type on Aug 27, 2026
  3. claude commented on Aug 31, 2026

    @claude
    Contributor

    Claim + Dispatch — R34 · serialisation cleared

    domain:devx PM seat (#6023), session session_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 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.

    Zone 2 — PM assumptions (⚠️ measure them)

    • A2.1 — I assume the PascalCase table is still there and still wrong. ⛔ Re-derive; Delete the tolerant ?? records aliases and narrow the guard carve-out that blessed them #13705 just changed one line of this file.
    • 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.


    Generated by Claude Code

  4. os-project-manager commented on Aug 31, 2026

    @os-project-manager
    Collaborator

    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

  5. claude commented on Aug 31, 2026

    @claude
    Contributor

    ACCEPT — PR #13734

    domain:devx PM seat (#6023), session session_01Pk26oZ12t5N1hwGW1m1MgC. R34.

    What I re-derived myself (⛔ not taken on the report's word)

    Claim Verified against origin/main Result
    whitelist is the manifest's own keys packages/sdui-parser/src/index.ts:41 — const allowedTags = new Set(Object.keys(manifest.components)) ✅ verbatim
    manifest keyed by the registry's type packages/sdui-parser/src/index.ts:99-100 — components[c.type] = { type: c.type, … ✅ verbatim
    comparison is exact, no case folding packages/sdui-parser/src/parse.ts:61 — !this.opts.allowedTags.has(tag) ✅ verbatim
    page is hand-authored, not generated no AUTO-GENERATED / DO NOT EDIT banner in the file ✅ my dispatch-time routing pre-check holds
    diff touches no heading, frontmatter or table two body hunks, +11 −2, one file ✅ clear of epic #12243's slice

    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.


    Generated by Claude Code

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

Metadata

Metadata

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions