f17733570e
Follow-up to the loop guard + artifact cap that addressed the root cause of issue #2081's runaway captures. The reporter then noted Ctrl+X/Ctrl+C remaining unresponsive — confirming the secondary symptom: per-keystroke TUI repaints walked every visible bash row and re-ran `split` / `replaceTabs` / `truncateToVisualLines` over the stored output. With a 1,000+ message transcript and a 50KB-tail per row, that string work was what pinned the main thread, not the loop itself. The eval renderer already caches its computed lines keyed by `(width, previewLines)` — see `eval-render.ts:709-752`. Mirrored that pattern in the bash result renderer with a slightly wider key (`width`, `previewLines`, `expanded`, `rawOutput`, `isPartial`) so the cache is busted whenever any input that affects the produced lines actually changes. `invalidate()` continues to clear `CachedOutputBlock` and now also clears the lines cache, so callers that already drive invalidation keep working unchanged. A render() with cache-equivalent inputs is now an array-reference return; the `CachedOutputBlock` round trip is skipped entirely. New test in `test/tools/bash-sixel-render.test.ts` pins the contract: identical inputs → same array reference; width change → cache miss; invalidate() → fresh array. Refs #2081