diff options
| author | user <user@clank> | 2026-07-22 21:36:51 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-12 19:34:35 +0200 |
| commit | bdbd58464f94fe3546836f640cd53bc35fdb44d6 (patch) | |
| tree | 2d935b58cb8b0a79a985ff0b1028803ff5d93fc2 | |
| parent | slop-tool-boxes: remove animation timer; time-based pulse (fix streaming flic... (diff) | |
| download | sl0ppi-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.md | 5 | ||||
| -rw-r--r-- | pi-ext/slop-tool-boxes.ts | 52 |
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; }, |
