@xynogen/pix-todo 0.1.13 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-todo",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "Pi tool — durable execution checklist (todo)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -34,6 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
+ "@xynogen/pix-pretty": "^1.7.20",
37
38
  "typebox": "^1.1.38"
38
39
  },
39
40
  "peerDependencies": {
package/src/todo.test.ts CHANGED
@@ -47,16 +47,21 @@ function makeHost(
47
47
  context: unknown,
48
48
  ) => { render(width: number): string[] })
49
49
  | null = null;
50
+ let capturedRenderCall:
51
+ | ((args: unknown, theme: unknown, context: unknown) => { render(width: number): string[] })
52
+ | null = null;
50
53
 
51
54
  const pi = {
52
55
  registerTool(def: {
53
56
  name: string;
54
57
  parameters: unknown;
55
58
  execute: typeof capturedExecute;
59
+ renderCall?: typeof capturedRenderCall;
56
60
  renderResult?: typeof capturedRender;
57
61
  }) {
58
62
  capturedParameters = def.parameters;
59
63
  capturedExecute = def.execute;
64
+ if (def.renderCall) capturedRenderCall = def.renderCall;
60
65
  if (def.renderResult) capturedRender = def.renderResult;
61
66
  },
62
67
  appendEntry(type: string, data: unknown) {
@@ -93,6 +98,10 @@ function makeHost(
93
98
  if (!capturedExecute) throw new Error("execute not captured");
94
99
  return capturedExecute;
95
100
  },
101
+ get renderCall() {
102
+ if (!capturedRenderCall) throw new Error("renderCall not captured");
103
+ return capturedRenderCall;
104
+ },
96
105
  get render() {
97
106
  if (!capturedRender) throw new Error("render not captured");
98
107
  return capturedRender;
@@ -772,6 +781,15 @@ describe("renderTodoLines (colored TUI render)", () => {
772
781
  });
773
782
  });
774
783
 
784
+ describe("todo card layout", () => {
785
+ test("keeps the call row empty so the collapsed card is one line", () => {
786
+ const host = makeHost();
787
+ registerTodo(host.pi);
788
+ const call = host.renderCall({ action: "list" }, tagTheme, {});
789
+ expect(call.render(80).join("\n")).toBe("");
790
+ });
791
+ });
792
+
775
793
  describe("renderResult snapshot isolation", () => {
776
794
  // The card snapshots `todos` on first render; later execute() mutations must
777
795
  // NOT bleed into an already-rendered card. Guards the invariant the inline
@@ -801,15 +819,19 @@ describe("renderResult snapshot isolation", () => {
801
819
  });
802
820
 
803
821
  describe("renderTodoSummaryLine (collapsed one-liner)", () => {
804
- test("empty list renders muted placeholder", () => {
805
- expect(renderTodoSummaryLine([], tagTheme)).toBe("[muted](no todos)[/]");
822
+ test("empty list renders a compact tool row", () => {
823
+ expect(renderTodoSummaryLine([], tagTheme)).toBe(
824
+ "[success]✓[/] [toolTitle]<b>todo</b>[/] [muted]empty[/]",
825
+ );
806
826
  });
807
827
 
808
- test("renders single dim done/total line with check", () => {
828
+ test("renders active work and progress in one row", () => {
809
829
  const items: TodoItem[] = [
810
830
  { id: 1, text: "a", status: "done" },
811
- { id: 2, text: "b", status: "pending" },
831
+ { id: 2, text: "b", status: "in_progress" },
812
832
  ];
813
- expect(renderTodoSummaryLine(items, tagTheme)).toBe("[muted]Todos 1/2 done ✓[/]");
833
+ expect(renderTodoSummaryLine(items, tagTheme)).toBe(
834
+ "[success]✓[/] [toolTitle]<b>todo</b>[/] [muted]#2 b[/] [dim]·[/] [dim]1/2 done[/]",
835
+ );
814
836
  });
815
837
  });
package/src/todo.ts CHANGED
@@ -13,6 +13,7 @@ import { existsSync, readFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
15
  import { Text } from "@earendil-works/pi-tui";
16
+ import { formatCollapsedToolRow } from "@xynogen/pix-pretty/utils";
16
17
  import { Type } from "typebox";
17
18
 
18
19
  import { once } from "./once.ts";
@@ -99,9 +100,19 @@ export type TodoTheme = {
99
100
 
100
101
  /** One-line dim summary used once a card has collapsed. */
101
102
  export function renderTodoSummaryLine(items: TodoItem[], theme: TodoTheme): string {
102
- if (!items.length) return theme.fg("muted", "(no todos)");
103
+ if (!items.length) return formatCollapsedToolRow(theme, "todo", "empty");
103
104
  const done = items.filter((t) => t.status === "done").length;
104
- return theme.fg("muted", `Todos ${done}/${items.length} done ✓`);
105
+ const active = items.find((t) => t.status === "in_progress");
106
+ const blocked = items.filter((t) => t.status === "blocked").length;
107
+ const meta = [`${done}/${items.length} done`, blocked > 0 ? `${blocked} blocked` : ""]
108
+ .filter(Boolean)
109
+ .join(" · ");
110
+ const target = active
111
+ ? `#${active.id} ${active.text}`
112
+ : done === items.length
113
+ ? "complete"
114
+ : "checklist";
115
+ return formatCollapsedToolRow(theme, "todo", target, meta);
105
116
  }
106
117
 
107
118
  /** Colored checklist for the TUI: glyphs tinted by status, active row bold. */
@@ -202,6 +213,11 @@ export default function registerTodo(pi: ExtensionAPI): void {
202
213
  }),
203
214
  ),
204
215
  }),
216
+ // The result already owns the checklist and its collapsed `✓ todo …` row.
217
+ // Keeping the call renderer empty prevents a duplicate standalone header.
218
+ renderCall() {
219
+ return new Text("", 0, 0);
220
+ },
205
221
  renderResult(_result, _options, theme, context) {
206
222
  // Snapshot this row's todos once (live `todos` mutate across calls;
207
223
  // a card should keep the state it was created with).