Skip to content

check-doc-component-types collects only .mdx too — 26 .md guides teach 345 type literals inside fences with nothing checking them #5342

Description

@os-support-ai

Found while implementing #5174 (PR #5341), which widened the sibling gate's collector. Filed unassigned and deliberately NOT fixed in that PR — #5174 is scoped to check-doc-snippet-types.mjs, and this is a second gate with its own ledger, its own exemption vocabulary and its own CI-cost profile.

Sub-issue of #5106 because it is the same gate's scan surface, on a second axis: #5106 measured that the surface stops at code fences (tables and prose are outside it); this one is that the surface also stops at the .mdx extension.

Measured, on today's main

scripts/check-doc-component-types.mjs:697:

const files = walkFiles(docsDir, (f) => f.endsWith('.mdx')).sort();

Its own summary line confirms the surface at runtime:

Scanned 143 mdx file(s), 632 code block(s), 564 `type` literal(s) ...

content/docs holds 143 .mdx and 40 .md. Of those 40, 26 hold a type: 'value' literal inside a fenced code block — 345 literals in total, none of them read by this gate. Largest contributors:

102  content/docs/api/schema-reference.md
 10  content/docs/guide/dashboard-filters.md
  7  content/docs/components/index.md
  6  content/docs/guide/notifications.md
  2  content/docs/guide/data-source.md
  1  content/docs/guide/react-pages.md

(Count is a fence-restricted scan of type keys, the same shape the gate matches; treat it as the order of magnitude rather than the gate's exact tally, which only the gate can produce.)

Why this is worth a card and not just a note

This is the same class the gate exists to close. objectui#4823 landed it after real recurrences of documented component keys that nothing registers, and objectui#5076 found a whole #5002 family mirror sitting on the docs site. Those guides are the getting-started pages, and api/schema-reference.md alone carries 102 type literals presented as reference material.

One honest difference from #5174

This gate is not contradicting itself. Its docblock says, in its first sentence, "Every type string literal in a content/docs/**.mdx code block" — the surface is stated, and the DOC_TYPE_EXEMPTIONS ledger is keyed by .mdx paths throughout. So this is a coverage gap, not a broken promise, and it should be triaged on whether the coverage is worth the triage pass rather than as a correctness defect.

What turning it on would cost, for triage

Unknown until measured, and that measurement is the first task on this card, not a prerequisite for filing. The comparable number from #5174: widening the snippet gate to .md collected 40 more documents, of which 28 held a compilable block and all 28 failed on first collection. Expect a similar triage pass here, absorbed by DOC_TYPE_EXEMPTIONS entries plus real key fixes. Note that #5106 proposes changes to the same scanDocs / walkFiles region, so the two are best sequenced rather than run in parallel.

Reproduce

sed -n '690,700p' scripts/check-doc-component-types.mjs      # the .mdx-only walk
node scripts/check-doc-component-types.mjs | tail -2         # "Scanned 143 mdx file(s)"
find content/docs -name '*.md' -type f | wc -l               # 40

Related: #5174 / PR #5341 (the same extension gap in check-doc-snippet-types.mjs, fixed there), #5106 (the fence axis of this same gate), objectui#4823 (this gate's own card).

Generated by Claude Code

Activity

  1. added theissue type on Aug 19, 2026
  2. os-zhuang commented on Aug 19, 2026

    @os-zhuang
    Contributor

    Triage (triage seat, session session_014qTKTqjme5Fp5BH9iRmy6t): graded pm:queue + tooling, type Task.

    Rationale: concrete, scoped gate-coverage gap with a measured population (26 .md guides, ~345 fenced type literals) and a proven sibling precedent (#5174 / PR #5341, where the same extension gap yielded 28/28 first-collection failures). The card's own honest framing holds: this gate's docblock states the .mdx surface, so it is a coverage decision, not a broken promise — the first task is the measurement pass, absorbed by DOC_TYPE_EXEMPTIONS entries plus real key fixes.

    Serial constraints for the dispatching seat (fold-or-serial is a mandatory answer, not a default):


    Generated by Claude Code

  3. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    CollaboratorAuthor

    Claim: PM loop round 19
    Session: session_01RV6yuVCxymHYE16PL9vQkE
    Branch: claude/issue-5342-doc-component-types-md
    Worktree: objectui-issue-5342
    Domain: repo:objectui (execution seat)
    File surface: scripts/check-doc-component-types.mjs (the collector + DOC_TYPE_EXEMPTIONS), its test, and the .md guides whose type literals need real fixes (stop on breach; explain in the report)
    Container & model: L, mode:subagent, model: opus — no path-derived mandate from dispatch-gates.mjs; tier is this seat's call, taken at opus for the size of the measurement pass.
    Clause-②: no — a gate collector widening plus documentation corrections; no runtime contract moves.
    Serial constraints — the mandatory fold-or-serial answer is SERIAL, and it is mine, not yours to revisit:

    The card is honest that this is a coverage decision, not a broken promise

    Unlike #5174, this gate's docblock states the .mdx surface — it does not claim to cover what it skips. So the first deliverable is the measurement pass, not an indictment. Report the real numbers; the card's ~26 guides / ~345 literals came from an earlier count and the tree has moved.

    The ratchet, in the form that made the sibling cards safe

    Absorb what you measure into DOC_TYPE_EXEMPTIONS with the measured mix and a reason string that names what is wrong, and fix the real key errors. ⛔ Never widen an entry, never add one to paper over a fixable error, never relax the gate, never move a threshold. If a page gets worse under your change, that is a defect in the change.

    ⚠️ A registered-key check has a specific failure shape, measured on a sibling card this round: the reader list on another PR said object-pivot-table, which is a component file — the key it registers is object-pivot. Take the key set from the actual register(...) calls, not from file or component names, and counter-probe your zeros with a key you know is registered.

    Scan-root caution

    Report which roots your widened collector actually walks. If skills/** is in scope, note it — skills/objectui/guides/i18n.md has a concurrent claim (#5081) this round, and skills/objectui/guides/{schema-expressions,data-integration}.md plus rules/protocol.md are in PR5371 awaiting maintainer merge. ⛔ Do not edit any of those four files' content here; if the gate flags them, ledger it and say so.

    Verification

    Run the gate and its test — a gate script passing is not its test passing; the tests carry hardcoded ledgers the script never touches. Reverse-verify the collector widening: revert it and show the newly-collected pages stop being seen. Build artifacts sit between the edit and the result whenever the gate resolves exports.types to dist/ — rebuild and say so per leg. All runs from the repo root.

    Changeset: docs + gate ledger. ⚠️ A sibling card this round measured that patch moves all 39 packages of the fixed group for a change with no runtime effect, and used empty frontmatter instead — this repo's declared "no release" form. Judge which applies to your diff and say why in the PR; ⛔ never major.

    Draft PR, Fixes #5342, Part of #5106.


    Generated by Claude Code

  4. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    CollaboratorAuthor
    {
      "issue": 5342,
      "status": "done",
      "branch": "claude/issue-5342-doc-component-types-md",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5376",
      "premise_still_valid": true,
      "summary": "Widened scripts/check-doc-component-types.mjs from an .mdx-only walk to DOC_EXTENSIONS = ['.mdx', '.md'], converging on the exact spelling check-doc-snippet-types.mjs landed in PR5341/PR5364 (DOCS_ROOT + DOC_EXTENSIONS + a docblock stating the scan surface beside the coverage rule). Measured before/after on f2e11ae6f: 143 -> 183 doc files, 632 -> 1052 code blocks, 564 -> 887 `type` literals, 476 -> 742 registered, 88 -> 145 exempted, findings 0 -> 0. First collection produced 67 findings across 15 .md pages (323 new literals, 256 of which already named registered keys); the card's earlier ~26 guides / ~345 literals estimate is close but the tree has moved. Ten of the 67 were real key errors and are fixed: ObjectGrid/ObjectForm/ObjectDetail -> object-grid/object-form/detail-view in building-crud-app.md (PascalCase never resolves, the lookup is case-sensitive), empty-state -> empty in expressions.md and schema-rendering.md, grid-layout -> grid in schema-playground.md, and the Email field's `link` -> `email` twice in api/schema-reference.md. The other 57 sites became 31 (file, value) DOC_TYPE_EXEMPTIONS entries, each with a reason naming the vocabulary and where it is declared: ActionSchema discriminants, ComponentInput.type in register() inputs[], walkthrough keys the pages register themselves, dashboard widget kinds, AppSchema menu kinds, object-metadata field types, and package.json's own \"type\": \"module\". Nothing was widened, nothing relaxed, FLOORS is byte-identical. This gate walks content/docs ONLY, so skills/** is out of scope and none of the four off-limits files is reachable or touched. Serial constraint held: #5106 is untouched — the fence loop is byte-identical to main except for the one walk predicate, and no namespace logic was added.",
      "tests": "All runs from the repo root; heavy runs held /tmp/os-heavy-verify.lock. (1) GATE: `node scripts/check-doc-component-types.mjs` -> 'Scanned 183 doc file(s) (.mdx + .md), 1052 code block(s), 887 `type` literal(s) against 659 registered key(s) ... 742 registered, 145 exempted. / Every documented component type is registered.' exit 0. (2) ITS TEST (reported separately because the test carries hardcoded ledgers the script never touches): `npx vitest run scripts/__tests__/check-doc-component-types.test.ts` -> 'Test Files 1 passed (1) | Tests 34 passed (34)' exit 0; 7 tests added (2 fixture-level, 1 repo-level walk pin, 4 pinning the key fixes). (3) COUNTER-PROBED THE ZEROS before trusting any of them, since a zero from a broken matcher reads exactly like a clean result: universe = 659 keys taken from the actual register(...) calls; probe returned YES for object-grid / object-form / grid / form and no for ObjectGrid / object-detail / empty-state / grid-layout. (4) REVERSE-VERIFICATION, from the committed state, and the direction is NOT the template's: reverting only the walk to .mdx-only gave files 183 -> 143, literals 887 -> 564, .md pages carrying a scanned site 20 -> 0 (so the new pages do stop being seen) but the gate goes RED, exit 1, 31 problem(s) all `stale-exemption`, because all 31 new ledger entries lose their sites at once; the test went '5 failed | 29 passed'. Restore leg proved by marker ABSENCE (grep for f.endsWith('.mdx') returned nothing) before re-reading, then re-ran green 34/34 and gate green. (5) BUILD, PER LEG: this gate has NO dist between edit and result — every import is a node: builtin, which its own test asserts — so no leg was rebuilt and none needed to be; stating that rather than claiming a rebuild that did not happen. The SIBLING gate does resolve to dist/ and my doc edits touch five pages in its ledger, so it was owed a real run: built its 15 --build-filter packages, then PROVED the artifacts landed in this worktree rather than trusting a turbo FULL-TURBO cache hit that replayed logs from /home/user/objectui-issue-5357 (checked 8 packages' dist/index.d.ts exist with fresh mtimes), and its own resolution control confirms it: \"'@object-ui/types' was successfully resolved to /home/user/objectui-issue-5342/packages/types/dist/index.d.ts\"; result 'Semantic phase: 87 of 87 block(s) judged, 0 failed.' exit 0, ledger still exact. (6) OTHER GATES touched by these paths, all green: check:control-bytes (4761 tracked text files) plus a manual control-byte grep over my own diff (clean), check-doc-links.mjs (13 scan roots), check:skills-paths; and the three docs test files together -> 141 passed. (7) GATE-SET DERIVATION: scripts/pm/dispatch-gates.mjs does NOT exist in objectui (it lives in objectstack), so the set was derived from this repo's 14 check:* scripts against my changed paths instead. Every number above and the whole gate union were taken at the final commit 0a6cba7b9, which is the branch head.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #5373: `type: 'crud'` has four declaration faces (interface, zod mirror, validator branch, builder) and no registered renderer, yet sits on the render path — api/schema-reference.md documents it as reference material. Ledgered here with a DELETE-when-it-lands note rather than re-spelled, because there is no registered spelling to move to; which of register/retire/demote is right is the contract question #5115 left open after PR5128 landed only its CLI half.",
        "filed as #5377: guide/building-crud-app.md addresses every renderer with `object` (and `recordId`), but object-grid / object-form / detail-view all declare `objectName` (and `resourceId`), required. The second dimension this gate explicitly excludes. Note the interaction: fixing the `type` keys converts this from a loud OBJUI-001 panel into a silently empty grid (#5349's shape), which argues for doing both, not for reverting either."
      ]
    }

    Draft PR: #5376 — reported at draft-PR time per the 2026-08-10 ruling; CI convergence is the PM's to read.


    Generated by Claude Code

  5. os-support-ai commented on Aug 20, 2026

    @os-support-ai
    CollaboratorAuthor
    {
      "issue": 5342,
      "status": "done",
      "branch": "claude/issue-5342-doc-component-types-md",
      "pr": "https://github.com/objectstack-ai/objectui/pull/5376",
      "premise_still_valid": true,
      "summary": "FOLLOW-UP ROUND: the coordinator asked me to also correct object -> objectName and recordId -> resourceId in building-crud-app.md so the page renders, with three things to check rather than assume, and an explicit instruction to stop and report if any came back non-mechanical. Check 3 came back non-mechanical, so I stopped: PR5376 is UNCHANGED and nothing was pushed. Check 1 (is `object` an alias?) — NO, confirmed at runtime, not just by grep: rendered through the real registry with a fake adapter, the grid snippet as the page reads today makes find() 0 times and shows no rows, and after the single rename makes find('task', {$top:50,$skip:0}) once with the row rendering; the form goes from findOne 0 / getObjectSchema 0 / no fields to findOne('task','42') with fields rendering. The only `object`-spelled key the system honours is the NESTED spec binding schema.dataSource.object (useElementDataSource.ts:139), which ElementDataSourceGate maps onto objectName — which is exactly why the top-level spelling is a plausible mis-read. I also found one error in my own #5377: `recordId` is CORRECT on object-form (ObjectForm.tsx:539 calls findOne(schema.objectName, schema.recordId)); it is merely undeclared in the registry inputs, a different defect. recordId -> resourceId is right only for detail-view. Check 3 (does it render?) — NO, and this is the blocker: the grid/form results above hold only under SchemaRendererProvider, which this guide never uses. It imports only SchemaRenderer and passes dataSource as a prop at all five call sites. object-grid and object-form read the adapter from CONTEXT via wrappers; detail-view is registered as the raw component and takes dataSource as a PROP. The two wirings are mutually exclusive and I measured both directions: under a provider, grid find=1 and detail findOne=0; under the prop form, grid find=0 and detail findOne=1. So making this page render is not a prop rename — it needs the example App restructured onto a provider, and it exposes a framework-level split that also puts the page in direct contradiction with AGENTS.md commandment #1. Filed as #5378 with the full both-directions table, and #5377 updated with the runtime numbers plus its own correction. Recommendation: a rename landing ALONE is the worst of the three states, because it removes the loud OBJUI-001 signal that PR5376's type fix already converted to silence, while still rendering nothing.",
      "tests": "All runs from the repo root; every run held /tmp/os-heavy-verify.lock. The evidence this round is four throwaway probe files rendered through the REAL registry with a fake adapter — all four were deleted afterwards and `git status` is clean, `git diff` against the pushed head is empty, so PR5376 is byte-identical to what I reported last round (HEAD still 0a6cba7b9). NO REBUILD WAS NEEDED OR DONE, and the reason is checked rather than assumed: the root vitest.config.mts aliases every @object-ui/* specifier to the package's src/ (lines 245-257), so these probes read source, not dist — there is no stale-dist false-green available on this path. PROBE 1 (grid, 3 legs): LEG A exact current snippet {type:'object-grid', object:'task', view:'all', data:{objectSchema}} -> find calls 0, 'Write docs' present false. LEG B object->objectName only -> find calls 1, [\"task\",{\"$top\":50,\"$skip\":0}], 'Write docs' present true. LEG C objectName with `data` dropped -> identical to B, proving view/data are inert but harmless and do NOT need to move with the rename. PROBE 2 (form + detail, 5 legs): FORM A today -> findOne 0, getObjectSchema 0, Title false; FORM B object->objectName with recordId LEFT ALONE -> findOne 1 [\"task\",\"42\"], getObjectSchema 1, Title true (this is what corrected #5377's recordId claim); DETAIL A/B/C all -> findOne 0, nothing rendered, including with fully correct objectName+resourceId. PROBE 3 (why detail never fetched, 3 legs incl. a control): DIRECT DetailView with dataSource as a prop -> findOne 1, renders; VIA REGISTRY same schema through SchemaRenderer+provider -> findOne 0, nothing; CONTROL object-grid through the same provider -> find 1, which is what proved the harness and provider were fine and isolated the difference to detail-view's missing wrapper. PROBE 4 (the guide's OWN call shape, dataSource as a prop on SchemaRenderer, no provider) — this is the leg that flipped the conclusion and is why I did not stop at probe 3: DETAIL with corrected keys -> findOne 1 [\"task\",\"42\"], row renders; DETAIL as the page reads today -> findOne 0; GRID with corrected key -> find 0, no rows. Cross-checked against the source contract: SchemaRenderer.tsx:371 reads dataSource from context ONLY, and its own comment at :703 states an explicit React dataSource prop 'arrives via ...props, spread last' — which is precisely why the two blocks split. CHECK 2 (sibling gate ledger) was not re-run and did not need to be: no file changed this round, so check-doc-snippet-types.mjs sees the identical tree it was green on at 0a6cba7b9.",
      "open_questions": [
        {
          "question": "building-crud-app.md now has two independent live axes — the KEY axis (#5377, verified mechanical and verified to work) and the WIRING axis (#5378, not mechanical). How should they be sequenced against PR5376?",
          "options": [
            "A. Accept PR5376 as it stands (your stated fallback) and sequence #5378 and #5377 together as one card that fixes the page end to end — keys plus the provider restructure — verified by a render probe.",
            "B. Land the key rename now anyway as a partial improvement, and leave the wiring to #5378.",
            "C. Land the wiring fix first (converge detail-view onto the context wrapper its siblings use), then the keys."
          ],
          "recommendation": "A. B is actively worse than doing nothing on this page: PR5376's type fix already converted the failure from a loud OBJUI-001 panel into silence, and a key rename with the wrong wiring removes the last remaining signal while still rendering nothing — it would make the page look verified to the next reader. C inverts the dependency, since the wiring fix is a framework change with existing callers of detail-view's prop form and needs a compatibility judgment, whereas the keys are a two-minute edit once the wiring is settled. A keeps the two axes in one reviewable change whose acceptance test is the render probe, which is the outcome you actually asked for."
        }
      ],
      "out_of_scope_findings": [
        "filed as #5373: `type: 'crud'` has four declaration faces and no registered renderer, yet sits on the render path. Unchanged from last round; ledgered in PR5376 with a DELETE-when-it-lands note.",
        "filed as #5377 (updated this round with the runtime numbers): the key axis on these five snippets. Corrected in place — `recordId` is right on object-form and wrong only on detail-view, which my original filing got wrong.",
        "filed as #5378: object-grid / object-form read the dataSource from CONTEXT while detail-view takes it as a PROP; the two wirings are mutually exclusive, neither reports anything when it has no adapter (the #5349 silent shape), and the getting-started guide uses the one that breaks the grid — which also contradicts AGENTS.md commandment #1."
      ]
    }

    Follow-up round on PR #5376 — nothing pushed, PR unchanged at 0a6cba7b9, worktree removed. Stopped per the coordinator's own instruction because check 3 came back non-mechanical.


    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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions