fix(web): make the armed stop press legible without hover

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.
This commit is contained in:
Yichen Jiang committed 2026-08-26 16:35:11 +08:00
1 parent 8f3103b511
commit bb1d472e94
4 files changed
+22 -4

No files matched your search

@@ -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);
}
@@ -329,6 +329,9 @@ function TaskItem({ row, view, expanded, now, onToggle, kill, t }: {
onClick={kill.onPress}
>
<IconStopFill16 size={12} />
{/* The armed press must be legible without hover: the button
widens into a labeled confirm pill instead of a tint only. */}
{kill.state === 'armed' ? <span className={css.stopLabel}>{t('kill.confirmAction')}</span> : null}
</button>
)
: null}
@@ -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<ActivityKey, string> = {
'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',
@@ -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