diff --git a/packages/client/ui-activity/src/client/ActivityListAction.module.css b/packages/client/ui-activity/src/client/ActivityListAction.module.css index 404c77e5b9..90ecd4d70e 100644 --- a/packages/client/ui-activity/src/client/ActivityListAction.module.css +++ b/packages/client/ui-activity/src/client/ActivityListAction.module.css @@ -119,15 +119,26 @@ color: var(--dsw-alias-label-secondary); } +/* The armed press widens into a labeled danger pill so the confirm step is + legible without hover; the state-error token is the one StateDot uses. */ .stopArmed, .stopArmed:hover, .stopArmed:focus-visible { - background: var(--dsw-alias-fill-l1); - color: var(--dsw-alias-label-error, var(--dsw-alias-label-secondary)); + gap: 4px; + width: auto; + padding: 0 8px; + background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 12%, transparent); + color: var(--dsw-alias-state-error-primary); +} + +.stopLabel { + font-size: 11px; + line-height: 18px; + white-space: nowrap; } .stopFailed { - color: var(--dsw-alias-label-error, var(--dsw-alias-label-secondary)); + color: var(--dsw-alias-state-error-primary); } .row { @@ -268,5 +279,5 @@ } .noticeError { - color: var(--dsw-alias-label-error, var(--dsw-alias-label-secondary)); + color: var(--dsw-alias-state-error-primary); } diff --git a/packages/client/ui-activity/src/client/ActivityListAction.tsx b/packages/client/ui-activity/src/client/ActivityListAction.tsx index 44e1887c52..b728d2656e 100644 --- a/packages/client/ui-activity/src/client/ActivityListAction.tsx +++ b/packages/client/ui-activity/src/client/ActivityListAction.tsx @@ -329,6 +329,9 @@ function TaskItem({ row, view, expanded, now, onToggle, kill, t }: { onClick={kill.onPress} > + {/* The armed press must be legible without hover: the button + widens into a labeled confirm pill instead of a tint only. */} + {kill.state === 'armed' ? {t('kill.confirmAction')} : null} ) : null} diff --git a/packages/client/ui-activity/src/client/locales.ts b/packages/client/ui-activity/src/client/locales.ts index c798b775a4..2a673f27c6 100644 --- a/packages/client/ui-activity/src/client/locales.ts +++ b/packages/client/ui-activity/src/client/locales.ts @@ -16,6 +16,7 @@ export const zh = { 'row.collapseAria': '收起 {label} 的实时输出', 'kill.stop': '停止任务 {label}', 'kill.confirm': '再次点击确认停止', + 'kill.confirmAction': '确认停止', 'kill.failed': '停止失败', 'status.running': '运行中', 'status.stopping': '正在停止', @@ -56,6 +57,7 @@ export const en: Record = { 'row.collapseAria': 'Hide live output of {label}', 'kill.stop': 'Stop task {label}', 'kill.confirm': 'Click again to confirm', + 'kill.confirmAction': 'Confirm stop', 'kill.failed': 'Stop failed', 'status.running': 'running', 'status.stopping': 'stopping', diff --git a/packages/client/ui-activity/tests/activity-list-action.client.spec.tsx b/packages/client/ui-activity/tests/activity-list-action.client.spec.tsx index ae4c8745fe..7a5572a43d 100644 --- a/packages/client/ui-activity/tests/activity-list-action.client.spec.tsx +++ b/packages/client/ui-activity/tests/activity-list-action.client.spec.tsx @@ -405,6 +405,8 @@ describe('ActivityListAction human kill', () => { expect(killJob).not.toHaveBeenCalled() expect(stop.getAttribute('data-kill-state')).toBe('armed') expect(screen.getByTitle(zh['kill.confirm'])).toBe(stop) + // The armed step is legible without hover: the button carries the label. + expect(stop.textContent).toBe(zh['kill.confirmAction']) await act(async () => { fireEvent.click(stop) }) expect(killJob).toHaveBeenCalledWith(SESSION, 'bash-1') // An admitted kill stays pending: the unary response and the jobs frames