bermudis-pi-goodies 0.13.0 → 0.13.2

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 (3) hide show
  1. package/README.md +39 -1
  2. package/clean-tui.ts +35 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -23,7 +23,7 @@ extensions. One entry point, eleven independent features.
23
23
  After publishing the package to npm:
24
24
 
25
25
  ```bash
26
- pi install npm:bermudis-pi-goodies@0.13.0
26
+ pi install npm:bermudis-pi-goodies@0.13.2
27
27
  ```
28
28
 
29
29
  Remove any old `bermudis-pi-goodies.ts` symlink before reloading Pi. Each
@@ -67,6 +67,44 @@ Failures degrade gracefully: the raw command stays visible as a heuristic
67
67
  hint, each distinct failure is logged once (naming the model), and repeated
68
68
  failures back off exponentially instead of hammering the provider.
69
69
 
70
+ ### Render-safety rules (why summaries only refresh running rows)
71
+
72
+ These rules apply to pi's **regular** TUI mode (`tuiMode: "regular"`, the
73
+ default — `TuiMainScreen`, the scrolling scrollback renderer). Pi also has a
74
+ **fullscreen** mode (`TuiAltScreen`): no scrollback, a fixed `height`-line
75
+ slice of the transcript diffed row-by-row, full clears only on first
76
+ render/resize/image redraws. Neither escalation below exists there — a
77
+ summary arrival re-rendering an old row is either invisible (scrolled off
78
+ the slice) or a one-line rewrite. Check `~/.pi/agent/settings.json`
79
+ `tuiMode` before reasoning about render escalation.
80
+
81
+ Pi's regular-mode diff renderer answers two situations with
82
+ `fullRender(true)` — clear screen, wipe scrollback, repaint everything —
83
+ which reads as a full-screen flash:
84
+
85
+ 1. total rendered height dropping below the session high-water mark
86
+ (`clearOnShrink`), and
87
+ 2. any content change to a line **above the scrolled viewport**
88
+ (`firstChanged < prevViewportTop` — width-independent; on a long
89
+ transcript this is any row more than a screenful above the input box).
90
+
91
+ clean-tui therefore follows two rules in its render paths:
92
+
93
+ - **Height-neutral swaps:** text that may be replaced by a summary later is
94
+ capped at the same width as summaries (`BASH_BULLET_WIDTH`), so an
95
+ arriving summary never collapses a wrapped line (rule 1).
96
+ - **Pending-only refresh:** when a summary lands, only rows whose tool is
97
+ still executing are re-rendered — those sit at the transcript tail, inside
98
+ the viewport, so the swap is a cheap differential update. Finished rows
99
+ (including replayed ones from before a `/resume`) keep the raw command
100
+ text for the rest of the session; the summary stays cached, and future
101
+ rows of the same command render it from the start (rule 2).
102
+
103
+ To catch a flash red-handed, run `PI_DEBUG_REDRAW=1 pi`, reproduce, then
104
+ `grep fullRender ~/.pi/agent/pi-debug.log` — every line is one screen wipe
105
+ with its reason (`clearOnShrink`, `firstChanged < viewportTop`, resize, …).
106
+ Note the log is append-only across sessions; check timestamps.
107
+
70
108
  ## Per-model thinking levels (retired in favor of Pi 0.84.3)
71
109
 
72
110
  This package previously shipped a `model-thinking` module with a `/levels`
package/clean-tui.ts CHANGED
@@ -537,8 +537,19 @@ function requestSummary(cmd: string): void {
537
537
  }
538
538
 
539
539
  function invalidateRowsForCommand(cmd: string): void {
540
+ // Only rows that are STILL EXECUTING get refreshed when a summary lands.
541
+ // Executing rows sit at the transcript tail, inside pi's viewport, so the
542
+ // re-render is a cheap differential line update. Finished rows — including
543
+ // replayed ones from before a /resume — can sit far above the viewport on a
544
+ // long transcript, and pi's diff renderer answers any change above the
545
+ // viewport with fullRender(true): clear screen + scrollback wipe + full
546
+ // repaint, i.e. the "flicker while pi is working" seen on 0.11.x (fits any
547
+ // width; reproduced reasoning from tui-main-screen.js `firstChanged <
548
+ // prevViewportTop`). Finished rows simply keep the raw command text, which
549
+ // is more informative than the summary anyway; the summary stays cached
550
+ // and future rows of the same command render it from the start.
540
551
  for (const e of entries) {
541
- if (e.args?.command === cmd) {
552
+ if (!e.result && e.args?.command === cmd) {
542
553
  const fn = invalidateById.get(e.toolCallId);
543
554
  if (fn) fn();
544
555
  }
@@ -621,15 +632,24 @@ function formatBashHeader(args: any, theme: any): string {
621
632
  if (isSummarizable(cmd) && summaryCache.has(cmd)) {
622
633
  return theme.fg("accent", summaryCache.get(cmd)!);
623
634
  }
624
- return formatBashCommand(cmd, theme, 120);
635
+ // Cap must match BASH_BULLET_WIDTH, not exceed it: when a summary lands it
636
+ // replaces this text, and summaries are capped at SUMMARY_THRESHOLD_CHARS.
637
+ // A larger raw cap means the raw header can wrap where the summary won't,
638
+ // so the swap shrinks total line count mid-run — and pi-tui answers any
639
+ // drop below its high-water mark with a full clear-screen + scrollback wipe
640
+ // (clearOnShrink), i.e. a visible full-screen flash per summarized command
641
+ // (reproduced in panes narrower than ~124 cols; see clean-tui.test.ts
642
+ // "summary swap is height-neutral at 110 columns"). Equal caps keep the
643
+ // swap height-neutral wherever 80 columns fit.
644
+ return formatBashCommand(cmd, theme, BASH_BULLET_WIDTH);
625
645
  }
626
646
 
627
647
  function formatBashBullet(entry: Entry, theme: any): string {
628
648
  const cmd = entry.args.command || "...";
629
649
  const bullet = `${theme.fg("muted", "•")} `;
630
650
  // Failed calls get red text so a single failure is visible without
631
- // poisoning the whole burst's background (see renderCall: box color now
632
- // follows the leader's status only).
651
+ // poisoning the whole burst's background (see renderCall: the grouped box
652
+ // never takes the error color, whichever call failed).
633
653
  const accent = (s: string) => theme.fg(entry.isError ? "error" : "accent", s);
634
654
  if (isSummarizable(cmd) && summaryCache.has(cmd)) {
635
655
  return ` ${bullet}${accent(summaryCache.get(cmd)!)}`;
@@ -875,12 +895,17 @@ export default function cleanTui(pi: ExtensionAPI): void {
875
895
  const isGrouped = burst && burst.entries.length > 1;
876
896
  const isLeader =
877
897
  isGrouped && burst.entries[0].toolCallId === ctx.toolCallId;
878
- // Box color follows the leader's own status. Aggregating any-pending/
879
- // any-error over the whole burst paints 37 rows red for one failure;
880
- // per-entry failures are marked on their own bullet instead
881
- // (formatBashBullet).
882
- const pending = !entry.result;
883
- const isError = !!entry.isError;
898
+ // The burst box is shared by every call in the group, so its background
899
+ // never takes the error color — a failure (the leader included) is
900
+ // marked on its own bullet instead (formatBashBullet). "Follow the
901
+ // leader's status" (b80a14d) still painted the whole block red whenever
902
+ // the first call itself was the one that failed. Pending does
903
+ // aggregate: the box stays in its running state until every call in
904
+ // the burst has landed.
905
+ const pending = isGrouped
906
+ ? burst.entries.some((e) => !e.result)
907
+ : !entry.result;
908
+ const isError = isGrouped ? false : !!entry.isError;
884
909
 
885
910
  if (isGrouped && !isLeader) {
886
911
  // Refresh the leader's header/count. Single hop: a leader's renderCall
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bermudis-pi-goodies",
3
- "version": "0.13.0",
3
+ "version": "0.13.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/bermudi/agent-extensions.git",