Skip to content

fix(app-shell): 颜色 radiogroup 有可访问名,widget-color 的 for 不再悬空 (#4010) - #4872

Merged
yinlianghui merged 3 commits into
mainfrom
claude/issue-4010-widget-color-labelling
Aug 16, 2026
Merged

fix(app-shell): 颜色 radiogroup 有可访问名,widget-color 的 for 不再悬空 (#4010)#4872
yinlianghui merged 3 commits into
mainfrom
claude/issue-4010-widget-color-labelling

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #4010

ColorVariantPicker 的色块行渲染在一个 div role="radiogroup" 里。每个色块早就有自己的颜色名("Blue"、"Success"),组本身一个名都没有 —— 焦点进组读到的是匿名 radiogroup:八个颜色,不知道改的是哪个字段;旁边那行可见文本无人归属。

DashboardWidgetInspector 还多坏一档:它的 Field 包装器渲染 Label htmlFor={id} 并约定被包控件带同一个 id,而 picker 根本不收 id,于是 htmlFor="widget-color"悬空 IDREF —— 静态检查看到 for 存在就以为命名闭合了,实际指向一个没有任何元素持有的 id。

先复核前提(行号已漂移,结论成立)

分诊席的锚点是 08-10 的读数,此后 main 多次合并。按 origin/main @ 65e88e6c2 逐条重测:

分诊锚点 现状 结论
Field 包装器 ~:447 :496 位置漂移,形状不变
Field id="widget-color" :377 :426 同上
color-variant-field.tsx:43-48 props 无 id/aria 通道 :43-48 一字不差 成立
radiogroup 无名 :54 :55 成立
PageBlockInspector.tsx:438 :476 位置漂移,形态不同(见下)
widgets.tsx:1790 :1790 成立

实测读数(真组件渲染,探针逐站点数)

改前(origin/main @ 65e88e6c2):

                                label[for]                          radiogroup accName
A DashboardWidgetInspector      widget-color      resolves=FALSE     ""
B PageBlockInspector            (该字段没有 for)   —                  ""
C SchemaForm color-picker       mdf-colorVariant  resolves=FALSE     ""

改后:

A  label 改发 id=widget-color-label 且不再发 for;组 aria-labelledby 解析到该 label
   accName="Color Variant"。面板内 label[for] 由 6 条降为 5 条,且全部 resolves=true
B  label 发布 useId 铸的 id;组 aria-labelledby 解析到该 label,accName="Color"
C  组自带 aria-label="Color Variant",accName="Color Variant"
   宿主那句 for 仍 resolves=FALSE —— 已知残留,见下

修法:ARIA 组命名,类型层强制且单一通道

for 修不了这个:role="radiogroup" 是容器不是 labelable 元素,for 指过去是惰性 HTML,谁也命名不了。这正是本仓把 group-labelled field widget 迁到 WAI-ARIA group pattern 的理由(#3961#3990),也是 form.tsx 在该分支把 for 整个去掉的理由 —— 它的注释写得很直白:留着就是「一个 label 两条关联通道、其中一条是坏的」。

所以 picker 改为通过 ARIA 拿名字,ariaLabelledByariaLabel 二选一、且必选,无名的颜色组不再可编译(与 #3997InspectorComboField 定下的同一契约)。刻意没有 id 变体:把 for 指向的 id 塞给容器只会让 IDREF「解析得到」而依然什么都不命名 —— 那是本单要消除的形状,不是要搬家的形状。

三个调用点逐处审计:同形同修,异形不外推

  • A DashboardWidgetInspector(悬空 for + 匿名组,两半齐全):Field 新增 labelling="group",该分支只发 id={groupLabelId(id)} 不发 htmlFor;组用 aria-labelledby 应答。可见文本即可访问名,zh-CN 下无需第二个待翻译串。另外五个 field 逐字节不变。
  • B PageBlockInspector(异形:只有匿名组这一半):那个 label 从未带过 for,没有悬空可修 —— 它只是无归属文本。匿名组这一半与 A 完全同形,用同样的 IDREF 修好。抽成独立组件 ColorPropField 是为了用 React.useId() 逐实例铸 id:renderField 是在循环里跑的函数(会带 keyPrefix 递归进数组项),既不能在里面调 hook,按字段名派生的 id 也会在两行之间相撞 —— 相撞是静默的,两个 label 都解析到第一个组。
  • C widgets.tsxcolor-picker(半异形:悬空 for 这一半只有宿主侧修得了):MetadataField 在得知该字段渲染的是色块组、还是可 label 的 input type="color" 之前就写好了 Label htmlFor={id},而这个分支是 widget 自己按 schema/fieldSpec 决定的。宿主因此发不出可供应答的 IDREF。匿名组这一半按本文件既有惯例自带 aria-label(文本取宿主自己的首选 label 源,与可见文本一致,免得踩 WCAG 2.5.3);宿主那句仍解析不到的 for 需要 per-widget labelling 声明,已另开 [app-shell] metadata-admin 的 SchemaForm 无条件给每个 widget 发 Label htmlFor={id},但 17 个注册 widget 里只有 5 个消费 id —— 分组类控件面的 for 悬空且组无可访问名 #4871,本 PR 不扩围,并在测试里留了路标断言。

顺带核到的第四处 widget-color:plugin-designer/src/DashboardEditor.tsx:337 是原生 select id="widget-color" —— 可 label 元素,关联正确,无缺陷,未动。

反向验证(方向在跑之前预判,变异未提交)

两次都是普通 RED,且失败点指名:

  1. 断开 picker 的 IDREF 消费(radiogroup 不再读 ariaLabelledBy)→ 预判 A/B 的命名钉子红、C 绿、for 那半的钉子绿。实测 9 红 20 绿,失败信息 expected null to be 'widget-color-label';SchemaForm.colorPicker.labelling.test.tsx 整文件绿(它走 aria-label 通道),leaves no for… / introduces no for… 也绿 —— 它们守的是另一个事实。
  2. 把悬空 for 放回去(Field 的 group 分支同时发 htmlFor)→ 预判只有 A 红。实测 2 红 27 绿,expected [ { text: 'Color Variant', … } ] to deeply equal [],B/C 全绿。

两半由不同断言分别把守,是刻意的:变异 1 下 for 那半仍然绿(它没被破坏),不会因为「什么都没产出」而假绿。

测试

  • vitest run packages/app-shell/src/views/metadata-admin(消费半径全量):171 文件 / 1708 通过、1 skipped
  • 四个钉子文件:30 通过。
  • pnpm --filter @object-ui/app-shell type-check(含 tsconfig.test.json,即 @ts-expect-error 真的被编译):通过。中途它抓到我自己写错的一处 —— 联合类型不兼容报在元素上而非那个属性上,写在属性行的指令成了 Unused '@ts-expect-error' directive 并让编译失败,已改正并在注释里写明原因,这也反证了这些指令是有载荷的。
  • 仓根 turbo run type-check --concurrency=2:81/81 成功
  • node scripts/check-control-bytes.mjs:OK;改动文件另做了 grep -naP 自查,无控制字节。
  • eslint 改动文件:0 error(仅 widgets.tsx / color-variant-field.tsx 既有的 any 与 fast-refresh warning,均非本次新增)。

changeset:.changeset/color-variant-picker-radiogroup-name.md(app-shell patch)。


Generated by Claude Code

`ColorVariantPicker` 的 `div role="radiogroup"` 既无 `aria-label` 也无
`aria-labelledby`:组内每个色块各自有颜色名("Blue"),组本身完全匿名 ——
焦点进组只知道有八个颜色,不知道改的是哪个字段,旁边那行可见文本无人归属。

`DashboardWidgetInspector` 还多坏一档。它的 `Field` 包装器渲染
`Label htmlFor={id}` 并约定被包控件带同一个 id,而 picker 根本不收 id,于是
`htmlFor="widget-color"` 是**悬空 IDREF** —— 工具看到 `for` 就认为命名闭合,
指向的却是没有任何元素持有的 id,比「没有标签」更难发现。

`for` 修不了这个:`role="radiogroup"` 是容器不是 labelable 元素,`for` 指过去
是惰性 HTML,谁也命名不了(`form.tsx` 在 group-labelled 分支正是为此把 `for`
整个去掉)。改为 ARIA 命名,且在类型层强制、单一通道 —— `ariaLabelledBy` 与
`ariaLabel` 二选一,无名的颜色组不再可编译(#3997 给 `InspectorComboField`
定下的同一契约)。

三个调用点逐处核过,形态不同,处置也不同:

* `DashboardWidgetInspector`(悬空 `for` + 匿名组):`Field` 新增
  `labelling="group"`,该分支只发 `id={groupLabelId(id)}` 不发 `htmlFor`,组用
  `aria-labelledby` 应答。可见文本即可访问名,zh-CN 无需第二个待翻译串。
* `PageBlockInspector`(**无** `for`,仅匿名组):那个 `<Label>` 从未声明过关联,
  没有悬空可修;同样的匿名组用同样的 IDREF 修好。抽成独立组件是为了用
  `React.useId()` 逐实例铸 id —— `renderField` 是在循环里跑的函数,既不能调
  hook,也会让按字段名派生的 id 在数组行之间相撞。
* `widgets.tsx` 的 `color-picker`(悬空 `for` + 匿名组,但宿主侧才修得了):
  `MetadataField` 在得知该字段渲染的是色块组还是可 label 的
  `input type="color"` **之前**就写好了 `Label htmlFor={id}`(分支由 widget 自己
  按 schema/fieldSpec 决定),宿主因此发不出可供应答的 IDREF。此处按本文件既有
  惯例自带 `aria-label`(文本取宿主自己的首选 label 源,与可见文本一致);宿主
  那个仍然解析不到的 `for` 需要 per-widget `labelling` 声明,已另开单,不在此
  PR 扩围,并由测试留下路标。

反向验证方向在跑之前已预判:两处 IDREF 通道任一断开 → 对应钉子红且指名,
其余绿。测得结果记在 PR 正文。

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the tests label Aug 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 25.13KB 5.40KB
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) 38.46KB 10.17KB
auth (createAuthenticatedFetch.js) 6.34KB 2.43KB
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) 5.02KB 0.88KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
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) 6.05KB 2.52KB
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) 496.22KB 110.42KB
core (index.js) 3.79KB 1.52KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 157.05KB 43.28KB
fields (index.js) 231.73KB 57.60KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.84KB 1.45KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.86KB 10.83KB
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.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.35KB 3.31KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.42KB 1.42KB
permissions (evaluator.js) 5.12KB 1.74KB
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.81KB 0.83KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.44KB 18.28KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 128.14KB 32.58KB
plugin-designer (index.js) 212.39KB 42.83KB
plugin-detail (index.js) 239.81KB 59.97KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 115.34KB 27.74KB
plugin-gantt (index.js) 164.06KB 39.94KB
plugin-grid (index.js) 192.23KB 51.30KB
plugin-kanban (index.js) 52.72KB 14.54KB
plugin-list (index.js) 111.23KB 26.97KB
plugin-map (index.js) 17.91KB 5.72KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.38KB 11.09KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 83.81KB 20.49KB
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.66KB 3.50KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 27.53KB 9.41KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.28KB 0.68KB
react (schema-input.js) 1.45KB 0.83KB
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 (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
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

该面板的 en-US 标签是 "Color Variant",注释里写成了 "Color"。仅注释,无行为改动。

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 25.13KB 5.40KB
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) 38.46KB 10.17KB
auth (createAuthenticatedFetch.js) 6.34KB 2.43KB
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) 5.02KB 0.88KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
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) 6.05KB 2.52KB
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) 496.22KB 110.42KB
core (index.js) 3.79KB 1.52KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 157.05KB 43.28KB
fields (index.js) 231.73KB 57.60KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.84KB 1.45KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.86KB 10.83KB
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.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.35KB 3.31KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.42KB 1.42KB
permissions (evaluator.js) 5.12KB 1.74KB
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.81KB 0.83KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.44KB 18.28KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 128.14KB 32.58KB
plugin-designer (index.js) 212.39KB 42.83KB
plugin-detail (index.js) 239.81KB 59.97KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 115.34KB 27.74KB
plugin-gantt (index.js) 164.06KB 39.94KB
plugin-grid (index.js) 192.23KB 51.30KB
plugin-kanban (index.js) 52.72KB 14.54KB
plugin-list (index.js) 111.23KB 26.97KB
plugin-map (index.js) 17.91KB 5.72KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.38KB 11.09KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 83.81KB 20.49KB
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.66KB 3.50KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 27.53KB 9.41KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.28KB 0.68KB
react (schema-input.js) 1.45KB 0.83KB
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 (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
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

:1450 那个自带 `aria-label` 的 radiogroup 属于 `FilterModeWidget`,注释里写成了
`FilterElementWidget`("Filter element" 是它的 aria-label 文本,不是函数名)。
仅注释,无行为改动。

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 9.56KB 3.59KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 8.92KB 3.41KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 25.13KB 5.40KB
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) 38.46KB 10.17KB
auth (createAuthenticatedFetch.js) 6.34KB 2.43KB
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) 5.02KB 0.88KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 26.07KB 7.56KB
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) 6.05KB 2.52KB
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) 496.22KB 110.42KB
core (index.js) 3.79KB 1.52KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 157.05KB 43.28KB
fields (index.js) 231.73KB 57.60KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.35KB 1.38KB
i18n (pickLocalized.js) 3.69KB 1.73KB
i18n (provider.js) 23.12KB 7.62KB
i18n (useDisplayLocale.js) 2.84KB 1.45KB
i18n (useObjectLabel.js) 27.59KB 6.63KB
i18n (useSafeTranslation.js) 7.77KB 3.13KB
layout (index.js) 38.86KB 10.83KB
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.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 9.35KB 3.31KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 4.42KB 1.42KB
permissions (evaluator.js) 5.12KB 1.74KB
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.81KB 0.83KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.62KB 12.83KB
plugin-charts (index.js) 64.44KB 18.28KB
plugin-chatbot (index.js) 181.21KB 43.14KB
plugin-dashboard (index.js) 128.14KB 32.58KB
plugin-designer (index.js) 212.39KB 42.83KB
plugin-detail (index.js) 239.81KB 59.97KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 115.34KB 27.74KB
plugin-gantt (index.js) 164.06KB 39.94KB
plugin-grid (index.js) 192.23KB 51.30KB
plugin-kanban (index.js) 52.72KB 14.54KB
plugin-list (index.js) 111.23KB 26.97KB
plugin-map (index.js) 17.91KB 5.72KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 41.38KB 11.09KB
plugin-timeline (index.js) 26.68KB 7.66KB
plugin-tree (index.js) 8.50KB 2.88KB
plugin-view (index.js) 83.81KB 20.49KB
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.66KB 3.50KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 27.53KB 9.41KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.28KB 0.68KB
react (schema-input.js) 1.45KB 0.83KB
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 (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 3.05KB 1.52KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
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

Copy link
Copy Markdown
Collaborator Author

【PM 验收 · ACCEPT】objectui 分片 PM(session_01GTRjn8xBqp75dk7kFupVRt)对 #4010 的实施验收:

实物核验:9 文件全在 app-shell + changeset,零越包、模型标识 0、releases 0。两半同修兑现:ColorVariantPicker 类型层 ariaLabelledBy XOR ariaLabel(无名颜色组不再可编译,与 #3997 契约同形);Field 的 labelling="group" 分支只发 id 不发 htmlFor —— 拒绝「IDREF 解析得到却什么都不命名」的坏配对,与 form.tsx group-labelled 分支同理正确。同形审计超预期:分诊两点各判半异形并如实回报(PageBlockInspector 从无悬空、widgets.tsx 宿主发不出 IDREF)+ 额外核出第四处(plugin-designer 原生 select,关联正确未动)—— 同形同修、异形立 #4871 不外推,行号漂移三处全复核。

读数:before/after 探针(checkout 回 origin/main 实测,⛔stash)三站点 accName ''→'Color Variant'/'Color',A 站点面板 label[for] 全 resolves。反向验证双臂:断 IDREF 消费 9 红 20 绿、放回悬空 for 2 红 27 绿,两半由不同断言分别把守且论证了绿不是空绿。type-check 抓到自己 @ts-expect-error 位置错误的自纠(反证指令有载荷)是加分读数。CI 亲读:19 项全 completed(17 success + 2 skipped),零失败。

新 finding #4871(SchemaForm 宿主 17 widget 仅 5 消费 id,需逐 widget labelling 声明的契约层一步,与 #4857/#4788 同类不同宿主、边界写清)记账合格,留分诊;C 站点 aria-label 与宿主翻译链可能不一致的残留已被路标断言钉住(#4871 落地时变红提醒),诚实。

处置:undraft + auto-merge(SQUASH)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 16, 2026 20:02
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 16, 2026
Merged via the queue into main with commit 14f8b7a Aug 16, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4010-widget-color-labelling branch August 16, 2026 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants