Skip to content

fix(app-shell,components,plugin-detail): 对象表单之外五处渲染器把必填状态送进 a11y 树 (#3299) - #3340

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3299-required-a11y-five-sites
Aug 4, 2026
Merged

fix(app-shell,components,plugin-detail): 对象表单之外五处渲染器把必填状态送进 a11y 树 (#3299)#3340
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3299-required-a11y-five-sites

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes#3299

改动

issue #3290 / PR #3298 修掉了 form.tsx「必填只画星号、控件无必填状态」的缺陷;本 PR 按 #3299 清单把其范围栅栏之外的五处逐一收敛到同一参照形状(EmbeddableForm.tsx:481,489):

  • 控件:aria-required={required || undefined}(可选字段不带属性,而不是写 "false");
  • 星号:aria-hidden="true"(必填状态只经状态通道播报一次,不再折进可访问名被读成 "asterisk")。
位置控件处理
packages/app-shell/src/views/ActionParamDialog.tsx布尔行 + 默认分支的字段 widgethost 侧传 aria-required,经 fields 的 toDomProps 白名单(aria-* 前缀放行,#3291/#3313)落到真实控件
packages/app-shell/src/views/CreateViewDialog.tsx显示名 / 机器名两个 Input + 各视图类型必选字段 select三处均静态必填 → 字面 aria-required="true"
packages/components/src/custom/action-param-dialog.tsxtextarea / number / select / date / text 五个分支select 的状态落在 Radix trigger(可聚焦的 combobox;root 不渲染元素)
packages/components/src/custom/field.tsxFieldContainer 的 Slot 注入与既有 id / aria-describedby / aria-invalid 同通道补一个 aria-required,一处覆盖全部使用方;其星号是 CSS 伪元素、本就不进 a11y 树,无需改
packages/plugin-detail/src/InlineCreateRelated.tsxCreate 页签的 Input同型收敛

刻意不用原生 required#3290 裁决):这些宿主各自有校验(requiredError 消息 / isCreateValid 门控 / 提交时校验),原生 required 会让浏览器约束校验气泡与之双轨。issue「明确排除」段照单全收:renderers/form/{input,textarea,select,checkbox}.tsxbasic/text-input.tsx 已设原生 required 且无双轨问题,不动。

不动 spec、不动 widget props 契约(aria-required 已在既有 widget props 类型里,无需新键)。

验证

命令结果
仓根 pnpm exec vitest run 五个新测试文件 + 既有 ActionParamDialog.test.tsx / ActionParamDialog.uploading.test.tsx7 files / 58 tests 全绿(16.29s)
turbo run type-check --filter=@object-ui/components --filter=@object-ui/app-shell --filter=@object-ui/plugin-detail31 tasks successful

破坏验证实录(摘掉 → 红 → 还原 → 绿):

  1. custom/field.tsx 摘掉 Slot 注入的 aria-requiredfield-container-aria-required.test.tsx 5 例中 4 例红sets aria-required="true" on the slotted control / never sets the native required / coexists with the pre-existing Slot injections / keeps the label association intact)→ 还原全绿。这是承重位:一处覆盖 FieldContainer 全部使用方。
  2. app-shell/ActionParamDialog.tsx 摘掉默认分支 Widget 的 aria-requiredActionParamDialog.ariaRequired.test.tsx 5 例中 2 例红 → 还原全绿。该用例走真实字段 widget 全链路(host → widget props → toDomProps → DOM),widget 侧若吞掉 aria-* 同样会红。

changeset:@object-ui/app-shell / @object-ui/components / @object-ui/plugin-detailpatch(fixed group,无 major)。

按 push-first 纪律:scoped 测试全绿 + 破坏验证完成后即推送;三个包的全量回归随后本地补跑,并由 CI 把关。

🤖 Generated with Claude Code

https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa


Generated by Claude Code

… five renderer sites outside the object form (#3299)
Five renderers computed `required`, painted a red asterisk, and put NO
required state on the control — the same defect #3290/#3298 fixed in
form.tsx, at the five sites its scope fence excluded. Four of them were
worse than pre-fix form.tsx: the bare `*` had no `aria-hidden`, so the
only "signal" was a literal asterisk folded into the accessible name
("Title asterisk").
Every site converges on the reference shape (EmbeddableForm.tsx:481,489):
- control: `aria-required={required || undefined}` — absence, not
"false", for optional fields;
- asterisk: `aria-hidden="true"` — announced once, as a state, never as
part of the name.
Sites:
- app-shell/ActionParamDialog: both branches (boolean row + default);
the state rides the widget props and reaches the DOM through the
fields' `toDomProps` whitelist, which forwards `aria-*` by prefix.
- app-shell/CreateViewDialog: display label, machine name, and each
type-specific required-field selector (statically required, so a
literal `aria-required="true"`).
- components/custom/action-param-dialog: all five typed branches; for
select the state lands on the Radix trigger (the focusable combobox —
the root renders no element).
- components/custom/field: FieldContainer injects `aria-required` via
the same Slot that already injects id/aria-describedby/aria-invalid —
one line covers every consumer. Its asterisk is a CSS pseudo-element,
which never enters the a11y tree, so no marker change is needed there.
- plugin-detail/InlineCreateRelated: create-tab inputs.
Deliberately NOT native `required` (#3290 ruling): each host runs its
own validation; native required would arm the browser's constraint-
validation bubble beside it. The SDUI controls that already set native
`required` (renderers/form/{input,textarea,select,checkbox}.tsx,
basic/text-input.tsx) are excluded per the issue — no second validator
there, so their channel is already correct.
Tests per site, with mutation verification on the two load-bearing
deliveries (FieldContainer's Slot injection; the app-shell dialog's
widget-chain delivery): attribute removed -> red, restored -> green.
Fixes#3299
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa
@vercel

vercelBot commented Aug 4, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 4, 2026 5:19am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)28.1 KB350 KB
Entry fileindex-L1Cm0zM-.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.47KB3.09KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.25KB0.53KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)477.05KB104.72KB
core (index.js)2.25KB0.80KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)136.23KB34.75KB
fields (index.js)224.99KB55.14KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)26.14KB6.07KB
i18n (useSafeTranslation.js)3.26KB1.44KB
layout (index.js)38.53KB10.71KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.05KB1.53KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)8.75KB3.06KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.98KB12.37KB
plugin-charts (index.js)60.54KB17.13KB
plugin-chatbot (index.js)180.09KB42.72KB
plugin-dashboard (index.js)112.01KB28.86KB
plugin-designer (index.js)210.51KB42.51KB
plugin-detail (index.js)230.68KB56.83KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)111.54KB26.97KB
plugin-gantt (index.js)162.25KB39.55KB
plugin-grid (index.js)185.08KB49.04KB
plugin-kanban (index.js)47.89KB13.18KB
plugin-list (index.js)104.94KB25.32KB
plugin-map (index.js)16.81KB5.24KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)40.55KB10.59KB
plugin-timeline (index.js)25.76KB7.33KB
plugin-tree (index.js)8.34KB2.82KB
plugin-view (index.js)83.67KB20.43KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)19.28KB6.38KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.47KB2.03KB
sdui-parser (parse.js)10.04KB2.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)2.46KB1.21KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)0.20KB0.18KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 4, 2026 06:38
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queueAug 4, 2026
Merged via the queue into main with commit 532cf8bAug 4, 2026
14 of 16 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-3299-required-a11y-five-sites branch August 4, 2026 06:38
Sign up for freeto 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.

对象表单之外的一批渲染器仍把必填状态画成星号而不送进 a11y 树(#3290 的同类缺陷,另外五处)

2 participants

@xuyushun441-sys@claude