Skip to content

fix(console): preview 画廊的 page 样例改用注册过的块类型,并加注册表可解析断言 - #3453

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3446-preview-heading-type
Aug 6, 2026
Merged

fix(console): preview 画廊的 page 样例改用注册过的块类型,并加注册表可解析断言#3453
yinlianghui merged 1 commit into
mainfrom
claude/issue-3446-preview-heading-type

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3446

前提复核(先证实,再动手)

origin/main 上逐条验证了 issue 的说法,全部成立:

  • apps/console/src/preview-samples.ts:29,32 两个 { type: 'heading' } 仍在。
  • heading 仍未注册:全仓 register('heading' / registerLazy('heading' 零命中。仅有的两处同名物是 packages/components/src/renderers/basic/elements.tsx:84element:textvariant 'heading'(渲染 h2),以及不经注册表的 ui/typography 排版原语。
  • 裸名别名确认:textui:textseparatorui:separator(element:text/element:dividerskipFallback: true,不占裸名)。

spec 拦不住它,而且是设计如此:PageComponentSchema.typez.union([ PageComponentType, z.string() ]) —— 枚举任意字符串,好让插件贡献自己的块。所以拼错/杜撰的类型是完全合法的元数据,preview-samples-spec-valid.test.ts 永远看不见它。这正是本 issue 要求补一道注册表断言的原因。

顺带查出的第二张脸:props

同一个样例把配置放在 props 下。PageComponentSchema.strict(),按名字拒收 props(ADR-0089 D3a)—— 这是 page 一直躺在 preview-samples-spec-valid.test.tsKNOWN_STALE 账本里的唯一原因(实测:改前 4 条 issue,全部是 props)。

两件事其实是一个缺陷的两张脸,必须一起修:page:headerschema / schema.properties 上读 title(containers.tsx:920),只改类型不改 bag,标题栏照样是空的。浏览器复核也确认了原样例里两个 text 节点本就是空白卡片 —— ui:textschema.content || schema.value,从不看 props.text

改法

类型全部取自 PageComponentType,与 designer 调色板(block-types.tsBLOCK_TYPE_META)一致:

理由
heading level 1 page:header 页面标题由它承担 h1、副标题、面包屑/操作槽与下边框;recordChrome: false 选非记录页的裸标题布局
text element:text 配置键是 content,不是 text
separator element:divider separator 确实能解析(ui:separator 占着裸名),但它不是页面块类型
heading level 3 element:text + variant: 'subheading' 渲染 h3,正是原来 level: 3 想要的。heading 是 element:text 的变体,从来不是类型 —— 原样例就错在这个擦肩而过

新增断言:preview-samples-registry-resolvable.test.ts

递归走遍所有样例的 regions[].components[](含嵌套 children / body / itemsproperties.* 下的同名键 —— 与运行时容器自己走的候选键一致),断言每个 type 都能 has()hasLazy()

  • 注册表从 console 真正启动的那两个模块图读取(@object-ui/components + ../register-plugins),与 public-contract.test.ts 同一姿势:手抄一份已知类型清单只会自己跟自己吻合。
  • hasLazy 一并算数:懒注册的 tag 是契约的正式成员,只是 chunk 何时导入不同(objectui#2953)。
  • 两道防空转断言:遍历确实走到了 page 样例;以及塞进 page:card 里的未注册类型确实会被报出来(证明递归有牙,而不是靠"什么都没找到"绿着)。
  • 明确不走 dashboard widgets[]:其中 metric / pivot / dashboard-grid 只由 preview-gallery.tsx 自己的 registerLazy 块注册,而该模块在导入时就 mount React,测试无法 import;照这个 bootstrap 去查会报出一批在画廊里其实解析得好好的类型,而把画廊那份清单再抄一遍就正是上面说的自我吻合。范围写在文件头,免得把绿色读过头。

验证(仓根跑,均为实跑输出)

新断言对 origin/main 的样例是红的 —— 方向预先声明后再跑,命中预测:预测「恰好两条 heading,text/separator 不出现(裸名别名能解析)」,实测:

× every page-component type names a registered renderer
+ [
+   "page.regions[0].components[0]: \"heading\"",
+   "page.regions[0].components[3]: \"heading\"",
+ ]
 Tests  1 failed | 2 passed (3)

账本方向的反向验证(把 page 临时放回 KNOWN_STALE 再跑),预测「反向断言收到 0 条 issue 而失败」,实测:

× page sample still fails as recorded (promote it to SPEC_CLEAN once fixed)
AssertionError: expected 0 to be greater than 0

—— 所以把 page 提升进 SPEC_CLEAN 不是可选项,是账本自身的反向断言强制的("A sample only leaves this ledger by being FIXED")。这也是本 PR 动到第三个文件的原因。

修完后:

pnpm exec vitest run apps/console --maxWorkers=2
 Test Files  23 passed (23)
      Tests  211 passed (211)

pnpm --workspace-concurrency=2 --filter @object-ui/console type-check   # 干净(需先 build 依赖)
pnpm exec eslint <三个改动文件>                                          # 0
node scripts/check-control-bytes.mjs                                    # OK (3640 files)

浏览器复核(preview-gallery.html?only=page,无后端,Playwright 驱动):

  • 改前:两个 Unknown component type: heading (OBJUI-001) 红框,两个 text 是空白卡片,页面上 h1/h3 一个都没有。
  • 改后:H1 :: Welcome to the CRMH3 :: Quick links、正文与分隔线齐全,Unknown component / heading 标记全部为 false

范围说明

无 changeset:改的是 dev-only 画廊素材(文件头写明 Not shipped in production builds)与测试,不面向用户;仓里也没有强制 changeset 的闸门(changeset-guard.yml 只在 .changeset/** 变动时触发,且只拦 major)。

未碰 packages/** —— 样例去适配注册表,而不是反过来。


Generated by Claude Code

`preview-samples.ts` 的 page 样例用 `{ type: 'heading' }` 开头,而 `heading`
在本仓从未注册过——全仓 `register('heading'` / `registerLazy('heading'` 零命中。
spec 并不会拦下它:`PageComponentSchema.type` 是 `z.union([PageComponentType,
z.string()])`,枚举**或**任意字符串(好让插件贡献块),所以拼错的类型是完全合法的
元数据。两个节点因此一路解析干净、渲染成 ComponentRegistry fallback:画廊里
「一个组合页面长什么样」的样板,标题与小节标题的位置各摆着一个红色错误框。

同一处还有第二个毛病:配置放在 `props` 下。`PageComponentSchema` 是 `.strict()`,
按名字拒收 `props`(ADR-0089 D3a)——这正是 `page` 一直躺在
`preview-samples-spec-valid.test.ts` 的 KNOWN_STALE 账本里的唯一原因。两件事其实
是一个缺陷的两张脸:`page:header` 从 `schema` / `schema.properties` 上读 `title`,
只改类型不改 bag 的话,标题栏照样是空的。

改法(类型全部取自 `PageComponentType`):
  • 页面标题 → `page:header`,由它承担 h1、副标题、面包屑/操作槽与下边框;
    `recordChrome: false` 选择非记录页的裸标题布局。
  • 正文小节标题 → `element:text` + `variant: 'subheading'`(渲染 h3,正是原来
    `level: 3` 想要的)。`heading` 是 element:text 的**变体**,从来不是类型——
    原样例就错在这个擦肩而过。
  • 分隔线 → `element:divider`。原来的裸 `separator` 确实能解析(`ui:separator`
    占着裸名),但它不是页面块类型。

新增 `preview-samples-registry-resolvable.test.ts`:递归走遍所有样例的
`regions[].components[]`(含嵌套 children/body/items 与 properties.* 下的同名键),
断言每个 type 都能 `has()` 或 `hasLazy()`。注册表从 console 真正启动的那两个模块图
读取,不手抄已知类型清单——手抄的清单只会自己跟自己吻合。另配两道防空转断言:
遍历确实走到了 page 样例;以及塞进 page:card 里的未注册类型确实会被报出来。

`page` 随之从 KNOWN_STALE 提升进 SPEC_CLEAN——这不是可选项,账本的反向断言
(「修好了就提升」)在样例变得合法的那一刻就会红。

浏览器复核(preview-gallery.html?only=page,无后端):改前两个 `heading` 是
「Unknown component type: heading (OBJUI-001)」错误框,两个 `text` 是空白卡片
(`ui:text` 读 `schema.content`,根本不看 `props.text`);改后 h1「Welcome to the
CRM」、h3「Quick links」、正文与分隔线齐全,页面上再无 fallback 标记。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 6, 2026 5:41am

Request Review

@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-CRLyqkJx.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.47KB 3.09KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 21.35KB 5.70KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.65KB 0.73KB
collaboration (useCollaborationTranslation.js) 5.30KB 2.24KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 479.51KB 105.39KB
core (index.js) 2.47KB 0.91KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 136.23KB 34.75KB
fields (index.js) 229.92KB 56.48KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
i18n (useObjectLabel.js) 26.14KB 6.07KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.53KB 10.71KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 61.04KB 17.31KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 112.03KB 28.88KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 232.53KB 57.37KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 111.54KB 26.97KB
plugin-gantt (index.js) 162.55KB 39.57KB
plugin-grid (index.js) 185.08KB 49.04KB
plugin-kanban (index.js) 47.89KB 13.18KB
plugin-list (index.js) 105.02KB 25.36KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.58KB 10.58KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.34KB 2.82KB
plugin-view (index.js) 83.67KB 20.43KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.46KB 1.21KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghui
yinlianghui marked this pull request as ready for review August 6, 2026 05:43
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 112b209 Aug 6, 2026
17 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3446-preview-heading-type branch August 6, 2026 05:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] preview-samples 的 page 样例用了未注册的 heading 组件类型,designer 预览画廊里两行标题渲染成 fallback

2 participants