@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 +2 -1
- package/src/todo.test.ts +15 -2
- package/src/todo.ts +6 -4
- package/src/once.ts +0 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-todo",
|
|
3
|
-
"version": "0.1.
|
|
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("[
|
|
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
|
|
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("
|
|
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
|
-
}
|