@xynogen/pix-core 0.1.5 → 0.1.6

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-core",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Pi extension — core UI/UX bundle (welcome banner, footer, model picker, self-update)",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -1,5 +1,12 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import registerTodo from "./todo.ts";
2
+ import registerTodo, { renderTodoLines, type TodoItem } from "./todo.ts";
3
+
4
+ // Stub theme tags each fragment with its color/bold so assertions can verify
5
+ // which status got which tint, without depending on real ANSI codes.
6
+ const tagTheme = {
7
+ fg: (color: string, text: string) => `[${color}]${text}[/]`,
8
+ bold: (text: string) => `<b>${text}</b>`,
9
+ };
3
10
 
4
11
  // ─── Helpers ────────────────────────────────────────────────────────────────
5
12
 
@@ -600,3 +607,40 @@ describe("restore", () => {
600
607
  expect(text(result)).toBe("(no todos)");
601
608
  });
602
609
  });
610
+
611
+ describe("renderTodoLines (colored TUI render)", () => {
612
+ const items: TodoItem[] = [
613
+ { id: 1, text: "alpha", status: "done" },
614
+ { id: 2, text: "bravo", status: "in_progress" },
615
+ { id: 3, text: "charlie", status: "pending" },
616
+ { id: 4, text: "delta", status: "blocked" },
617
+ ];
618
+
619
+ test("empty list renders muted placeholder", () => {
620
+ expect(renderTodoLines([], tagTheme)).toBe("[muted](no todos)[/]");
621
+ });
622
+
623
+ test("tints each glyph by status", () => {
624
+ const out = renderTodoLines(items, tagTheme);
625
+ expect(out).toContain("[success]●[/]"); // done
626
+ expect(out).toContain("[accent]◐[/]"); // in_progress
627
+ expect(out).toContain("[muted]○[/]"); // pending
628
+ expect(out).toContain("[error]⊘[/]"); // blocked
629
+ });
630
+
631
+ test("highlights the in-progress row bold + accent", () => {
632
+ const out = renderTodoLines(items, tagTheme);
633
+ expect(out).toContain("<b>[accent]2. bravo[/]</b>");
634
+ });
635
+
636
+ test("dims completed rows and uses text color for active-but-not-running", () => {
637
+ const out = renderTodoLines(items, tagTheme);
638
+ expect(out).toContain("[muted]1. alpha[/]"); // done body muted
639
+ expect(out).toContain("[text]3. charlie[/]"); // pending body text
640
+ });
641
+
642
+ test("shows the done/total count header", () => {
643
+ const out = renderTodoLines(items, tagTheme);
644
+ expect(out).toContain("[muted]Todos 1/4 done:[/]");
645
+ });
646
+ });
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
+ import { Text } from "@earendil-works/pi-tui";
13
14
  import { Type } from "typebox";
14
15
 
15
16
  export type TodoStatus = "pending" | "in_progress" | "done" | "blocked";
@@ -27,6 +28,38 @@ const TODO_GLYPH: Record<TodoStatus, string> = {
27
28
  blocked: "⊘",
28
29
  };
29
30
 
31
+ /** Theme color key per status — drives both glyph and (for active) row tint. */
32
+ const TODO_COLOR: Record<TodoStatus, string> = {
33
+ pending: "muted",
34
+ in_progress: "accent",
35
+ done: "success",
36
+ blocked: "error",
37
+ };
38
+
39
+ export type TodoTheme = {
40
+ fg: (color: string, text: string) => string;
41
+ bold: (text: string) => string;
42
+ };
43
+
44
+ /** Colored checklist for the TUI: glyphs tinted by status, active row bold. */
45
+ export function renderTodoLines(items: TodoItem[], theme: TodoTheme): string {
46
+ if (!items.length) return theme.fg("muted", "(no todos)");
47
+ const done = items.filter((t) => t.status === "done").length;
48
+ const head = theme.fg("muted", `Todos ${done}/${items.length} done:`);
49
+ const lines = items.map((t) => {
50
+ const color = TODO_COLOR[t.status];
51
+ const glyph = theme.fg(color, TODO_GLYPH[t.status]);
52
+ const body = `${t.id}. ${t.text}`;
53
+ // Highlight the in-flight task so the eye lands on it first.
54
+ const label =
55
+ t.status === "in_progress"
56
+ ? theme.bold(theme.fg("accent", body))
57
+ : theme.fg(t.status === "done" ? "muted" : "text", body);
58
+ return `${glyph} ${label}`;
59
+ });
60
+ return `${head}\n${lines.join("\n")}`;
61
+ }
62
+
30
63
  const parseItems = (raw: string): string[] =>
31
64
  raw
32
65
  .split("\n")
@@ -102,6 +135,10 @@ export default function registerTodo(pi: ExtensionAPI): void {
102
135
  }),
103
136
  ),
104
137
  }),
138
+ renderResult(_result, _options, theme) {
139
+ return new Text(renderTodoLines(todos, theme as TodoTheme), 0, 0);
140
+ },
141
+
105
142
  async execute(_id, params) {
106
143
  // AgentToolResult now requires a `details` field. These todo results have
107
144
  // no structured details, so emit `undefined` via small local helpers.