diff --git a/docs/design-system.md b/docs/design-system.md
index 4c28a54533..5d0f74ebad 100644
--- a/docs/design-system.md
+++ b/docs/design-system.md
@@ -618,7 +618,7 @@
| `useDcFormSubmit` | `@dc-ui/components/form` | 提交状态机 composable:`{ status, run, reset }`;`run(fn)` 驱动 idle→submitting→success/error,成功/失败按时长自动回退 |
| `useDcForm` | `@dc-ui/components/form` | 读取 `DcForm` 注入上下文(`DC_FORM_INJECTION_KEY`),供自定义提交控件使用 |
| `DcBadge` | `@dc-ui/components/badge` | shadcn Badge 语义化封装:default/secondary/outline/destructive 透传 + `success`/`warning`/`danger`/`active`/`neutral`(带 dark 变体,与 DcStatusPill 色系一致) |
-| `DcCopyButton` | `@dc-ui/components/copy-button` | 复制按钮:`copy-text` + `navigator.clipboard`,成功图标短暂变 ✅(`success-duration`);`label`/`show-toast` 可选;`@copied`/`@error` 事件 |
+| `DcCopyButton` | `@dc-ui/components/button` | 复制按钮:复用 `DcButton` 契约,`variant` 缺省 `ghost`;`copy-text` 必填触发复制(`useClipboard`),成功图标切 ✅(1200ms 自动回退、色值变 emerald);`@copied`/`@error` 事件;`label`/`tooltip` 提供可访问名,`copy-text` 兜底 |
| `DcDropdownActionItem` | `@dc-ui/components/dropdown-action-item` | 菜单动作项:`icon`/`label`/`danger`(destructive 变体)/`disabled`/`inset` + `@select`,收敛 `Icon mr-2 + span` 散写 |
| `DcFormActions` | `@dc-ui/components/form-actions` | 表单底部操作组:「取消 + 提交(DcSubmitButton)」+ 中间插槽;`submit-status`/`cancel-label`/`submit-label`/`danger-submit` |
@@ -635,10 +635,11 @@
- **tooltip 补齐边界**:只为可操作的 icon-only 控件补齐;文字按钮、纯展示 Icon、以及说明型 Switch/Checkbox/链接提示维持原行为,不为统一形式强行增加 tooltip。
- 新增 dc-ui 组件需先证明既有组件无法覆盖;在本文档登记聚合后的完整契约,并符合 §20/§21 的密度与 token 规则。
-### 22.3 迁移状态(2026-08 批次,未提交)
+### 22.3 迁移状态(2026-08 批次)
- **`DcButton` 聚合**:`DcIconButton` 已并入并删除(13 处调用迁入);手写 Tooltip+按钮迁移 10 文件 30 处(MessageToolbar 12 / WindowSideBar 8 / ChatStatusBar 2 / ModelConfigItem / MessageBlockImage / MessageBlockToolCallImagePreview / SkillsIndicator / SkillsPanel / ChatInputToolbar 4)。`DcButton` 已补齐 tooltip 定位、延迟、content class 与 keyboard-focus 参数,保留复杂提示契约。
+- **`DcCopyButton` 归位**:由 `@dc-ui/components/copy-button` 并入 `@dc-ui/components/button`(旧目录删除),消费方(MessageToolbar / ArtifactBlock / CodeArtifact / McpJsonViewer / McpServers / TraceDialog / MessageBlockToolCall)全部经 `copy-text` 传入复制内容,MessageToolbar 增加 `copy-text` prop。
- **`DcConfirmDialog`**:迁移 5 文件 7 框(ChatPage / ModelConfigDialog ×2 / KnowledgeFileItem ×2 / SkillInstallDialog / SkillDetailDialog ×2)+ ChatPage.test.ts 适配。
- **`DcEmpty`**:迁移 10 文件 12 处空态(含 MemoryEmptyState 用 `#action` 插槽,组件接口不变)。
- **`DcSectionCard`** 4 文件 11 卡(MemoryDiagnosticsPanel / MemoryDirectivesPanel / MemoryConfigInlinePanel / OfficialPluginDetailPage);**`DcStatusPill`** 2 文件(RemoteSettings 5 渠道 / AcpDebugDialog);**`DcInlineError`** 12 文件(McpServerForm 5 处等)。
-- **有意保留(记录在案)**:WindowSideBar remote-control(多行 tooltip)、WindowSideBarSessionItem pin/delete(依赖 scoped CSS)、MessageDialog / UpdateTaskCheckDialog(动态多按钮)、SettingsLeaveGuardDialog(路由守卫)、DataSettings 单按钮错误弹窗、desc-only 空态提示、带重试按钮的错误横幅。
\ No newline at end of file
+- **有意保留(记录在案)**:WindowSideBar remote-control(多行 tooltip)、WindowSideBarSessionItem pin/delete(依赖 scoped CSS)、MessageDialog / UpdateTaskCheckDialog(动态多按钮)、SettingsLeaveGuardDialog(路由守卫)、DataSettings 单按钮错误弹窗、desc-only 空态提示、带重试按钮的错误横幅。
diff --git a/src/dc-ui/components/button/DcButton.vue b/src/dc-ui/components/button/DcButton.vue
index 7f5f5c7a48..23d17bae8c 100644
--- a/src/dc-ui/components/button/DcButton.vue
+++ b/src/dc-ui/components/button/DcButton.vue
@@ -1,6 +1,4 @@
diff --git a/src/dc-ui/components/button/index.ts b/src/dc-ui/components/button/index.ts
index 27c43dc106..45a7628e0b 100644
--- a/src/dc-ui/components/button/index.ts
+++ b/src/dc-ui/components/button/index.ts
@@ -1,38 +1,3 @@
-import type { VariantProps } from 'class-variance-authority'
-import { cva } from 'class-variance-authority'
-
export { default as DcButton } from './DcButton.vue'
-
-export const dcButtonVariants = cva(
- 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-[color,background-color,border-color,scale] duration-[var(--dc-motion-fast)] ease-[var(--dc-ease-out-soft)] active:scale-[0.97] motion-reduce:active:scale-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 [&_svg]:shrink-0 shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
- {
- variants: {
- variant: {
- default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
- destructive:
- 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
- outline:
- 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
- secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
- ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
- link: 'text-primary underline-offset-4 hover:underline'
- },
- size: {
- default: 'h-9 gap-2 px-4 py-2 has-[>svg]:px-3',
- sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
- xs: 'h-7 gap-1.5 rounded-md px-2.5 text-xs has-[>svg]:px-2',
- lg: 'h-10 gap-2 rounded-md px-6 has-[>svg]:px-4',
- icon: 'size-8',
- 'icon-sm': 'size-7',
- 'icon-xs': 'size-6',
- 'icon-lg': 'size-10'
- }
- },
- defaultVariants: {
- variant: 'default',
- size: 'default'
- }
- }
-)
-
-export type DcButtonVariants = VariantProps
+export { default as DcCopyButton } from './DcCopyButton.vue'
+export * from './props'
diff --git a/src/dc-ui/components/button/props.ts b/src/dc-ui/components/button/props.ts
new file mode 100644
index 0000000000..3e54df4b21
--- /dev/null
+++ b/src/dc-ui/components/button/props.ts
@@ -0,0 +1,62 @@
+import type { PrimitiveProps } from 'reka-ui'
+import type { HTMLAttributes } from 'vue'
+
+export interface DcButtonProps extends PrimitiveProps {
+ variant?: DcButtonVariants['variant']
+ size?: DcButtonVariants['size']
+ icon?: string
+ iconSize?: DcIconSize
+ iconClass?: HTMLAttributes['class']
+ loading?: boolean
+ disabled?: boolean
+ active?: boolean
+ /** Visible tooltip. `label` alone only provides the accessible name. */
+ tooltip?: string
+ tooltipSide?: DcTooltipSide
+ tooltipSideOffset?: number
+ tooltipDelayDuration?: number
+ tooltipContentClass?: HTMLAttributes['class']
+ tooltipIgnoreNonKeyboardFocus?: boolean
+ label?: string
+ class?: HTMLAttributes['class']
+}
+
+import type { VariantProps } from 'class-variance-authority'
+import { cva } from 'class-variance-authority'
+
+type DcIconSize = '3' | '3.5' | '4'
+type DcTooltipSide = 'top' | 'bottom' | 'left' | 'right'
+
+export const dcButtonVariants = cva(
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-[color,background-color,border-color,scale] duration-[var(--dc-motion-fast)] ease-[var(--dc-ease-out-soft)] active:scale-[0.97] motion-reduce:active:scale-100 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*="size-"])]:size-4 [&_svg]:shrink-0 shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
+ {
+ variants: {
+ variant: {
+ default: 'bg-primary text-primary-foreground shadow-xs hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
+ outline:
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
+ secondary: 'bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80',
+ ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
+ link: 'text-primary underline-offset-4 hover:underline'
+ },
+ size: {
+ default: 'h-9 gap-2 px-4 py-2 has-[>svg]:px-3',
+ sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
+ xs: 'h-7 gap-1.5 rounded-md px-2.5 text-xs has-[>svg]:px-2',
+ lg: 'h-10 gap-2 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-8',
+ 'icon-sm': 'size-7',
+ 'icon-xs': 'size-6',
+ 'icon-lg': 'size-10'
+ }
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default'
+ }
+ }
+)
+
+export type DcButtonVariants = VariantProps
diff --git a/src/dc-ui/components/copy-button/DcCopyButton.vue b/src/dc-ui/components/copy-button/DcCopyButton.vue
deleted file mode 100644
index 296dd1ef9a..0000000000
--- a/src/dc-ui/components/copy-button/DcCopyButton.vue
+++ /dev/null
@@ -1,142 +0,0 @@
-
-
-
-
-
-
-
- {{ label }}
-
-
-
- {{ tooltip }}
-
-
-
-
- {{ label }}
-
-
diff --git a/src/dc-ui/components/copy-button/index.ts b/src/dc-ui/components/copy-button/index.ts
deleted file mode 100644
index ef90f141d8..0000000000
--- a/src/dc-ui/components/copy-button/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { default as DcCopyButton } from './DcCopyButton.vue'
diff --git a/src/dc-ui/components/index.ts b/src/dc-ui/components/index.ts
new file mode 100644
index 0000000000..723978ff93
--- /dev/null
+++ b/src/dc-ui/components/index.ts
@@ -0,0 +1,16 @@
+export * from './button'
+export * from './status-pill'
+export * from './confirm-dialog'
+export * from './toggle-row'
+export * from './empty'
+export * from './skeleton'
+export * from './toast'
+export * from './tooltip'
+export * from './sheet-panel'
+export * from './popover'
+export * from './section-card'
+export * from './inline-error'
+export * from './form'
+export * from './badge'
+export * from './dropdown-action-item'
+export * from './form-actions'
diff --git a/src/dc-ui/components/toggle-row/DcToggleRow.vue b/src/dc-ui/components/toggle-row/DcToggleRow.vue
index 803aaf6459..6bb45a807c 100644
--- a/src/dc-ui/components/toggle-row/DcToggleRow.vue
+++ b/src/dc-ui/components/toggle-row/DcToggleRow.vue
@@ -35,10 +35,13 @@ const handleChecked = (value: boolean | 'indeterminate') => {
-
+