fix: enable link click handling in VSelectableTextView (LUM-748)#23986
Conversation
Add NSTextViewDelegate to Coordinator with textView(_:clickedOnLink:at:) to open clicked links in the default browser via NSWorkspace. The NSTextView was already styling links (tint color, underline, pointer cursor) but had no delegate to handle click events, making links visually styled but inert. Closes LUM-748 Co-Authored-By: ashlee@vellum.ai <ashlee@vellum.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
| public func textView(_ textView: NSTextView, clickedOnLink link: Any, at charIndex: Int) -> Bool { | ||
| if let url = link as? URL { | ||
| NSWorkspace.shared.open(url) | ||
| return true | ||
| } | ||
| if let string = link as? String, let url = URL(string: string) { | ||
| NSWorkspace.shared.open(url) | ||
| return true | ||
| } | ||
| return false |
There was a problem hiding this comment.
🚩 Delegate link handler replicates default NSTextView behavior for non-editable text views
For a non-editable NSTextView (isEditable = false), the default behavior already opens links via NSWorkspace.shared.open() when no delegate intercepts the click. The new textView(_:clickedOnLink:at:) at line 249 does exactly what the default handler would do — open the URL in the default browser. This means the explicit delegate is currently a no-op from a behavioral standpoint.
This is likely intentional as a foundation for future customization (e.g., intercepting specific URL schemes, adding analytics, or routing internal links differently). However, if the only goal was to "enable" link clicking, the delegate wasn't needed — links already worked in the non-editable text view. Worth confirming the intent.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
The claim that the default NSTextView behavior already opens links for non-editable text views is not accurate in this configuration. The user confirmed links are visually styled but completely inert (LUM-748 screenshot shows pointer cursor and underline but no click response).
Several factors can prevent the default link-opening fallback from firing in practice:
- Explicit TextKit 1 stack — this
NSTextViewis created with a manually constructed TextKit 1 stack (NSTextStorage→NSLayoutManager→NSTextContainer), not the default initializer. The defaultclicked(onLink:at:)fallback behavior may not be wired identically in this path. isSelectable = true+isEditable = false— in this mode, mouse events are primarily routed through the selection machinery. Link click detection depends on the text view correctly distinguishing a click-on-link from a selection gesture, which can be unreliable without an explicit delegate.- Previous fix attempt — an earlier branch (
devin/1774970368-lum-635-selectable-text-view) independently reached the same conclusion and used the delegate approach.
The explicit delegate is the Apple-recommended pattern for handling link clicks (NSTextViewDelegate.textView(_:clickedOnLink:at:)). It provides reliable, deterministic behavior regardless of the TextKit stack configuration, and gives us a hook for future customization (e.g., routing internal links, analytics).
Add NSTextViewDelegate to Coordinator with textView(_:clickedOnLink:at:) to open clicked links in the default browser via NSWorkspace. The NSTextView was already styling links (tint color, underline, pointer cursor) but had no delegate to handle click events, making links visually styled but inert. Closes LUM-748 Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai>
* fix: use borderElement for unchecked checkbox visibility (LUM-751) (#23992) The ToS checkbox border used VColor.borderBase (#24292E dark) which is identical to VCard's VColor.surfaceLift background (#24292E dark), making the unchecked checkbox completely invisible in dark mode. Switch to VColor.borderElement (#5A6672 dark / #CFCCC9 light) which is the semantic token for interactive element borders and provides clear contrast against the card background in both color schemes. Closes LUM-751 Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> * fix: enable link click handling in VSelectableTextView (#23986) Add NSTextViewDelegate to Coordinator with textView(_:clickedOnLink:at:) to open clicked links in the default browser via NSWorkspace. The NSTextView was already styling links (tint color, underline, pointer cursor) but had no delegate to handle click events, making links visually styled but inert. Closes LUM-748 Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> * fix: prevent onboarding from repeating name confirmation after tool calls (#23989) The agentic loop calls the LLM again after tool results return. Combined with "talk before you work" (SOUL.md) and "save immediately" (BOOTSTRAP.md), the model would confirm the user's name, call file_edit, then re-confirm the name in the continuation response. Add explicit guidance in both BOOTSTRAP.md and SOUL.md to not repeat text that was already shown before tool calls. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update Vellum Cloud onboarding copy to emphasize always-on availability (#24022) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [LUM-753] Improve client-side dictation resilience (#24042) * [LUM-753] Improve dictation resilience: reduce timeout, use warning-level logs, add timing - Reduce dictation HTTP timeout from 10s to 5s so the client falls back to raw transcription faster when the daemon is unreachable - Downgrade failure logs from error to warning since these are expected failures with graceful recovery (not actionable errors) - Add elapsed time to all failure log messages for debugging latency - Improve fallback log to include transcription length for diagnostics Co-Authored-By: tkheyfets <timur@vellum.ai> * Apply suggestions from code review Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai> Co-authored-by: Noa Flaherty <noa@vellum.ai> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> Co-authored-by: asharma53 <64060709+asharma53@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: tkheyfets <timur@vellum.ai>
* Release v0.6.2 * Cherry-pick fixes into release/v0.6.2 (#24074) * fix: use borderElement for unchecked checkbox visibility (LUM-751) (#23992) The ToS checkbox border used VColor.borderBase (#24292E dark) which is identical to VCard's VColor.surfaceLift background (#24292E dark), making the unchecked checkbox completely invisible in dark mode. Switch to VColor.borderElement (#5A6672 dark / #CFCCC9 light) which is the semantic token for interactive element borders and provides clear contrast against the card background in both color schemes. Closes LUM-751 Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> * fix: enable link click handling in VSelectableTextView (#23986) Add NSTextViewDelegate to Coordinator with textView(_:clickedOnLink:at:) to open clicked links in the default browser via NSWorkspace. The NSTextView was already styling links (tint color, underline, pointer cursor) but had no delegate to handle click events, making links visually styled but inert. Closes LUM-748 Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> * fix: prevent onboarding from repeating name confirmation after tool calls (#23989) The agentic loop calls the LLM again after tool results return. Combined with "talk before you work" (SOUL.md) and "save immediately" (BOOTSTRAP.md), the model would confirm the user's name, call file_edit, then re-confirm the name in the continuation response. Add explicit guidance in both BOOTSTRAP.md and SOUL.md to not repeat text that was already shown before tool calls. Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Update Vellum Cloud onboarding copy to emphasize always-on availability (#24022) Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [LUM-753] Improve client-side dictation resilience (#24042) * [LUM-753] Improve dictation resilience: reduce timeout, use warning-level logs, add timing - Reduce dictation HTTP timeout from 10s to 5s so the client falls back to raw transcription faster when the daemon is unreachable - Downgrade failure logs from error to warning since these are expected failures with graceful recovery (not actionable errors) - Add elapsed time to all failure log messages for debugging latency - Improve fallback log to include transcription length for diagnostics Co-Authored-By: tkheyfets <timur@vellum.ai> * Apply suggestions from code review Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tkheyfets <timur@vellum.ai> Co-authored-by: Noa Flaherty <noa@vellum.ai> --------- Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> Co-authored-by: asharma53 <64060709+asharma53@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: tkheyfets <timur@vellum.ai> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Noa Flaherty <noa@vellum.ai> Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: ashlee@vellum.ai <ashlee@vellum.ai> Co-authored-by: asharma53 <64060709+asharma53@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: tkheyfets <timur@vellum.ai>
Summary
Links in chat messages were visually styled (tint color, underline, pointer cursor) but not clickable. The root cause:
VSelectableTextView'sNSTextViewhadlinkTextAttributesconfigured but no delegate to handle click events.This PR adds
NSTextViewDelegateconformance to the existingCoordinator, implementstextView(_:clickedOnLink:at:)to open URLs viaNSWorkspace.shared.open(), and wires the delegate inmakeNSView.Review & Testing Checklist for Human
[text](url)) in assistant chat messages — should open in default browserhttps://example.com) in chat messagesNotes
Coordinatornow inherits fromNSObject(required for the ObjC delegate protocol). This is standard forNSViewRepresentablecoordinators that conform to AppKit delegate protocols.URLandStringlink values, covering allNSAttributedStringlink attribute types.devin/1774970368-lum-635-selectable-text-view) using the same delegate approach, but it targeted an older file location beforeVSelectableTextViewwas moved to the design system.Link to Devin session: https://app.devin.ai/sessions/c95781541a1246b49843bb46c01d006e
Requested by: @ashleeradka