fix: eliminate spurious user edits and remove edit button from notifications

- Add system_update_todos message type to distinguish system-generated
  todo updates from actual user edits
- Remove unnecessary edit button from todo list update notifications
- Update UI rendering logic to handle system updates appropriately
- Update tests to reflect new message type behavior
This commit is contained in:
Toray Altas 2026-01-17 17:01:24 -05:00
parent 4c9dd28d40
commit 0a245c49a7
6 changed files with 16 additions and 30 deletions

View file

@ -180,6 +180,7 @@ export const clineSays = [
"sliding_window_truncation",
"codebase_search_result",
"user_edit_todos",
"system_update_todos",
] as const
export const clineSaySchema = z.enum(clineSays)

View file

@ -537,7 +537,7 @@ describe("History resume delegation - parent metadata transitions", () => {
const parentMessagesWithCompletedTodos = [
{
type: "say",
say: "user_edit_todos",
say: "system_update_todos",
text: JSON.stringify({
tool: "updateTodoList",
todos: [
@ -564,8 +564,10 @@ describe("History resume delegation - parent metadata transitions", () => {
expect(saveTaskMessages).toHaveBeenCalled()
const savedCall = vi.mocked(saveTaskMessages).mock.calls[0][0]
// Find the user_edit_todos message that was added for the write-back
const todoEditMessages = savedCall.messages.filter((m: any) => m.type === "say" && m.say === "user_edit_todos")
// Find the system_update_todos message that was added for the write-back
const todoEditMessages = savedCall.messages.filter(
(m: any) => m.type === "say" && m.say === "system_update_todos",
)
// Should have at least 2 todo edit messages (original + write-back)
expect(todoEditMessages.length).toBeGreaterThanOrEqual(1)
@ -627,7 +629,7 @@ describe("History resume delegation - parent metadata transitions", () => {
const parentMessagesWithTodos = [
{
type: "say",
say: "user_edit_todos",
say: "system_update_todos",
text: JSON.stringify({
tool: "updateTodoList",
todos: [{ id: "todo-1", content: "Some task", status: "completed" }],
@ -650,7 +652,9 @@ describe("History resume delegation - parent metadata transitions", () => {
const savedCall = vi.mocked(saveTaskMessages).mock.calls[0][0]
// Find todo edit messages (if any were added beyond the original)
const todoEditMessages = savedCall.messages.filter((m: any) => m.type === "say" && m.say === "user_edit_todos")
const todoEditMessages = savedCall.messages.filter(
(m: any) => m.type === "say" && m.say === "system_update_todos",
)
// Should only have the original todo edit, no write-back because child isn't in childIds
// The fallback should NOT be triggered for an unrelated child

View file

@ -3223,7 +3223,7 @@ export class ClineProvider
{
ts: Date.now(),
type: "say",
say: "user_edit_todos",
say: "system_update_todos",
text: JSON.stringify({
tool: "updateTodoList",
todos,
@ -3376,7 +3376,7 @@ export class ClineProvider
parentClineMessages.push({
ts: Date.now(),
type: "say",
say: "user_edit_todos",
say: "system_update_todos",
text: JSON.stringify({
tool: "updateTodoList",
todos,

View file

@ -4,7 +4,8 @@ export function getLatestTodo(clineMessages: ClineMessage[]) {
const todos = clineMessages
.filter(
(msg) =>
(msg.type === "ask" && msg.ask === "tool") || (msg.type === "say" && msg.say === "user_edit_todos"),
(msg.type === "ask" && msg.ask === "tool") ||
(msg.type === "say" && (msg.say === "user_edit_todos" || msg.say === "system_update_todos")),
)
.map((msg) => {
try {

View file

@ -1418,6 +1418,8 @@ export const ChatRowContent = ({
return <CodebaseSearchResultsDisplay results={results} />
case "user_edit_todos":
return <UpdateTodoListToolBlock userEdited onChange={() => {}} />
case "system_update_todos":
return <UpdateTodoListToolBlock userEdited={false} onChange={() => {}} />
case "tool" as any:
// Handle say tool messages
const sayTool = safeJsonParse<ClineSayTool>(message.text)

View file

@ -179,28 +179,6 @@ const UpdateTodoListToolBlock: React.FC<UpdateTodoListToolBlockProps> = ({
Todo List Updated
</span>
<div className="flex-grow" />
{editable && (
<button
onClick={() => setIsEditing(!isEditing)}
style={{
border: isEditing
? "1px solid var(--vscode-button-border)"
: "1px solid var(--vscode-button-secondaryBorder)",
background: isEditing
? "var(--vscode-button-background)"
: "var(--vscode-button-secondaryBackground)",
color: isEditing
? "var(--vscode-button-foreground)"
: "var(--vscode-button-secondaryForeground)",
borderRadius: 4,
padding: "2px 8px",
cursor: "pointer",
fontSize: 13,
marginLeft: 8,
}}>
{isEditing ? "Done" : "Edit"}
</button>
)}
</div>
</ToolUseBlockHeader>
<div className="overflow-x-auto max-w-full" style={{ padding: "6px 0 2px 0" }}>