<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppi.git/pi-ext/slop-tool-boxes.ts, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppi.git/atom/pi-ext/slop-tool-boxes.ts?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppi.git/atom/pi-ext/slop-tool-boxes.ts?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/'/>
<updated>2026-08-12T17:34:35Z</updated>
<entry>
<title>slop-tool-boxes: green heartbeat pulse; unfreeze top border when it fits</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T14:25:18Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=521212c3afc3db4b7fbce736b8146608f07f5cba'/>
<id>urn:sha1:521212c3afc3db4b7fbce736b8146608f07f5cba</id>
<content type='text'>
Two changes that both come back to "what should an in-flight box look like",
plus the icon that was left out of it.

PULSE: single hue, brightness only. The old pulse interpolated a 4-color
synthwave ring (amber -&gt; pink -&gt; violet -&gt; cyan) over 4s, which at that
period reads as a slow hue *drift*, not a pulse -- you notice the color has
changed, never that it is beating. Now it is one hue (green, i.e. "in
progress") modulated dim &lt;-&gt; bright: #0d4023 to #3bff7a, via a raised
cosine so there is no direction-reversal snap at the turn, then gamma ^1.7
to bias it toward the dim end. The border rests dark and spikes bright,
which is what makes it read as a heartbeat.

CYCLE_MS 4000 -&gt; 800 (1.25Hz), and 800 is now the only pulse-speed knob.
There is a hard floor of ~400ms documented at the constant: a brightness
pulse IS a flicker, and 3-30Hz is the photosensitive-seizure band -- this
repo has already shipped one seizure-grade flicker bug (NOTES.md), so the
constant carries the warning rather than relying on memory. The upper bound
is render cadence, not taste: the pulse never forces a repaint, it samples
wall-clock time on renders that were happening anyway (~10fps while the
spinner runs), so 800ms is ~8 samples per beat. Much faster and consecutive
renders land on distant brightnesses -- the beat degrades into jitter.

TOP BORDER: freeze only when we actually must. 0d671ce stopped the
full-redraw flicker by freezing the call slot's top border for the whole
time the result streams. That was broader than the bug requires. The bug is
specifically "recolor a line that sits ABOVE the viewport": tui.js does
`if (firstChanged &lt; prevViewportTop) fullRender(true)`, so an off-screen
recolor repaints everything, every tick. While the box still fits on
screen, firstChanged lands inside the viewport and tui diffs from there
down -- cheap. So canPulseTopBorder() compares callH + resultH against
rows - RESERVED_ROWS and only freezes past that, which gives short tool
calls their top and bottom borders beating in unison again.

resultH is one frame stale (the call slot renders before the result slot).
Harmless, because a box only grows: worst case is one extra full redraw on
the frame it crosses the threshold, and then it is frozen. RESERVED_ROWS
(14, for input box + footer + topbar) is deliberately generous in the same
direction -- over-estimating freezes a box that could have animated, which
nobody can see; under-estimating buys back the flicker.

A frozen border now holds HOLD_PAINT, a fixed mid-beat green (pulseRGB(0.5)),
instead of whatever phase the pulse happened to be at when content last
changed. Same number of writes, but it looks deliberately held rather than
randomly stuck. Resuming the pulse invalidates callActiveW so the frozen
lines cannot be served stale.

ICON: the pending ◉ takes the border's paint, so it beats with the frame
instead of sitting there as a static amber dot. This is why the label is
built inside build() now -- it has to be rebuilt per paint. It costs
nothing extra: the icon shares the top border's line, which is already
being rewritten.

Also picks up the sl0peration -&gt; turn doc-comment rename in the header,
which is unified into the same hunk as the pulse comment above it.
</content>
</entry>
<entry>
<title>chrome: drop side rails across boxes (zellij draws the pane border)</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-10T22:08:58Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=129749f0e62526831797ab2ea318a75dd0ce12b0'/>
<id>urn:sha1:129749f0e62526831797ab2ea318a75dd0ce12b0</id>
<content type='text'>
We run the agent inside a zellij fork that already draws a border around
the pane, so per-box verticals were redundant chrome. Body lines are now
indented into the box interior instead of railed, and the top/bottom
rules use straight end caps (a corner would curve toward a vertical that
is no longer there). Each file keeps the original glyphs documented so
the rounded box can be restored by flipping the cap constants back.

Backported from the installed set in ~/.pi/agent/extensions, which had
drifted ahead of this repo.
</content>
</entry>
<entry>
<title>slop-tool-boxes: stop full-redraw flicker on tall, top-clipped active boxes</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-26T07:32:49Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=84483b6d6020eee944ada9822c5d6229dc567e04'/>
<id>urn:sha1:84483b6d6020eee944ada9822c5d6229dc567e04</id>
<content type='text'>
An in-flight tool box taller than the viewport (top scrolled off-screen)
flickered violently the whole time it was active. Cause: the working spinner
(and pi's default spinner) drive ui.requestRender() at ~10-16fps, and the
active call slot recomputed a wall-clock pulse color every render, recoloring
the TOP border. tui.js falls back to a full-screen redraw when the first
changed line is above the viewport (firstChanged &lt; prevViewportTop), so every
spinner tick = one full clear+repaint.

Fix: once the result slot is streaming (box can be tall), freeze the call
slot pulse color, caching built lines per (width, inner). The header/command
is static, so the top border is byte-identical between render ticks and never
triggers an above-viewport change. The pre-output phase (box still fully
visible) keeps the live pulse + ticking runtime; the result slot still pulses
its in-viewport bottom border. NOTES.md documents this as flicker #4.

Confirmed by repro: massive flicker with the fix reverted, calm with it in.
</content>
</entry>
<entry>
<title>slop-tool-boxes: syntax-highlighted diffs with gutter + bg band</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-22T22:03:03Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=460366a59f5ef4c70e020563d80b6183f3b14297'/>
<id>urn:sha1:460366a59f5ef4c70e020563d80b6183f3b14297</id>
<content type='text'>
Replace the post-hoc applyDiffBackgrounds hack (which recolored the
native flat green/red diff lines) with a real diff renderer built from
the raw diff string (result.details.diff, and the live preview.diff on
the call component).

Each code line is syntax-highlighted via highlightCode/getLanguageFromPath,
drawn with a `± lineNum` gutter, and add/remove rows get a subtle dark
bg band across the full width. The band is re-asserted after fg resets so
syntax colors do not punch holes in it. Context rows are highlighted with
no band; skipped-context collapses to a dim ellipsis.

Applies to both slots: the live edit preview (renderCall) busts its width
cache on preview identity change, and the settled result (renderResult)
falls through to native output when there is no diff (e.g. errors).
</content>
</entry>
<entry>
<title>slop-tool-boxes: stamp bash runtime in bottom border (right-aligned)</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-22T21:52:20Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=d8e28dc347f57a8b4b690572b854cdded0345bc6'/>
<id>urn:sha1:d8e28dc347f57a8b4b690572b854cdded0345bc6</id>
<content type='text'>
Track per-toolCallId execution timing via tool_execution_start/end and
render the elapsed duration into the bottom border of bash rows, aligned
to the right and kept one dash clear of the corner.

Respects the no-forced-repaint invariant: the live timer is
`end ?? Date.now()`, so it only advances on renders that already happen
(bash output streaming re-renders the result slot border); the final
duration is stamped once the row settles. Muted time text sits outside
the pulsing dash paint. Gated to bash via RUNTIME_TOOLS (extensible).
</content>
</entry>
<entry>
<title>slop-tool-boxes: dotted gray rails in-flight, solidify on finalize</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-22T19:36:51Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=bdbd58464f94fe3546836f640cd53bc35fdb44d6'/>
<id>urn:sha1:bdbd58464f94fe3546836f640cd53bc35fdb44d6</id>
<content type='text'>
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 -&gt; stays flicker-safe.
</content>
</entry>
<entry>
<title>slop-tool-boxes: remove animation timer; time-based pulse (fix streaming flicker)</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-22T19:31:46Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=87b042214d057f8b60858b788a419ec86ac108ba'/>
<id>urn:sha1:87b042214d057f8b60858b788a419ec86ac108ba</id>
<content type='text'>
Third flicker bug: a taller-than-screen bash box flickered because the 10fps
timer forced a full updateDisplay() repaint on top of the stream's own
re-renders. Root fix: NEVER force a repaint.

- Pulse color is now a pure function of wall-clock time (Date.now() % CYCLE_MS);
  no tick counter, no setInterval. Any render shows the current color.
- The pulse advances only on renders that already happen: streaming output
  ('the stream hydrates the pulse', irregular cadence) or start/end nudges.
- Silent long-running tools hold a static active color (flicker-free) rather
  than animating.
- Also drop body side rails entirely (open-sided box); only top/bottom pulse.

Self-test: restored rows never pulse, active top border advances with time,
settles solid on end.
</content>
</entry>
<entry>
<title>slop-tool-boxes: fix startup render storm + streaming flicker</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-22T19:16:42Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=df63c33b996bd02fd8fc5121cf9a6680e54d502d'/>
<id>urn:sha1:df63c33b996bd02fd8fc5121cf9a6680e54d502d</id>
<content type='text'>
Two bugs from the pending-tool border pulse:

1. Streaming flicker: pulsing the side rails recolored every body line each
   frame, forcing a full-box repaint per streamed token. Now only top/bottom
   borders pulse; rails/body use a static status color.

2. Startup render storm (pi -c never accepts input): 'pending' was inferred
   from the render-time default status, so every RESTORED tool row rendered as
   pending -&gt; all added to the 10fps animation set -&gt; unbounded invalidate loop.
   Animation is now driven strictly by live tool_execution_start/end events
   keyed by toolCallId; restored rows fire no start event so they never animate.

Self-test covers: restored rows don't pulse, live rows pulse only between
start/end, rails static during a run.
</content>
</entry>
<entry>
<title>slop-tool-boxes: pulse the border of in-progress (pending) tools</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-22T18:24:22Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=4b58e214ca4e05ccfc884d63655226f5748c1422'/>
<id>urn:sha1:4b58e214ca4e05ccfc884d63655226f5748c1422</id>
<content type='text'>
Apply the last-prompt palette pulse to tool rows while they're running:
- module-level 10fps timer re-paints only PENDING rows via context.invalidate()
  (the tool render context's rerender trigger), and stops when none are pending
- render() reuses width+content-cached inner lines (persisted on the row state
  so they survive the timer's updateDisplay rebuilds) and only re-frames cheap
  strings with the pulse color; native tool render is NOT re-run per tick
- completed rows keep the existing width-memoized full-output cache untouched,
  so no perf regression for finished tools
- frame builders take a Paint fn (solid theme color when idle, truecolor pulse
  when pending); label/icon stay static, only the border rails pulse
- agent_settled/session_shutdown safety net guarantees the timer stops
</content>
</entry>
<entry>
<title>Initial commit: pi agent extensions</title>
<updated>2026-08-12T17:34:35Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-07-22T17:36:45Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=41e13354091125fc80fdbc6ff30fc25982f40bfd'/>
<id>urn:sha1:41e13354091125fc80fdbc6ff30fc25982f40bfd</id>
<content type='text'>
</content>
</entry>
</feed>
