@xynogen/pix-edit 0.2.2 → 0.2.3

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-edit",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Pi tool — precise text replacement edit with diff rendering",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -38,7 +38,7 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "@xynogen/pix-pretty": "^1.11.2",
41
+ "@xynogen/pix-pretty": "^1.19.0",
42
42
  "@xynogen/pix-runtime": "^0.8.0"
43
43
  },
44
44
  "peerDependencies": {
package/src/edit.test.ts CHANGED
@@ -16,6 +16,10 @@ class MockTextComponent {
16
16
  getText() {
17
17
  return this.text;
18
18
  }
19
+ render(_width: number) {
20
+ return this.text.split("\n");
21
+ }
22
+ invalidate() {}
19
23
  }
20
24
 
21
25
  describe("registerEditTool", () => {
@@ -76,7 +80,7 @@ describe("registerEditTool", () => {
76
80
  () => {},
77
81
  );
78
82
  const theme: ThemeLike = {
79
- fg: (_key: string, value: string) => value,
83
+ fg: (key: string, value: string) => `[${key}]${value}[/${key}]`,
80
84
  bold: (value: string) => value,
81
85
  };
82
86
  const result = registered.renderResult?.(
@@ -102,8 +106,10 @@ describe("registerEditTool", () => {
102
106
  } as unknown as RenderContextLike,
103
107
  );
104
108
 
105
- expect(result?.getText()).toContain("rendering diff");
106
- expect(result?.getText()).not.toContain("✓ edit");
109
+ const lines = result?.render(80) ?? [];
110
+ expect(lines[0]).toBe(`[success]${"─".repeat(80)}[/success]`);
111
+ expect(lines.join("\n")).toContain("rendering diff");
112
+ expect(lines.join("\n")).not.toContain("✓ edit");
107
113
  });
108
114
 
109
115
  it("renders single-step output inline as one compact line, framed when expanded", () => {
@@ -183,7 +189,7 @@ describe("registerEditTool", () => {
183
189
  () => {},
184
190
  );
185
191
  const theme: ThemeLike = {
186
- fg: (_key: string, value: string) => value,
192
+ fg: (key: string, value: string) => `[${key}]${value}[/${key}]`,
187
193
  bold: (value: string) => value,
188
194
  };
189
195
  const diagnostic = "oldText was not found in sample.ts";
@@ -199,19 +205,26 @@ describe("registerEditTool", () => {
199
205
  editLine: 0,
200
206
  },
201
207
  };
202
- const render = (state: Record<string, unknown>, expanded = false) =>
208
+ const render = (state: Record<string, unknown>, expanded = false, isPartial = false) =>
203
209
  registered
204
- .renderResult?.(result, { isPartial: false }, theme, {
210
+ .renderResult?.(result, { isPartial }, theme, {
205
211
  expanded,
206
212
  isError: true,
207
213
  invalidate: () => {},
208
214
  state,
209
215
  } as unknown as RenderContextLike)
210
- ?.getText() ?? "";
216
+ ?.render(80) ?? [];
211
217
 
212
- expect(render({ timer: 1 })).toContain(diagnostic);
213
- expect(render({ collapsed: true })).toContain("✗ edit sample.ts · failed");
214
- expect(render({ collapsed: true }, true)).toContain(diagnostic);
218
+ expect(render({ timer: 1 })[0]).toBe(`[error]${"─".repeat(80)}[/error]`);
219
+ expect(render({ timer: 1 }).join("\n")).toContain(diagnostic);
220
+ const collapsed = render({ collapsed: true }).join("\n");
221
+ expect(collapsed).toContain("edit");
222
+ expect(collapsed).toContain("sample.ts");
223
+ expect(collapsed).toContain("failed");
224
+ expect(render({ collapsed: true })[0]).not.toContain("────");
225
+ expect(render({ collapsed: true }, true).join("\n")).toContain(diagnostic);
226
+ expect(render({ collapsed: true }, true)[0]).toContain("[error]────");
227
+ expect(render({}, false, true)[0]).not.toContain("[error]────");
215
228
  });
216
229
  });
217
230
 
package/src/edit.ts CHANGED
@@ -30,6 +30,7 @@ import type {
30
30
  import {
31
31
  dotJoin,
32
32
  fillToolBackground,
33
+ frameToolResult,
33
34
  getTextContent,
34
35
  hideCollapsedToolCall,
35
36
  isTextContent,
@@ -37,6 +38,7 @@ import {
37
38
  renderToolError,
38
39
  setResultDetails,
39
40
  termW,
41
+ unframeToolResult,
40
42
  } from "@xynogen/pix-pretty/utils";
41
43
  import { type CollapseState, tickCollapse } from "@xynogen/pix-runtime/collapse";
42
44
 
@@ -232,14 +234,15 @@ export function registerEditTool(
232
234
  renderCtx: RenderContextLike<EditRenderState>,
233
235
  ) {
234
236
  resolveBaseBackground(theme);
235
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
237
+ const text = unframeToolResult(renderCtx.lastComponent ?? new TextComponent("", 0, 0));
236
238
  const d = result.details as Record<string, unknown> | undefined;
237
239
  const isPartial = _opt?.isPartial === true;
240
+ const completed = () => frameToolResult(text, theme, renderCtx.isError);
238
241
  const structuredError =
239
242
  renderCtx.isError && (d?._type === "editInfo" || d?._type === "multiEditInfo");
240
243
  if (renderCtx.isError && (!structuredError || isPartial)) {
241
244
  text.setText(renderToolError(getTextContent(result) || "Error", theme));
242
- return text;
245
+ return isPartial ? text : completed();
243
246
  }
244
247
 
245
248
  // Auto-collapse: show summary line after delay
@@ -271,7 +274,7 @@ export function registerEditTool(
271
274
 
272
275
  if (renderCtx.isError) {
273
276
  text.setText(renderToolError(getTextContent(result) || "Error", theme));
274
- return text;
277
+ return completed();
275
278
  }
276
279
 
277
280
  // Single edit — full split diff
@@ -302,7 +305,7 @@ export function registerEditTool(
302
305
  });
303
306
  }
304
307
  text.setText(renderCtx.state._edt ?? ` ${d.summary}`);
305
- return text;
308
+ return isPartial ? text : completed();
306
309
  }
307
310
 
308
311
  // Multi-edit — stacked diffs
@@ -340,13 +343,13 @@ export function registerEditTool(
340
343
  });
341
344
  }
342
345
  text.setText(renderCtx.state._edt ?? ` ${d.editCount} edits ${d.summary}`);
343
- return text;
346
+ return isPartial ? text : completed();
344
347
  }
345
348
 
346
349
  const fallback = result.content?.[0];
347
350
  const fallbackText = fallback && isTextContent(fallback) ? fallback.text : "edited";
348
351
  text.setText(fillToolBackground(` ${theme.fg("dim", String(fallbackText).slice(0, 120))}`));
349
- return text;
352
+ return isPartial ? text : completed();
350
353
  },
351
354
  });
352
355
  }