Skip to content

Clickable OSC 8 hyperlinks in rendered output#207

Merged
jeremy merged 7 commits intomainfrom
hyperlinks
Mar 6, 2026
Merged

Clickable OSC 8 hyperlinks in rendered output#207
jeremy merged 7 commits intomainfrom
hyperlinks

Conversation

@jeremy
Copy link
Copy Markdown
Member

@jeremy jeremy commented Mar 6, 2026

Summary

  • Adds OSC 8 terminal hyperlink support so links are clickable in iTerm2, Kitty, WezTerm, GNOME Terminal, and Windows Terminal. Unsupported terminals silently ignore the sequences.
  • URLs are sanitized to strip C0 control characters and DEL before embedding in OSC 8 sequences, preventing terminal injection via BEL/ESC in URLs.
  • Hyperlinks applied across all rendering paths: glamour post-processing, campfire chat, schema-backed presenter (todos, cards, messages), generic CLI table/object output, and TUI preview title.

Changes

internal/richtext/hyperlink.goHyperlink(), sanitizeURL(), LinkifyMarkdownLinks(), LinkifyURLs() with double-wrap prevention.

internal/richtext/richtext.goRenderMarkdown* post-processes with LinkifyURLs for bare URL clickability.

internal/tui/workspace/views/campfire.go — ANSI-aware wrapLine via ansi.StringWidth, escape-safe overflow handling, LinkifyMarkdownLinks + LinkifyURLs pipeline.

internal/presenter/render.goStyled flag on Styles, hyperlinkFromData helper, hyperlinks on role: "title" fields in list rows and detail headlines.

internal/output/render.go — Title/name cells hyperlinked via app_url in styled table and object renderers.

internal/tui/workspace/widget/preview.goSetTitleURL() wraps preview title in OSC 8.

internal/tui/workspace/views/detail.goappURL captured from SDK types, propagated to preview.

Test plan

  • make check passes (fmt, vet, lint, test, test-e2e, tidy)
  • Manual: basecamp todos list in iTerm2/Kitty — todo content column clickable
  • Manual: basecamp todos show <id> — headline clickable
  • Manual: basecamp tui → campfire — link text clickable, bare URLs clickable
  • Manual: basecamp tui → detail view — preview title clickable
  • Manual: basecamp todos list | cat — no visible escape artifacts (OSC 8 only when TTY)

Summary by cubic

Make links clickable across the CLI and TUI using OSC 8 hyperlinks. Adds safe URL sanitization (C0/C1/DEL) and balanced-parentheses handling; only applies when styled output is enabled.

  • New Features

    • Wrap link text and bare URLs with OSC 8; unsupported terminals ignore them.
    • Linkify bare URLs in rendered Markdown; campfire converts text and bare URLs with balanced-parens.
    • Hyperlink title/name fields in styled tables, objects, list rows, and detail headlines using app_url.
    • Preview titles can be clickable via SetTitleURL.
    • Sanitize URLs to strip C0/C1 controls and DEL; avoid double-wrapping existing hyperlinks.
  • Refactors

    • Use ANSI-aware width and escape-safe truncation (ansi.StringWidth/Truncate).
    • Add Styles.Styled flag to gate styling and hyperlinks.
    • Capture and pass app_url through detail view to the preview.
    • Clear titleURL when SetTitle is called to prevent stale preview hyperlinks.
    • Move go-runewidth to an indirect dependency.
    • Add tests for hyperlinking, sanitization, balanced parens, and campfire truncation.

Written for commit 2883ae8. Summary will update on new commits.

jeremy added 4 commits March 6, 2026 13:19
Hyperlink(text, url) wraps text in OSC 8 escape sequences, with
sanitizeURL stripping C0 control characters and DEL to prevent
sequence injection via BEL/ESC in URLs.

LinkifyMarkdownLinks converts [text](url) to clickable OSC 8 links
for non-glamour rendering paths. LinkifyURLs wraps bare https?://
URLs with double-wrap prevention for post-glamour processing.
Glamour path: RenderMarkdown* post-processes output with LinkifyURLs
so bare URLs in rendered markdown become clickable.

Campfire: applies LinkifyMarkdownLinks + LinkifyURLs after
HTMLToMarkdown, replaces runewidth with ansi.StringWidth for
ANSI/OSC-aware width calculation, and avoids rune-by-rune splitting
of tokens containing escape sequences to preserve hyperlink state.

Presenter: adds Styled flag to Styles, wraps title-role fields in
renderListRow and headlines in RenderDetail with OSC 8 hyperlinks
keyed off app_url. Covers schema-backed output (todos, cards) not
just the generic fallback renderer.

CLI table/object: wraps title/name cells in OSC 8 hyperlinks using
app_url metadata when styled output is active.
Adds titleURL field and SetTitleURL to Preview widget, wrapping the
title in an OSC 8 hyperlink in View(). Detail view captures AppURL
from SDK types (todo, message, card, generic) into detailData and
passes it to the preview via syncPreview.
Campfire's wrapLine now uses charmbracelet/x/ansi.StringWidth instead
of mattn/go-runewidth directly.
Copilot AI review requested due to automatic review settings March 6, 2026 21:21
@github-actions github-actions bot added tui Terminal UI tests Tests (unit and e2e) output Output formatting and presentation deps enhancement New feature or request labels Mar 6, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 10 files

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c802a18a68

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/richtext/hyperlink.go Outdated
Comment thread internal/richtext/hyperlink.go Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OSC 8 terminal hyperlink support so Basecamp URLs become clickable across CLI and TUI rendering paths, with URL sanitization to mitigate terminal escape-sequence injection risks.

Changes:

  • Introduces internal/richtext OSC 8 hyperlink utilities (URL sanitization + linkification for Markdown links and bare URLs) with tests.
  • Applies hyperlinking in presenter- and generic-rendered CLI output plus TUI preview title; propagates app_url into the detail view preview.
  • Updates campfire rendering to linkify URLs/Markdown links and to use ANSI-aware width calculations for wrapping.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/richtext/hyperlink.go New OSC 8 hyperlink + URL sanitization + linkification helpers.
internal/richtext/hyperlink_test.go Unit tests covering OSC 8 output, sanitization, and double-wrap prevention.
internal/richtext/richtext.go Post-process glamour output to linkify bare URLs.
internal/tui/workspace/views/campfire.go Linkify content, switch wrapping to ANSI-aware width, and avoid splitting escape sequences.
internal/presenter/render.go Adds Styles.Styled flag and hyperlinks schema “title” fields/headlines via app_url.
internal/presenter/presenter_test.go Tests for hyperlink behavior in styled vs unstyled presenter rendering.
internal/output/render.go Hyperlinks title/name fields via app_url in styled table/object rendering.
internal/tui/workspace/widget/preview.go Adds SetTitleURL() and wraps the preview title in an OSC 8 hyperlink when present.
internal/tui/workspace/views/detail.go Captures app_url from SDK/generic types and forwards it to the preview title.
go.mod Adjusts dependency directness (runewidth now indirect).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/tui/workspace/views/campfire.go Outdated
Comment thread internal/richtext/hyperlink.go Outdated
jeremy added 2 commits March 6, 2026 13:48
…runcation

- reMarkdownLink and reBareURL now handle balanced parentheses in URLs
  (e.g., Wikipedia links like /wiki/Foo_(bar))
- sanitizeURL strips C1 control characters (U+0080–U+009F) including
  8-bit ST which can terminate OSC sequences in some terminals
- Campfire wrapLine uses ansi.Truncate for long hyperlinked words
  instead of writing them whole and overflowing the viewport
Constructs an OSC 8-wrapped URL wider than the wrap width and asserts
visible width is capped, the opener is preserved, and the reset
sequence is present.
Copilot AI review requested due to automatic review settings March 6, 2026 22:33
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/tui/workspace/widget/preview.go Outdated
SetTitle now resets titleURL so callers that set a title without an
accompanying URL (e.g. Messages.clearPreview, Messages.updatePreview)
don't accidentally inherit a stale URL from a previous detail view.
@jeremy jeremy merged commit ea3a15c into main Mar 6, 2026
22 checks passed
@jeremy jeremy deleted the hyperlinks branch March 6, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deps enhancement New feature or request output Output formatting and presentation tests Tests (unit and e2e) tui Terminal UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants