chore(scripts): 教学面 prose 的 OBJUI-001 棘轮 —— 文档代码块的 type 必须是注册键 - #4900
Merged
Conversation
…4823) `content/docs/**.mdx` code blocks are rendered by nothing, parsed by nothing and compared against nothing, so a snippet could teach a `type` that no package registers while every check in the repo stayed green — and a reader who copied it got the renderer's red "Unknown component type" panel (OBJUI-001). The catalog side has had the equivalent ratchet since #4616 (`examples/schema-catalog/test/catalog-gallery-render.test.tsx`); this is the missing half. The same defect had already landed three times on the teaching surface — #4786 `stats-card`, #4796 `plugin:grid` and `plugin:map` — each found by a human probe rather than by a check. `scripts/check-doc-component-types.mjs` derives the registered-key universe from the `register(…)` / `registerLazy(…)` calls themselves (direct literals, the three loop forms, and two collection-driven helpers named explicitly), reading `namespace` and `skipFallback` out of each call's own balanced argument span rather than a fixed window — a window bug there drops real keys and turns correct prose red, which is measurable on `plugin-grid/src/index.tsx` where `object-grid` sits 12 lines above a `skipFallback` that is not its own. A registration whose key cannot be resolved fails the gate instead of being skipped, for the same reason. `type` is not one vocabulary in these pages: 558 literals across 143 files spell action schemas, block schemas, theme/report schemas, field and JSON-Schema data types, validation rules and nav items under the same key. A structural discriminator (classify by enclosing key path) was built and rejected on measurement — TypeScript annotations read as object keys to a brace tracker, and `items` carries nav entries on one page and renderable children on another, so any global rule is a silent false green somewhere. So every literal is a candidate component key, and a value outside the universe must be DECLARED in `DOC_TYPE_EXEMPTIONS`, keyed by (file, value) with a written reason naming the vocabulary it belongs to. Entries are re-derived per run; a stale one is red. Three snippets of #4823's own shape are fixed here, found by the first full scan: `utilities/runner.mdx` and `utilities/vscode-extension.mdx` taught `heading` (nothing registers it — now `h1`, which `html-elements.tsx` registers and which renders the node's `children`; the unread `level` key goes with it), and `plugins/plugin-form.mdx` taught `multi-step-form`, a string that appears nowhere else in the repo — now the `object-form` + `formType: 'wizard'` + `sections` shape `WizardFormSchema` itself declares. Wired as its own workflow, not a step in `ci.yml`: that job's relevance filter excludes `content/**`, so a docs-only PR — the change that introduces this defect — would start the gate nowhere. Fifth instance of the shape after `docs-links.yml`, `control-bytes.yml`, `changeset-guard.yml` and `skills-paths.yml`; no install, no build. Not in scope, per the issue: whether a snippet's OTHER keys are read by the renderer its type resolves to. Co-authored-by: Claude <noreply@anthropic.com>
…cate add (#4823) `content/docs/blocks/block-schema.mdx` teaches `{ type: 'slot' }` inside `BlockSchema.template`, which is declared as a `SchemaNode` — so unlike the four block discriminants beside it, that one sits on the render path and nothing registers it. The correct spelling is not one thing (register a slot node, or route the snippet through the declared `slotContent` key), which is a direction #4823 deliberately does not pre-decide, so it is filed as #4895 and the exemption's reason now names it plus the instruction to delete the entry when #4895 lands. The stale-exemption check makes that instruction enforceable rather than a note. Also collapses the redundant second `add()` for `PROTOCOL_COMPONENTS` — its entries are already namespaced strings, so the bare form is what the namespace branch above had produced a line earlier. Same key set, one site list per key. Co-authored-by: Claude <noreply@anthropic.com>
Collaborator
Author
|
PM 验收:ACCEPT(#4823,批次 15,PM 会话 实物核验(已过)
反向验证
CI(亲读终态):19 项 check runs 全 completed,17 success + 2 skipped(path-filter 计绿),零失败;新门 Doc Component Type Check 自身已在本 PR 上跑绿。 附带产出:新 finding #4895 已立卡入池。 → undraft + auto-merge (SQUASH)。 Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4823
catalog 侧从 #4616 起就有棘轮(
examples/schema-catalog/test/catalog-gallery-render.test.tsx渲染每条 entry,出 OBJUI-001 就红)。教学面 prose 没有等价物:content/docs/**.mdx的代码块不被任何东西渲染、解析或比对,所以片段可以写任意type字符串而 CI 全绿 —— 而照抄的读者拿到红框。同一形状已复发三次(#4786stats-card,#4796plugin:grid/plugin:map),三次都靠人工探针发现。本 PR 按卡内裁量取最小静态门补上缺失的那一半。⛔ 第二维度(片段其余键 vs renderer 读点)按卡明确不做。
门的形状
新脚本
scripts/check-doc-component-types.mjs+ 其测试 + 独立 workflow。判据一句话:注册键全集:派生,不是清单
⛔ 没有硬编码键表 —— 每次运行都从 register 调用本身派生,且不需要 build(这正是它能作为 per-PR 轻量检查、和
check-doc-links.mjs同级运行的原因):X.register('key', …)/registerLazy,namespace与skipFallback从该调用自己的配对实参跨度里读,不是定长窗口。这一点是承重的,而且是量出来的:用 1500 字符窗口时,plugin-grid/src/index.tsx:129的object-grid会读到 12 行之下grid那次注册的skipFallback: true,裸键object-grid被悄悄丢掉 —— 而文档里有 13 处正确地教它。派生的窗口 bug 表现为对正确文案的假红,所以跨度是配对出来的,不是猜的。for (const v of ['a','b'])、for (const v of ARR)、ARR.forEach(v => …)(html-elements.tsx的TAGS、semantic.tsx的tags、apps/console的三个 variant 循环)。placeholders.tsx的PROTOCOL_COMPONENTS、fields/src/index.tsx的fieldWidgetMap+FIELD_TYPES_SKIP_FALLBACK),各自在INDIRECT_REGISTRATIONS里指名它读的集合,每次重新派生;集合没了就报stale-indirect-registration而不是让全集缩水。PluginScopeImpl转发第三方插件自己的 type、WidgetRegistry的manifest.type)在OPEN_REGISTRATION_SITES里带理由列出。其余任何解析不出键的调用点都让门红(unresolved-registration):漏掉一条动态注册路径会缩小全集,而缩小的全集把正确文档判红。全集刻意宽:union 仓内任何包/app 能注册的键,不建模「哪个 host 加载了哪个包」。判据是「这个字符串命名的组件存在吗」,不是「它在这页所描述的 host 里注册了吗」—— 后者每页答案不同,猜它正是产生假红、进而让门被删掉的路子。
判别策略:为什么没有结构判别器
type在这些页面里不是一个词汇表。全量测过 143 个文件、代码块内 558 处type字面量,至少七种词汇表共用这个键:{ type: 'object-grid' }action: { type: 'submit' }@object-ui/typesActionSchematype: 'block-instance'packages/types/src/blocks.tstype: 'theme-switcher'、'matrix'types/src/theme.ts、reports.tstype: 'string'、'currency'validation: [{ type: 'minLength' }]type: 'item'、'comment'显而易见的判别器 —— 按外层键路径分类(
validation下的是规则,children下的是节点)—— 先建后量再否。两个测量结果杀掉了它:const heroBlock: BlockSchema = {让heroBlock成了其内一切的外层键,92 处 off-registry 里有 31 处算出人看不懂的路径。items在一页承载 nav 条目、在另一页承载可渲染 children,任何全局的父键规则必然在其中一页上是静默假绿。误分类的判别器比没有判别器更坏 —— 它的错误在两个方向上都不可见。所以规则是平的、写明的,判别落在显式豁免表上,键为
(file, value):blocks/block-schema.mdx同一份文档里同时有type: 'block'和type: 'div'。submit教成组件。stale-exemption(红)。判别规则原文写在脚本头,连同上面这次测量。
mdx 全扫读数
首轮全扫:143 个 mdx 文件、632 个代码块、558 处
type字面量,对 661 个派生键(来自 135 个源文件:236 个直接调用点、131 个间接、2 个 open)。(file, value)声明stale-exemption/unresolved-registration修正的三处(同 #4823 三次复发同形)
content/docs/utilities/runner.mdx:322headingh1(html-elements.tsx的TAGS注册,渲染节点children);无读点的level一并去掉content/docs/utilities/vscode-extension.mdx:90headingcontent/docs/plugins/plugin-form.mdx:210multi-step-formobject-form+formType: 'wizard'+sections,即WizardFormSchema自己声明的形状(WizardForm.tsx:46-68);section 列字段名,因为向导从对象元数据解析字段豁免清单(23 文件 / 35 条)
blocks/authentication.mdx、ecommerce.mdx、forms.mdxsubmitaction键下)blocks/dashboard.mdxnavigateblocks/marketing.mdxanalyticsblocks/block-schema.mdxblock、block-instance、block-library、block-editorpackages/types/src/blocks.ts的判别式 + Zodblocks/block-schema.mdxslotblocks/block-schema.mdxstringBlockVariable.type(变量数据类型)components/complex/filter-ui.mdxdate-rangecrud.ts:329、views.ts:804)components/complex/view-switcher.mdxsharecore/app-schema.mdxitem、groupcore/enhanced-actions.mdxaction、messagecore/report-schema.mdxline、page-break、report-builder、stringreports.ts:210段落枚举 /reports.ts:464/ 报表字段数据类型core/theme-schema.mdxtheme、theme-preview、theme-switchertypes/src/theme.ts的判别式 + Zodcore/schema-renderer.mdxmy-widgetguide/objectos-integration.mdxmy-custom-widgetutilities/runner.mdxmy-component、your-componentfields/object.mdxarray、stringplugins/plugin-dashboard.mdxbar、linewidgets[]的 widget 种类plugins/plugin-detail.mdxcomment、field_changeplugins/plugin-form.mdxminLength、maxLengthvalidation[]的规则判别式plugins/plugin-grid.mdxcount_uniquecolumns[].summary的聚合plugins/plugin-report.mdxmatrix、joinedutilities/vscode-extension.mdxajax、apionSubmit的 action /dataSource的数据源种类slot是唯一一条「已知有问题、但不在本单裁量内」的豁免:BlockSchema.template声明为SchemaNode,所以它在渲染路径上,而slot仓内不存在。正确写法不唯一(给插槽占位符定一个真节点类型并注册,还是改走已声明的slotContent),两种读法通向不同架构 —— 已立 #4895,豁免理由里写明「#4895 落地时删除本条」,stale-exemption检查让这条指令可执行而不只是一句话。CI 接线
新 workflow
.github/workflows/doc-component-types.yml,不是ci.yml里的一步。理由比同族三个更硬:ci.yml的type-checkjob 用一个排除content/**的git diff决定是否跑它的门,所以只改content/docs/**.mdx的 PR 会报 context 却一个门都不跑 —— 而 docs-only PR 正是引入这个缺陷的那种改动。把门放那儿,它就看不见任何能触发它的变更。这是仓内该形状的第五例(前四:
docs-links.yml、control-bytes.yml、changeset-guard.yml、skills-paths.yml),⛔ 无paths/paths-ignore,无 install,无 build —— checkout 加一次node调用。测试断言:脚本只 importnode:内建、workflow 里没有pnpm install、没有任何 path filter、且只有一个家。同时补了
content/docs/guide/ci-cd-pipeline.md(该仓有门禁要求每个 workflow 有自己的章节 + inventory 行)。测试
scripts/__tests__/check-doc-component-types.test.ts,24 例,按「门会怎么坏」的顺序排:skipFallback归属(即上面那个定长窗口 bug 的定型)、三种循环形态、注释/字符串里的 register 不算注册、解析不出的键报错而不是丢、测试文件里的注册不入全集。type=或schema.type当站点、未闭合围栏报错而不是猜。content/docs/**.mdx代码块)没有 catalog 侧 #4616 那样的「不许出现 OBJUI-001」棘轮,同一缺陷已复发三次 #4823 那三次复发(stats-card/plugin:grid/plugin:map)逐条红,含 file:line。夹具一律临时树,⛔ 不用真
content/docs:committed 的夹具页必须含一个故意写错的type,而这道门自己会扫到它。验证读数
反向验证(三个方向,均先预判后跑)
fields/grid.mdx的真键改回plugin:grid(即 #4796 原缺陷)content/docs/fields/grid.mdx:180 [unregistered-doc-type] type 'plugin:grid'+ 源行;还原后绿DOC_TYPE_EXEMPTIONSunregistered-doc-type(= 当前豁免站点数),0 条stale-exemption(表空则无条目可失效)unregistered-doc-type,0 exempted;还原后绿object-grid掉出全集 → 13 处正确站点变红(派生 bug 的表现是对正确文案假红)第三个方向是刻意加的:这道门最贵的坏法不是漏报,而是派生缺键导致的假红,而那正是量出来、也是脚本头里写明的那处。
顺带发现
content/docs/blocks/block-schema.mdx教的整族 block 词汇表(含节点位置的slot)没有任何 renderer #4895(observation-class finding,finding标签、无pm:queue、未指派):content/docs/blocks/block-schema.mdx教的整族 block 词汇表没有任何 renderer,其中slot处在真节点位置上。⛔ 不在本 PR 修 —— 正确写法需要维护者定方向。Generated by Claude Code