@xynogen/pix-edit 0.2.2 → 0.2.4

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.4",
4
4
  "description": "Pi tool — precise text replacement edit with diff rendering",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -13,6 +13,7 @@
13
13
  },
14
14
  "files": [
15
15
  "src",
16
+ "!src/**/*.test.*",
16
17
  "README.md",
17
18
  "LICENSE"
18
19
  ],
@@ -38,7 +39,7 @@
38
39
  "access": "public"
39
40
  },
40
41
  "dependencies": {
41
- "@xynogen/pix-pretty": "^1.11.2",
42
+ "@xynogen/pix-pretty": "^1.19.0",
42
43
  "@xynogen/pix-runtime": "^0.8.0"
43
44
  },
44
45
  "peerDependencies": {
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
  }
package/src/edit.test.ts DELETED
@@ -1,242 +0,0 @@
1
- import { describe, expect, it } from "bun:test";
2
- import type { CursorStore, FffState } from "@xynogen/pix-pretty/fff";
3
- import type {
4
- PiPrettyApi,
5
- RenderContextLike,
6
- TextComponentCtor,
7
- ThemeLike,
8
- } from "@xynogen/pix-pretty/types";
9
- import { getEditOperations, registerEditTool, summarizeEditOperations } from "./edit";
10
-
11
- class MockTextComponent {
12
- private text = "";
13
- setText(v: string) {
14
- this.text = v;
15
- }
16
- getText() {
17
- return this.text;
18
- }
19
- }
20
-
21
- describe("registerEditTool", () => {
22
- it("registers a self-rendered edit tool", () => {
23
- const tools: Array<{ name: string; renderShell?: string }> = [];
24
- const mockPi: PiPrettyApi = {
25
- registerTool(t: unknown) {
26
- tools.push(t as { name: string; renderShell?: string });
27
- },
28
- registerCommand() {},
29
- on() {},
30
- };
31
-
32
- registerEditTool(
33
- mockPi,
34
- () => ({ execute: async () => ({ content: [], details: undefined }) }),
35
- {
36
- cwd: process.cwd(),
37
- sp: (p: string) => p,
38
- TextComponent: MockTextComponent as unknown as TextComponentCtor,
39
- fffState: {
40
- module: null,
41
- finder: null,
42
- partialIndex: false,
43
- dbDir: null,
44
- } satisfies FffState,
45
- cursorStore: {
46
- store: () => "",
47
- get: () => undefined,
48
- } as unknown as CursorStore,
49
- },
50
- (_id: string, _inv: () => void) => {},
51
- );
52
- expect(tools).toHaveLength(1);
53
- expect(tools[0]?.name).toBe("edit");
54
- expect(tools[0]?.renderShell).toBe("self");
55
- });
56
-
57
- it("restores the bounded diff when an elapsed card is expanded", () => {
58
- const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
59
- const mockPi: PiPrettyApi = {
60
- registerTool(tool: unknown) {
61
- Object.assign(registered, tool);
62
- },
63
- registerCommand() {},
64
- on() {},
65
- };
66
- registerEditTool(
67
- mockPi,
68
- () => ({ execute: async () => ({ content: [], details: undefined }) }),
69
- {
70
- cwd: process.cwd(),
71
- sp: (p: string) => p,
72
- TextComponent: MockTextComponent as unknown as TextComponentCtor,
73
- fffState: { module: null, finder: null, partialIndex: false, dbDir: null },
74
- cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
75
- },
76
- () => {},
77
- );
78
- const theme: ThemeLike = {
79
- fg: (_key: string, value: string) => value,
80
- bold: (value: string) => value,
81
- };
82
- const result = registered.renderResult?.(
83
- {
84
- content: [{ type: "text", text: "edited" }],
85
- details: {
86
- _type: "editInfo",
87
- filePath: "sample.ts",
88
- summary: "+1 -1",
89
- oldContent: "old",
90
- newContent: "new",
91
- language: "typescript",
92
- editLine: 1,
93
- },
94
- },
95
- undefined,
96
- theme,
97
- {
98
- expanded: true,
99
- isError: false,
100
- invalidate: () => {},
101
- state: { collapsed: true },
102
- } as unknown as RenderContextLike,
103
- );
104
-
105
- expect(result?.getText()).toContain("rendering diff");
106
- expect(result?.getText()).not.toContain("✓ edit");
107
- });
108
-
109
- it("renders single-step output inline as one compact line, framed when expanded", () => {
110
- const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
111
- const mockPi: PiPrettyApi = {
112
- registerTool(tool: unknown) {
113
- Object.assign(registered, tool);
114
- },
115
- registerCommand() {},
116
- on() {},
117
- };
118
- registerEditTool(
119
- mockPi,
120
- () => ({ execute: async () => ({ content: [], details: undefined }) }),
121
- {
122
- cwd: process.cwd(),
123
- sp: (p: string) => p,
124
- TextComponent: MockTextComponent as unknown as TextComponentCtor,
125
- fffState: {
126
- module: null,
127
- finder: null,
128
- partialIndex: false,
129
- dbDir: null,
130
- } as unknown as FffState,
131
- cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
132
- },
133
- () => {},
134
- );
135
- const theme: ThemeLike = { fg: (_k: string, v: string) => v, bold: (v: string) => v };
136
- const strip = (s: string) => s.replace(/\u001b\[[0-9;]*m/g, "");
137
- // edit today shows summary + diff; the ask is that single-step call already covers summary
138
- // so collapsed result stays compact — this test documents current behavior does NOT duplicate
139
- const result = {
140
- content: [{ type: "text", text: "edited" }],
141
- details: {
142
- _type: "editInfo",
143
- filePath: "app.py",
144
- summary: "-1",
145
- oldContent: "old line\n",
146
- newContent: "new line\n",
147
- language: "python",
148
- editLine: 1,
149
- },
150
- };
151
- const out =
152
- registered
153
- .renderResult?.(result, { isPartial: false }, theme, {
154
- expanded: false,
155
- isError: false,
156
- invalidate: () => {},
157
- state: {},
158
- } as unknown as RenderContextLike)
159
- ?.getText() ?? "";
160
- // collapsed/resting: diff placeholder path covered; when expanded framing should not duplicate header
161
- expect(strip(out).split("\n").length).toBeGreaterThanOrEqual(1);
162
- });
163
-
164
- it("collapses structured errors and restores the exact diagnostic on expansion", () => {
165
- const registered: { renderResult?: (...args: unknown[]) => MockTextComponent } = {};
166
- const mockPi: PiPrettyApi = {
167
- registerTool(tool: unknown) {
168
- Object.assign(registered, tool);
169
- },
170
- registerCommand() {},
171
- on() {},
172
- };
173
- registerEditTool(
174
- mockPi,
175
- () => ({ execute: async () => ({ content: [], details: undefined }) }),
176
- {
177
- cwd: process.cwd(),
178
- sp: (p: string) => p,
179
- TextComponent: MockTextComponent as unknown as TextComponentCtor,
180
- fffState: { module: null, finder: null, partialIndex: false, dbDir: null },
181
- cursorStore: { store: () => "", get: () => undefined } as unknown as CursorStore,
182
- },
183
- () => {},
184
- );
185
- const theme: ThemeLike = {
186
- fg: (_key: string, value: string) => value,
187
- bold: (value: string) => value,
188
- };
189
- const diagnostic = "oldText was not found in sample.ts";
190
- const result = {
191
- content: [{ type: "text", text: diagnostic }],
192
- details: {
193
- _type: "editInfo",
194
- filePath: "sample.ts",
195
- summary: "+1 -1",
196
- oldContent: "old",
197
- newContent: "new",
198
- language: "typescript",
199
- editLine: 0,
200
- },
201
- };
202
- const render = (state: Record<string, unknown>, expanded = false) =>
203
- registered
204
- .renderResult?.(result, { isPartial: false }, theme, {
205
- expanded,
206
- isError: true,
207
- invalidate: () => {},
208
- state,
209
- } as unknown as RenderContextLike)
210
- ?.getText() ?? "";
211
-
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);
215
- });
216
- });
217
-
218
- describe("getEditOperations", () => {
219
- it("extracts array edits", () => {
220
- const ops = getEditOperations({
221
- path: "f.ts",
222
- edits: [{ oldText: "a", newText: "b" }],
223
- });
224
- expect(ops).toEqual([{ oldText: "a", newText: "b" }]);
225
- });
226
-
227
- it("filters ops where old === new", () => {
228
- const ops = getEditOperations({
229
- path: "f.ts",
230
- edits: [{ oldText: "x", newText: "x" }],
231
- });
232
- expect(ops).toHaveLength(0);
233
- });
234
- });
235
-
236
- describe("summarizeEditOperations", () => {
237
- it("returns a summary string", () => {
238
- const { summary } = summarizeEditOperations([{ oldText: "a\nb", newText: "c\nd\ne" }]);
239
- expect(typeof summary).toBe("string");
240
- expect(summary.length).toBeGreaterThan(0);
241
- });
242
- });