@xynogen/pix-edit 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 `edit` tool with an enhanced version that renders a side-b
8
8
 
9
9
  ## Auto-collapse
10
10
 
11
- After a configurable delay (default 10 seconds) the diff card automatically collapses. Controlled via the `collapse` section of `~/.pi/agent/pix.json`:
11
+ After a configurable delay (default 10 seconds), a completed diff collapses to a one-line status row. Structured failures use a compact `✗` row after the delay. Expanding restores the existing bounded diff 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": { "edit": true }
19
19
  }
20
20
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xynogen/pix-edit",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Pi tool — precise text replacement edit with diff rendering",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/edit.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 { getEditOperations, registerEditTool, summarizeEditOperations } from "./edit";
5
10
 
6
11
  class MockTextComponent {
@@ -46,6 +51,111 @@ describe("registerEditTool", () => {
46
51
  );
47
52
  expect(tools).toEqual(["edit"]);
48
53
  });
54
+
55
+ it("restores the bounded diff when an elapsed card is expanded", () => {
56
+ const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
57
+ const mockPi: PiPrettyApi = {
58
+ registerTool(tool: unknown) {
59
+ Object.assign(registered, tool);
60
+ },
61
+ registerCommand() {},
62
+ on() {},
63
+ };
64
+ registerEditTool(
65
+ mockPi,
66
+ () => ({ execute: async () => ({ content: [], details: undefined }) }),
67
+ {
68
+ cwd: process.cwd(),
69
+ sp: (p: string) => p,
70
+ TextComponent: MockTextComponent as unknown as TextComponentCtor,
71
+ fffState: { module: null, finder: null, partialIndex: false, dbDir: null },
72
+ cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
73
+ },
74
+ () => {},
75
+ );
76
+ const theme: ThemeLike = {
77
+ fg: (_key: string, value: string) => value,
78
+ bold: (value: string) => value,
79
+ };
80
+ const result = registered.renderResult?.(
81
+ {
82
+ content: [{ type: "text", text: "edited" }],
83
+ details: {
84
+ _type: "editInfo",
85
+ filePath: "sample.ts",
86
+ summary: "+1 -1",
87
+ oldContent: "old",
88
+ newContent: "new",
89
+ language: "typescript",
90
+ editLine: 1,
91
+ },
92
+ },
93
+ undefined,
94
+ theme,
95
+ {
96
+ expanded: true,
97
+ isError: false,
98
+ invalidate: () => {},
99
+ state: { collapsed: true },
100
+ } as unknown as RenderContextLike,
101
+ );
102
+
103
+ expect(result?.getText()).toContain("rendering diff");
104
+ expect(result?.getText()).not.toContain("✓ edit");
105
+ });
106
+
107
+ it("collapses structured errors and restores the exact diagnostic on expansion", () => {
108
+ const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
109
+ const mockPi: PiPrettyApi = {
110
+ registerTool(tool: unknown) {
111
+ Object.assign(registered, tool);
112
+ },
113
+ registerCommand() {},
114
+ on() {},
115
+ };
116
+ registerEditTool(
117
+ mockPi,
118
+ () => ({ execute: async () => ({ content: [], details: undefined }) }),
119
+ {
120
+ cwd: process.cwd(),
121
+ sp: (p: string) => p,
122
+ TextComponent: MockTextComponent as unknown as TextComponentCtor,
123
+ fffState: { module: null, finder: null, partialIndex: false, dbDir: null },
124
+ cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
125
+ },
126
+ () => {},
127
+ );
128
+ const theme: ThemeLike = {
129
+ fg: (_key: string, value: string) => value,
130
+ bold: (value: string) => value,
131
+ };
132
+ const diagnostic = "oldText was not found in sample.ts";
133
+ const result = {
134
+ content: [{ type: "text", text: diagnostic }],
135
+ details: {
136
+ _type: "editInfo",
137
+ filePath: "sample.ts",
138
+ summary: "+1 -1",
139
+ oldContent: "old",
140
+ newContent: "new",
141
+ language: "typescript",
142
+ editLine: 0,
143
+ },
144
+ };
145
+ const render = (state: Record<string, unknown>, expanded = false) =>
146
+ registered
147
+ .renderResult?.(result, { isPartial: false }, theme, {
148
+ expanded,
149
+ isError: true,
150
+ invalidate: () => {},
151
+ state,
152
+ } as unknown as RenderContextLike)
153
+ ?.getText() ?? "";
154
+
155
+ expect(render({ timer: 1 })).toContain(diagnostic);
156
+ expect(render({ collapsed: true })).toContain("✗ edit sample.ts · failed");
157
+ expect(render({ collapsed: true }, true)).toContain(diagnostic);
158
+ });
49
159
  });
50
160
 
51
161
  describe("getEditOperations", () => {
package/src/edit.ts CHANGED
@@ -199,15 +199,18 @@ export function registerEditTool(
199
199
  renderCtx: RenderContextLike<EditRenderState>,
200
200
  ) {
201
201
  const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
202
- if (renderCtx.isError) {
202
+ const d = result.details as Record<string, unknown> | undefined;
203
+ const isPartial = _opt?.isPartial === true;
204
+ const structuredError =
205
+ renderCtx.isError && (d?._type === "editInfo" || d?._type === "multiEditInfo");
206
+ if (renderCtx.isError && (!structuredError || isPartial)) {
203
207
  text.setText(renderToolError(getTextContent(result) || "Error", theme));
204
208
  return text;
205
209
  }
206
- const d = result.details as Record<string, unknown> | undefined;
207
210
 
208
211
  // Auto-collapse: show summary line after delay
209
212
  const cs = renderCtx.state as CollapseState;
210
- if (tickCollapse("edit", cs, renderCtx.invalidate)) {
213
+ if (!isPartial && tickCollapse("edit", cs, renderCtx.invalidate, renderCtx.expanded)) {
211
214
  const summary =
212
215
  d?._type === "editInfo"
213
216
  ? (d.summary as string)
@@ -220,7 +223,20 @@ export function registerEditTool(
220
223
  const ops = d.ops as Array<Record<string, unknown>> | undefined;
221
224
  filePath = String(ops?.[0]?.filePath ?? "");
222
225
  }
223
- text.setText(renderCollapsedToolRow(theme, "edit", sp(filePath), summary));
226
+ text.setText(
227
+ renderCollapsedToolRow(
228
+ theme,
229
+ "edit",
230
+ sp(filePath),
231
+ renderCtx.isError ? "failed" : summary,
232
+ renderCtx.isError ? "error" : "success",
233
+ ),
234
+ );
235
+ return text;
236
+ }
237
+
238
+ if (renderCtx.isError) {
239
+ text.setText(renderToolError(getTextContent(result) || "Error", theme));
224
240
  return text;
225
241
  }
226
242