Skip to content

[CPLAT-12912] Adopt tuikit selection v2 and open URLs with o - #180

Merged
gavin-jeong merged 1 commit into
masterfrom
CPLAT-12912-link-open
Oct 21, 2026
Merged

gavin-jeong merged 1 commit into
masterfrom
CPLAT-12912-link-open

Conversation

@gavin-jeong

Copy link
Copy Markdown
Collaborator

JIRA: https://sendbird.atlassian.net/browse/CPLAT-12912

What

Follow-up to #179. tuikit goes from c19aa4f to b0beaa4. That version adds selection v2 and the link package.

  1. Selection v2. The session TUI and the ccx pick picker now make one sel.Update call. App-side workarounds that the kit now covers are removed. A drag that rests past a preview edge keeps scrolling (AutoScrollMsg).
  2. Links. Labels that hide a URL (PR key, Jira key, artifact title) are OSC 8 hyperlinks, so cmd/ctrl+click opens them even under mouse capture. A new open_link binding (o) in the actions menus opens the selected item's link.

Selection v2

  • handleSelectionMouse now calls sel.Update(msg, panes, frame, now). The kit builds the panes and the frame only when the event needs them, so the hand-written Wants guard is gone. The dragResizing guard stays.
  • r.Offset is set on the pane (viewport.SetYOffset). It is no longer ScrollUp/Down(r.Scroll).
  • selection.AutoScrollMsg is routed back to the selection in both App.update and pickerModel.Update.
  • When the selection does not consume an event, its command is batched with the app's own command. Before, it was dropped.
  • Removed workarounds:
Removed Replaced by
selectionStyle / pickerSelectionStyle (renderer pinned to ANSI) selection.DefaultStyle()
reportCopied / copiedNotice hand-built text CopiedMsg.NoteWith("Copied %d chars", …) (TUI keeps its capitalized toast) / CopiedMsg.Note() (picker)
tests: markSelection + highlighted reverse/mark parsers tuitest.Reversed(frame)
picker previewTextRect: X += 2; W -= 2 previewRect().InsetStyle(pickerPreviewFrame). View draws the box with the same style.
  • muesli/termenv is now indirect again.

Links

Surface URL behind it OSC 8 on Open
session list, x actions menu session PR / Jira / artifact refs — new x o: link.OpenFirst(refs). If the refs are not loaded yet, it reads them from the transcript offline, inside the command.
References preview (refs) ref URL label (sendbird/ccx#5 OPEN) existing ↵ / o (kept)
Outputs digest (session + day pane) PR / Jira / artifact URL title existing o and the x output-actions o (kept)
URL menu (x u) each URL label existing ↵ opens the selection. New o opens the cursor row and keeps the menu open.
conversation, x actions menu URLs inside the selected message or focused block — (URLs are already printed in full) new x o: link.Find + link.OpenFirst ("the first of N links")
ccx pick picker (urls / refs) each item's URL label existing o / ↵ (kept)
conversation meta "Refs & URLs" rows, WebFetch tool rows — not wrapped Meta rows go through the block text renderer. WebFetch prints the URL in full.
  • o already has a meaning in two contexts, so the new binding does not take it there. keys.Conflicts reports both: duplicate: "o" in session: session.fold_group, link.open and duplicate: "o" in conversation: conversation.jump_to_tree, link.open. The new action therefore lives in the actions scope, where o was free. The fold and jump keys are unchanged.
  • Every existing open path (openInBrowser, the picker for urls/refs) now refuses URLs that are not http(s), such as javascript: or file: from a transcript. The picker still opens local paths for files, changes and images.
  • tui.UseOpenerForLinks routes link.Open through open.command_template when one is set. main.go and run_pick.go call it once at startup. It is never called from NewApp, so tests keep their link.Launcher stub.
  • The new binding appears in the actions hint boxes and in the ? overlay (x o). open_link can be rebound in the keymap yaml.

Tests

Positions and links come from the rendered View(). The tests stub the opener (link.Launcher, pickerOpen, a.openURL) and never run a real opener or clipboard. Tests that read config use t.Setenv("HOME", t.TempDir()).

Each new guard was checked with a compiling mutation, then restored:

Mutation Failing test
outputs title not wrapped TestOutputsDigestLinksTitlesThatHideTheirURL
refs label not wrapped TestRefsPreviewLinksLabel
day pane title not wrapped TestDayPaneLinksPRTitle
URL menu label not wrapped TestURLMenuLinksLabelsAndOOpensCursorRow
picker label not wrapped TestPickerURLRowLabelIsLinked
openInBrowser without link.Valid TestRefsPreviewOpenRefusesNonHTTPURL
picker never filters URLs TestPickerOpenSkipsNonHTTPURL
picker filters paths too TestPickerFilesStillOpenPaths
x o opens a URL-less item TestSessionActionsOpenLinkWithoutLinkSaysSo
conversation menu loses the open-link arm TestConversationActionsOpenLinkOpensFirstURL
conversation text without link.Find TestConversationActionsOpenLinkOpensFirstURL
link.OpenedMsg not shown TestSessionActionsOpenLinkOpensFirstRef
open_link missing from the hint box TestOpenLinkBindingShowsInHelp
drop r.Offset apply (TUI) TestDragPastPreviewTopScrollsToSelectionOffset, TestDragPastPreviewBottomScrollsAndKeepsLines
apply r.Offset + r.Scroll TestDragPastPreviewTopScrollsToSelectionOffset
AutoScrollMsg not routed (TUI / picker) TestDragRestingPastPreviewBottomAutoScrolls / TestPickerDragRestingPastPreviewBottomAutoScrolls
picker drops r.Offset TestPickerDragPastPreviewBottomScrolls
picker preview rect not inset TestPickerDragFromPreviewTowardListStaysInPreview

Two mutations survive by design:

  • A plain default-renderer Reverse(true) style no longer breaks NO_COLOR. selection.Render falls back to DefaultStyle when a style draws nothing.
  • Dropping the padding from pickerPreviewFrame moves the box and the inset together. Sharing the style is the point.

gofmt -l is clean. go vet ./... passes. go test ./..., go test -race ./... (one run) and env -u LC_ALL -u LC_CTYPE LANG=ko_KR.UTF-8 go test ./... all pass.

Bump tuikit to b0beaa4 (selection v2, link, tuitest.Links/Reversed).

Selection: one sel.Update call in the session TUI and the picker, with
r.Offset set on the pane (not Scroll added) and AutoScrollMsg routed back,
so a drag resting past a preview edge keeps scrolling. The selection's
command is batched with the app's own when the event is not consumed.
The ANSI-pinned highlight renderers, the hand-made copy toast text, the
test reverse-video parsers and the picker's hand-computed preview inset
are replaced by selection.DefaultStyle, CopiedMsg.NoteWith,
tuitest.Reversed and Rect.InsetStyle.

Links: PR/Jira/artifact labels in the refs preview, outputs digest, day
pane, URL menu and picker are OSC 8 links. A new actions binding
open_link (o) opens the session's first ref (x o) or the first URL in the
selected conversation message or block (conversation x o), via
link.OpenFirst; the URL menu gets o for the cursor row. o was already
fold_group in the session list and jump_to_tree in the conversation, so
it stays there. Existing open paths now refuse non-http(s) URLs, and the
open key honors open.command_template.
@upwind-code-us

upwind-code-us Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Upwind Upwind Code Scan - ✅ Passed

0 newly introduced vulnerabilities · 0 resolved · 2 total in this PR vs master

Total breakdown: 🔶 2 High

View full analysis in Upwind Console

Scan completed in 17s

Scan history (1 scan)
Commit Scanned at New Resolved Net
8f65cd7 < 2026-10-21 09:54 UTC 0 0 0

Last scanned: 8f65cd7 · 2026-10-21 09:54 UTC

@Kairo-Kim Kairo-Kim added the auto-review/approved Auto-approved by the Slack auto-reviewer bot label Oct 21, 2026

@jinsekim jinsekim left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM!

@gavin-jeong
gavin-jeong merged commit 62ec44e into master Oct 21, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-review/approved Auto-approved by the Slack auto-reviewer bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants