diff --git a/pi/extensions/todo/state.test.ts b/pi/extensions/todo/state.test.ts index d24eae9..66cba4a 100644 --- a/pi/extensions/todo/state.test.ts +++ b/pi/extensions/todo/state.test.ts @@ -407,3 +407,19 @@ test("list output keeps active todos first and caps completed history", () => { assert.doesNotMatch(result, /done task 1\b/) assert.match(result, /done task 30\b/) }) + +test("reply chains are bounded in list output with an omission indicator", () => { + const manyReplies = Array.from({ length: 12 }, (_, i) => `reply ${i + 1}`) + const todo: Todo = { + id: 1, + text: "task with long reply history", + status: "in_progress", + replies: manyReplies, + } + const result = formatTodoList([todo]) + assert.match(result, /reply 12/) + assert.match(result, /reply 11/) + assert.match(result, /reply 10/) + assert.doesNotMatch(result, /reply 9\b/) + assert.match(result, /9 earlier replies omitted/) +}) diff --git a/pi/extensions/todo/state.ts b/pi/extensions/todo/state.ts index 875edd7..08905a0 100644 --- a/pi/extensions/todo/state.ts +++ b/pi/extensions/todo/state.ts @@ -562,6 +562,7 @@ export const todoStatusMark: (status: TodoStatus) => string = status => })[status] const COMPLETED_HISTORY_LIMIT = 10 +const REPLY_HISTORY_LIMIT = 3 export const formatTodoList: (todos: ReadonlyArray) => string = todos => { if (todos.length === 0) return "No todos" @@ -573,8 +574,19 @@ export const formatTodoList: (todos: ReadonlyArray) => string = todos => { ? ` — deferred until ${new Date(todo.remindAt).toISOString()}` : "" const replies = - todo.replies?.map(reply => `\n ↳ reply: ${reply}`).join("") ?? "" - return `${todoStatusMark(todo.status)} #${todo.id}: ${todo.text}${detail}${replies}` + (todo.replies?.length ?? 0) > 0 + ? [ + ...((todo.replies?.length ?? 0) > REPLY_HISTORY_LIMIT + ? [ + ` … ${(todo.replies?.length ?? 0) - REPLY_HISTORY_LIMIT} earlier ${(todo.replies?.length ?? 0) - REPLY_HISTORY_LIMIT === 1 ? "reply" : "replies"} omitted`, + ] + : []), + ...(todo.replies ?? []) + .slice(-REPLY_HISTORY_LIMIT) + .map(reply => ` ↳ reply: ${reply}`), + ].join("\n") + : "" + return `${todoStatusMark(todo.status)} #${todo.id}: ${todo.text}${detail}${replies ? "\n" + replies : ""}` } // Active work must survive result truncation: render it first, then a // bounded tail of closed history (completed and cancelled), most recent