@xzzpig/pi-tidy 0.0.0-stage → 0.1.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +1104 -0
  2. package/FORK-CHANGELOG.md +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +416 -2
  5. package/fork/defaults.ts +77 -0
  6. package/fork/index.ts +2 -0
  7. package/fork/install.ts +58 -0
  8. package/fork/thinking-row.ts +221 -0
  9. package/package.json +68 -3
  10. package/src/clean-copy.ts +313 -0
  11. package/src/clipboard.ts +29 -0
  12. package/src/config.ts +292 -0
  13. package/src/draw/frame-draw.ts +307 -0
  14. package/src/draw/icons.ts +2014 -0
  15. package/src/draw/paint.ts +57 -0
  16. package/src/frame.ts +20 -0
  17. package/src/hover.ts +185 -0
  18. package/src/index.ts +322 -0
  19. package/src/live.ts +91 -0
  20. package/src/messages/bash-mode-frame.ts +112 -0
  21. package/src/messages/error-lines.ts +331 -0
  22. package/src/messages/extension-notice.ts +204 -0
  23. package/src/messages/hex-swatches.ts +245 -0
  24. package/src/messages/message-frame.ts +515 -0
  25. package/src/messages/user-message.ts +207 -0
  26. package/src/theme-access.ts +26 -0
  27. package/src/tools/background-jobs.ts +617 -0
  28. package/src/tools/bash-highlight.ts +330 -0
  29. package/src/tools/codemode-body.ts +602 -0
  30. package/src/tools/result-preview.ts +56 -0
  31. package/src/tools/split-diff.ts +606 -0
  32. package/src/tools/tool-frame.ts +1019 -0
  33. package/src/tools/tool-group-view.ts +1160 -0
  34. package/src/tools/tool-groups.ts +706 -0
  35. package/src/tools/tool-lines.ts +621 -0
  36. package/src/tools/write-preview.ts +81 -0
  37. package/src/turns/code-blocks.ts +273 -0
  38. package/src/turns/turn-lifecycle.ts +43 -0
  39. package/src/turns/turn-state.ts +257 -0
  40. package/src/turns/turn-view.ts +504 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,1104 @@
1
+ # Changelog
2
+
3
+ What changed in each released version of pi-tidy (published as
4
+ `@andy8647/pi-toolbox` up to 0.4.1). Versions follow
5
+ [semantic versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.9.6] - 2026-11-13
8
+
9
+ ### Fixed
10
+
11
+ **An `edit` another extension registers keeps its file and its diff.** aft's hashline mode (`edit_mode: "hashline"`) takes
12
+ `{ patch }` instead of `{ path }` and brings renderers of its own, so the box showed no path chip, no file-type icon and no
13
+ panes: it stacked the tool's own `edited … (+a/-d)` row under a border that repeated the path. The file now comes from the
14
+ result's `details.files` and the diff from `details.diff` when the call carries neither — the same shape pi's own edit
15
+ result uses — and a relative path is resolved against the working directory, so the chip copies all of it. pi's own
16
+ `edit` is unchanged: its preview still wins while the edit streams, and a preview error still keeps pi's rendering.
17
+
18
+ **A `/reload` with new settings reaches every box and message.** The render caches named the options they read, field by
19
+ field, so an option no cache listed could leave the old frame on screen — 0.9.4's split diff ignoring a theme switch was
20
+ that bug. Each cache now records one revision that every option change bumps, so a setting added later needs nothing
21
+ remembered.
22
+
23
+ ## [0.9.5] - 2026-11-09
24
+
25
+ ### Added
26
+
27
+ **A color in a message gets a swatch.** A reply or your own message that names a color now shows a `●` painted in it:
28
+ `#7f849c`, `#f00` and `#7f849c80` for hex, `rgb(127, 132, 156)` / `rgba(…)` and `hsl(240, 10%, 15%)` / `hsla(…)`
29
+ for the CSS functions, commas or spaces between the parts. Alpha is ignored because there is nothing to blend against,
30
+ and named colors (`red`, `tomato`) are not matched at all: they are ordinary words in prose. Fenced code blocks,
31
+ inline code and thinking keep their text, so a shell comment that happens to look like a color stays a comment.
32
+ `tidy.hexSwatches: false` leaves the markdown to pi.
33
+
34
+ **A shell's `-c` script is highlighted as the script it is.** `bash -c 'set -e` plus five more lines used to arrive as
35
+ one string token: every row the same green, and no way to read the commands. A quoted argument after `-c` (or a
36
+ combined form like `-lc`) is now tokenized as shell code, three levels of nesting deep, with the quotes still read as
37
+ strings. Heredocs, double-quoted payloads and `grep -c 'x'` keep the plain string treatment.
38
+
39
+ **Words the grammar cannot name take a colour of their own.** Arguments, paths, globs, filenames and the value of
40
+ `VAR=value` / `--flag=value` were passed through uncoloured, so a path-heavy row was a wall of the terminal's default
41
+ foreground. They take `toolOutput`: readable, and clearly not the grey a comment gets.
42
+
43
+ ## [0.9.4] - 2026-11-09
44
+
45
+ ### Added
46
+
47
+ **A framed user message can stop short of the full width.** `userMessageMaxWidth` takes `"auto"` (the default, the
48
+ whole width), a fraction of it (`0.7`) or a column count (`88`). The text wraps earlier and the frame hugs the narrower
49
+ column, so a long prompt keeps the chat-bubble shape under `userMessageAlign: "right"` instead of spanning the screen.
50
+ The label still gets room to draw itself, so a cap narrower than the icon and time is widened to fit them. The rail and
51
+ the rules are full-width by design and ignore it.
52
+
53
+ ### Fixed
54
+
55
+ **Split diffs now follow the theme's appearance.** Light themes get pale row backgrounds and stronger word highlights;
56
+ dark themes and older hosts keep their existing colours. No new setting is needed. Existing edit diffs also refresh
57
+ on theme switches and reloads, even when the added-text colour stays the same.
58
+
59
+ Thanks to @s5unty for the split diff fix, the `appearance` read and the instance-keyed cache included (#73), and to
60
+ @cheney12138 for proposing `userMessageMaxWidth` and the three value forms it takes (#58).
61
+
62
+ ## [0.9.3] - 2026-11-08
63
+
64
+ ### Added
65
+
66
+ **A message an extension draws itself now wears the transcript's frame.** `frameMessages` framed compaction, branch, skill and
67
+ pi's own custom boxes, but a custom message whose renderer returns a component was left exactly as the extension drew it —
68
+ on a default pi-subagents install, a full-width slab in `customMessageBg` sitting between two framed runs. The rows are still
69
+ the extension's, since nothing here can know where a label belongs inside them; the chrome is Tidy's. The frame, the kind's
70
+ color, the fill stripped, the blank edges trimmed, and clicks mapped back through the border to the component that drew the
71
+ row, so its own click still opens it.
72
+
73
+ pi-subagents' notices are drawn from the message rather than from its renderer: the border says who it is about and how it
74
+ went (`╭─ wiki-verifier failed ──╮`, `╭─ worker needs attention ──╮`) in that status's own color — green for a
75
+ completion, red for a failure, yellow for a stall — the message's own text is the body with the title line it repeats taken
76
+ out, one row of it shows until a click, and the plugin's `(ctrl+o to expand)` is gone because the row is already a header.
77
+ The label is read from `details`: `runs[]` on a completion notice, `event` on a control notice, and the same fields on the
78
+ other twelve types wherever they exist, so a notice that names nobody is still drawn, with the glyph alone.
79
+ Session entries (`reply sent to …`) keep their extension's styling: they render through `CustomEntryComponent`, which pi does
80
+ not export, and the entry renderer registry answers whichever extension registered first.
81
+
82
+ ### Fixed
83
+
84
+ **A background job's notifications no longer break the transcript.** `hideJobNotifications` has been on by default since
85
+ 0.4.1, but it only applied with `collapseTurns: "off"`. 0.8.0 made turn folding the default, so from then on every
86
+ `[process] "ticker" matched stdout: READY` came back as a block of its own, between runs, on a default install.
87
+
88
+ The blocks are still not drawn. The part a job's row cannot hold is the matched log line; the row only carries the
89
+ pattern. That line is kept per job and drawn as a row under it, inside the group the job belongs to: `· matched stdout:
90
+ tick 5`, at most three, with `· +N earlier` above them, and only while the group is open. A folded turn says how many
91
+ arrived (`▸ … · 2 matched`), because the rows carrying them are inside it. The model still receives every message, and a
92
+ watcher that ticks every second costs a folded transcript nothing.
93
+
94
+ `hideJobNotifications: false` keeps the blocks and draws no rows, rather than reporting the same match twice. A
95
+ notification whose job has no row to absorb it keeps its own block, so nothing is lost: another session, a row not
96
+ rendered again after a `/reload`, a pinned call, `groupTools: false`, pi-processes not loaded.
97
+
98
+ ## [0.9.2] - 2026-11-07
99
+
100
+ ### Added
101
+
102
+ **A file box's path is one click away.** A `write` or `edit` box's top border carries a copy chip after the path
103
+ (`╭─ 󰛦 src/app.ts 󰆒 ────╮`): click it for `~/…/app.ts` on the clipboard, and the chip shows a tick for a moment. Under the
104
+ pointer it lights up and the pointer becomes a hand, like the code block chip. `icons: false` draws the word `copy`
105
+ instead, and a box too narrow for the chip keeps its plain border.
106
+
107
+ ### Fixed
108
+
109
+ **Selecting a path on a box's border copied nothing.** A frame's top border was taken for chrome and dropped whole,
110
+ so a drag along the `write` or `edit` border produced an empty selection: no hint, and `ctrl+x` silently did nothing
111
+ because pi treats an empty selection as no selection. A top border's label now stays when it names a file, with the
112
+ corner, the padding rules, the title's icons and the chip taken off; a reply's `model 14:06` label is still chrome.
113
+
114
+ **The code block chip's hit range was one cell left of it on a framed reply** — the frame's padding was not counted,
115
+ which the four-cell word `copy` hid. It is measured on the drawn row now.
116
+
117
+ **A reply with no frame had a chip nothing answered.** `presentTurn` returns early when a message has no turn header
118
+ and no reply frame, and that return came before the layout and the mouse handler were installed: with
119
+ `finalReplyStyle: "off"`, and on every reply while it streams, the chip was painted and a click did nothing, hover included.
120
+ The early return now only takes the messages that have no hit target, so a reply without chrome keeps its fast path and a
121
+ reply with a chip gets the handler it needs. Thanks to @indesperate for the report and for the trace: the early return
122
+ was exactly where they said it was, and the three bugs it hid were found in the code they pointed at.
123
+
124
+ **A block on an open turn's rail had no joint and no chip.** The rules were matched after the rail went on, and a railed
125
+ row's `│` never equals a rule's own text, so the block kept a plain border and its chip was dead. The rules are measured
126
+ before the rail now: a block on the rail joins it (`├─ fish 󰆒 ────`) and its chip copies like any other.
127
+
128
+ **One block could stop every later chip in the reply, and a repeated block could copy the wrong one.** A rule was
129
+ recognised by the cells it was drawn with, so a block inside a quote or a list item (drawn narrower, with a `│` or a
130
+ `1.` in front) never matched, and no later block was matched either. On pi 1.5 a block pi has already drawn is reused
131
+ without `renderToken` running, so the blocks tidy knew about and the blocks on the rows drifted apart: with two identical
132
+ blocks and a third, the second chip copied the third block's text. The rules are matched by their shape and the blocks
133
+ are read from Markdown's tokens now. A rule too narrow for its label draws no chip and no longer gets an invisible hit
134
+ target on its last cell.
135
+
136
+ ### Changed
137
+
138
+ **The copy chips are glyphs**: `󰆒` (md-content_copy) at rest and `󰌌` (fa-check) after a copy, instead of `copy` and
139
+ `copied`. They take one cell instead of four, so the rule around them is three cells longer. `icons: false` keeps the
140
+ words.
141
+
142
+ ## [0.9.1] - 2026-11-06
143
+
144
+ ### Added
145
+
146
+ **A reply's fenced code blocks are drawn as a section** (`tidy.codeBlocks`). The two fence rows become rules that
147
+ join the reply's frame or rail (`├─ fish copy ────┤`), with the language on the opening one and a chip beside it:
148
+ clicking it puts the block's own text on the clipboard, and the chip reads `copied` for a moment. Under the pointer the chip
149
+ lights up and the pointer becomes a hand, the same hover the folded rows use. The code rows stay
150
+ as pi drew them. A `diff` block goes side by side instead, under the same `splitDiff` switch and `splitDiffMinWidth`
151
+ as an edit's diff, and keeps the unified form on a row too narrow for two panes. A language highlight.js has no
152
+ grammar for gets the closest one that exists: `fish` is read as a shell, and `.env` and TOML as INI, where pi alone
153
+ paints them one flat colour. `tidy.codeBlocks: false` leaves pi's own fences. A pi whose Markdown draws its code
154
+ blocks itself (no `renderToken` to hook) is unaffected, and tidy says so once at session start.
155
+
156
+ **The run summary's counts wear kind glyphs, and its LLM time wears the provider's.** Folded, every `kind×N` and
157
+ the `LLM` timing get a Nerd Font glyph in front: the tool kind's own, and for the timing the same provider glyph
158
+ the reply's border draws, so both lines name the same vendor. `tidy.icons: false` turns them off. A row too narrow
159
+ to hold them drops the glyphs whole and falls back to plain `bash×8 · read×3`: no count is ever truncated to make
160
+ room for an icon.
161
+
162
+ **`codemode` has a kind glyph of its own** (`fa-code`) instead of the generic wrench.
163
+
164
+ ## [0.9.0] - 2026-11-05
165
+
166
+ ### Added
167
+
168
+ **`tidy.messageTimeFormat` sets how a message's time reads**, in user messages and the final reply's border (#59): `auto`,
169
+ or a pattern such as `"M月D日 HH:mm:ss"`.
170
+
171
+ **`tidy.messageHeaderStyle: "split"` sets a rule between who a message is from and its time** (#59):
172
+ `╭── model ── 13:09 ───╮`, in user messages and the final reply's border. The default stays `inline`.
173
+
174
+ **The final reply and user messages can have rules without side borders** (#56). `tidy.finalReplyStyle` and
175
+ `tidy.userMessageStyle` both take `"rules"`: a rule above and below and nothing beside the text, so a terminal's own
176
+ selection copies it clean too.
177
+
178
+ **`tidy.finalReplyStyle` replaces `frameFinalReply`**, which keeps working, and also takes `"rail"`: the rules plus a `│` in
179
+ front of the text, like a rail user message.
180
+
181
+ **`tidy.messageBorderColors.assistant` colors the final reply** (#59): its frame and an opened run's rail and marker, as a
182
+ theme color name or `#rrggbb`. The default stays `accent`.
183
+
184
+ ### Fixed
185
+
186
+ **`bashResultLines` no longer depends on `highlightBash`.** With highlighting off, the setting was ignored and collapsed
187
+ bash boxes showed pi's 5 lines. They now follow the setting, whose default is 1. If you had highlighting off and want the
188
+ 5 lines back, set `tidy.bashResultLines: 5`.
189
+
190
+ **`thinkingColor` accepts `#rrggbb`, as documented.** A hex value made every thinking row disappear.
191
+
192
+ ### Changed
193
+
194
+ **The configuration reference is grouped by what each key affects**, and every key now says when it applies. It also
195
+ records two limits that were not written down: `diffLines` only works while an edit's diff is drawn as two panes, and
196
+ `hideJobNotifications` only works with `collapseTurns: "off"`.
197
+
198
+ ## [0.8.1] - 2026-11-05
199
+
200
+ ### Added
201
+
202
+ **Copies leave the frames behind.** Text selected in pi's fullscreen mode now reaches the clipboard without tidy's
203
+ frames, corners and rail glyphs, on select and with the copy key, with or without pi-starline (#56). A terminal's own
204
+ selection (shift or option and drag) never reaches tidy, so it still brings them along. If you relied on the old copies,
205
+ `tidy.cleanCopy: false` turns this off. Until pi-starline's next release, its copy key still handles the selections it
206
+ recognises, and its hint counts characters its own way.
207
+
208
+ **A rail message carries its icon and time in the top rule.** With `userMessageStyle: "rail"`, the user icon moves out of
209
+ the text into the top rule, next to the time the frame style already showed. `userMessageAlign: "right"` moves that label to
210
+ the right end of the rule; the rail and the text stay left. The text also sits one cell closer to the rail: a cell of pi's
211
+ padding that was meant to come off never did.
212
+
213
+ **`tidy.userMessageTime: false` hides a user message's time**, in both styles. The final reply's border keeps its own.
214
+
215
+ ### Fixed
216
+
217
+ **An opened run draws fast again.** 0.8.0 slowed down an opened run by 40-90%: settled thinking rows were rebuilt on
218
+ every frame. They are reused now, which takes 20-46% off a frame in that state at 1,500 components. The render benchmark
219
+ now measures opened runs, which is how this got through.
220
+
221
+ **A short message's frame has room around its label.** The box was exactly as wide as the icon and time in its top
222
+ border, leaving a stub of border on each side (#49). It is now four cells wider.
223
+
224
+ ### Changed
225
+
226
+ **The startup list may name `pi-tidy/src`.** The sources moved under `src/`. Nothing else changes for you.
227
+
228
+ ## [0.8.0] - 2026-11-04
229
+
230
+ **A run now folds by default.** When a run ends, its process folds to one `▸ took …` line above a framed final reply.
231
+ This changes what you see on update unless your settings already name these keys. To go back:
232
+
233
+ ```json
234
+ { "tidy": { "collapseTurns": "off", "frameFinalReply": false } }
235
+ ```
236
+
237
+ ### Changed
238
+
239
+ **An opened run hangs on a rail.** The summary's marker moves to the first column, and a rail runs down from it into the
240
+ final reply's left border, in the reply's colour. Groups sit one cell in, boxes two. A box from `alwaysShowTools` joins
241
+ the rail of a group that is open right above or below it. The summary drops its call counts while opened, because the
242
+ group headers carry them.
243
+
244
+ **A group's rows are easier to scan.** The `✓` or `✗` follows the tool's name, so tool icons and the thinking brain share
245
+ a column. Thinking text is dim and runs up to the metrics column. bash commands are highlighted (`highlightBash`), and a
246
+ leading `cd` shows only its last directory (`cd …/pi-tidy && rg x`); the opened box keeps the full command.
247
+
248
+ **Rows say what happened.** A bash call that printed nothing says `no output`, and reading an image says `image`, where it
249
+ said `1 line`.
250
+
251
+ ### Added
252
+
253
+ **`toolRowLines`.** Set it to `2` and a call that does not fit its row goes on to a second one: the rest of a long command,
254
+ or the next line of a multi-line one with `(+N lines)`. Calls that fit keep one row. The default stays `1`.
255
+
256
+ ### Fixed
257
+
258
+ **Opening an always-shown box no longer throws thinking out of its group.** Opening a `write` or `edit` box moved its
259
+ step's thinking out of the group and took one off the `N thoughts` count.
260
+
261
+ **A failed run keeps its failure in view when it folds.** A run that ended in an error with no text folded the error away,
262
+ and only the summary's `error` was left.
263
+
264
+ **The hover band reaches the end of a clipped row.** It used to stop at the `…`.
265
+
266
+ **The metrics columns follow a result that lands late.** They were only measured again when a call joined the group.
267
+
268
+ **An opened row keeps its columns.** Opening a call moved its row one cell right, and opening a thought moved it two.
269
+
270
+ ## [0.7.3] - 2026-11-03
271
+
272
+ ### Fixed
273
+
274
+ **One reply, one group.** The first reply after pi started could show two `▾` headers, with some of its calls
275
+ under the second. Folding then kept both headers, in reverse order. A reply is one group again, whichever extensions
276
+ restyle your messages.
277
+
278
+ **Thinking stays under its group's header.** With `groupOpen: true`, a step's `thinking` row could sit above the
279
+ group's summary line instead of inside the group.
280
+
281
+ **An empty step no longer starts a new group** (#50, by dreanzy).
282
+
283
+ **Failed requests are framed, once.** A request that fails for good now shows its `Error: …` in the same
284
+ `╭─ error ─╮` box as other errors (#45). When pi retries, the attempts share one box that counts them
285
+ (`error ×3`), and pi's "Retry failed after N attempts" takes its place when it gives up. An error after part of
286
+ the reply, and an abort with Esc, stay as pi drew them. `tidy.dedupeErrors: false` turns all of this off.
287
+
288
+ ### Changed
289
+
290
+ **pi can show you this changelog.** tidy now ships its changelog, so pi 1.5 can list what changed after an update.
291
+ pi starts showing it from the next update on.
292
+
293
+ ## [0.7.2] - 2026-11-02
294
+
295
+ ### Added
296
+
297
+ **A failed request is one framed row, not one row per retry.** pi reports its own errors — a failed request, an
298
+ extension that threw — by appending a plain row per attempt: `Error: 429: {"message":…}` three times for one
299
+ failure, provider body included, with no frame. tidy now keeps the first row of a repeated error, puts `×N` in its
300
+ border (`╭─ error ×3 ─────╮`) and hides the repeats, so one failure reads as one line. The surviving row is the only
301
+ record that a turn failed, so it stays; warnings and notes are not touched. `tidy.dedupeErrors: false` restores pi's
302
+ behaviour.
303
+
304
+ ### Changed
305
+
306
+ **A summary box's label sits in the border, not on its own row.** Compaction, branch and expanded skill boxes
307
+ draw the `[compaction]` / `[branch]` / `[skill]` label on a row of its own; a custom message draws `[type]` the
308
+ same way. That row is a title, so it now goes into the top border next to the kind icon — `╭─ 󰊢 [branch] ───╮`
309
+ — and the blank row that separated it from the body goes with it. A collapsed skill box keeps its label inline:
310
+ there it shares the row with the skill's name. User messages are never touched: their first row is the user's
311
+ own text, which may well be a bare `[word]`.
312
+
313
+ ## [0.7.1] - 2026-11-01
314
+
315
+ A rendering release: nothing changes on screen, the frame costs a fraction of what it did.
316
+
317
+ ### Changed
318
+
319
+ **A row is measured before it is truncated.** pi-tui's `truncateToWidth` segments the whole string whether or not
320
+ anything is cut — 17 µs for a styled 100-cell row against 90 ns for `visibleWidth` — and every drawn row went through
321
+ it. Rows that already fit now skip it entirely; at 500 components with every tool output open that was 78% of the
322
+ frame. Measured on the benchmark fixture with every tool output open: 5.5 ms → 0.8 ms at 500 components, 21.2 ms →
323
+ 2.6 ms at 1500.
324
+
325
+ **A settled tool's rows are drawn once.** An opened call rebuilt its body rows every frame — concatenation, trim and a
326
+ format per row — from a box that had not changed. They are now cached and rebuilt when the group, the pointer, the
327
+ tool's own result or the box under it moves. An active run, a partial call, a background job or a scheduled reveal all
328
+ still draw fresh, so streaming is unchanged.
329
+
330
+ **A turn summary is built once per turn, not once per frame.** Folding a run is now the cheapest state to render
331
+ instead of the most expensive: 3.8 ms → 0.53 ms per frame at 1500 components, where the summary used to iterate the
332
+ turn's messages and tools four times per turn per frame. It is rebuilt when the turn moves or a second passes.
333
+
334
+ ### Added
335
+
336
+ **The render benchmark folds runs, and reports on the pull request.** `pnpm bench` now measures three states — tool
337
+ outputs closed, runs folded, tool outputs open — because turn folding is a different path, not a smaller version of
338
+ the other two. CI rewrites a single comment on the PR with the table, found by a marker in its body so every run
339
+ patches it rather than stacking a new one. Pull requests from forks skip that step, and a base older than 0.7.0
340
+ reports the PR's own numbers for the state it cannot render.
341
+
342
+ ## [0.7.0] - 2026-11-01
343
+
344
+ ### Experimental (opt-in)
345
+
346
+ Turn folding and final-reply framing are experimental and disabled by default. Updating does not enable
347
+ them. To try both, merge these keys into the `tidy` object in `~/.pi/agent/settings.json`:
348
+
349
+ ```json
350
+ {
351
+ "tidy": {
352
+ "collapseTurns": "afterRun",
353
+ "frameFinalReply": true
354
+ }
355
+ }
356
+ ```
357
+
358
+ The two keys work independently. Use `collapseTurns: "live"` to fold while the run is still active.
359
+ To disable them, restore `collapseTurns: "off"` and `frameFinalReply: false`. Restart Pi after upgrading
360
+ to load the new rendering patches.
361
+
362
+ **A run can fold to one summary.** `tidy.collapseTurns` takes `off` (default), `afterRun` or `live`. One run
363
+ follows steering, retries and automatic continuations until pi settles. Its summary sits at the top of the turn
364
+ and reads wall time, the main model's request time, the union of top-level tool time, per-tool call counts and
365
+ failure counts; failures stay named inline. Click it, or use `/tidy turn [N|last]`, to open the process; the row
366
+ stays in place and folds it again, with the pointer band and hand. `ctrl+o` still controls tool outputs and their
367
+ turn. Metadata is saved outside model context and restored on the active branch; older turns show `—` for the
368
+ timings they never recorded.
369
+
370
+ **Frame the final reply.** `tidy.frameFinalReply: true` frames only the settled final text, full width,
371
+ with that message's model and a dim italic `HH:MM` (`MM-DD` for older messages). It can be enabled without
372
+ turn folding.
373
+
374
+ ### Added
375
+
376
+ **User frames carry a clock and can align right.** The clock appears next to the user icon. Set
377
+ `tidy.userMessageAlign: "right"` for right alignment; it defaults to `"left"` and requires
378
+ `tidy.userMessageStyle: "frame"` with `tidy.frameMessages: true` (both defaults).
379
+
380
+ **File tools put their title in the border.** read/edit/write boxes carry the kind icon and path in the top
381
+ border; the body starts with the result or diff. `write` no longer leaves a gap under its title.
382
+
383
+ ### Fixed
384
+
385
+ - A framed `codemode` box puts its summary row in the top border, where read/edit/write put their path
386
+ (`╭─ ✓ codemode bash×1 read×4 0.1s ─╮`), and the body starts with the script's fold row. It was the one box
387
+ whose title stayed inside the frame.
388
+ - The final reply's border names the model that answered, not the provider (`╭─ 🐳 deepseek-v4-pro 14:03 ─╮`). The
389
+ provider keeps the glyph. A model id too long for the border is clipped with `…` instead of dropping the whole
390
+ label.
391
+ - Folded turns preserve tool group boundaries, so thinking counts and rows stay with their own groups after
392
+ restoring or expanding a turn, including groups split by `alwaysShowTools`.
393
+ - Hovering a turn summary no longer highlights the first tool group header too, or vice versa.
394
+ - OSC 133 zone markers stay outside the final frame. A zone that started after the left border made Ghostty
395
+ cache the row and paint the first answer line blank; selection then exposed stale copies of it.
396
+ - The final frame contains only the answer; the final message's thinking is not drawn inside it.
397
+ - A group's `N thoughts` no longer over-counts when a streaming step turns into a boundary: the run
398
+ counted as a carrier is given back when its text arrives. Thanks to @ncuiigai for reporting the mismatch
399
+ and tracing its cause ([#39](https://github.com/Andy8647/pi-tidy/issues/39)).
400
+ - A pinned, expanded edit no longer leaves pi's bare `Thinking...` above its box.
401
+ - Deepseek uses the whale emoji; Nerd Fonts has no brand glyph for it.
402
+
403
+ ## [0.6.3] - 2026-10-31
404
+
405
+ ### Added
406
+
407
+ **Tool groups can start open.** Set `tidy.groupOpen: true` to start each new group with its `▾` header
408
+ and one row per call, without opening the calls' full outputs. The default is `false`, so existing
409
+ setups stay unchanged. Clicking the header or using `ctrl+o`'s collapse-all keeps the group folded;
410
+ the setting only decides its initial state. Changes apply to new groups after `/reload`, and fold
411
+ state is not persisted across session restores. Thanks to @dreanzy for the proposal and implementation
412
+ ([#37](https://github.com/Andy8647/pi-tidy/pull/37)).
413
+
414
+ ## [0.6.2] - 2026-10-30
415
+
416
+ ### Fixed
417
+
418
+ **A script's call whose arguments wrap reads as its summary, not as raw JSON.** Inside an open `codemode` box pi
419
+ writes each call's arguments as compact JSON, and wraps a row longer than the box onto the rows below it. tidy glued
420
+ those rows back together to find the arguments and put the summary in their place, but pi's box pads each row out to
421
+ its width, indents the next one and drops the space it broke at, so the glued text never matched the arguments and
422
+ the row kept pi's JSON, clipped with `…`. Below about 200 columns that was every `write` and `edit` and any long
423
+ `bash` command. The rows are now glued back with the one space pi dropped, and matched with spaces let through, so
424
+ the row shows the path or the command like the others, and its duration lines up with theirs again. A row that does
425
+ get clipped keeps its duration's colour: the clip used to take the colour with the arguments and leave the duration
426
+ white.
427
+
428
+ ## [0.6.1] - 2026-10-30
429
+
430
+ ### Changed
431
+
432
+ **A row you can click shows a hand.** The band that lights a clickable row under the pointer now comes with the
433
+ pointer shape to match: tidy asks the terminal for a hand (OSC 22) while a row is lit and for the arrow again when
434
+ none is, and puts the arrow back before pi leaves the alt screen. Ghostty, kitty, foot and WezTerm draw it; other
435
+ terminals ignore the sequence.
436
+
437
+ **A `codemode` box reads as its call list with grouping off, too**
438
+ ([#27](https://github.com/Andy8647/pi-tidy/issues/27)). 0.6.0 built the call list into the group: with
439
+ `groupTools: false` the box was still pi's own — ten lines of script, the last eight calls, the head of the output
440
+ and a `Took` row. Now it is the same list in a frame of its own: the row a group would show for the call
441
+ (`✓ codemode bash×3 read×1 1 failed · 0.1s`) as its title, every call the script made, and the script and the
442
+ output folded to one row each that open on click and light up under the pointer. `ctrl+o`, or a click anywhere
443
+ else in the box, opens both. The list is drawn from a second, expanded rendering of pi's own renderer, so nothing
444
+ is cut short and pi's box is left as it was. The same goes for a grouped session with `codemode` in
445
+ `alwaysShowTools`. Each box keeps its list until something in it changes: 0.6.0 re-decorated every framed
446
+ `codemode` box on every frame, which on a session of 300 scripts cost about 38 ms a frame; it is under 2 ms now.
447
+ Thanks to @indesperate for asking for the call list in the first place and for noticing it was missing with
448
+ grouping off.
449
+
450
+ **Thinking is tidy's row with grouping off, too.** The thinking row was wired into grouping, so with
451
+ `groupTools: false` every step left pi's bare `Thinking...` floating over its box. The row has nothing to do with
452
+ folding calls: with grouping off each step's thinking is now the same `thinking` row a message that writes text
453
+ gets inside a group — the brain, one line of what it thought, a click to open it in its box — and the step loses
454
+ pi's padding rows the way it does with grouping on.
455
+
456
+ **An open thinking box carries its label in the border.** Opening a message's thinking used to keep the
457
+ `󰧑 thinking Now demo…` row above the box, repeating the box's first words. The row now folds into the box's
458
+ top border, `╭─ 󰧑 thinking ───╮`, so the open box is one row shorter and still says what it holds; a click
459
+ anywhere on it, the border included, folds it back into the row.
460
+
461
+ ### Fixed
462
+
463
+ **bash's `Command exited with code N` trailer comes off a failed call inside a script.** 0.6.0 said so, but
464
+ it only matched the row without pi's box around it: drawn for real, the row is one cell further in, and the
465
+ trailer stayed, two blank rows above it.
466
+
467
+ **Long sessions scroll smoothly again.** pi's fullscreen mode draws the whole transcript on every frame — every
468
+ wheel tick, every streamed token, every hover change — and three things tidy draws were rebuilt from scratch each
469
+ time. Edit boxes were the worst: the side-by-side diff had a cache with room for one diff, so with more than one
470
+ edit in the transcript each box pushed out the one before it, and every frame re-parsed and re-highlighted every
471
+ diff in the history. The lag grew with the number of edits, which is why only long sessions felt it. On a
472
+ 1,500-message session with 121 edits a scroll frame took about 50 ms (pi on its own: under 1 ms); it takes about
473
+ 1.4 ms now. The diff is kept per box, and so are each group's summary line and each message's thinking rows, each
474
+ rebuilt only when what it shows changes. Nothing looks different.
475
+
476
+ **…and with every box open, too.** With `ctrl+o`'s expand-all on, each opened call's rows went through a
477
+ trailing-whitespace regex that is quadratic on a padded row (the padding is followed by a colour reset, so it
478
+ never sits at the end), and the same session took about 250 ms a frame. It is about 24 ms now, with nothing
479
+ looking different.
480
+
481
+ ## [0.6.0] - 2026-10-30
482
+
483
+ ### Changed
484
+
485
+ **A `codemode` row says what its script ran.** Nested tool calls never become tool rows of their own, so the
486
+ folded row used to show only the script's first line. It counts them now: `codemode bash×3 read×1 0.1s`, growing
487
+ while the script runs, with a red `1 failed` when a call inside the script failed and the script caught it and
488
+ still succeeded.
489
+
490
+ **Inside the box, the nested calls read like tidy's rows.** pi draws them with the arguments as compact JSON;
491
+ they now carry the same kind icon as the outer rows, and the arguments become the same one-line summary
492
+ (`pwd`, `src/app.ts`, `(+12 lines)` for a multi-line one), read back from `result.details.calls`: JSON when the
493
+ 200-character clip survived it, the surviving key when it did not, pi's own text when neither works. bash's
494
+ `Command exited with code N` trailer is dropped from a nested failure, where the error above it already says
495
+ what happened.
496
+
497
+ **The body opens as a call list.** Inside a group, a `codemode` box no longer dumps the script and the output:
498
+ the script folds to `▸ script · 13 lines` and the output to `▸ output · 4 items`, each opening in place on
499
+ click (and lighting up under the pointer, like every other row), with a dim rule between the sections and a
500
+ rail of its own down the body. The call rows take a name column and a duration column, and a nested
501
+ `process start` hangs inside the body with them. A script that called nothing folds the same way, using the
502
+ output's first marker (`==> text N/M <==`, `<console_output>`, `Script error:`) as the boundary. Nothing wraps
503
+ onto a second row: an argument that does not fit is clipped, and the duration is the part that never gives way.
504
+ pi's `Took` row is gone, since the row above the body carries the time. The output stays one block: a script
505
+ builds its `text()` items from whatever it computed, so an item can be a filtered, merged summary of several
506
+ calls and nothing ties it back to the call that fed it.
507
+
508
+ **`bash` takes the md-console glyph.** `fa-terminal` is missing from some monospace Nerd Fonts and drew as a
509
+ blank; `\u{f018d}` renders wherever the rest of the set does. The file-type map's `bash` entry was still on
510
+ codicons' terminal glyph, so shell files and the `bash` tool now agree.
511
+
512
+ **Group rows share a name column.** A call's arguments used to start wherever its name ended, so a list of
513
+ `bash` and `read` and `process` rows had a ragged left edge. The name is padded to the widest one in the group
514
+ (capped at 14 cells, so one long MCP name cannot push everything out of reach), and a thinking row's label
515
+ pads the same way so its text starts in that column too.
516
+
517
+ ### Fixed
518
+
519
+ **No blank row above a framed message box.** pi inserts a `Spacer(1)` before a user message and before the
520
+ compaction, branch and skill boxes, and the frame added an empty row of its own on top of it: the box floated
521
+ one row below the transcript, and for the summary boxes that turned pi's filled padding row into a second blank
522
+ one. The top border is the first row now, for all four (the user message's `OSC 133` zone marker moved with it).
523
+ Custom messages keep the spacer, because pi puts no spacer in front of those and it is the only separation they
524
+ have.
525
+
526
+ **A click on message text lands where it is drawn** ([#30](https://github.com/Andy8647/pi-tidy/pull/30), by
527
+ @s5unty). Trimming the message edges and restyling a thinking run changed the layout, but the mouse regions
528
+ kept using the untrimmed one: an interactive Markdown element such as pi-code-render's `[COPY]` had a click
529
+ target a few rows off, and a click outside a thinking run was swallowed instead of reaching the Markdown, which
530
+ left raw code unfoldeable. Rendering and hit testing now go through the same component tree, with one
531
+ coordinate transform for the trimmed rows.
532
+
533
+ ## [0.5.0] - 2026-10-30
534
+
535
+ ### Changed
536
+
537
+ **User messages are tidy's now, in a frame that hugs them.** pi-starline used to restyle user messages, tied to its
538
+ editor switch: turning Starline's input box off sent the transcript's prompts back to pi's filled block. They are
539
+ tidy's to draw now, independently of Starline, and by default (`userMessageStyle: "frame"`) in tidy's rounded frame,
540
+ as wide as the text plus its inset — a one-word prompt gets a small box, a long one stops at the screen's width —
541
+ with the user icon set into the top border (`╭─ ───╮`), so the text starts clean and copies without it. The
542
+ border is `messageBorderColors.user`.
543
+
544
+ `"rail"` keeps the look pi-starline drew: a full-width rule above and below, an accent `│` and (with `icons`) the
545
+ user icon in front of the text, later rows hanging under it; `userMessagePaddingY` and `userMessageColors` (`rail`,
546
+ `rule`; theme names or `#rrggbb`) tune it. `"off"` leaves pi's look. Both styles render pi's own Markdown child, so
547
+ markdown transformers keep working. `frameUserMessages` is no longer read: `true` asked for what is now the default.
548
+ pi-starline 0.9 drops its own version; with an older pi-starline, Starline's still draws on top.
549
+
550
+ **Your own `!` commands get a frame, marked as yours.** pi drew `!` / `!!` runs between two full-width `bashMode`
551
+ rules, which read like the agent's bash box without its frame. Under `frameMessages` they now get tidy's rounded frame
552
+ with the user icon, the `!` or `!!` that was typed in place of `$`, and the user messages' color — the agent's bash
553
+ box changes color with its state, this one does not, and pi's own `Running...` / `(exit N)` / `(cancelled)` rows report
554
+ the outcome. `!!` stays dim. The 20-row preview and `ctrl+o` are pi's, unchanged.
555
+
556
+ ## [0.4.14] - 2026-10-28
557
+
558
+ ### Fixed
559
+
560
+ **Background jobs started through codemode are tracked too.** Nested `process start` calls now keep
561
+ independent status rows under their calling tool, including multiple processes started by one script.
562
+ Completion and log-match notifications fold into those rows instead of appearing separately. A script
563
+ failure does not discard jobs it already started, and saved associations survive session restoration.
564
+ Direct starts and processes without a corresponding row keep their existing behaviour.
565
+
566
+ **Each nested process has its own hover and expand target.** Clicking a process no longer expands
567
+ codemode. It opens that process's command, working directory and the last 20 output lines; expanded
568
+ logs update as output arrives. Closing one process leaves its siblings and the script unchanged.
569
+ The group header and collapse-all close child details too. Unavailable logs are reported explicitly,
570
+ and process output is stripped of terminal control sequences before display.
571
+
572
+ ## [0.4.13] - 2026-10-27
573
+
574
+ ### Fixed
575
+
576
+ **The group header keeps its click when a thinking run above it is expanded.** `installCarrierMouse` answered an
577
+ expanded run before it looked at which row was hit, and the summary line is drawn by the same component, so
578
+ clicking `▾ 3 calls` folded the run instead of shutting the group: the header stayed `▾` and the reasoning
579
+ disappeared. The fold now only answers on the run's own rows.
580
+
581
+ ## [0.4.11] - 2026-10-24
582
+
583
+ ### Fixed
584
+
585
+ **A user message keeps its own padding while tools are folded.** Grouping trims the blank rows pi pads every
586
+ message with, so a folded group sits against the line above it. A user message is not one of those: it is a
587
+ filled box whose first and last rows are padding *with a background fill*, not empty rows, and the trim
588
+ stripped them as well — the highlighted bubble hugged its text on both sides and the `▸ 2 calls` header
589
+ pressed against it. The boundary wrapper gained an `untrimmed` branch, and the messages that are pi's own
590
+ padded boxes (user, plus custom in the same shape) take it. Assistant steps keep their edges compacted
591
+ around a group, which is what the compacting was for.
592
+
593
+ No setting was added: keeping the padding is now the only behaviour, because the tight version was a
594
+ mistake for messages rather than a preference.
595
+
596
+ Reported by @bumaociyuan (#17) and @frostime (#19); the fix is @bumaociyuan's `untrimmed` change from #18.
597
+
598
+ **Settings are read from the native home directory.** Before `PI_CODING_AGENT_DIR`, the fallback was
599
+ `${HOME}/.pi/agent` with `/tmp` behind it, so a Windows session without `HOME` looked in `/tmp/.pi/agent` and
600
+ silently ran on defaults. It asks `os.homedir()` now, which is right on Windows too.
601
+
602
+ ## [0.4.10] - 2026-10-23
603
+
604
+ ### Fixed
605
+
606
+ **A multi-line argument no longer paints the rows below it off by a line.** A row is one string in the lines
607
+ array the renderer hands back, and the summary took a call's `command` apart at its first newline but passed
608
+ every other argument through whole. So a tool whose first string argument is a script
609
+ (`chrome-devtools_evaluate_script`'s `function`, most MCP tools) returned a "row" with real newlines in it:
610
+ the terminal drew four physical rows where pi's frame counted one, and every row below painted off by the
611
+ difference. What you saw was the last few lines of a script sitting in the middle of the transcript, with
612
+ `Working` wedged between them and the durations stranded on their own rows.
613
+
614
+ Every argument now goes through the same rule: first line, then `(+N lines)` for the rest, trimmed. That is
615
+ also what the collapsed row has always claimed, so a script now reads as `evaluate_script () => { (+3 lines)`.
616
+
617
+ ## [0.4.9] - 2026-10-23
618
+
619
+ ### Fixed
620
+
621
+ **A thinking row no longer takes the whole message with it.** An assistant step streams as a carrier and
622
+ becomes the group boundary when its text arrives, and the click handler installed while it was a carrier
623
+ stayed on the component: it answered "this is my row" for every `y`, and consumed the click. So on any
624
+ message that had streamed its thinking, clicking the text (a fenced code block included) opened the
625
+ reasoning instead of reaching what was under the pointer. The handler now records the rows it actually
626
+ drew each frame and hands everything else back to the handler it replaced.
627
+
628
+ **One row under a thinking row no longer toggles it.** pi places its own regions on the untrimmed
629
+ layout, and the padding rows this extension trims away move every row by one, so a click on the first
630
+ line below a run landed on pi's hidden label. The runs are ours once restyled — the click table covers
631
+ every one of them, open or shut — so a click that misses them all now falls through instead.
632
+
633
+ ## [0.4.8] - 2026-10-23
634
+
635
+ ### Added
636
+
637
+ **Thinking is a row inside the group, and it stays live while it arrives.** A model step that only
638
+ thinks and calls a tool used to vanish when you opened a group: its `Thinking...` label was hidden, so
639
+ the transcript showed the call but nothing about the reasoning behind it. Now that step is a `thinking`
640
+ line under the `│` gutter — a brain, the word, then one line of what the model actually thought, above
641
+ the call it belongs to and in transcript order, at one consistent height whether the group is folded or
642
+ open. The summary line counts the runs (`▾ 5 calls · bash×3 · 🧠 4 thoughts · 1 failed · 1.3s`),
643
+ which while folded is the only sign the reply did any thinking at all.
644
+
645
+ - While a run is still arriving, the group keeps one line beneath the summary: a sliding window over the
646
+ *tail* of that thinking, so the newest words are what you see and the row takes the full width. It
647
+ moves on a 300ms tick rather than per frame — pi repaints ~30x/s, and a window recomputed on every
648
+ paint slides a cell per token, which read as flicker — and the cut lands on a word break. The summary
649
+ says `◐ thinking` while it runs and goes back to `N thoughts` once it stops; the row itself does not
650
+ animate. A message's thinking **outside** a group (the label above its text, or a turn that has not
651
+ called a tool yet) is live on the same terms, except that it has no summary above it, so the row wears
652
+ the spinner itself.
653
+ - Live means three things at once: an agent run is open (`agent_start` … `agent_end`/`agent_settled`,
654
+ tracked in a live slot so `/reload` reaches the patches), the message is still streaming, and its text
655
+ grew inside the last 1.5 seconds. Growth is the signal that cannot lie — a run that stops sending
656
+ tokens goes quiet on its own — and a repaint is scheduled for the moment the window lapses, because by
657
+ then pi may have nothing left to paint. A restored session never looks live: a component that arrives
658
+ with its text already finished did not grow in front of us.
659
+ - A click on the row opens that step's thinking: pi renders its own text, untruncated, inside the same
660
+ rounded box a tool result gets — the border uses the new `thinkingColor` (default `warning`). Another
661
+ click folds it back. pi's `thinkingVisibilityOverrides` is the switch behind it, so pi's own click and
662
+ ours cannot disagree about what is open. A message that is nothing but thinking toggles on any click,
663
+ and the hit map is shifted with the rows when padding is trimmed — pi's own region measures the
664
+ untrimmed layout.
665
+ - A message with both thinking **and** text is the group boundary, and its `Thinking...` label used to
666
+ float above the text as bare italic. It now wears the same row — same colour, same format, same
667
+ preview — right where pi draws it, above the text. Its run is **not** added to the group's count: the
668
+ group lists only the runs it folded, and counting one that renders above the text would promise a row
669
+ that opening the group cannot show.
670
+ - The summary line of an open group is carried by the leading thinking row when there is one — a carrier
671
+ renders *before* its own call, so the header would otherwise land under it. Folded, the first call
672
+ still carries the line, which keeps the group's first frame from being blank.
673
+ - pi's `hideThinkingBlock` setting is deliberately ignored: the row is the same whether the setting is
674
+ on or off, and a click is the one way to read the text. One behaviour, not two.
675
+ - The brain is `nf-md-brain`, **U+F09D1** — not the emoji 🧠, which is in no monospace Nerd Font and
676
+ falls back to a two-cell colour glyph that breaks the row it sits in. The preview is capped at **56
677
+ cells**: filling the row turned a glance into a paragraph wedged between the calls.
678
+
679
+ ### Changed
680
+
681
+ **An opened call keeps its row.** It used to *become* a box: the row vanished and a framed block took
682
+ its place, which made a group hard to follow — the thing you clicked disappeared. Inside the group the
683
+ rail already says what belongs to what, so a box there was a frame inside a frame. Now the row stays
684
+ where it is (the anchor never moves), takes a tick off the rail (`├─`) and its output hangs underneath —
685
+ without repeating the command, which the row already spells out. Boxes are kept where they earn their
686
+ keep: an `edit`/`write` call (never folded into a group), and a message's own thinking outside one —
687
+ where the expanded run now keeps its `thinking` title above the box.
688
+
689
+ **The metrics sit in columns.** `1 line` / `12 lines` and `2.0s` / `0.1s` used to start wherever the
690
+ arguments happened to end, so a group read as a ragged block of numbers. The note and the duration are
691
+ now padded to the group's widest of each and the whole tail is right-aligned, so every row ends on the
692
+ same cell.
693
+
694
+ **No padding rows left anywhere in the transcript.** The gaps were not the box's spacer but pi's own
695
+ message padding: a user message draws a blank row above and below its text, and an assistant text
696
+ message starts with a `Spacer`. Every message block is now trimmed at both edges — a reply sits directly
697
+ under the prompt, and a group line directly under the text that introduced it. The OSC 133 zone markers
698
+ that ride on those dropped rows move onto the rows that survive, so shell integration (prompt placement,
699
+ `Ctrl+Shift+Up`) is unaffected.
700
+
701
+ ### Fixed
702
+
703
+ **A tool row now spends the terminal's width on the command.** The argument column was a fixed 60 cells
704
+ and the row was cut at the right edge afterwards, which meant on a narrow terminal the
705
+ `51 lines · 1.6s` went first and a chopped command stayed behind — and on a wide one the command was
706
+ still cut at cell 60 with 80 cells of row left empty. The row now measures what it has: the arguments
707
+ fill the space left after the tail, the `(+12 lines)` marker is dropped before the command is shortened,
708
+ and the tail only gives way when there is no room for both.
709
+
710
+ **A grouped box no longer opens with a blank row.** pi puts a blank line between a call and its output;
711
+ under the gutter that read as a hole between the row and the box. Inside a group the blank is dropped
712
+ and the click geometry follows it. The split-diff body lost its own blank row under the `↳ diff +N -M`
713
+ summary too: a 20-row diff box was spending three of them on air.
714
+
715
+ ## [0.4.7] - 2026-10-21
716
+
717
+ ### Changed
718
+
719
+ **The copy no longer says "Claude Code-style".** The comparison set the wrong expectations and invited
720
+ the wrong argument: the parts that carry this extension — split diffs with word-level emphasis,
721
+ failures pinned at level 1 while the rest of the group is folded, live background-job rows, the
722
+ per-tool never-fold list — do not exist there. The README, the npm description and the cover now say
723
+ what the extension does instead of what it resembles. `claude-code` stays a search keyword, because
724
+ that is what people type when they want this interaction.
725
+
726
+ The cover also shows the split edit diff (`old │ new`) rather than the old stacked one, at 16:9.
727
+
728
+ ## [0.4.6] - 2026-10-20
729
+
730
+ ### Fixed
731
+
732
+ **Tidy no longer registers any tools, so sandbox extensions can load alongside it.** Registering a
733
+ tool under a built-in name is a hard failure in pi (`Tool "write" conflicts with
734
+ …/pi-tidy/index.ts`), and Tidy did it for `bash` (to highlight the command row) and, since 0.4.4, for
735
+ `write` (to make the preview height a setting). Any extension that legitimately owns those tools — a
736
+ sandbox such as pi-playpen, which must wrap execution or lose its enforcement — could therefore not
737
+ be installed at the same time. Both now travel as a `pi.registerToolRenderer()` resolver: the
738
+ installed tool keeps execution, schema, shell and result renderer, and `next()` hands Tidy the
739
+ downstream renderers so the call row can be replaced and the collapsed preview budget lowered on top
740
+ of whatever it returned.
741
+
742
+ - `bash`: the command row is still syntax highlighted and still folds to one line while collapsed,
743
+ and `bashResultLines` still caps the output preview. The `startedAt`/`endedAt` state pi's own
744
+ result renderer reads for its `Took 1.2s` footer is still maintained (we own the call renderer, so
745
+ nobody else would). `highlightBash: false` leaves bash completely untouched.
746
+ - `write`: unchanged behaviour, now on top of whichever result renderer is downstream instead of
747
+ pi's built-in one.
748
+ - `pi ≥ 1.3` is required for those two features. On an older pi they are skipped and a single warning
749
+ names what is unavailable, and only when the setting was actually changed away from pi's own
750
+ default — someone who never touched `writeLines` is not lectured.
751
+
752
+ Thanks to @s5unty for the diagnosis, the constraint that made the alternative (asking a sandbox
753
+ extension to stand down) unacceptable, and the write half in #8.
754
+
755
+ ## [0.4.5] - 2026-10-20
756
+
757
+ ### Added
758
+
759
+ **`diffLines`: the edit box's height is a setting.** pi's edit renderer draws the entire diff, collapsed
760
+ or not — a large edit owns the screen no matter what `alwaysShowTools` says. The collapsed box now
761
+ draws at most `diffLines` rows (default 20), windowed at the *first changed line* with a third of the
762
+ budget of context above it, and closes with `… (N more lines, M total, ctrl+o to expand)`. Cutting at
763
+ the top of the diff would hide the change itself on a large file, which is why the window follows the
764
+ first change instead. `ctrl+o` draws all of it; `0` keeps just the `↳ diff +N -M` summary.
765
+
766
+ **Hover on folded rows.** Pointing at a group summary line turns `▸` into `▶` and tints the row;
767
+ pointing at a call row in an open group lights that row and shows a `›` at its end. The whole row is
768
+ the click target, so the whole row lights up. Nothing is consumed — pi still receives the packet and
769
+ drag-selection across rows behaves exactly as before — and the band is dropped the moment the pointer
770
+ leaves, via a wrapper over the alt screen's mouse handler. Only available with 1003 all-motion
771
+ tracking, which pi enables outside tmux/screen; inside a multiplexer the rows simply never light up.
772
+
773
+ ### Changed
774
+
775
+ **Failures stopped shouting, and stopped taking up room.** A folded group used to hang one red row
776
+ per failed call under its summary. In a 50-call reply with two failures that is a three-line "folded"
777
+ group — the fold accomplishes nothing — and three reds (`1 failed`, `✗`, `exit 1`) described one probe.
778
+ Now level 0 is one line again: the summary carries `2 failed` in red — the colour is the signal, no
779
+ cross beside it — and the failed rows wait in level 1 with the rest. Open the group and each failed row shows its own `✗` (the count goes `muted`,
780
+ so red appears once at either level), its note in `muted` (`exit 1` is data, not an alarm), and — for a
781
+ failure that printed nothing at all (`sed` on a directory, a `test`, anything that only exits
782
+ non-zero) — a whole row dropped to `dim` while keeping the red glyph. A timeout or an abort counts as
783
+ something to read and keeps full contrast. The expanded box's red border is unchanged.
784
+
785
+ ## [0.4.4] - 2026-10-20
786
+
787
+ ### Added
788
+
789
+ **Side-by-side edit diffs.** An edit's diff is now two panes, `old │ new`, with line numbers on both
790
+ sides, a coloured bar and a tinted row for every changed line, and the changed words emphasised
791
+ inside the line. pi hands its edit renderer a unified diff string, so the panes are re-derived from
792
+ that string (the rendered lines are not reversible) and the row tint is mixed from the theme's diff
793
+ colours — it follows whatever theme is active. Removed and added blocks pair row by row, so a
794
+ replaced block reads as before/after instead of two stacked runs. Below `splitDiffMinWidth` (72 by
795
+ default) the box keeps pi's unified diff; `splitDiff: false` turns it off entirely.
796
+
797
+ The frame keeps the tinted rows' background fill and strips every other one, so the box stays
798
+ transparent everywhere else.
799
+
800
+ **`writeLines`: the write preview's height is a setting.** pi's write box hard-codes ten lines of the
801
+ written file while collapsed. The tool is re-registered (the same way bash already was) with a
802
+ configurable budget: `"writeLines": 10` is pi's number, `0` keeps just the header until `ctrl+o`.
803
+ `alwaysShowTools` remains the switch for whether edits and writes show their own box or fold into
804
+ the group — `[]` folds both.
805
+
806
+ ## [0.4.3] - 2026-10-19
807
+
808
+ ### Changed
809
+
810
+ **A cover image, a demo video and screenshots.** The README opens with a cover
811
+ and links a 40-second demo; `pi.image` and `pi.video` give the
812
+ [pi package gallery](https://pi.dev/packages) a preview instead of a line of
813
+ text. The media live in `assets/` on GitHub and stay out of the npm tarball.
814
+
815
+ ## [0.4.2] - 2026-10-19
816
+
817
+ ### Changed
818
+
819
+ **Renamed to pi-tidy.** `@andy8647/pi-toolbox` said nothing about what the
820
+ package does and sat under a personal scope; it is now the unscoped `pi-tidy`,
821
+ and the repository is `Andy8647/pi-tidy` (GitHub redirects the old URLs). Swap
822
+ the package with `pi remove npm:@andy8647/pi-toolbox && pi install npm:pi-tidy`.
823
+ Settings move to a `"tidy"` key; the `"toolbox"` key is still read, so existing
824
+ configs keep working. The old package is deprecated with a pointer here.
825
+
826
+ **Peer dependencies are `*` again**, as pi asks of extensions. 0.4.0 set them to
827
+ `>=1.1.0`, but npm treats a peer conflict as an error for the whole install, so
828
+ one mismatched range can block every package in pi's directory — an older pi
829
+ now gets a warning at session start instead.
830
+
831
+ ## [0.4.1] - 2026-10-19
832
+
833
+ ### Added
834
+
835
+ **Background jobs on the row that started them.** A `process start` call
836
+ (pi-processes) finishes in milliseconds while the process it started runs on, so
837
+ the row used to say `✓ … 0.0s` and the process's life showed up as separate
838
+ `[process] …` messages further down. The row now tracks the process:
839
+ `◐ process ticker running 12s · READY matched`, changing in place to
840
+ `✓ … exited 0 · 20s`, `✗ … exit 1 · 3s` or `⊘ … killed SIGTERM · 3s`. A running or
841
+ failed job keeps its row under a folded summary, which spins and counts them
842
+ (`▸ 5 calls · … · ◐ 1 running`); failed jobs count toward `N failed`.
843
+
844
+ The job's notification messages are no longer drawn once its row shows them —
845
+ pi-processes still delivers them to the model, toolbox only skips drawing them;
846
+ a job without a row (one started from `/ps`) keeps its messages. A reloaded
847
+ session is rebuilt from those persisted messages, and a job pi-processes no
848
+ longer knows (a session from an earlier pi) reads `not running` instead of
849
+ spinning forever. The spinner turns once a second for a job's first five
850
+ minutes, then stands still and the clock ticks once a minute, so a dev server
851
+ left running for hours doesn't repaint the transcript every second.
852
+
853
+ This is an adapter: pi has no notion of background jobs, so `background-jobs.ts`
854
+ has a small provider interface and pi-processes (its `processes:*` event
855
+ protocol and `ad-process:notification` messages) is the first provider. If that
856
+ protocol changes, rows simply go back to plain tool rows. New options
857
+ `backgroundJobs` and `hideJobNotifications` (both default `true`).
858
+
859
+ ### Fixed
860
+
861
+ **A quick call flashed a row under the summary.** A folded group shows the row
862
+ of a call that is still running — so a call that ran for 40 ms popped out under
863
+ `▸ 4 calls` and folded back a frame later, and a `process start` stopped within
864
+ a second did the same. A running call (or job) now gets its row, and its
865
+ `running …` mention in the summary, after one second; long commands still
866
+ surface, quick ones never move. Likewise a step still streaming its thinking in
867
+ the middle of a group no longer flashes `Thinking...` under the summary before
868
+ its tool call arrives; at the start of a turn, with nothing folded yet, the label
869
+ still shows.
870
+
871
+ The README's "No mouse handling" section described a design removed long ago
872
+ and contradicted the click support; it's rewritten, along with the broken
873
+ screenshot placeholder and a duplicate border-color bullet.
874
+
875
+ ## [0.4.0] - 2026-10-19
876
+
877
+ ### Added
878
+
879
+ **Grouped tool calls: one line per assistant segment, three levels deep.** A reply
880
+ that ran six tools used to cost six boxes; it now costs one line —
881
+ `▸ 6 calls · bash×4 read×2 · 1.2s` — with the per-tool icons toolbox already
882
+ uses for call rows. Clicking the line turns it into a `▾` header with exactly one
883
+ row per call under a `│` gutter
884
+ (`│ ✓ bash cd ~ && python3 - <<'PY' (+26 lines) 0.1s`); clicking a row opens that
885
+ call's full box (still on the gutter, so it's clear which group it belongs to),
886
+ clicking the box folds it back into its row, and clicking the `▾` header folds the
887
+ whole group — boxes included — back to its summary line.
888
+
889
+ Edits and writes never fold. Their diff is the thing you want to see, so they keep
890
+ pi's own box and end the group before them; the calls after an edit start a new
891
+ group, which keeps the transcript in order. New `alwaysShowTools` option (default
892
+ `["edit", "write"]`, `[]` folds everything).
893
+
894
+ A group is everything between two boundaries: a user message, or an assistant
895
+ message with visible text. The text always renders, so a reply that drifts is
896
+ still visible, and each segment keeps its own summary. pi gives every model step
897
+ its own assistant message, and a step that only calls a tool has no text — those
898
+ steps don't split the group, and their `Thinking...` label folds away with it,
899
+ reappearing next to that call's box when you open it. The running tool keeps its
900
+ own row under the summary so a long command doesn't look frozen.
901
+
902
+ Running and failed calls keep their own row under a folded summary — a failure
903
+ should never hide inside `▸ 6 calls` — and the summary counts them
904
+ (`▸ 6 calls · … · 1 failed · 1.2s`). Clicking any row opens that call's box,
905
+ including those two under a folded summary. Every row also says what the call
906
+ produced, Claude Code style: `12 lines`, `3 matches`, `no files`, `exit 1` for the
907
+ built-in tools (truncated output reports the original total); other tools get no
908
+ note rather than a guessed one.
909
+
910
+ `ctrl+o` expands every box (thinking included, like pi's native transcript), and
911
+ pressing it again folds every group back to its summary line; pi's own click on a
912
+ full box folds just that box. Membership is derived at render time from transcript
913
+ order, so groups survive a session reload without persisting anything. New
914
+ `groupTools` option (default `true`).
915
+
916
+ ### Changed
917
+
918
+ **Requires pi ≥ 1.1.0** (peer dependencies were `*`). Component mouse events
919
+ arrived in 0.86 and tool durations in 1.1.0; on anything older the groups can't
920
+ be opened and the summary has no times. The dev dependencies moved from 0.82 to
921
+ 1.3, so the tests now run pi's real click dispatch instead of a stand-in — the
922
+ stand-in is how a `button: "left"` vs `0` mismatch went unnoticed.
923
+
924
+ **Settings apply on `/reload`.** The prototype patches are installed once per
925
+ process, and used to keep the options of the first load; a reload's new settings
926
+ (including `enabled: false` and `frameMessages: false`) were silently dropped.
927
+ The patches now read their options from a global slot each load updates.
928
+
929
+ **Bash call rows collapse to one line by default.** The call row rendered the
930
+ command verbatim, so a heredoc (`python3 - <<'PY'` plus forty lines of body) or a
931
+ long `&&` chain filled the box before the command had even run, pushing every
932
+ previous tool call off screen. The collapsed row now shows the first line only,
933
+ cut at 100 characters (pi's own `COLLAPSED_ARGS_CHARS`, so bash folds like every
934
+ other tool), followed by `(+N more lines, ctrl+o to expand)`. `ctrl+o` still
935
+ gives the whole command back.
936
+
937
+ **Collapsed result previews shrank to one line too.** pi's bash renderer caps the
938
+ collapsed output at a hard-coded `BASH_PREVIEW_LINES = 5` with no setting, so even
939
+ a one-line command cost five output rows. The collapsed view now keeps a single
940
+ row and `ctrl+o` still brings back all of it. New `bashResultLines` option (default
941
+ `1`, `0` hides output until expanded) — applied by lowering the budget on the
942
+ `VisualLinePreview` pi built, so timers, truncation warnings and image fallbacks
943
+ stay pi's own.
944
+
945
+ `frame.ts` is split by job (`frame-draw.ts`, `tool-frame.ts`, `tool-group-view.ts`,
946
+ `message-frame.ts`); it keeps re-exporting the same API.
947
+
948
+ ### Fixed
949
+
950
+ **Clicking a framed box toggled the wrong row — or nothing at all.** pi maps a
951
+ click on a box through its own unframed layout, and for `renderShell: "self"`
952
+ tools (edit, and MCP tools via pi-mcp-adapter) through a height that only pi's
953
+ own render records; toolbox replaces that render, so those clicks were always
954
+ dropped. Clicks are now translated through the frame's geometry into the source
955
+ component's coordinates and reach pi's own toggle.
956
+
957
+ `formatBashCallHighlighted(args, theme, options?)` takes the new third argument
958
+ (`{ expanded, maxChars, hint }`); omitting it renders the full command, so
959
+ existing callers are unaffected. `collapseBashCommand` is exported for tests.
960
+
961
+ ## [0.3.3] - 2026-10-05
962
+
963
+ ### Fixed
964
+
965
+ **A running tool box no longer freezes on a stale frame.** The frame cache
966
+ keyed on `(line count, first line, last line)`; tool boxes are bordered by
967
+ constant frame and padding rows, so any change confined to the middle left the
968
+ key untouched. A `sleep 580` bash call kept showing `Elapsed 0.0s` until
969
+ something else — a click expanding the box — invalidated the cache. The same
970
+ hole hid streaming output that stayed on the same number of lines. The cache
971
+ now compares the lines it framed, which costs a pointer comparison per row
972
+ while `Box`/`Text` hand back their cached arrays. The message and container box
973
+ caches, which shared the fingerprint helper, were made sound the same way.
974
+
975
+ Also: framing no longer writes into the line array `Text` returns from its
976
+ cache (`content[0] = row` on an untrimmed array duplicated the kind icon on the
977
+ next render).
978
+
979
+ ## [0.3.2] - 2026-09-22
980
+
981
+ ### Removed
982
+
983
+ **The `collapseAnchor` option and its `(ctrl+o to collapse)` row are gone.**
984
+ The row existed because, before Pi 0.86.0, the only way to close one expanded
985
+ box was `ctrl+o` collapsing every box at once. Pi now toggles a single tool
986
+ box (and compaction/branch/skill message boxes) on a click anywhere in its
987
+ content, so the anchor row is redundant. A `toolbox.collapseAnchor` key in
988
+ `settings.json` is ignored.
989
+
990
+ ## [0.3.1] - 2026-09-20
991
+
992
+ ### Fixed
993
+
994
+ **File icon placement for home-relative paths.** pi's `renderToolPath`
995
+ shortens `$HOME` to `~` in the display text, so an edit on
996
+ `~/.pi/agent/settings.json` never contained the raw args path — the icon
997
+ fell back into the prefix (`✏️{} edit ~/.pi/...`) instead of sitting in
998
+ front of the path. The matcher now tries both the raw and the `~`-shortened
999
+ form.
1000
+
1001
+ ### Added
1002
+
1003
+ **File icons render in their brand colors, with full-set coverage.** The
1004
+ icon map now contains the complete nvim-web-devicons set — 736 extensions
1005
+ and 1172 special filenames — each with its brand color applied as a
1006
+ truecolor escape (TypeScript blue, Rust orange, Go cyan, ...), like lazyvim
1007
+ and yazi. Unknown extensions fall back to an uncolored generic file icon.
1008
+ Tool-kind icons (FA glyphs) intentionally stay uncolored; they follow the
1009
+ call row.
1010
+
1011
+ ### Changed
1012
+
1013
+ **The call row drops the tool-name word for file tools.** With the kind
1014
+ icon in front, ` 󰛦 edit ~/x.ts` reads choppier than
1015
+ ` 󰛦 ~/x.ts` — the word is redundant, so read/edit/write rows are
1016
+ just `kind icon + file icon + path`. Rows that don't start with the tool
1017
+ name are left untouched.
1018
+
1019
+ ## [0.3.0] - 2026-09-19
1020
+
1021
+ ### Added
1022
+
1023
+ **Message boxes get the same rounded transparent frame as tool boxes, each
1024
+ kind in its own border color.** User messages, compaction and branch
1025
+ summaries, skill invocations, and extension custom messages previously kept
1026
+ pi's default filled-background bars, since they never go through
1027
+ `ToolExecutionComponent`. Their components are now patched the same way.
1028
+ User messages keep their OSC 133 zone markers, and custom messages rendered
1029
+ by an extension-provided renderer are left untouched — the renderer owns its
1030
+ styling.
1031
+
1032
+ **Tool boxes can take per-tool border colors and Nerd Font icons.** A
1033
+ successful box uses its tool's configured color instead of green (pending
1034
+ stays grey, errors stay red, unlisted tools take the theme accent), and every
1035
+ box's call row leads with a kind icon — file tools put the target file's
1036
+ nvim-web-devicons glyph right in front of the path (`write /tmp/x.ts`
1037
+ renders ` write 󰛦 /tmp/x.ts`).
1038
+
1039
+ New settings under `toolbox`: `frameMessages` (default `true`) toggles the
1040
+ message frames; `frameUserMessages` (default `false`) opts user messages in
1041
+ — they are skipped by default because pi-starline patches the same
1042
+ prototype, and two render patches on one prototype fight over the output;
1043
+ `icons` (default `true`, requires a Nerd Font) toggles the icons;
1044
+ `toolColors` maps tool names to success border colors (replaces defaults
1045
+ when set); `messageBorderColors` picks a border color per message
1046
+ kind — `user` (`toolTitle`), `compaction` (`customMessageLabel`), `branch`
1047
+ (`mdCode`), `skill` (`accent`), `custom` (`warning`). Values are theme fg
1048
+ color names, so they follow the active theme; tool boxes keep their
1049
+ status-driven grey/green/red.
1050
+
1051
+ ## [0.2.3] - 2026-09-18
1052
+
1053
+ ### Fixed
1054
+
1055
+ **Bash syntax highlighting can no longer freeze the whole interface.** A `#`
1056
+ that was neither at the start of a word nor inside quotes — `fill:#242f60`,
1057
+ `a#b`, `url#frag` — was treated as a word break that no branch handled. The
1058
+ scanner stopped advancing and pushed empty strings until the process ran out of
1059
+ memory. Because the call row is re-rendered on every tool-argument delta, a
1060
+ streaming command containing one of these deadlocked the TUI: 100% CPU, no
1061
+ repaint, `ctrl+c` unresponsive, and the session file stopped updating. The
1062
+ process could not recover on its own and survived closing the terminal tab.
1063
+
1064
+ Bare `\r` and other non-space whitespace (`\u00a0`, `\v`, `\f`) reached the same
1065
+ dead end, because they are word breaks too.
1066
+
1067
+ The tokenizer now keeps a mid-word `#` inside the word, and the scanner can
1068
+ never loop without consuming a character — an unhandled break character is
1069
+ emitted as plain text instead of spinning. `test/bash-highlight.test.ts` covers
1070
+ the reported shapes, a heredoc with a hex colour, and 400 randomized inputs
1071
+ containing `#`, `\r` and non-ASCII spaces; every case asserts termination (a hang
1072
+ is detected by a worker deadline) and that no character is dropped.
1073
+
1074
+ ## [0.2.2] - 2026-08-10
1075
+
1076
+ ### Added
1077
+
1078
+ **A collapse anchor row inside expanded tool boxes.** When a tool box is
1079
+ expanded, the frame now ends with a `(ctrl+o to collapse)` row. With
1080
+ [pi-starline](https://github.com/Andy8647/pi-starline) installed the row is
1081
+ clickable (click it to collapse the box again); without Starline it reads as
1082
+ an ordinary keyboard hint — `ctrl+o` really does collapse the box in Pi.
1083
+ Set `toolbox.collapseAnchor: false` to opt out of the row entirely.
1084
+
1085
+ The anchor row is painted by Starline's `expandHintAction` matching it, so
1086
+ neither extension needs to know about the other.
1087
+
1088
+ ## [0.2.1] - 2026-07-26
1089
+
1090
+ ### Changed
1091
+
1092
+ - Tool boxes stand down when another extension owns the mouse; clicking to
1093
+ expand a box is now opt-in rather than the default.
1094
+
1095
+ ## [0.2.0] - 2026-07-26
1096
+
1097
+ ### Added
1098
+
1099
+ - Rounded, status-aware tool box frames with bash highlighting.
1100
+ - Click a tool box's border to expand just that box.
1101
+
1102
+ ### Removed
1103
+
1104
+ - The fixed-editor-era click-to-expand that required Starline's compositor.