Commit ef0ac45
Fix inline-code link underline hidden by run background in agent output (warpdotdev#13030)
## Description
Fixes "wrong link highlighting" in agent rich output (reported in
Slack): a detected link rendered as inline code (gray code background)
lost all or part of its hover underline, in a width-dependent way.
**Root cause:** A paint-ordering bug in `Line::paint_internal`
(`crates/warpui_core/src/text_layout.rs`). The run's background/border
rect was painted *after* the run's glyphs and per-glyph underline. The
hyperlink underline is a filled rect in the **same paint layer** as the
background, so drawing the background last painted over and hid the
underline on any backgrounded run (e.g. an inline-code link). Plain-text
links (no run background) were never affected, which matches the
reported symptom.
**Fix:** Split background/border painting into a new
`paint_run_background` and call it *before* the glyph loop, so glyphs
and the underline render on top of the background.
`paint_run_decorations` now only handles the error underline and
strikethrough (drawn after glyphs, unchanged); its now-unused
`font_cache`/`baseline_position_fn` params were dropped.
**Truncation correctness (from review):** Because the background is now
painted before the glyph loop, it must not be drawn for glyphs that the
loop then truncates. Each run's actually-drawn glyph x-span is
precomputed before drawing — mirroring the same ellipsis cutoff and
`remaining_width <= 0` stop conditions used by the glyph loop, for both
clip directions including start-clipping's reversed iteration. A run
with no visible glyphs paints no background, and a partially-truncated
run's background/border is clamped to its visible glyph span (so it no
longer extends past the visible glyphs or behind an ellipsis). The
background is still painted before the per-glyph underline rects,
preserving the underline fix.
Affected file: `crates/warpui_core/src/text_layout.rs`.
## Linked Issue
Slack thread:
https://warpdev.slack.com/archives/C0BCE7AELJ2/p1782330944826139?thread_ts=1782330944.826139&cid=C0BCE7AELJ2
## Testing
Deterministic regression tests in
`crates/warpui_core/src/text_layout_tests.rs`:
- `test_run_background_painted_before_underline` — asserts (via the
resulting `Scene` draw order) the background rect is drawn *before* the
underline rect, so the underline renders on top. Fails on the pre-fix
paint order.
- `test_run_background_clamped_to_visible_glyph_span` — drives
`paint_run_background` with a visible span narrower than the run and
asserts the painted background rect is clamped to that span (36px), not
the full run width (120px).
- `test_fully_truncated_run_paints_no_background` — a leading run
consumes the paint bounds so a trailing backgrounded run is fully
truncated; asserts no background rect is painted for it.
`cargo nextest run -p warpui_core` (298 tests) passes, `cargo clippy -p
warpui_core --all-targets` is clean, and `cargo fmt -p warpui_core --
--check` is clean.
> Note on the ellipsis-truncation path: the platform test `FontDB`
reports a zero advance for the `…` glyph, so `ellipsis_width` is always
`0` in unit tests and the *end-to-end* ellipsis branch in
`paint_internal` cannot be driven from a unit test. The clamping
arithmetic that fixes the ellipsis case is therefore pinned directly via
`test_run_background_clamped_to_visible_glyph_span`, and the
visible-span guard via `test_fully_truncated_run_paints_no_background`.
- [ ] I have manually tested my changes locally with `./script/run`
## Verification
The underline fix was verified on a real running Warp build by rendering
a detected link as inline code (gray code background) at multiple
terminal widths and comparing against a plain-text-link control:
- **Before:** the inline-code link's hover underline was partially or
fully missing in a width-dependent way (the gray run background was
painted over it). The plain-text control link always underlined fully.
- **After:** the inline-code link shows a full-width underline at every
tested width (820 / 1000 / 1180 / 1360 px), matching the plain-text
control. Pixel inspection of the cropped link token confirmed a
continuous underline row spanning the full token width at each width,
where the before-fix crops showed gaps / missing underline pixels under
the backgrounded glyphs.
Before/after screenshots (full-width renders at the four widths plus the
plain-text control, and the zoomed token crops) are posted in the Slack
thread linked above; the "after" comparison image is also attached there
(`fixed_after.png`).
Image embedding note: these screenshots were captured in a sandboxed
environment and are shared via the Slack thread rather than embedded
inline in this PR body. The behavior is additionally locked in by the
regression tests above.
## Agent Mode
- [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode
<!--
CHANGELOG-BUG-FIX: Fixed agent rich output where an inline-code link's
hover underline could be partially or fully hidden by the link's
background.
-->
<!-- factory-client:
{"source":"factory-client","slack_channel":"C0BCE7AELJ2","slack_thread_ts":"1782330944.826139","slack_permalink":"https://warpdev.slack.com/archives/C0BCE7AELJ2/p1782330944826139?thread_ts=1782330944.826139&cid=C0BCE7AELJ2","oz_run_id":"019efb33-f4d8-7b78-aaf7-cfb2e224eec9","repo":"warpdotdev/warp"}
-->
_Conversation:
https://staging.warp.dev/conversation/cd3fc717-924c-4770-ac4d-f91f7f2e5774_
_Run:
https://oz.staging.warp.dev/runs/019efb3c-3a78-772e-9788-7e10e517a52c_
_This PR was generated with [Oz](https://warp.dev/oz)._
---------
Co-authored-by: Oz <oz-agent@warp.dev>1 parent 67f0c83 commit ef0ac45
2 files changed
Lines changed: 329 additions & 30 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1189 | 1189 | | |
1190 | 1190 | | |
1191 | 1191 | | |
| 1192 | + | |
| 1193 | + | |
| 1194 | + | |
| 1195 | + | |
| 1196 | + | |
| 1197 | + | |
| 1198 | + | |
| 1199 | + | |
| 1200 | + | |
| 1201 | + | |
1192 | 1202 | | |
1193 | | - | |
| 1203 | + | |
1194 | 1204 | | |
1195 | | - | |
1196 | 1205 | | |
1197 | 1206 | | |
1198 | 1207 | | |
| 1208 | + | |
| 1209 | + | |
1199 | 1210 | | |
1200 | 1211 | | |
1201 | 1212 | | |
| |||
1224 | 1235 | | |
1225 | 1236 | | |
1226 | 1237 | | |
1227 | | - | |
1228 | | - | |
1229 | | - | |
1230 | | - | |
1231 | | - | |
1232 | | - | |
1233 | | - | |
1234 | | - | |
1235 | | - | |
1236 | | - | |
1237 | | - | |
1238 | | - | |
1239 | | - | |
1240 | | - | |
1241 | | - | |
1242 | | - | |
1243 | | - | |
| 1238 | + | |
| 1239 | + | |
| 1240 | + | |
| 1241 | + | |
| 1242 | + | |
| 1243 | + | |
| 1244 | + | |
| 1245 | + | |
| 1246 | + | |
| 1247 | + | |
| 1248 | + | |
| 1249 | + | |
| 1250 | + | |
| 1251 | + | |
| 1252 | + | |
| 1253 | + | |
1244 | 1254 | | |
1245 | | - | |
| 1255 | + | |
1246 | 1256 | | |
1247 | | - | |
| 1257 | + | |
1248 | 1258 | | |
1249 | 1259 | | |
1250 | 1260 | | |
| |||
1272 | 1282 | | |
1273 | 1283 | | |
1274 | 1284 | | |
| 1285 | + | |
1275 | 1286 | | |
| 1287 | + | |
| 1288 | + | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
1276 | 1295 | | |
1277 | 1296 | | |
1278 | 1297 | | |
| |||
1491 | 1510 | | |
1492 | 1511 | | |
1493 | 1512 | | |
| 1513 | + | |
| 1514 | + | |
| 1515 | + | |
| 1516 | + | |
| 1517 | + | |
| 1518 | + | |
| 1519 | + | |
| 1520 | + | |
| 1521 | + | |
| 1522 | + | |
| 1523 | + | |
| 1524 | + | |
| 1525 | + | |
| 1526 | + | |
| 1527 | + | |
| 1528 | + | |
| 1529 | + | |
| 1530 | + | |
| 1531 | + | |
| 1532 | + | |
| 1533 | + | |
| 1534 | + | |
| 1535 | + | |
| 1536 | + | |
| 1537 | + | |
| 1538 | + | |
| 1539 | + | |
| 1540 | + | |
| 1541 | + | |
| 1542 | + | |
| 1543 | + | |
| 1544 | + | |
| 1545 | + | |
| 1546 | + | |
| 1547 | + | |
| 1548 | + | |
| 1549 | + | |
| 1550 | + | |
| 1551 | + | |
| 1552 | + | |
| 1553 | + | |
| 1554 | + | |
| 1555 | + | |
| 1556 | + | |
| 1557 | + | |
| 1558 | + | |
| 1559 | + | |
| 1560 | + | |
| 1561 | + | |
| 1562 | + | |
| 1563 | + | |
| 1564 | + | |
| 1565 | + | |
| 1566 | + | |
| 1567 | + | |
| 1568 | + | |
1494 | 1569 | | |
1495 | 1570 | | |
1496 | 1571 | | |
| |||
1577 | 1652 | | |
1578 | 1653 | | |
1579 | 1654 | | |
1580 | | - | |
1581 | | - | |
1582 | | - | |
1583 | | - | |
1584 | | - | |
1585 | | - | |
1586 | | - | |
1587 | | - | |
1588 | | - | |
| 1655 | + | |
1589 | 1656 | | |
1590 | 1657 | | |
1591 | 1658 | | |
| |||
0 commit comments