Summary
When a bordered box inside a scrollbox is partially scrolled out of the viewport, its border lines are still drawn outside the scrollbox bounds and bleed into neighboring UI (e.g., a header bar above). Text content inside the same box is clipped correctly — only the border cells leak. This is a leftover gap from the scissor-clipping fix in #389: drawText/fillRect are scissor-aware, but drawBox's border loops are not.
Expected
Content (text and borders) is clipped to the scrollbox viewport. A card partially scrolled out at the top edge should show only the visible portion of its border, disappearing cleanly at the boundary.
Actual
Border segments outside the viewport are painted over the UI above the scrollbox. The further the card is scrolled out, the more border characters accumulate over the header area:
Expected Actual
┌───────────────────┐ ┌───────────────────┐
│ header │ │┌─────────────────┐│ ← card top border bleeds
│ │ ││ ││ into header row
└───────────────────┘ └│─────────────────│┘
│ ┌───────────────┐ │ │ ┌───────────────┐ │
│ │ Thinking done│ │ │ │ Thinking done│ │
│ │ reasoning... │ │ │ │ reasoning... │ │
│ └───────────────┘ │ │ └───────────────┘ │
│ │ │ │
│ card scrolled out │ │ card scrolled out │
│ cleanly at edge │ │ borders keep │
└───────────────────┘ └───────────────────┘
Concrete rendering (80×24 terminal, conversation scrollbox scrolled up 100 lines):
00|┌─ App title ─────────────────────────────────────────────────────────┐
01|│┌──────────────────────────────────────────────────────────────────┐ │ ← card top border + corners on header row
02|└│──────────────────────────────────────────────────────────────────│─┘ ← card borders overlay header's bottom border
03| │ REASONING CONTENT 3 │
04| └──────────────────────────────────────────────────────────────────┘
Steps to Reproduce
- Render a
scrollbox with height: 17, scrollY, overflow: "hidden", stickyScroll, stickyStart="bottom".
- Inside it, render multiple
<box border={true}> cards (e.g., a "Thinking" card and a "Tool" card) whose total height exceeds the viewport.
- Scroll (or rely on sticky-bottom) so a card straddles the top edge of the viewport.
- Observe the card's border characters drawn over the UI above the scrollbox.
Observed with the React renderer on macOS (Terminal.app/iTerm2) using @opentui/core@0.4.5.
Root Cause
In packages/core/src/zig/buffer.zig, drawBox (line ~1863):
- Entry check is only a coarse whole-box reject:
if (!self.isRectInScissor(...)) return (line ~1897). A box that partially intersects the scissor passes and continues drawing.
- The horizontal/vertical border loops only check buffer bounds (
0..width/height) and never intersect against the current scissor rect.
- The
useTransparentBorderFastPath path writes directly to self.buffer.char/fg/attributes with no clipping at all.
For comparison, drawText (line ~1026) performs both isRectInScissor (line ~1169) and clipRectToScissor (line ~1175), which is why text never leaks.
Suggested Fix
In drawBox, clip the border loops per-cell against the current scissor rect (similar to fillRectClipped / drawText), and route useTransparentBorderFastPath through a scissor-aware path. Related prior work: #388 / #389 (nested scrollbox text clipping) — this is the border-specific gap those left behind.
Environment
@opentui/core 0.4.5 (also reproducible against main)
- React renderer (
@opentui/react)
- macOS, Terminal.app / iTerm2 / kitty
Summary
When a bordered box inside a
scrollboxis partially scrolled out of the viewport, its border lines are still drawn outside the scrollbox bounds and bleed into neighboring UI (e.g., a header bar above). Text content inside the same box is clipped correctly — only the border cells leak. This is a leftover gap from the scissor-clipping fix in #389:drawText/fillRectare scissor-aware, butdrawBox's border loops are not.Expected
Content (text and borders) is clipped to the scrollbox viewport. A card partially scrolled out at the top edge should show only the visible portion of its border, disappearing cleanly at the boundary.
Actual
Border segments outside the viewport are painted over the UI above the scrollbox. The further the card is scrolled out, the more border characters accumulate over the header area:
Concrete rendering (80×24 terminal, conversation scrollbox scrolled up 100 lines):
Steps to Reproduce
scrollboxwithheight: 17,scrollY,overflow: "hidden",stickyScroll,stickyStart="bottom".<box border={true}>cards (e.g., a "Thinking" card and a "Tool" card) whose total height exceeds the viewport.Observed with the React renderer on macOS (Terminal.app/iTerm2) using
@opentui/core@0.4.5.Root Cause
In
packages/core/src/zig/buffer.zig,drawBox(line ~1863):if (!self.isRectInScissor(...)) return(line ~1897). A box that partially intersects the scissor passes and continues drawing.0..width/height) and never intersect against the current scissor rect.useTransparentBorderFastPathpath writes directly toself.buffer.char/fg/attributeswith no clipping at all.For comparison,
drawText(line ~1026) performs bothisRectInScissor(line ~1169) andclipRectToScissor(line ~1175), which is why text never leaks.Suggested Fix
In
drawBox, clip the border loops per-cell against the current scissor rect (similar tofillRectClipped/drawText), and routeuseTransparentBorderFastPaththrough a scissor-aware path. Related prior work: #388 / #389 (nested scrollbox text clipping) — this is the border-specific gap those left behind.Environment
@opentui/core0.4.5 (also reproducible againstmain)@opentui/react)