Skip to content

feat(web): open chat links in new tab with external link icon#1059

Open
msukkari wants to merge 3 commits intomainfrom
cursor/SOU-822-7df0
Open

feat(web): open chat links in new tab with external link icon#1059
msukkari wants to merge 3 commits intomainfrom
cursor/SOU-822-7df0

Conversation

@msukkari
Copy link
Copy Markdown
Contributor

@msukkari msukkari commented Mar 28, 2026

Overview

This PR makes links in Ask Sourcebot chat responses open in new tabs and adds a subtle external link icon (↗) to indicate this behavior.

Changes

Added a custom anchor renderer (renderAnchor) to the MarkdownRenderer component that:

  1. Sets target="_blank" and rel="noopener noreferrer" on all links for new-tab behavior and security
  2. Displays an ExternalLinkIcon from lucide-react inline after the link text
  3. Uses subtle styling (opacity-60, w-3 h-3) so the icon doesn't overwhelm the content

Demo

external_link_demo.mp4

Screenshots

Chat message showing links with external link icons

Close-up of external link icon styling

Testing

  • Verified links display with the external link icon
  • Confirmed links open in new browser tabs
  • Verified icon styling works in both light and dark themes
  • Tested that file references and code blocks remain unaffected

Fixes #SOU-822

Linear Issue: SOU-822

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features
    • Links in Ask Sourcebot chat responses now open in new tabs and display an external-link icon indicator for improved clarity.

- Add custom anchor renderer to MarkdownRenderer component
- Set target='_blank' and rel='noopener noreferrer' on all links
- Display subtle ExternalLinkIcon (↗) after link text
- Icon uses opacity-60 for muted appearance in both themes

Fixes SOU-822

Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 28, 2026

Walkthrough

Updates the Markdown renderer in the chat component to render links with target="_blank" and an external-link icon indicator, along with corresponding changelog documentation. No exported entities or control flow logic changes.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md
Added entry under "Changed" section documenting the new behavior of links opening in a new tab with an external-link icon in Ask Sourcebot chat responses.
Link Rendering Enhancement
packages/web/src/features/chat/components/chatThread/markdownRenderer.tsx
Added ExternalLinkIcon import from lucide-react and implemented a custom renderAnchor callback that renders anchor elements with target="_blank", rel="noopener noreferrer", inline-flex styling, and an appended external-link icon component.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • brendan-kellam
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main changes: adding external link icons to chat links and making them open in new tabs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/SOU-822-7df0

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

cursoragent and others added 2 commits March 28, 2026 19:23
Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com>
Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com>
@cursor cursor bot marked this pull request as ready for review April 2, 2026 16:45
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/features/chat/components/chatThread/markdownRenderer.tsx`:
- Around line 174-187: The spread {...rest} in the renderAnchor component can
override explicit security props (target, rel, className); move {...rest} before
the explicit props inside the <a> element so incoming props cannot overwrite
target="_blank" and rel="noopener noreferrer". If you need to preserve incoming
className from the markdown AST, merge it with the existing class via the cn
utility (e.g., cn(rest.className, "inline-flex items-center gap-0.5")) and
remove className from rest before spreading to avoid duplication.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e28e8d3-6521-49d9-acd9-e2672506455d

📥 Commits

Reviewing files that changed from the base of the PR and between 9e07fcd and 1427d39.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • packages/web/src/features/chat/components/chatThread/markdownRenderer.tsx

Comment on lines +174 to +187
const renderAnchor = useCallback(({ href, children, ...rest }: React.JSX.IntrinsicElements['a']) => {
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-0.5"
{...rest}
>
{children}
<ExternalLinkIcon className="inline w-3 h-3 mb-0.5 opacity-60" />
</a>
);
}, []);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Move {...rest} before explicit props to preserve security attributes.

The spread {...rest} appears after target, rel, and className. If rest contains any of these props (e.g., from the markdown AST), they will override your explicit values. This could inadvertently remove the rel="noopener noreferrer" security attribute.

🛡️ Proposed fix
 const renderAnchor = useCallback(({ href, children, ...rest }: React.JSX.IntrinsicElements['a']) => {
     return (
         <a
+            {...rest}
             href={href}
             target="_blank"
             rel="noopener noreferrer"
-            className="inline-flex items-center gap-0.5"
-            {...rest}
+            className={cn("inline-flex items-center gap-0.5", rest.className)}
         >
             {children}
             <ExternalLinkIcon className="inline w-3 h-3 mb-0.5 opacity-60" />
         </a>
     );
 }, []);

Note: If you want to preserve any incoming className from markdown parsing, use the cn utility (already imported) to merge them. Otherwise, simply placing the spread first is sufficient.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const renderAnchor = useCallback(({ href, children, ...rest }: React.JSX.IntrinsicElements['a']) => {
return (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-0.5"
{...rest}
>
{children}
<ExternalLinkIcon className="inline w-3 h-3 mb-0.5 opacity-60" />
</a>
);
}, []);
const renderAnchor = useCallback(({ href, children, ...rest }: React.JSX.IntrinsicElements['a']) => {
return (
<a
{...rest}
href={href}
target="_blank"
rel="noopener noreferrer"
className={cn("inline-flex items-center gap-0.5", rest.className)}
>
{children}
<ExternalLinkIcon className="inline w-3 h-3 mb-0.5 opacity-60" />
</a>
);
}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/chat/components/chatThread/markdownRenderer.tsx`
around lines 174 - 187, The spread {...rest} in the renderAnchor component can
override explicit security props (target, rel, className); move {...rest} before
the explicit props inside the <a> element so incoming props cannot overwrite
target="_blank" and rel="noopener noreferrer". If you need to preserve incoming
className from the markdown AST, merge it with the existing class via the cn
utility (e.g., cn(rest.className, "inline-flex items-center gap-0.5")) and
remove className from rest before spreading to avoid duplication.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants