From bb1d472e940035ec7802c5f80120c950855f8db7 Mon Sep 17 00:00:00 2001 From: Yichen Jiang Date: Wed, 26 Aug 2026 16:35:11 +0800 Subject: [PATCH] fix(web): make the armed stop press legible without hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The armed state relied on a color swap through --dsw-alias-label-error, a token the themes never define, so the confirm step was effectively invisible. The first press now widens the button into a labeled danger pill (确认停止 / Confirm stop) tinted with the real state-error token StateDot uses; the stream-failure notice moves off the same phantom token. Component spec pins the inline label. --- .../src/client/ActivityListAction.module.css | 19 +++++++++++++++---- .../src/client/ActivityListAction.tsx | 3 +++ .../client/ui-activity/src/client/locales.ts | 2 ++ .../activity-list-action.client.spec.tsx | 2 ++ 4 files changed, 22 insertions(+), 4 deletions(-) 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