Skip to content

fix(app-shell): name the copy buttons in an action's result dialog and announce the copied state - #28265

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-yjvy62
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-yjvy62

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #28186

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:动作结果弹窗(邀请链接、新 API 密钥、2FA 密钥等只显示一次的值)里每个仅图标的「复制」按钮现在都有可读的名称,例如「复制 Email」;没有字段标签时叫「复制值」,备用码列表按序号叫「复制 备用码 第 2 项」。复制成功后,读屏会念出「已复制到剪贴板」。
  • 为什么改:读屏或语音控制用户以前只听到「按钮」,分不清它是复制哪个值,也没法按名字点它;值只显示一次,漏复制就得把动作重做一遍。
  • 风险与回滚:只改渲染层,弹窗的布局与复制行为不变(新的播报区是视觉隐藏的);十个语言包各加 5 个键。回滚即 revert 本 PR。
  • 先看:ui/packages/app-shell/src/views/ActionResultDialog.tsx(CopyButton 与 ResultField 里的 copyName);ui/packages/app-shell/src/views/__tests__/ActionResultDialog.test.tsx 新增的 describe;ui/packages/i18n/src/locales/en.ts 的 actions.resultDialog。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/__tests__/ActionResultDialog.test.tsx,预期 1 个文件、12 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

Names (ui/packages/app-shell/src/views/ActionResultDialog.tsx). ResultField already resolves the field's I18nLabel; it now builds the accessible name of that field's copy buttons from it and hands it to the format block (QrcodeBlock, SecretBlock, TextBlock, JsonBlock, CodeListBlock):

  • a labelled field: actions.resultDialog.copyField ("Copy LABEL");
  • an unlabelled field, including the whole-payload JSON fallback: actions.resultDialog.copyValue ("Copy value");
  • each code of a code list, by 1-based position: copyFieldItem ("Copy LABEL, item N") or copyItem ("Copy item N").

CopyButton takes either a visible label (that text is its name, as "Copy all" already was) or a name that becomes its aria-label; the prop type makes one of the two required, so a new icon-only use cannot compile without a name. The Copy / Check icons are marked aria-hidden.

Copied state. Each CopyButton renders a visually hidden role="status" region (implicitly polite) beside the button. It is mounted empty and filled with actions.resultDialog.copied ("Copied to clipboard") while the copied state lasts, so a screen reader announces the change. It is positioned out of flow by sr-only, so the flex rows keep their layout.

Translations. The five keys are added under actions.resultDialog in all ten packs (en, zh, ja, ko, de, fr, es, pt, ru, ar), next to the existing copyAll.

#27521's focus pins. The change does not touch useActionDialogReturnFocus or the dialog's focus props; actionDialogFocus-27521.test.tsx is run below as a control.

Tests

ui/packages/app-shell/src/views/__tests__/ActionResultDialog.test.tsx gains a describe, "every copy button has an accessible name", with role-and-name queries. The file's useObjectTranslation stub now echoes interpolation params after the key, so a name shows which label reached it (existing assertions use no params and are unchanged).

  • each of a text, secret, qrcode and json field's copy button is found by getByRole('button', { name: 'actions.resultDialog.copyField(label=LABEL)' });
  • the whole-payload fallback's button is named copyValue;
  • a labelled code list's per-code buttons are named copyFieldItem with index 1 and 2, and "Copy all" keeps its visible name; an unlabelled list's are named copyItem;
  • no button in the dialog is left without a name (all buttons equal the buttons with a non-blank name);
  • the status region exists and is empty before the click, writeText receives the value, and the same region node then reads actions.resultDialog.copied.

Acceptance notes

  • "Copy all" keeps its visible text as its accessible name. Two code-list fields in one dialog would show two "Copy all" buttons with the same name; no shipped resultDialog declares two code lists, so this is noted, not changed.
  • The live region announces only a successful copy. A refused clipboard write still does nothing visible or audible, as before; the value stays on screen for a manual copy.

Verification

Tree: objectstack-ai/objectstack, branch head named in the os-dev report on the card.

Result dialog tests
pnpm --dir ui exec vitest run packages/app-shell/src/views/__tests__/ActionResultDialog.test.tsx
 Test Files  1 passed (1)
      Tests  12 passed (12)
os-verify-lock: VERDICT command-exit 0
Ablation: the fix removed, the new pins go red

aria-label on CopyButton and the status span deleted (0 hits for each confirmed on disk), same command:

     × names each field's copy button after the field label
     × names an unlabelled value's copy button generically
     × names each code of a code list by its position, and keeps the visible name of Copy all
     × names the codes of an unlabelled code list by position alone
     × leaves no button in the dialog without a name
     × announces the copied state in a live region that is mounted before it fills
      Tests  6 failed | 6 passed (12)
os-verify-lock: VERDICT command-exit 1

Restored with git checkout HEAD --; git diff HEAD empty afterwards. The 6 pre-existing tests stayed green in both legs.

Derived gates (dispatch-gates --commands, head f68fcad): 34 of 34 exit 0, reconciliation 34 run, 0 NOT-MEASURED, 0 UNRUN
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0

check:type-check-debt first hit a 500 s timeout wrapper while the type-check below held the box (exit 124); re-run alone, exit 0, and the record carries that code.

Type-check, sibling tests and line citations
pnpm --filter @object-ui/i18n run type-check && pnpm --filter @object-ui/app-shell run type-check   (after a full dts build of @object-ui/app-shell^...)
os-verify-lock: VERDICT command-exit 0

pnpm --dir ui exec vitest run packages/app-shell/src/views/actionDialogFocus-27521.test.tsx packages/app-shell/src/views/ObjectView.inviteOutcome-27797.test.tsx packages/i18n/
 Test Files  95 passed (95)
      Tests  1445 passed | 13 skipped (1458)

pnpm --dir ui run check:new-line-citations
VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0

Generated by Claude Code

…e copied state

Each icon-only copy button carries an accessible name built from its
field's label (or its position in a code list), from new
actions.resultDialog keys in all ten packs. A polite live region,
mounted empty, announces the copied state.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L9qibvSA9x5ZD2RncR2GRx
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling labels Oct 30, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN

席位核对

ACCEPT · seat domain:ui#1 · head f68fcadb · 2026-10-30T21:52Z

席位意见:动作结果对话框(邀请链接、新 API 密钥、2FA 密钥等只显示一次的值)里,每个只有图标的复制按钮现在都有可读的名称,例如「复制 Email」。代码列表中的每一项按位置命名,复制成功后会礼貌地播报「已复制到剪贴板」。读屏和语音控制用户可以按名称找到并使用这些按钮。

  1. Shape: Fixes #28186, draft, base main. 13 files, +249/−23: ui/packages/app-shell/src/views/ActionResultDialog.tsx, five actions.resultDialog keys in all ten packs, the result-dialog tests, and a root changeset (app-shell, i18n patch). This matches the claim's file surface.
  2. Seat read: CopyButton's props are typed so that each button carries either a visible label (Copy all) or an aria-label name, never neither. The icons are aria-hidden. Each button's live region is mounted empty and fills on copy, so it is read reliably. The reveal button already had its name (showValue/hideValue). fix(ui,platform-objects): invitation screens — labelled statuses, role help, resend as a confirmation, dialog focus and email check #28224's focus pins and Setup Invitations: with no mail transport the toast says "Invitation sent to …" and there is no Copy invite link, so the invitation dead-ends (#27521 row 6) #27797's invite test stay green.
  3. Contract review: none owed. Clause-②: no was measured on the diff (a renderer accessibility fix with no export, key, accept set or result field change), and the claim agrees.
  4. Gates / check-runs: on f68fcadb: 55 runs, 44 success, 11 skipped, 0 failure. Dev's local run: 34/34 derived gates exit 0, 12/12 result-dialog tests (6 new role-and-name pins; the ablation turns all 6 red), app-shell and i18n type-check exit 0.
  5. Landing: the seat readies the PR and arms auto-merge on the maintainer's 「按 pm-dispatch 审过的pr你可以自主落地」 (seat thread, 2026-10-30T14:19Z).

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 21:53
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit ef777b8 Oct 30, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-yjvy62 branch October 30, 2026 22:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Action result dialog: the copy button has no accessible name

1 participant