From 19edecaa489537298623daf907d9038c49cbd9bd Mon Sep 17 00:00:00 2001 From: can1357 Date: Tue, 11 Aug 2026 15:03:13 +0200 Subject: [PATCH] fix: kept out-of-order todo completions visible --- packages/coding-agent/src/tools/todo.ts | 13 ++++++------- packages/coding-agent/test/tools/todo.test.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/coding-agent/src/tools/todo.ts b/packages/coding-agent/src/tools/todo.ts index 7afd15642..0469086ad 100644 --- a/packages/coding-agent/src/tools/todo.ts +++ b/packages/coding-agent/src/tools/todo.ts @@ -321,10 +321,10 @@ function selectWithinCap( * * Applied to `tasks` in todo order: the open tasks run through * {@link selectWithinCap}, led by the last {@link COLLAPSED_CLOSED_CONTEXT} - * closed tasks that sit above them so a checked row walks down the list as work - * lands. The lead is additive — it never costs an open row — and a phase with no - * open work left falls back to its closed tasks so the sticky HUD's closed-todo - * persistence still has something to render. + * closed tasks in todo order so a checked row remains visible even when callers + * complete work out of sequence. The lead is additive — it never costs an open + * row — and a phase with no open work left falls back to its closed tasks so the + * sticky HUD's closed-todo persistence still has something to render. * * `summary` counts the open tasks that did not fit; the closed lead is context, * not part of the budget. @@ -337,9 +337,8 @@ export function selectCollapsedTodos( const open = tasks.filter(task => !isClosedTodo(task)); // Closed tasks are never active, so a settled phase selects over itself. if (open.length === 0) return selectWithinCap(tasks, isMatched, cap); - // Everything before the first open task is closed by construction. - const firstOpenIdx = tasks.indexOf(open[0]); - const lead = tasks.slice(Math.max(firstOpenIdx - COLLAPSED_CLOSED_CONTEXT, 0), firstOpenIdx); + // `done` accepts any named task, so closed tasks are not necessarily a prefix. + const lead = tasks.filter(isClosedTodo).slice(-COLLAPSED_CLOSED_CONTEXT); const selected = selectWithinCap(open, isMatched, cap); return { items: [...lead, ...selected.items], summary: selected.summary }; } diff --git a/packages/coding-agent/test/tools/todo.test.ts b/packages/coding-agent/test/tools/todo.test.ts index a2a9852e7..1f9ea22bc 100644 --- a/packages/coding-agent/test/tools/todo.test.ts +++ b/packages/coding-agent/test/tools/todo.test.ts @@ -732,6 +732,16 @@ describe("selectCollapsedTodos walking viewport (#5873)", () => { expect(sel.summary).toBe(""); }); + it("keeps an out-of-order completion as the closed lead row", () => { + const tasks: TodoItem[] = [ + { content: "current", status: "in_progress" }, + { content: "next", status: "pending" }, + { content: "finished early", status: "completed" }, + ]; + const sel = selectCollapsedTodos(tasks, never, 5); + expect(contents(sel)).toEqual(["finished early", "current", "next"]); + }); + it("keeps the closed lead row additive to the open-task cap", () => { const tasks: TodoItem[] = [{ content: "closed", status: "completed" }, ...mk(5, [1])]; const sel = selectCollapsedTodos(tasks, never, 5);