@xynogen/pix-todo 0.1.15 → 0.1.17

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.15",
3
+ "version": "0.1.17",
4
4
  "description": "Pi tool — durable execution checklist (todo)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -36,6 +36,7 @@
36
36
  "dependencies": {
37
37
  "@xynogen/pix-data": "^0.3.2",
38
38
  "@xynogen/pix-pretty": "^1.7.20",
39
+ "@xynogen/pix-runtime": "^0.1.1",
39
40
  "typebox": "^1.1.38"
40
41
  },
41
42
  "peerDependencies": {
package/src/todo.test.ts CHANGED
@@ -27,6 +27,7 @@ function makeHost(
27
27
  }> = [],
28
28
  ) {
29
29
  let capturedParameters: unknown;
30
+ let capturedRenderShell: unknown;
30
31
  let capturedExecute:
31
32
  | ((
32
33
  id: string,
@@ -57,10 +58,12 @@ function makeHost(
57
58
  name: string;
58
59
  parameters: unknown;
59
60
  execute: typeof capturedExecute;
61
+ renderShell?: unknown;
60
62
  renderCall?: typeof capturedRenderCall;
61
63
  renderResult?: typeof capturedRender;
62
64
  }) {
63
65
  capturedParameters = def.parameters;
66
+ capturedRenderShell = def.renderShell;
64
67
  capturedExecute = def.execute;
65
68
  if (def.renderCall) capturedRenderCall = def.renderCall;
66
69
  if (def.renderResult) capturedRender = def.renderResult;
@@ -99,6 +102,9 @@ function makeHost(
99
102
  if (!capturedExecute) throw new Error("execute not captured");
100
103
  return capturedExecute;
101
104
  },
105
+ get renderShell() {
106
+ return capturedRenderShell;
107
+ },
102
108
  get renderCall() {
103
109
  if (!capturedRenderCall) throw new Error("renderCall not captured");
104
110
  return capturedRenderCall;
@@ -777,13 +783,20 @@ describe("renderTodoLines (colored TUI render)", () => {
777
783
  expect(out).toContain("[text]3. charlie[/]"); // pending body text
778
784
  });
779
785
 
780
- test("shows the done/total count header", () => {
786
+ test("shows the done/total count header in blue", () => {
781
787
  const out = renderTodoLines(items, tagTheme);
782
- expect(out).toContain("[muted]Todos 1/4 done:[/]");
788
+ expect(out).toContain("[accent]Todos 1/4 done:[/]");
789
+ expect(out).not.toContain("[muted]Todos 1/4 done:[/]");
783
790
  });
784
791
  });
785
792
 
786
793
  describe("todo card layout", () => {
794
+ test("uses the self-rendered shell so the compact checkmark has no leading space", () => {
795
+ const host = makeHost();
796
+ registerTodo(host.pi);
797
+ expect(host.renderShell).toBe("self");
798
+ });
799
+
787
800
  test("keeps the call row empty so the collapsed card is one line", () => {
788
801
  const host = makeHost();
789
802
  registerTodo(host.pi);
package/src/todo.ts CHANGED
@@ -13,10 +13,9 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
13
  import { Text } from "@earendil-works/pi-tui";
14
14
  import { type CollapseState, tickCollapse } from "@xynogen/pix-data/collapse";
15
15
  import { formatCollapsedToolRow } from "@xynogen/pix-pretty/utils";
16
+ import { once } from "@xynogen/pix-runtime/once";
16
17
  import { Type } from "typebox";
17
18
 
18
- import { once } from "./once.ts";
19
-
20
19
  export type TodoStatus = "pending" | "in_progress" | "done" | "blocked";
21
20
 
22
21
  export interface TodoItem {
@@ -54,7 +53,7 @@ export type TodoTheme = {
54
53
  bold: (text: string) => string;
55
54
  };
56
55
 
57
- /** One-line dim summary used once a card has collapsed. */
56
+ /** One-line shared summary used once a card has collapsed. */
58
57
  export function renderTodoSummaryLine(items: TodoItem[], theme: TodoTheme): string {
59
58
  if (!items.length) return formatCollapsedToolRow(theme, "todo", "empty");
60
59
  const done = items.filter((t) => t.status === "done").length;
@@ -75,7 +74,7 @@ export function renderTodoSummaryLine(items: TodoItem[], theme: TodoTheme): stri
75
74
  export function renderTodoLines(items: TodoItem[], theme: TodoTheme): string {
76
75
  if (!items.length) return theme.fg("muted", "(no todos)");
77
76
  const done = items.filter((t) => t.status === "done").length;
78
- const head = theme.fg("muted", `Todos ${done}/${items.length} done:`);
77
+ const head = theme.fg("accent", `Todos ${done}/${items.length} done:`);
79
78
  const lines = items.map((t) => {
80
79
  const color = TODO_COLOR[t.status];
81
80
  const glyph = theme.fg(color, TODO_GLYPH[t.status]);
@@ -134,6 +133,9 @@ export default function registerTodo(pi: ExtensionAPI): void {
134
133
  pi.registerTool({
135
134
  name: "todo",
136
135
  label: "Todo",
136
+ // Avoid the default Box shell's one-cell x padding: Todo owns its compact
137
+ // result row and should align its status glyph with other compact tools.
138
+ renderShell: "self",
137
139
  description:
138
140
  "Track BUILD-phase execution progress. Durable across context compaction. Actions: list, set (replace all items from newline/numbered text), add, update (change one item's status), clear.",
139
141
  promptSnippet:
package/src/once.ts DELETED
@@ -1,27 +0,0 @@
1
- /**
2
- * Per-instance idempotency guard for extension activation.
3
- *
4
- * pix-core (the meta-package) invokes this package's factory, and a standalone
5
- * install makes Pi invoke it again — sometimes against the SAME `pi`. We must
6
- * dedupe that. But Pi rebuilds the extension runtime on /new, /resume, /fork,
7
- * and /reload, handing the factory a BRAND-NEW `pi`; that must re-register.
8
- *
9
- * Keying the registry on the `pi` instance satisfies both: same instance =>
10
- * skip, new instance => run. The registry lives on globalThis because jiti
11
- * (`moduleCache: false`) re-evaluates this module on every load pass, so a
12
- * module-scoped WeakMap would not be shared between the aggregator pass and the
13
- * standalone pass within a single session.
14
- */
15
- export function once(pi: object, key: string, fn: () => void): void {
16
- const g = globalThis as { __pixOnce?: WeakMap<object, Set<string>> };
17
- if (!g.__pixOnce) g.__pixOnce = new WeakMap<object, Set<string>>();
18
- const registry = g.__pixOnce;
19
- let loaded = registry.get(pi);
20
- if (!loaded) {
21
- loaded = new Set<string>();
22
- registry.set(pi, loaded);
23
- }
24
- if (loaded.has(key)) return;
25
- loaded.add(key);
26
- fn();
27
- }