fix(tui): follow a bare URL across a wrapped row when it is clicked - #1324
Open
Hotragn wants to merge 1 commit into
Open
fix(tui): follow a bare URL across a wrapped row when it is clicked#1324Hotragn wants to merge 1 commit into
Hotragn wants to merge 1 commit into
Conversation
PrimeIntellect-ai#1299 taught fullscreen hit-testing to recognize visible bare http(s) URLs alongside OSC 8 metadata, but it resolves one painted row at a time. A URL longer than the terminal is wrapped onto the next row, and each row is matched in isolation, so the result depends on which half was clicked: full: https://example.com/oauth/authorize?client_id=abcdefgh&state=xyz click row 1 -> "https://example.com/oauth/authorize?clien" (opened) click row 2 -> null (nothing) The head row is the bad case. It does not fail closed; it silently opens a truncated address. Login and OAuth URLs are the ones long enough to wrap, and they are the exact case PrimeIntellect-ai#1270 and PrimeIntellect-ai#1299 set out to make clickable. Resolve the wrap group instead of the single row. A row whose painted width fills the terminal had no room for a break, so its text continues on the row below; join that run, then hit-test the click against URL spans in the joined text. The renderer already records the width it painted at, so the wrap signal is exact rather than inferred from content. OSC 8 is untouched and still resolved first: the escape is re-emitted on every wrapped row, so explicit targets were already correct. A row that merely fills the width does not join into the row below unless a URL span actually crosses the boundary, so ordinary prose that happens to reach the last column is unaffected. The join is capped at 8 rows.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #1270 and #1299.
The gap
#1299 taught fullscreen hit-testing to recognize visible bare
http(s)URLs alongside OSC 8 metadata. It resolves one painted row at a time —hyperlinkAtreadsthis.lastFrame[row]and hands that single string tourlAtColumn. A URL longer than the terminal width is wrapped onto the next row, and each row is matched in isolation.Probing
urlAtColumndirectly against an 80-column wrap:The continuation row failing to open is a nuisance. The head row is the real problem: it does not fail closed, it silently opens a truncated address. The user gets a wrong page with no indication anything was dropped.
This lands hardest on exactly the URLs #1270 and #1299 set out to make clickable. Login and OAuth URLs are long enough to wrap on any normal terminal; short doc links generally are not.
OSC 8 targets were never affected — the escape is re-emitted on each wrapped row, so explicit hyperlinks already resolved correctly. This is specific to the bare-URL path #1299 added.
The fix
Resolve the wrap group rather than the single row.
A row whose painted width fills the terminal had no room left for a line break, so its text continues on the row below. That is the exact wrap signal, and it needs no guessing:
FullscreenViewportalready recordsprevWidth, the width the frame was actually painted at, sohyperlinkAtpasses it down instead of inferring wrapping from content.urlAtFramePosition(lines, row, column, width)walks back to the start of the run of full-width rows, walks forward to its end, joins the normalized text, maps the clicked column into that joined text, and hit-tests the URL spans. OSC 8 is checked first and short-circuits, so explicit targets stay authoritative.The bare-URL scanning and the tab/ANSI normalization that
urlAtColumnalready did are factored intobareUrlSpansandframePlainTextand shared by both paths, so single-row behaviour — trailing-punctuation trimming, unbalanced bracket trimming, visible-column arithmetic — is unchanged and still exercised by the existing tests.Two bounds keep the join conservative:
Tests
Three cases in
packages/tui/test/fullscreen.test.ts, using the existing virtual-terminal harness (40-column frame) and driving real SGR mouse press/release like the neighbouring tests:main: with thefullscreen.tschange reverted, this case fails and the other 48 still pass.https://example.com/docs.Verified locally:
npm run checkclean, fullpackages/tuisuite 764 passed / 0 failed, andpackages/coding-agentfullscreen-mode.test.ts11/11. The tui package's tests run fully on Windows, so unlike the daemon suites this one is verified end to end here rather than partially.Note
Fix bare URL click resolution across wrapped rows in the TUI fullscreen viewport
urlAtFramePositioninutils.tsto resolve bare HTTP(S) URLs that visually wrap across frame rows by joining adjacent rows when a URL spans the frame width boundary.FullscreenViewport.hyperlinkAtinfullscreen.tsto useurlAtFramePositioninstead ofurlAtColumn, so clicking either the head or continuation row of a wrapped URL opens the full address.Macroscope summarized 878a24d.