Skip to content

fix: links in chat could not be opened#8544

Open
matt2e wants to merge 7 commits intomainfrom
goose-2-project
Open

fix: links in chat could not be opened#8544
matt2e wants to merge 7 commits intomainfrom
goose-2-project

Conversation

@matt2e
Copy link
Copy Markdown
Collaborator

@matt2e matt2e commented Apr 15, 2026

Screenshot 2026-04-15 at 3 51 12 pm

Summary

  • Streamdown showed a dialog asking if the user wanted to open an external link, but that link just tried to navigate the browser to the link, which does nothing in a tauri app
  • We have to intercept the links being clicked in messages and show our own dialog, which opens the link in the user's default browser by calling back into the tauri backend

matt2e and others added 3 commits April 15, 2026 12:23
External links in chat messages were silently ignored because
useArtifactLinkHandler returned early without calling preventDefault.
Now intercepts external link clicks and opens them via openUrl from
@tauri-apps/plugin-opener.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Streamdown hardcodes target="_blank" on all links, which in Tauri's
WKWebView triggers a native new-window request that bypasses the JS
click handler entirely. Override the `a` component to omit target so
clicks bubble up to useArtifactLinkHandler's delegated handler.

Also update tests to mock @tauri-apps/plugin-opener and verify openUrl
is called for external links.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Streamdown's built-in linkSafety modal uses window.open() to open
external links, which doesn't work in Tauri's WKWebView. Provide a
custom renderModal that calls openUrl from @tauri-apps/plugin-opener
instead, preserving the existing confirmation dialog UX.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: 6f22b0da57

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +375 to +377
<span className="font-semibold text-lg">Open external link?</span>
<p className="text-muted-foreground text-sm">
You&apos;re about to visit an external website.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Localize new link-safety modal strings

This modal introduces user-facing literals ("Open external link?", "You’re about to visit…", button labels, and title="Close") directly in message.tsx, but this file is inside the enforced i18n scope (ui/goose2/scripts/check-i18n-strings.mjs includes src/shared/ui/ai-elements/message.tsx and flags JSX text/title literals). As written, pnpm check will fail on these new strings, so this change is not merge-safe until the copy is moved to translation keys (or narrowly annotated with i18n-check-ignore where truly intentional).

Useful? React with 👍 / 👎.

useMemo,
useState,
} from "react";
import { openUrl } from "@tauri-apps/plugin-opener";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Update plugin-opener mocks to export openUrl

Adding openUrl imports here requires every @tauri-apps/plugin-opener Vitest mock in dependent tests to provide that named export; otherwise Vitest throws a missing-export error when loading modules. MessageBubble.test.tsx currently mocks only openPath (lines 9–11), and it imports code paths that now import openUrl, so this change will break that test suite until those mocks are updated.

Useful? React with 👍 / 👎.

The openUrl call was using void which silently swallowed any errors.
Add .catch() to log failures so we can diagnose if the Tauri opener
plugin is rejecting the call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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: 0b263ba9c7

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +21 to +24
if (isExternalHref(href)) {
event.preventDefault();
void openUrl(href);
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Skip delegated external opener when link safety is active

MessageResponse now enables Streamdown linkSafety (custom modal in message.tsx), but this delegated click handler still opens any external href immediately. Because link clicks bubble to this parent handler, external links inside assistant markdown can be launched before the safety modal is confirmed (and can be triggered twice depending on ordering), which defeats the confirmation flow this commit introduced. Add a guard (for example, return when event.defaultPrevented) or avoid opening external links from this delegated handler when Streamdown is handling them.

Useful? React with 👍 / 👎.

matt2e added 3 commits April 15, 2026 15:27
useArtifactLinkHandler was opening external links immediately via
openUrl without any confirmation, bypassing Streamdown's linkSafety
modal. Remove the external-link handling from the hook so the
linkSafety confirmation dialog is the sole path for external URLs.

Also extract the custom LinkSafetyModal into its own component file,
replace the inline SVG close icon with XIcon from lucide-react, use
shared Button primitives, and add Escape key support for accessibility.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Replace the hand-rolled modal with the shared Dialog component built on
@radix-ui/react-dialog, which provides focus trapping, aria-modal,
portal rendering, animated open/close transitions, and built-in Escape
key handling out of the box.

Also add .catch() error handling to navigator.clipboard.writeText to
prevent unhandled rejections when clipboard permissions are denied.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
The copy link button had no visual feedback after clicking, leaving
users unsure whether the copy succeeded. Add isCopied state with a
2-second timeout that swaps the button label to "Copied!", matching
the pattern used by other copy buttons in the codebase (code-block,
terminal, snippet). Also guard against double-clicks while the copied
state is active.

Resolves review feedback from 550f6bc2.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
@matt2e matt2e changed the title fix: open external links in chat via Tauri opener plugin fix: links in chat could not be opened Apr 15, 2026
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: c4d608ca85

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
className,
)}
linkSafety={linkSafetyConfig}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve local artifact link routing when enabling linkSafety

Enabling linkSafety here changes Streamdown’s link renderer to its intercepted link flow, so markdown links are no longer handled as plain anchors. useArtifactLinkHandler still only matches closest("a"), which means assistant markdown links stop going through resolveMarkdownHref/openResolvedPath; internal artifact paths are then routed through the modal’s openUrl path (or fail), breaking artifact navigation and bypassing the allowed-roots policy.

Useful? React with 👍 / 👎.

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.

1 participant