@xynogen/pix-find 0.1.14 → 0.1.15

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/README.md CHANGED
@@ -8,13 +8,13 @@ Replaces Pi's default `find` tool with an enhanced version backed by `pix-pretty
8
8
 
9
9
  ## Auto-collapse
10
10
 
11
- After a configurable delay (default 10 seconds) the output card automatically collapses. Controlled via the `collapse` section of `~/.pi/agent/pix.json`:
11
+ After a configurable delay (default 10 seconds), completed output collapses to a row such as `✓ find **/*.ts · 24 files`. Structured failures use a compact `✗` row after the delay. Expanding restores the existing bounded result list or exact diagnostic without restarting the elapsed timer. This is controlled via the `collapse` section of `~/.pi/agent/pix.json`:
12
12
 
13
13
  ```jsonc
14
14
  {
15
15
  "collapse": {
16
16
  "enabled": true,
17
- "delayMs": 10000,
17
+ "delaySec": 10,
18
18
  "tools": { "find": true }
19
19
  }
20
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-find",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Pi tool — glob file search",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/find.test.ts CHANGED
@@ -1,6 +1,11 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
  import type { CursorStore, FffState } from "@xynogen/pix-pretty/fff";
3
- import type { PiPrettyApi, TextComponentCtor } from "@xynogen/pix-pretty/types";
3
+ import type {
4
+ PiPrettyApi,
5
+ RenderContextLike,
6
+ TextComponentCtor,
7
+ ThemeLike,
8
+ } from "@xynogen/pix-pretty/types";
4
9
  import { applyFindDefaults, DEFAULT_FIND_LIMIT, registerFindTool } from "./find";
5
10
 
6
11
  class MockTextComponent {
@@ -58,4 +63,103 @@ describe("registerFindTool", () => {
58
63
  );
59
64
  expect(tools).toEqual(["find"]);
60
65
  });
66
+
67
+ it("restores result paths when an elapsed card is expanded", () => {
68
+ const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
69
+ const mockPi: PiPrettyApi = {
70
+ registerTool(tool: unknown) {
71
+ Object.assign(registered, tool);
72
+ },
73
+ registerCommand() {},
74
+ on() {},
75
+ };
76
+ registerFindTool(
77
+ mockPi,
78
+ () => ({ execute: async () => ({ content: [], details: undefined }) }),
79
+ {
80
+ cwd: process.cwd(),
81
+ sp: (p: string) => p,
82
+ TextComponent: MockTextComponent as unknown as TextComponentCtor,
83
+ fffState: { module: null, finder: null, partialIndex: false, dbDir: null },
84
+ cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
85
+ },
86
+ );
87
+ const theme: ThemeLike = {
88
+ fg: (_key: string, value: string) => value,
89
+ bold: (value: string) => value,
90
+ };
91
+ const result = registered.renderResult?.(
92
+ {
93
+ content: [{ type: "text", text: "src/one.ts\nsrc/two.ts" }],
94
+ details: {
95
+ _type: "findResult",
96
+ text: "src/one.ts\nsrc/two.ts",
97
+ pattern: "**/*.ts",
98
+ matchCount: 2,
99
+ },
100
+ },
101
+ undefined,
102
+ theme,
103
+ {
104
+ expanded: true,
105
+ isError: false,
106
+ invalidate: () => {},
107
+ state: { collapsed: true },
108
+ } as unknown as RenderContextLike,
109
+ );
110
+
111
+ expect(result?.getText()).toContain("src/one.ts");
112
+ expect(result?.getText()).toContain("src/two.ts");
113
+ expect(result?.getText()).not.toContain("2 files");
114
+ });
115
+
116
+ it("collapses structured errors and restores the exact diagnostic on expansion", () => {
117
+ const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
118
+ const mockPi: PiPrettyApi = {
119
+ registerTool(tool: unknown) {
120
+ Object.assign(registered, tool);
121
+ },
122
+ registerCommand() {},
123
+ on() {},
124
+ };
125
+ registerFindTool(
126
+ mockPi,
127
+ () => ({ execute: async () => ({ content: [], details: undefined }) }),
128
+ {
129
+ cwd: process.cwd(),
130
+ sp: (p: string) => p,
131
+ TextComponent: MockTextComponent as unknown as TextComponentCtor,
132
+ fffState: { module: null, finder: null, partialIndex: false, dbDir: null },
133
+ cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
134
+ },
135
+ );
136
+ const theme: ThemeLike = {
137
+ fg: (_key: string, value: string) => value,
138
+ bold: (value: string) => value,
139
+ };
140
+ const diagnostic = "Invalid glob pattern: [";
141
+ const result = {
142
+ content: [{ type: "text", text: diagnostic }],
143
+ details: {
144
+ _type: "findResult",
145
+ text: diagnostic,
146
+ pattern: "[",
147
+ path: "src",
148
+ matchCount: 0,
149
+ },
150
+ };
151
+ const render = (state: Record<string, unknown>, expanded = false) =>
152
+ registered
153
+ .renderResult?.(result, { isPartial: false }, theme, {
154
+ expanded,
155
+ isError: true,
156
+ invalidate: () => {},
157
+ state,
158
+ } as unknown as RenderContextLike)
159
+ ?.getText() ?? "";
160
+
161
+ expect(render({ timer: 1 })).toContain(diagnostic);
162
+ expect(render({ collapsed: true })).toContain("✗ find [ in src · failed");
163
+ expect(render({ collapsed: true }, true)).toContain(diagnostic);
164
+ });
61
165
  });
package/src/find.ts CHANGED
@@ -132,21 +132,36 @@ export function registerFindTool(
132
132
  renderCtx: RenderContextLike,
133
133
  ) {
134
134
  const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
135
+ const d = result.details;
136
+ const isPartial = _opt?.isPartial === true;
137
+ const structuredError = renderCtx.isError && d?._type === "findResult";
135
138
 
136
- if (renderCtx.isError) {
139
+ if (renderCtx.isError && (!structuredError || isPartial)) {
137
140
  text.setText(renderToolError(getTextContent(result) || "Error", theme));
138
141
  return text;
139
142
  }
140
143
 
141
144
  // Auto-collapse: show summary line after delay
142
145
  const cs = renderCtx.state as CollapseState;
143
- if (tickCollapse("find", cs, renderCtx.invalidate)) {
144
- const d = result.details;
146
+ if (!isPartial && tickCollapse("find", cs, renderCtx.invalidate, renderCtx.expanded)) {
145
147
  const summary =
146
148
  d?._type === "findResult" && d.matchCount != null ? `${d.matchCount} files` : "found";
147
149
  const target = d?._type === "findResult" ? d.pattern : "";
148
150
  const scope = d?._type === "findResult" && d.path ? ` in ${sp(d.path)}` : "";
149
- text.setText(renderCollapsedToolRow(theme, "find", `${target}${scope}`, summary));
151
+ text.setText(
152
+ renderCollapsedToolRow(
153
+ theme,
154
+ "find",
155
+ `${target}${scope}`,
156
+ renderCtx.isError ? "failed" : summary,
157
+ renderCtx.isError ? "error" : "success",
158
+ ),
159
+ );
160
+ return text;
161
+ }
162
+
163
+ if (renderCtx.isError) {
164
+ text.setText(renderToolError(getTextContent(result) || "Error", theme));
150
165
  return text;
151
166
  }
152
167