@xynogen/pix-edit 0.2.1 → 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.1",
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
 
@@ -133,6 +135,7 @@ export function registerEditTool(
133
135
  try {
134
136
  result = (await origEdit.execute(
135
137
  tid,
138
+ // SAFETY: EditParams preserves the built-in edit schema and only adds legacy aliases.
136
139
  params as unknown as Parameters<typeof origEdit.execute>[1],
137
140
  sig,
138
141
  upd,
@@ -202,7 +205,7 @@ export function registerEditTool(
202
205
  )
203
206
  )
204
207
  return text;
205
- const hdr = `${theme.fg("toolTitle", theme.bold("edit"))} ${theme.fg("muted", sp(fp))}`;
208
+ const hdr = `${theme.fg("toolTitle", theme.bold("edit"))} ${theme.fg("dim", sp(fp))}`;
206
209
 
207
210
  if (operations.length === 0) {
208
211
  text.setText(fillToolBackground(hdr));
@@ -211,7 +214,7 @@ export function registerEditTool(
211
214
 
212
215
  const { summary } = summarizeEditOperations(operations);
213
216
  const coloredSummary = renderDiffSummary(summary, theme);
214
- const paint = (s: string) => theme.fg("dim", s);
217
+ const paint = (s: string) => theme.fg("muted", s);
215
218
  const suffix = dotJoin(
216
219
  [
217
220
  hdr,
@@ -231,14 +234,15 @@ export function registerEditTool(
231
234
  renderCtx: RenderContextLike<EditRenderState>,
232
235
  ) {
233
236
  resolveBaseBackground(theme);
234
- const text = renderCtx.lastComponent ?? new TextComponent("", 0, 0);
237
+ const text = unframeToolResult(renderCtx.lastComponent ?? new TextComponent("", 0, 0));
235
238
  const d = result.details as Record<string, unknown> | undefined;
236
239
  const isPartial = _opt?.isPartial === true;
240
+ const completed = () => frameToolResult(text, theme, renderCtx.isError);
237
241
  const structuredError =
238
242
  renderCtx.isError && (d?._type === "editInfo" || d?._type === "multiEditInfo");
239
243
  if (renderCtx.isError && (!structuredError || isPartial)) {
240
244
  text.setText(renderToolError(getTextContent(result) || "Error", theme));
241
- return text;
245
+ return isPartial ? text : completed();
242
246
  }
243
247
 
244
248
  // Auto-collapse: show summary line after delay
@@ -270,7 +274,7 @@ export function registerEditTool(
270
274
 
271
275
  if (renderCtx.isError) {
272
276
  text.setText(renderToolError(getTextContent(result) || "Error", theme));
273
- return text;
277
+ return completed();
274
278
  }
275
279
 
276
280
  // Single edit — full split diff
@@ -301,7 +305,7 @@ export function registerEditTool(
301
305
  });
302
306
  }
303
307
  text.setText(renderCtx.state._edt ?? ` ${d.summary}`);
304
- return text;
308
+ return isPartial ? text : completed();
305
309
  }
306
310
 
307
311
  // Multi-edit — stacked diffs
@@ -339,13 +343,13 @@ export function registerEditTool(
339
343
  });
340
344
  }
341
345
  text.setText(renderCtx.state._edt ?? ` ${d.editCount} edits ${d.summary}`);
342
- return text;
346
+ return isPartial ? text : completed();
343
347
  }
344
348
 
345
349
  const fallback = result.content?.[0];
346
350
  const fallbackText = fallback && isTextContent(fallback) ? fallback.text : "edited";
347
351
  text.setText(fillToolBackground(` ${theme.fg("dim", String(fallbackText).slice(0, 120))}`));
348
- return text;
352
+ return isPartial ? text : completed();
349
353
  },
350
354
  });
351
355
  }