aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-07-22 21:36:51 +0200
committeruser <user@clank>2026-08-12 19:34:35 +0200
commitbdbd58464f94fe3546836f640cd53bc35fdb44d6 (patch)
tree2d935b58cb8b0a79a985ff0b1028803ff5d93fc2
parentslop-tool-boxes: remove animation timer; time-based pulse (fix streaming flic... (diff)
downloadsl0ppi-bdbd58464f94fe3546836f640cd53bc35fdb44d6.tar.gz
sl0ppi-bdbd58464f94fe3546836f640cd53bc35fdb44d6.tar.xz
sl0ppi-bdbd58464f94fe3546836f640cd53bc35fdb44d6.zip
slop-tool-boxes: dotted gray rails in-flight, solidify on finalize
Bring back body side rails with an active/settled distinction: - in-flight: gray dotted (┊) rails + pulsing top/bottom borders - finalized: solid status-colored (│) rails + solid borders Rails are a STATIC per-render color (never carry the pulse), so streaming output can't recolor existing body lines -> stays flicker-safe.
-rw-r--r--pi-ext/NOTES.md5
-rw-r--r--pi-ext/slop-tool-boxes.ts52
2 files changed, 32 insertions, 25 deletions
diff --git a/pi-ext/NOTES.md b/pi-ext/NOTES.md
index b6254e2..f3125cb 100644
--- a/pi-ext/NOTES.md
+++ b/pi-ext/NOTES.md
@@ -10,7 +10,10 @@ hydrates the pulse", irregular cadence). A silent long-running tool just holds a
static active color until it streams output or finishes. This killed the third
bug: a tall (taller-than-screen) bash box flickered because the old 10fps timer
forced a full `updateDisplay()` repaint on top of the stream's own re-renders.
-Body has no side rails; only top/bottom borders pulse.
+
+Borders: only the top/bottom pulse. Side rails are a STATIC color (never carry
+the pulse) so they can't recolor body lines per frame: gray dotted `┊` while
+in-flight, solidifying to a status-colored `│` once the tool finalizes.
### Earlier iterations (kept for history)
diff --git a/pi-ext/slop-tool-boxes.ts b/pi-ext/slop-tool-boxes.ts
index cfa4150..3a0a3ee 100644
--- a/pi-ext/slop-tool-boxes.ts
+++ b/pi-ext/slop-tool-boxes.ts
@@ -3,8 +3,9 @@
*
* Reframes the built-in tool rows (bash, read, edit, write, grep, find, ls)
* in the same rounded-border "sl0peration" style used by the last-prompt
- * topbar widget — a labelled top border and a rounded bottom border (open sides,
- * no vertical rails). While a tool executes, the top/bottom borders pulse.
+ * topbar widget — a labelled top border, side rails, and a rounded bottom. While
+ * a tool executes the top/bottom borders pulse and the side rails are gray
+ * dotted; when it finalizes the rails solidify to the status color.
*
* It does NOT reimplement tool behavior or output: it grabs each built-in
* tool *definition* (execute + native renderCall/renderResult, so syntax
@@ -49,6 +50,7 @@ type ToolDef = {
const H = "─";
const V = "│";
+const DOTTED_RAIL = "┊"; // gray dotted side rail while a tool is in-flight
const TL = "╭";
const TR = "╮";
const BL = "╰";
@@ -78,8 +80,9 @@ function borderColorFor(status: Status): string {
// taller-than-screen streaming box is exactly what flickered. A silent tool
// just holds a static active color until it streams output or finishes.
//
-// 3. Only the horizontal top/bottom borders pulse; the body has no side rails
-// at all, so a repaint never has to recolor body lines.
+// 3. Only the horizontal top/bottom borders pulse. The body side rails are a
+// STATIC color (gray dotted while in-flight, solid status color once
+// settled), so a repaint never has to recolor body lines.
const PULSE: Array<[number, number, number]> = [
[0xff, 0x8a, 0x1e], // amber
[0xff, 0x2e, 0x88], // hot pink
@@ -267,20 +270,18 @@ function frameBottom(width: number, paint: Paint): string {
return paint(BL + H.repeat(innerWidth) + BR);
}
-// Body lines have NO left/right rails — only the top/bottom borders frame the
-// box (and pulse). This is the anti-flicker "middle ground": with no side glyphs
-// there is nothing to recolor per frame, so streaming never triggers a full-box
-// repaint, and the open sides read cleaner against the pulsing horizontals. A
-// 2-space left margin keeps content tucked under the top border; lines are
-// truncated to the old inner width so native render + diff backgrounds are
-// unaffected. `paint` is unused now (kept for a stable call signature).
-const BODY_INDENT = " ";
-function frameBody(lines: string[], width: number, _paint: Paint): string[] {
- const innerWidth = Math.max(0, width - 4);
+// Wrap inner lines with side rails. `rail` is the ALREADY-PAINTED rail glyph
+// (gray dotted while in-flight, solid status-colored when settled). Crucially the
+// rail is a STATIC string for the whole render pass — it does not carry the
+// pulse — so streaming output never recolors existing body lines and can't cause
+// a full-box repaint. Only the horizontal top/bottom borders pulse.
+function frameBody(lines: string[], width: number, rail: string): string[] {
+ const innerWidth = Math.max(0, width - 4); // 2 rails + 2 padding spaces
return lines.map((raw) => {
let line = raw;
if (visibleWidth(line) > innerWidth) line = truncateToWidth(line, innerWidth, "…");
- return BODY_INDENT + line;
+ const pad = Math.max(0, innerWidth - visibleWidth(line));
+ return `${rail} ${line}${" ".repeat(pad)} ${rail}`;
});
}
@@ -399,20 +400,22 @@ function registerFramedTool(pi: ExtensionAPI, def: ToolDef) {
// repaint while a tool streams output (edit/write) and reads as violent
// flicker. Keeping rails static means streaming only redraws the new
// content line, and the pulse only touches 1-2 border lines. (NOTES.md)
- const build = (border: Paint, rail: Paint): string[] => {
+ const build = (border: Paint, rail: string): string[] => {
const out = [frameTop(width, label, border), ...frameBody(inner, width, rail)];
// While the result slot hasn't started rendering yet (still-running
// tool), close the box here so the frame is never bottomless.
if (!st.resultActive) out.push(frameBottom(width, border));
return out;
};
- const rail = solidPaint(theme, borderColorFor(status));
if (activeNow) {
cw = -1; // don't cache while animating; recompute solid once settled
- return build(pulsePaint(), rail);
+ // in-flight: pulsing top/bottom borders + STATIC gray dotted rails
+ return build(pulsePaint(), theme.fg("muted", DOTTED_RAIL));
}
if (width === cw) return cl;
- cl = build(rail, rail);
+ // settled: solid borders + rails in the status color
+ const solid = solidPaint(theme, borderColorFor(status));
+ cl = build(solid, theme.fg(borderColorFor(status), V));
cw = width;
return cl;
},
@@ -472,11 +475,12 @@ function registerFramedTool(pi: ExtensionAPI, def: ToolDef) {
render(width: number): string[] {
const inner = innerLines(width);
const activeNow = isActiveRow(st);
- // Anti-flicker: static rails/body, only the bottom border pulses.
- const railColor = activeNow ? "warning" : borderColorFor(st.status);
- const rail = solidPaint(theme, railColor);
+ // in-flight: gray dotted rails; settled: solid status-colored rails.
+ const railStr = activeNow
+ ? theme.fg("muted", DOTTED_RAIL)
+ : theme.fg(borderColorFor(st.status), V);
const build = (border: Paint): string[] => [
- ...frameBody(inner, width, rail),
+ ...frameBody(inner, width, railStr),
frameBottom(width, border),
];
if (activeNow) {
@@ -484,7 +488,7 @@ function registerFramedTool(pi: ExtensionAPI, def: ToolDef) {
return build(pulsePaint());
}
if (width === cw) return cl;
- cl = build(rail);
+ cl = build(solidPaint(theme, borderColorFor(st.status)));
cw = width;
return cl;
},