Compare commits

...
Author SHA1 Message Date
Ahmed Allam b1d770ef2d fix(tui): link every wrapped line of the viewer URL to the full URL
The sidebar wraps the viewer URL, and terminals that linkify by text only
pick up the first line, so the click opened the viewer with a truncated
token. Emit each wrapped line as an OSC 8 hyperlink to the whole URL.
2026-10-03 20:12:09 +00:00
2 changed files with 21 additions and 2 deletions

No files matched your search

@@ -890,6 +890,14 @@ func TestRunningViewerShowsCompleteWrappedURL(t *testing.T) {
if want := strings.Count(model.viewerView(model.viewerContentWidth()), "\n") + 3; model.viewerHeight() != want {
t.Fatalf("viewer height = %d, want %d", model.viewerHeight(), want)
}
raw := model.viewerView(18)
if got, want := strings.Count(raw, ansi.SetHyperlink(url)), len(urlLines); got != want {
t.Fatalf("every wrapped line should link to the full URL: got %d links for %d lines in %q", got, want, raw)
}
if got := strings.Count(raw, ansi.ResetHyperlink()); got != len(urlLines) {
t.Fatalf("hyperlinks not closed: %d resets for %d lines", got, len(urlLines))
}
}
func TestVerticalScrollbarThumbTracksScrollOffset(t *testing.T) {
+13 -2
View File
@@ -177,6 +177,18 @@ func wrapBlock(value string, width int) string {
return strings.Join(out, "\n")
}
// hyperlinkBlock wraps a URL to the column width and marks every wrapped line
// as an OSC 8 hyperlink to the whole URL. Terminals that linkify by text only
// see the first line of a wrapped URL, which for the viewer meant opening it
// with a truncated token.
func hyperlinkBlock(url string, width int, style lipgloss.Style) string {
lines := strings.Split(wrapBlock(url, width), "\n")
for i, line := range lines {
lines[i] = ansi.SetHyperlink(url) + style.Render(line) + ansi.ResetHyperlink()
}
return strings.Join(lines, "\n")
}
// scrollbarThumb brightens the bar being dragged so the grab reads as taking
// hold of it.
func (m Model) scrollbarThumb(target scrollbarTarget) lipgloss.Color {
@@ -603,8 +615,7 @@ func (m Model) viewerView(width int) string {
case "running":
status := lipgloss.NewStyle().Foreground(green).Render("● Viewer running")
if m.snapshot.ViewerURL != nil && strings.TrimSpace(*m.snapshot.ViewerURL) != "" {
url := wrapBlock(strings.TrimSpace(*m.snapshot.ViewerURL), width)
return status + "\n" + lipgloss.NewStyle().Foreground(dim).Render(url)
return status + "\n" + hyperlinkBlock(strings.TrimSpace(*m.snapshot.ViewerURL), width, lipgloss.NewStyle().Foreground(dim))
}
return status
case "unavailable":