diff --git a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.i18n.yaml
index 7f36d0b961..9583c7c1de 100644
--- a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.i18n.yaml
+++ b/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md
-2026-09-04-busy-send-button-follows-enter-setting.md: 31f21cfa58f96e95bfda2e8aa04d41f8fa24c46a
-2026-09-04-busy-send-button-follows-enter-setting.zh.md: 0c7ddfe8e6f207ee489c519626b06e67c75530bf
+2026-09-04-busy-send-button-follows-enter-setting.md: 44109de724fc4fe5889fbf8e425bbf735938f5eb
+2026-09-04-busy-send-button-follows-enter-setting.zh.md: 7dba28385bc83cc1f3e3b6ead3c2fcfdd0042b91
diff --git a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md b/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md
index 31f21cfa58..44109de724 100644
--- a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md
+++ b/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md
@@ -10,7 +10,7 @@ The Web composer offers one user-facing choice for submitting while the agent is
## Decision
-The running Send button delivers through the same mode as plain Enter. `InputBar` computes `resolveSubmitMode(busyEnter, running, 'enter', steeringAvailable)` once per render, where `steeringAvailable` is the same ordinary-Session-or-continuable-child predicate the keyboard path uses, applies it to the primary click through `ComposerKeyboard.submit(mode)`, and applies it to the primary label exactly when the click would deliver a plain message: the composer is running and steer-capable, the button is enabled, and the draft is non-empty, unclaimed, and not a `/` line headed for command adjudication. That state shows `input.send.queue` ("Queue message" / "排队发送") or `input.send.steer` ("Steer message" / "插话发送") as both the tooltip and the accessible name; every other state — idle sessions, one-shot children, locked composers, empty drafts, and command drafts whose click executes the command rather than delivering a message — keeps `input.send` ("Send message"). Cmd/Ctrl+Enter still resolves to the opposite mode, and the empty-draft accelerated gesture still steers the whole queue.
+The running Send button delivers through the same mode as plain Enter. `InputBar` computes `resolveSubmitMode(busyEnter, running, 'enter', steeringAvailable)` once per render, where `steeringAvailable` is the same ordinary-Session-or-continuable-child predicate the keyboard path uses, applies it to the primary click through `ComposerKeyboard.submit(mode)`, and applies it to the primary label exactly when the click would deliver a plain message: the composer is running and steer-capable, the button is enabled (no file upload still pending), and the draft is non-empty, unclaimed, and not a `/` line headed for command adjudication. That state shows `input.send.queue` ("Queue message" / "排队发送") or `input.send.steer` ("Steer message" / "插话发送") as both the tooltip and the accessible name; every other state — idle sessions, one-shot children, locked composers, empty drafts, drafts with a pending upload, and command drafts whose click executes the command rather than delivering a message — keeps `input.send` ("Send message"). Cmd/Ctrl+Enter still resolves to the opposite mode, and the empty-draft accelerated gesture still steers the whole queue.
The composer bar's inject face carries the live preference instead of a resolver closure. `ComposerBarInjected.hooks.busyEnter` publishes `ComposerSubmissionPolicy.busyEnter`, so the bar receives a `useBusyEnter` selector hook and re-renders the label when the Settings row or a Host settings update changes the value. `resolveSubmitMode` is a pure exported function in `submission-policy.ts` taking the preference explicitly; the policy class keeps only the store and its Host adoption and write-through.
@@ -18,7 +18,7 @@ The Settings row is retitled to cover both inputs: "Send behavior while busy" /
## Verification
-`input-bar.client.spec.tsx` asserts that a running draft's button is labeled by mode and submits with that mode under both preferences, that flipping the preference store re-labels the mounted button before the next click, that idle Send keeps the plain label and Queue delivery regardless of the preference, that a continuable subagent's Send follows the same mode and label as an ordinary Session while its empty-draft disabled button and a one-shot child keep plain Send, and that a `/` line and a claimed command keep plain Send while running. `submission-policy.client.spec.ts` pins `resolveSubmitMode` for every preference, running, gesture, and steering-availability combination. `enter-behavior-row.client.spec.tsx` and the `settings-chrome` ARIA goldens carry the new Settings copy. The keyless `live-interactions` Web scenario waits for "Queue message" on the parked running draft and asserts that no "Send message" button exists at that moment, and its `running-draft.expected.md` golden records the new name.
+`input-bar.client.spec.tsx` asserts that a running draft's button is labeled by mode and submits with that mode under both preferences, that flipping the preference store re-labels the mounted button before the next click, that idle Send keeps the plain label and Queue delivery regardless of the preference, that a continuable subagent's Send follows the same mode and label as an ordinary Session while its empty-draft disabled button and a one-shot child keep plain Send, and that a `/` line, a claimed command, and a draft with a still-uploading file keep plain Send while running. `submission-policy.client.spec.ts` pins `resolveSubmitMode` for every preference, running, gesture, and steering-availability combination. `enter-behavior-row.client.spec.tsx` and the `settings-chrome` ARIA goldens carry the new Settings copy. The keyless `live-interactions` Web scenario waits for "Queue message" on the parked running draft and asserts that no "Send message" button exists at that moment, and its `running-draft.expected.md` golden records the new name.
## Alternatives considered
diff --git a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md b/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md
index 0c7ddfe8e6..7dba28385b 100644
--- a/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md
+++ b/.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md
@@ -10,7 +10,7 @@ Web composer 为 agent(智能体)运行期间的提交只提供一个面向
## 决策
-运行中的 Send 按钮按与 plain Enter 相同的模式投递。`InputBar` 每次渲染计算一次 `resolveSubmitMode(busyEnter, running, 'enter', steeringAvailable)`,其中 `steeringAvailable` 与键盘路径使用同一个"普通 Session 或可继续 child"判定;用它通过 `ComposerKeyboard.submit(mode)` 执行主按钮点击,并且仅在点击会投递一条普通消息时用它决定主按钮标签:composer 运行中且可 steering、按钮可用、草稿非空、未被认领且不是将进入命令 adjudication 的 `/` 行。该状态把 `input.send.queue`("Queue message" / "排队发送")或 `input.send.steer`("Steer message" / "插话发送")同时用作 tooltip 与可访问名称;其余所有状态——空闲会话、one-shot child、锁定的 composer、空草稿,以及点击会执行命令而非投递消息的命令草稿——保留 `input.send`("Send message")。Cmd/Ctrl+Enter 仍解析为相反模式,空草稿下的加速手势仍对整个队列执行 steering(中途引导)。
+运行中的 Send 按钮按与 plain Enter 相同的模式投递。`InputBar` 每次渲染计算一次 `resolveSubmitMode(busyEnter, running, 'enter', steeringAvailable)`,其中 `steeringAvailable` 与键盘路径使用同一个"普通 Session 或可继续 child"判定;用它通过 `ComposerKeyboard.submit(mode)` 执行主按钮点击,并且仅在点击会投递一条普通消息时用它决定主按钮标签:composer 运行中且可 steering、按钮可用(没有仍在上传的文件)、草稿非空、未被认领且不是将进入命令 adjudication 的 `/` 行。该状态把 `input.send.queue`("Queue message" / "排队发送")或 `input.send.steer`("Steer message" / "插话发送")同时用作 tooltip 与可访问名称;其余所有状态——空闲会话、one-shot child、锁定的 composer、空草稿、带待上传附件的草稿,以及点击会执行命令而非投递消息的命令草稿——保留 `input.send`("Send message")。Cmd/Ctrl+Enter 仍解析为相反模式,空草稿下的加速手势仍对整个队列执行 steering(中途引导)。
composer bar 的 inject 接口携带实时偏好,而不是解析闭包。`ComposerBarInjected.hooks.busyEnter` 发布 `ComposerSubmissionPolicy.busyEnter`,因此 bar 获得 `useBusyEnter` 选择器 hook,并在设置行或 Host 设置更新改变该值时重新渲染标签。`resolveSubmitMode` 是 `submission-policy.ts` 中导出的纯函数,显式接收偏好值;policy 类只保留 store 及其 Host 采纳与写回。
@@ -18,7 +18,7 @@ composer bar 的 inject 接口携带实时偏好,而不是解析闭包。`Comp
## 验证
-`input-bar.client.spec.tsx` 断言运行中草稿的按钮在两种偏好下都按模式标注并以该模式提交,切换偏好 store 会在下一次点击前重新标注已挂载的按钮,空闲 Send 无论偏好如何都保留普通标签与 Queue 投递,可继续 subagent 的 Send 与普通 Session 遵循同一模式与标签,而其空草稿下的禁用按钮与 one-shot child 保留普通 Send,运行中的 `/` 行与已认领命令也保留普通 Send。`submission-policy.client.spec.ts` 钉住 `resolveSubmitMode` 在偏好、运行状态、手势与 steering 可用性所有组合下的结果。`enter-behavior-row.client.spec.tsx` 与 `settings-chrome` ARIA golden 携带新的设置文案。无密钥的 `live-interactions` Web 场景在停住的运行中草稿上等待"Queue message",并断言此刻不存在"Send message"按钮,其 `running-draft.expected.md` golden 记录了新名称。
+`input-bar.client.spec.tsx` 断言运行中草稿的按钮在两种偏好下都按模式标注并以该模式提交,切换偏好 store 会在下一次点击前重新标注已挂载的按钮,空闲 Send 无论偏好如何都保留普通标签与 Queue 投递,可继续 subagent 的 Send 与普通 Session 遵循同一模式与标签,而其空草稿下的禁用按钮与 one-shot child 保留普通 Send,运行中的 `/` 行、已认领命令与带仍在上传文件的草稿也保留普通 Send。`submission-policy.client.spec.ts` 钉住 `resolveSubmitMode` 在偏好、运行状态、手势与 steering 可用性所有组合下的结果。`enter-behavior-row.client.spec.tsx` 与 `settings-chrome` ARIA golden 携带新的设置文案。无密钥的 `live-interactions` Web 场景在停住的运行中草稿上等待"Queue message",并断言此刻不存在"Send message"按钮,其 `running-draft.expected.md` golden 记录了新名称。
## 备选方案
diff --git a/packages/client/ui-conversation/README.i18n.yaml b/packages/client/ui-conversation/README.i18n.yaml
index de73d95f3c..d93d7d7dc5 100644
--- a/packages/client/ui-conversation/README.i18n.yaml
+++ b/packages/client/ui-conversation/README.i18n.yaml
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-conversation/README.md
-README.md: f3172c9849c5d6e65c5656fcc2949f598729f662
-README.zh.md: 9b3174c52d72047500c003b04d654fa88a20d022
+README.md: e06270a339a23fa17fb9d51642317fcf2aec6c0f
+README.zh.md: 315fd3d7798145df7dbb6f504fe7b89c64827024
diff --git a/packages/client/ui-conversation/README.md b/packages/client/ui-conversation/README.md
index f3172c9849..e06270a339 100644
--- a/packages/client/ui-conversation/README.md
+++ b/packages/client/ui-conversation/README.md
@@ -48,7 +48,7 @@ Default sends commit optimistically: Enter clears the draft, occurrence table, a
Queued submission echoes show “Sending…” beside disabled edit, remove, and steer buttons; a collapsed dock keeps the sending status in its header. A matching Host queue row replaces the echo and enables each action according to its normal text-content and running-state requirements. Prompt acknowledgement alone does not enable queue actions. A failed submission removes its echo and displays an error; the composer restores the failed draft when it is empty or still contains the previous automatic restoration, preserving subsequently typed text.
-While a normal composer is running, its primary pointer action remains Stop when the draft is empty or input is unavailable. Actionable text or attachments switch the same seat to Send; clearing or successfully submitting the draft restores Stop. The busy-Enter setting selects the Queue or Steer delivery for ordinary Sessions and continuable children, and the running Send button delivers through the same mode plain Enter resolves to; while it is enabled over a plain message draft its label names that mode (Queue message or Steer message), so the setting governs Enter and the button together while Cmd/Ctrl+Enter still uses the other mode, and idle sessions, empty drafts, and `/` command lines keep the plain Send label ([decision](../../../.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md)). Their QueueDock rows share Edit, Remove, and Steer, and an empty draft shares the steer-all chord. One-shot children remain read-only. Plan mode and active goals do not change attachment intake. Continuable children keep separate Send and Stop actions but expose no paperclip, paste, or drop intake; if their parent is offline, Send and the composer gestures lock while QueueDock controls for the live inbox remain available ([decisions](../../../.agents/notes/archived/bug-fix/2026-08-20-running-draft-primary-send.md), [inbox controls](../../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md)).
+While a normal composer is running, its primary pointer action remains Stop when the draft is empty or input is unavailable. Actionable text or attachments switch the same seat to Send; clearing or successfully submitting the draft restores Stop. The busy-Enter setting selects the Queue or Steer delivery for ordinary Sessions and continuable children, and the running Send button delivers through the same mode plain Enter resolves to; while it is enabled (no upload pending) over a plain message draft its label names that mode (Queue message or Steer message), so the setting governs Enter and the button together while Cmd/Ctrl+Enter still uses the other mode, and idle sessions, empty drafts, and `/` command lines keep the plain Send label ([decision](../../../.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.md)). Their QueueDock rows share Edit, Remove, and Steer, and an empty draft shares the steer-all chord. One-shot children remain read-only. Plan mode and active goals do not change attachment intake. Continuable children keep separate Send and Stop actions but expose no paperclip, paste, or drop intake; if their parent is offline, Send and the composer gestures lock while QueueDock controls for the live inbox remain available ([decisions](../../../.agents/notes/archived/bug-fix/2026-08-20-running-draft-primary-send.md), [inbox controls](../../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.md)).
## Temporary composer entries
diff --git a/packages/client/ui-conversation/README.zh.md b/packages/client/ui-conversation/README.zh.md
index 9b3174c52d..315fd3d779 100644
--- a/packages/client/ui-conversation/README.zh.md
+++ b/packages/client/ui-conversation/README.zh.md
@@ -48,7 +48,7 @@ Session 首次绑定或缓存的 Session 成为 current 时,shell 会在渲染
排队提交的本地回显在禁用的编辑、删除、插话按钮旁显示“发送中…”;折叠后的队列在标题栏保留发送状态。匹配的 Host 队列行替换回显后,各操作按原有的纯文本内容和运行状态要求启用。仅收到 prompt 确认不会启用队列操作。提交失败会移除回显并显示错误;输入框为空或仍保留上一次自动恢复的内容时,composer 恢复失败草稿,保留用户随后输入的文字。
-普通 composer 运行时,如果草稿为空或输入不可用,主指针操作保持为 Stop。可提交的文字或附件会把同一位置切换为 Send;清空或成功提交草稿后恢复 Stop。繁忙态 Enter 设置为普通 Session 与可继续 child 选择 Queue 或 Steer 投递,运行中的 Send 按钮按 plain Enter 解析出的同一模式投递;当它在普通消息草稿上可用时,其标签以该模式命名(排队发送或插话发送),因此该设置同时约束 Enter 与按钮,而 Cmd/Ctrl+Enter 仍使用另一模式;空闲会话、空草稿与 `/` 命令行保留普通的 Send 标签([决策](../../../.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md))。它们的 QueueDock 行共享 Edit、Remove 与 Steer,空草稿也共享 steer-all 组合键。One-shot child 继续只读。Plan Mode 与 active goal 不改变附件入口。可继续 child 保留独立的 Send 与 Stop 操作,但不提供回形针、粘贴或拖放入口;parent 离线时,Send 与 composer 手势锁定,但在线 inbox 的 QueueDock 控制仍可使用([决策](../../../.agents/notes/archived/bug-fix/2026-08-20-running-draft-primary-send.md)、[inbox 控制](../../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md))。
+普通 composer 运行时,如果草稿为空或输入不可用,主指针操作保持为 Stop。可提交的文字或附件会把同一位置切换为 Send;清空或成功提交草稿后恢复 Stop。繁忙态 Enter 设置为普通 Session 与可继续 child 选择 Queue 或 Steer 投递,运行中的 Send 按钮按 plain Enter 解析出的同一模式投递;当它在普通消息草稿上可用(没有待上传文件)时,其标签以该模式命名(排队发送或插话发送),因此该设置同时约束 Enter 与按钮,而 Cmd/Ctrl+Enter 仍使用另一模式;空闲会话、空草稿与 `/` 命令行保留普通的 Send 标签([决策](../../../.agents/notes/implemented/bug-fix/2026-09-04-busy-send-button-follows-enter-setting.zh.md))。它们的 QueueDock 行共享 Edit、Remove 与 Steer,空草稿也共享 steer-all 组合键。One-shot child 继续只读。Plan Mode 与 active goal 不改变附件入口。可继续 child 保留独立的 Send 与 Stop 操作,但不提供回形针、粘贴或拖放入口;parent 离线时,Send 与 composer 手势锁定,但在线 inbox 的 QueueDock 控制仍可使用([决策](../../../.agents/notes/archived/bug-fix/2026-08-20-running-draft-primary-send.md)、[inbox 控制](../../../.agents/notes/implemented/feature/2026-08-27-continuable-subagent-human-inbox-control.zh.md))。
## 临时 composer entry
diff --git a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
index 52853f01cc..aa37b85ecf 100644
--- a/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
+++ b/packages/client/ui-conversation/src/client/skeleton/InputBar.tsx
@@ -338,17 +338,18 @@ export const InputBar = memo(function InputBar({
// owner-blocked; an actionable draft gets the busy Send action, delivered
// through the same mode plain Enter resolves to. The label names that mode
// only when the click would deliver a plain message right now — an enabled
- // button over a non-empty draft that is neither a claimed command nor a
- // `/` line headed for adjudication — so it never describes a delivery the
- // click cannot or does not perform; every other state keeps plain Send. A
- // continuable child keeps Send primary and exposes Stop independently.
+ // button (no pending upload) over a non-empty draft that is neither a
+ // claimed command nor a `/` line headed for adjudication — so it never
+ // describes a delivery the click cannot or does not perform; every other
+ // state keeps plain Send. A continuable child keeps Send primary and
+ // exposes Stop independently.
const primaryStops = running && subagent === null && (empty || blocked !== undefined)
const interruptible = running && continuable
const primarySubmitMode = resolveSubmitMode(busyEnter, running, 'enter', steeringAvailable)
const plainMessageDraft = !empty && input?.phase === 'plain' && !draft.trimStart().startsWith('/')
const primaryLabel = primaryStops
? t('input.stop')
- : running && steeringAvailable && !disabled && plainMessageDraft
+ : running && steeringAvailable && !disabled && !uploadsPending && plainMessageDraft
? t(primarySubmitMode === 'steer' ? 'input.send.steer' : 'input.send.queue')
: t('input.send')
const onPrimary = (): void => {
diff --git a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx
index 966956135a..910faedd0e 100644
--- a/packages/client/ui-conversation/tests/input-bar.client.spec.tsx
+++ b/packages/client/ui-conversation/tests/input-bar.client.spec.tsx
@@ -24,7 +24,7 @@ import type { SubmitOutcome } from '../src/client/contract/input.ts'
import { SessionInputShell } from '../src/client/input/facade.ts'
import { $replaceDetectSpanWithText, $selectDetectSpan } from '../src/client/input/editor/span-map.ts'
import type {
- ComposerAttachment, ComposerAttachmentsOwnerProps,
+ ComposerAttachment, ComposerAttachmentsOwnerProps, DraftFileUploads,
} from '../src/client/contract/slots.ts'
import type { DraftAttachmentId } from '../src/client/contract/input.ts'
import { InputBar } from '../src/client/skeleton/InputBar.tsx'
@@ -89,6 +89,8 @@ interface BenchOptions {
rightItems?: React.ReactNode
footer?: React.ReactNode
attachments?: readonly ComposerAttachment[]
+ /** Upload states served for file-kind drafts (absent = every file is ready). */
+ fileUploads?: DraftFileUploads
addFiles?: (files: readonly File[]) => string | null
commandMenuOpen?: boolean
busyEnter?: 'queue' | 'steer'
@@ -180,7 +182,7 @@ function bench(over?: BenchOptions) {
inputActions: shell.actions,
keyboard: shell,
addFiles: over?.addFiles ?? (() => null),
- useFileUploads: bindSnapshotSelector(createSnapshotStore({})),
+ useFileUploads: bindSnapshotSelector(createSnapshotStore(over?.fileUploads ?? {})),
retryFileUpload: undefined,
removeAttachment,
resolveDraftAttachments: ids => ids.flatMap((id) => {
@@ -217,7 +219,9 @@ function bench(over?: BenchOptions) {
const steeringAvailable = over?.subagent === undefined || over.subagent.address.mode === 'continuable'
const composerLocked = over?.disabled === true || over?.inert === true || over?.blocked !== undefined
|| (over?.subagent?.address.mode === 'continuable' && over.subagent.parentAvailable !== true)
- const plainMessageDraft = sendableDraft && !(over?.draft?.trimStart().startsWith('/') ?? false)
+ const uploadsPending = (over?.attachments ?? []).some(attachment =>
+ attachment.kind === 'file' && over?.fileUploads?.[attachment.id]?.status !== 'ready')
+ const plainMessageDraft = sendableDraft && !uploadsPending && !(over?.draft?.trimStart().startsWith('/') ?? false)
const primaryLabel = primaryStops
? '停止生成'
: over?.running === true && steeringAvailable && !composerLocked && plainMessageDraft
@@ -782,6 +786,23 @@ describe('running and lock semantics', () => {
expect(claimed.view.container.querySelector('button[aria-label="插话发送"]')).toBeNull()
})
+ it('running Send keeps the plain label while a file upload is still pending', () => {
+ const file = { kind: 'file' as const, id: 'file-1' as DraftAttachmentId, file: new File(['x'], 'note.txt', { type: 'text/plain' }) }
+ const pending = bench({
+ running: true, busyEnter: 'steer', draft: '带附件', attachments: [file],
+ fileUploads: { [file.id]: { status: 'uploading', loaded: 0 } },
+ })
+ expect(pending.button.getAttribute('aria-label')).toBe('发送消息')
+ expect(pending.button.disabled).toBe(true)
+
+ const ready = bench({
+ running: true, busyEnter: 'steer', draft: '带附件', attachments: [file],
+ fileUploads: { [file.id]: { status: 'ready', receiptId: 'receipt-1' as never, file: { kind: 'file' } as never } },
+ })
+ expect(ready.button.getAttribute('aria-label')).toBe('插话发送')
+ expect(ready.button.disabled).toBe(false)
+ })
+
it('idle Send keeps the plain label regardless of the busy-state preference', () => {
const { button, sink } = bench({ busyEnter: 'steer', draft: '空闲发送' })
expect(button.getAttribute('aria-label')).toBe('发送消息')