From 1f094ae0762a36e103448aa82a35812ced721256 Mon Sep 17 00:00:00 2001 From: Chinesezjc Date: Sat, 25 Jul 2026 00:42:40 +0800 Subject: [PATCH] fix(gui): todo row keeps running/stopped execution states visible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The row rendered a status only for error, so a call cancelled before tool/result read as a completed plan update even though no todo/write occurred. Non-ok states now ride the generic row's StateDot semantics (ongoing dot while running, warning dot + 已中断 marker when interrupted); the ok badge stays for settled successful updates. --- .../src/client/toolviews/todo-row.tsx | 12 +++++++++--- .../ui-conversation/tests/todo-panel.spec.tsx | 15 ++++++++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/packages/client/ui-conversation/src/client/toolviews/todo-row.tsx b/packages/client/ui-conversation/src/client/toolviews/todo-row.tsx index 390361d20b..665fc4dfab 100644 --- a/packages/client/ui-conversation/src/client/toolviews/todo-row.tsx +++ b/packages/client/ui-conversation/src/client/toolviews/todo-row.tsx @@ -6,6 +6,7 @@ // row stays one line. import type { Context } from 'cordis' +import { StateDot } from '@deepseek-ai/dsh-client-ui-primitives' import type { ToolRowProps } from '../contract/slots.ts' import { toolRowModel } from '../contract/tool-call-model.ts' import css from './todo-row.module.css' @@ -38,17 +39,22 @@ function summarize(argsRaw: string): string | null { : head } -/** One-line plan update row (click opens the raw args in details). */ +/** One-line plan update row (click opens the raw args in details). Non-ok + * execution states keep the generic row's dot semantics — a cancelled call + * wrote no todo/write, so it must not read as a completed update. */ export function TodoRow({ toolName, block, openDetails }: ToolRowProps) { const model = toolRowModel(toolName, block) const argsRaw = ('kind' in block ? block.call?.argsRaw : block.argsRaw) ?? '' const summary = summarize(argsRaw) ?? model.summary return ( -
- +
+ {model.state === 'ok' + ? + : } 更新任务清单 {summary} {model.state === 'error' && failed} + {model.state === 'stopped' && 已中断}
) } diff --git a/packages/client/ui-conversation/tests/todo-panel.spec.tsx b/packages/client/ui-conversation/tests/todo-panel.spec.tsx index 196077e6a4..8971bba2cd 100644 --- a/packages/client/ui-conversation/tests/todo-panel.spec.tsx +++ b/packages/client/ui-conversation/tests/todo-panel.spec.tsx @@ -94,10 +94,23 @@ describe('TodoRow', () => { it('omits the active clause when no item is in progress and reads running-call args', () => { const args = JSON.stringify({ todos: [{ content: 'x', status: 'completed' }] }) - render() + render() expect(screen.getByText('1/1 已完成')).toBeTruthy() }) + it('keeps the non-ok execution states visible: running dot, interrupted marker', () => { + // A running call (no result yet) shows the ongoing dot, never the ok badge. + const args = JSON.stringify({ todos: LIST }) + const running = render() + expect(running.container.querySelector('[data-state="running"]')).not.toBeNull() + expect(running.container.querySelector('[data-state="running"] svg')).not.toBeNull() + running.unmount() + // A cancelled call wrote no todo/write: the row must not read as a completed update. + const stopped = render() + expect(stopped.container.querySelector('[data-state="stopped"]')).not.toBeNull() + expect(stopped.getByText('已中断')).toBeTruthy() + }) + it('falls back to the generic summary on malformed args and flags errors', () => { render() expect(screen.getByText('failed')).toBeTruthy()