Skip to content

Fix: Allow opening external links in Tauri app#293

Merged
AnthonyRonning merged 4 commits intomasterfrom
claude/issue-280-20251022-1553
Nov 5, 2025
Merged

Fix: Allow opening external links in Tauri app#293
AnthonyRonning merged 4 commits intomasterfrom
claude/issue-280-20251022-1553

Conversation

@AnthonyRonning
Copy link
Contributor

@AnthonyRonning AnthonyRonning commented Oct 31, 2025

Fixes #280

This PR implements intelligent link handling for both Tauri and web environments.

Changes

  • Created openUrl utility to intelligently handle URL opening for Tauri and web
  • Updated markdown component to use openExternalUrl for external links
  • Updated UnifiedChat tool renderer to use Markdown component for proper link parsing
  • Links in web search results and tool outputs now work in both Tauri and web

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Chat content (search results and tool outputs) now renders as Markdown, enabling links and richer formatting while preserving preview/expand behavior.
    • Added an external-link confirmation flow: a user-facing confirmation dialog, a global handler to queue confirmations, and a safe, environment-aware URL opener with fallback handling.
  • Chores

    • Platform permissions expanded to allow opening HTTPS links across supported targets.

@coderabbitai
Copy link

coderabbitai bot commented Oct 31, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Replaces preformatted tool and web-search outputs with a Markdown renderer, routes external link clicks through an environment-aware URL opener with optional user confirmation (new dialog + handler), and broadens Tauri opener allow-lists to permit HTTPS origins.

Changes

Cohort / File(s) Summary
Chat Output Rendering
frontend/src/components/UnifiedChat.tsx
Replaces preformatted div blocks for web_search_call, function_call, and function_call_output with a Markdown component using content={isExpanded ? output : preview} and fontSize={14} to enable markdown links/formatting while keeping expand/collapse behavior.
Markdown Link Handling
frontend/src/components/markdown.tsx
Intercepts external anchor clicks and delegates to openExternalUrlWithConfirmation (prevents default for external hrefs); internal links remain unchanged and externals get pointer cursor.
URL Utility Module
frontend/src/utils/openUrl.ts
New module exporting openExternalUrl, setUrlConfirmationCallback, and openExternalUrlWithConfirmation. Detects Tauri vs browser, dynamically imports @tauri-apps/plugin-opener in Tauri, falls back to window.open, centralizes error handling, and supports an optional user-confirmation callback.
External URL Confirmation UI & Handler
frontend/src/components/ExternalUrlConfirmDialog.tsx, frontend/src/components/ExternalUrlConfirmHandler.tsx, frontend/src/routes/__root.tsx
Adds ExternalUrlConfirmDialog and ExternalUrlConfirmHandler which register a confirmation callback, manage pending URL state, invoke openExternalUrl on confirm, and mount the handler in the app root next to <Outlet />.
Tauri Capabilities (allow-list updates)
frontend/src-tauri/capabilities/default.json, frontend/src-tauri/capabilities/mobile-android.json, frontend/src-tauri/capabilities/mobile-ios.json
Adds broad "https://*" entry to opener:allow-open-url allow lists to permit HTTPS URLs for the Tauri opener plugin.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Chat as UnifiedChat / Markdown
  participant Handler as ExternalUrlConfirmHandler
  participant Dialog as ExternalUrlConfirmDialog
  participant Utils as openExternalUrl
  participant Tauri as TauriOpener
  participant Browser as windowOpen

  User->>Chat: click external link
  Chat->>Handler: invoke confirmation callback with href
  Handler->>Dialog: show(url)
  alt User confirms
    Dialog->>Handler: onConfirm
    Handler->>Utils: openExternalUrl(href)
    Note right of Utils `#D3F9D8`: async open (success/error)
    alt running in Tauri
      Utils->>Tauri: dynamic import + openUrl(href)
      Tauri-->>Utils: success/error
    else running in Browser
      Utils->>Browser: window.open(href, "_blank", "noopener,noreferrer")
      Browser-->>Utils: success/error
    end
    Utils-->>Handler: result logged
  else User cancels
    Dialog->>Handler: close, clear pending
  end
  Handler-->>Chat: clear pending state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect dynamic import, error handling, and fallback behavior in frontend/src/utils/openUrl.ts.
  • Verify registration, concurrency handling, and cleanup of the confirmation callback in ExternalUrlConfirmHandler.tsx.
  • Confirm Markdown rendering preserves preview/expand behavior and that link clicks are correctly classified as internal vs external in markdown.tsx and UnifiedChat.tsx.
  • Check Tauri capability JSON patterns for correctness and expected policy effects.

Possibly related PRs

Poem

🐰 I nibbled text and stitched a link,
From plain lines to markdown ink.
A tiny dialog asks "Proceed?"
Tauri nods, browsers heed,
Click hops off — away they blink.

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main objective: implementing support for opening external links in the Tauri app, which directly addresses issue #280.
Linked Issues check ✅ Passed All coding requirements from issue #280 are met: whitelisting is bypassed via permissions, Markdown rendering is fixed with proper link parsing, and both Tauri and web environments are supported.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing external link opening functionality in both Tauri and web environments as required by issue #280.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/issue-280-20251022-1553

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9299b2 and 4563bc6.

📒 Files selected for processing (1)
  • frontend/src/components/ExternalUrlConfirmDialog.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/ExternalUrlConfirmDialog.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build-android
  • GitHub Check: build-ios
  • GitHub Check: build-macos (universal-apple-darwin)
  • GitHub Check: build-linux
  • GitHub Check: Cloudflare Pages

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

Copy link

@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: 0

🧹 Nitpick comments (1)
frontend/src/components/markdown.tsx (1)

365-375: Preserve existing anchor click handlers.

When we overwrite onClick, any handler supplied via aProps.onClick (e.g., footnote handling, analytics) no longer fires. Call the original handler first and respect event.defaultPrevented before invoking openExternalUrl.

           if (!isInternal && href) {
             return (
               <a
                 {...aProps}
                 target={target}
                 onClick={(e) => {
-                  e.preventDefault();
-                  openExternalUrl(href);
+                  aProps.onClick?.(e);
+                  if (e.defaultPrevented) return;
+                  e.preventDefault();
+                  void openExternalUrl(href);
                 }}
                 style={{ cursor: "pointer" }}
               />
             );
           }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb811f7 and 28f3c61.

📒 Files selected for processing (3)
  • frontend/src/components/UnifiedChat.tsx (3 hunks)
  • frontend/src/components/markdown.tsx (2 hunks)
  • frontend/src/utils/openUrl.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use 2-space indentation, double quotes, and a 100-character line limit for formatting
Use camelCase for variable and function names
Use try/catch with specific error types for error handling

Files:

  • frontend/src/utils/openUrl.ts
  • frontend/src/components/UnifiedChat.tsx
  • frontend/src/components/markdown.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript typing and avoid any when possible

Files:

  • frontend/src/utils/openUrl.ts
  • frontend/src/components/UnifiedChat.tsx
  • frontend/src/components/markdown.tsx
🧬 Code graph analysis (2)
frontend/src/components/UnifiedChat.tsx (1)
frontend/src/components/markdown.tsx (1)
  • Markdown (630-659)
frontend/src/components/markdown.tsx (1)
frontend/src/utils/openUrl.ts (1)
  • openExternalUrl (38-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: build-android
  • GitHub Check: build-ios
  • GitHub Check: build-macos (universal-apple-darwin)
  • GitHub Check: build-linux
  • GitHub Check: Cloudflare Pages

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR successfully implements intelligent link handling for external URLs in both Tauri and web environments by introducing a new openExternalUrl utility and integrating it with the Markdown component.

Key Changes:

  • Created openUrl.ts utility that detects the environment (isTauri()) and uses the appropriate method to open URLs
  • Updated Markdown component's link renderer to intercept external link clicks and use openExternalUrl
  • Modified UnifiedChat to render tool outputs and web search results as Markdown instead of plain text, enabling proper link parsing

Technical Approach:

  • In Tauri: Uses @tauri-apps/plugin-opener to open links in the system browser
  • In Web: Uses standard window.open() with security flags (noopener,noreferrer)
  • Includes comprehensive error handling with fallback to window.open() if Tauri plugin fails

Testing Recommendation:
Verify that tool outputs with special text formatting (multiple spaces, newlines, etc.) render correctly after removing whitespace-pre-wrap class, as the Markdown component now handles whitespace via the RemarkBreaks plugin.

Confidence Score: 4/5

  • This PR is safe to merge with minor testing recommended
  • The implementation follows existing patterns in the codebase (similar to AccountMenu.tsx), includes proper error handling with fallback mechanisms, and correctly uses the platform detection utility. The only minor concern is that replacing plain text rendering with Markdown might affect whitespace handling for non-markdown tool outputs, which should be tested but is unlikely to cause issues given the RemarkBreaks plugin and CSS handling.
  • Test frontend/src/components/UnifiedChat.tsx with tool outputs containing special formatting (multiple spaces, tabs, etc.) to ensure whitespace is preserved correctly

Important Files Changed

File Analysis

Filename Score Overview
frontend/src/utils/openUrl.ts 5/5 New utility for opening external URLs with Tauri/web environment detection and comprehensive error handling with fallback
frontend/src/components/markdown.tsx 4/5 Updated link renderer to use openExternalUrl for external links, needs verification that whitespace handling is preserved
frontend/src/components/UnifiedChat.tsx 4/5 Replaced plain text rendering with Markdown component for tool outputs to enable link parsing, removed whitespace-pre-wrap styling

Sequence Diagram

sequenceDiagram
    participant User
    participant UnifiedChat
    participant Markdown
    participant openExternalUrl
    participant Platform
    participant TauriOpener
    participant Browser

    User->>UnifiedChat: View tool output/web search
    UnifiedChat->>Markdown: Render content with Markdown component
    Note over Markdown: Parses markdown and renders links
    User->>Markdown: Click external link
    Markdown->>Markdown: preventDefault()
    Markdown->>openExternalUrl: openExternalUrl(href)
    openExternalUrl->>Platform: isTauri()
    
    alt Running in Tauri
        Platform-->>openExternalUrl: true
        openExternalUrl->>TauriOpener: import & call openUrl(url)
        TauriOpener->>Browser: Open in system browser
        Browser-->>User: External URL opened
    else Running in Web
        Platform-->>openExternalUrl: false
        openExternalUrl->>Browser: window.open(url, "_blank", "noopener,noreferrer")
        Browser-->>User: External URL opened
    end
    
    alt Error occurs
        openExternalUrl->>openExternalUrl: catch error
        openExternalUrl->>Browser: Fallback: window.open()
        Browser-->>User: External URL opened (fallback)
    end
Loading

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +249 to +250
<div className="pl-6 text-foreground/80">
<Markdown content={isExpanded ? output : preview} fontSize={14} />
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Check that removing whitespace-pre-wrap break-words doesn't affect plain text formatting in tool outputs. The Markdown component handles this via RemarkBreaks plugin and CSS, but verify with outputs containing multiple spaces or special formatting.

Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/components/UnifiedChat.tsx
Line: 249:250

Comment:
**style:** Check that removing `whitespace-pre-wrap break-words` doesn't affect plain text formatting in tool outputs. The Markdown component handles this via `RemarkBreaks` plugin and CSS, but verify with outputs containing multiple spaces or special formatting.

How can I resolve this? If you propose a fix, please make it concise.

@AnthonyRonning AnthonyRonning force-pushed the claude/issue-280-20251022-1553 branch from 28f3c61 to 970cd99 Compare November 2, 2025 18:18
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Nov 2, 2025

Deploying maple with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4563bc6
Status: ✅  Deploy successful!
Preview URL: https://e2109502.maple-ca8.pages.dev
Branch Preview URL: https://claude-issue-280-20251022-15.maple-ca8.pages.dev

View logs

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR addresses issue #280 by implementing intelligent link handling to allow external links to open in both Tauri desktop/mobile apps and web browsers.

Key Changes:

  • Created openUrl.ts utility that detects the environment (isTauri()) and uses either the Tauri opener plugin or window.open()
  • Updated markdown.tsx to intercept external link clicks and call openExternalUrl() instead of default browser behavior
  • Modified UnifiedChat.tsx to render tool outputs (web search results, tool outputs) as Markdown instead of plain text, enabling proper link parsing

Critical Issue Found:
The Tauri opener plugin has a strict URL whitelist configured in frontend/src-tauri/capabilities/*.json files. Currently, only specific domains are allowed (localhost, trymaple.ai, stripe.com, zaprite.com, opensecret.cloud, and a few others on Android). Web search results will contain arbitrary external URLs (e.g., Wikipedia, news sites, documentation) that will be blocked by this whitelist, causing link clicks to fail silently or throw errors. The fallback to window.open() in the catch block may not work properly in Tauri environments.

Recommendations:

  • Update the Tauri capabilities files to allow all HTTPS URLs with a wildcard pattern like {"url": "https://*/*"} and {"url": "http://*/*"}
  • Add user-facing error handling to display a message when URL opening fails
  • Test with actual web search results to verify links from various domains work correctly

Confidence Score: 1/5

  • This PR cannot be safely merged without fixing the URL whitelist configuration
  • The implementation introduces a critical bug where external links from web search results will fail to open in Tauri apps due to strict URL whitelist restrictions. While the code logic is sound, the Tauri security configuration blocks arbitrary URLs, making the core feature non-functional. This must be resolved before merging.
  • Pay critical attention to frontend/src/utils/openUrl.ts and the Tauri capabilities configuration files (frontend/src-tauri/capabilities/*.json)

Important Files Changed

File Analysis

Filename Score Overview
frontend/src/utils/openUrl.ts 1/5 New utility for opening external URLs - has critical issue with Tauri URL whitelist preventing arbitrary links from opening
frontend/src/components/markdown.tsx 4/5 Updated link renderer to use openExternalUrl utility with click handler - implementation is correct but depends on openUrl.ts whitelist fix
frontend/src/components/UnifiedChat.tsx 4/5 Changed tool output rendering to use Markdown component instead of plain text - enables link parsing but needs whitelist fix to work

Sequence Diagram

sequenceDiagram
    participant User
    participant Markdown
    participant OpenUrl as openExternalUrl()
    participant Platform as isTauri()
    participant TauriOpener as @tauri-apps/plugin-opener
    participant WindowOpen as window.open()
    participant Whitelist as Tauri Capabilities

    User->>Markdown: Click external link
    Markdown->>OpenUrl: openExternalUrl(href)
    OpenUrl->>Platform: Check environment
    
    alt Tauri Environment
        Platform-->>OpenUrl: true
        OpenUrl->>TauriOpener: openUrl(url)
        TauriOpener->>Whitelist: Check URL against allowed patterns
        alt URL not in whitelist
            Whitelist-->>TauriOpener: ❌ Denied
            TauriOpener-->>OpenUrl: Error thrown
            OpenUrl->>WindowOpen: Fallback: window.open()
            Note over WindowOpen: May not work in Tauri
        else URL in whitelist
            Whitelist-->>TauriOpener: ✅ Allowed
            TauriOpener-->>User: Opens in system browser
        end
    else Web Environment
        Platform-->>OpenUrl: false
        OpenUrl->>WindowOpen: window.open(url, '_blank')
        WindowOpen-->>User: Opens in new tab
    end
Loading

3 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@AnthonyRonning AnthonyRonning force-pushed the claude/issue-280-20251022-1553 branch from 970cd99 to 3019b88 Compare November 2, 2025 19:06
@AnthonyRonning
Copy link
Contributor Author

@TestFlight build

@github-actions
Copy link
Contributor

github-actions bot commented Nov 2, 2025

🚀 TestFlight deployment triggered! Check the Actions tab for progress.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR implements environment-aware external link handling by creating an openExternalUrl utility that routes URLs through the Tauri opener plugin in native apps and window.open() in web browsers. Tool outputs in UnifiedChat now render as Markdown instead of plain text, enabling proper link parsing. External links in the Markdown component use the new utility with onClick handlers that call preventDefault() to avoid default anchor behavior.

Key changes:

  • Created frontend/src/utils/openUrl.ts utility with Tauri/web detection and fallback handling
  • Updated frontend/src/components/markdown.tsx to use openExternalUrl for external links via onClick
  • Modified frontend/src/components/UnifiedChat.tsx to render tool outputs with Markdown component (3 locations)

Critical issue:
The Tauri opener plugin has a strict URL whitelist in capabilities/*.json files that only allows specific domains (localhost, trymaple.ai, stripe, zaprite, opensecret.cloud, and on Android: github.com, google.com). Web search results will contain arbitrary URLs that aren't whitelisted, causing openUrl() to fail. The fallback to window.open() may not work reliably in Tauri environments. This needs to be addressed by either updating the capabilities to allow all HTTPS URLs or handling rejections with user-facing error messages.

Confidence Score: 2/5

  • This PR has good structure but will likely fail in production for its primary use case (opening web search URLs in Tauri)
  • The implementation is well-structured with proper error handling and fallbacks, but there's a critical blocker: the Tauri capabilities whitelist will reject most URLs from web search results. The code won't crash, but the main feature won't work as intended. The whitelist must be updated to allow arbitrary HTTPS URLs, or error handling needs improvement to inform users why links aren't opening
  • Pay close attention to frontend/src/utils/openUrl.ts and Tauri capability files (frontend/src-tauri/capabilities/*.json). The URL whitelist must be expanded to support web search results.

Important Files Changed

File Analysis

Filename Score Overview
frontend/src/utils/openUrl.ts 2/5 New utility for opening external URLs - well-structured with fallback, but Tauri whitelist will block most web search URLs (see existing comment)
frontend/src/components/markdown.tsx 4/5 Integrates openExternalUrl for external links - implementation is correct and handles onClick properly with preventDefault
frontend/src/components/UnifiedChat.tsx 4/5 Switched from plain text to Markdown rendering for tool outputs - enables link parsing, but whitespace handling should be verified (see existing comment)

Sequence Diagram

sequenceDiagram
    participant User
    participant UnifiedChat
    participant Markdown
    participant openExternalUrl
    participant Platform
    participant Tauri as Tauri opener
    participant Browser as System Browser

    User->>UnifiedChat: Views tool output with links
    UnifiedChat->>Markdown: Renders content with Markdown component
    Markdown->>Markdown: Parses markdown and detects links
    User->>Markdown: Clicks external link
    Markdown->>openExternalUrl: Opens URL via utility function
    openExternalUrl->>Platform: Check isTauri()
    
    alt In Tauri Environment
        Platform-->>openExternalUrl: isTauri = true
        openExternalUrl->>Tauri: Call openUrl(url)
        Tauri->>Tauri: Check URL against capabilities whitelist
        alt URL matches whitelist
            Tauri->>Browser: Opens URL in system browser
            Browser-->>User: Display external page
        else URL NOT in whitelist
            Tauri-->>openExternalUrl: Throws error/rejects
            openExternalUrl->>Browser: Fallback to window.open()
            Browser-->>User: May or may not work
        end
    else In Web Environment
        Platform-->>openExternalUrl: isTauri = false
        openExternalUrl->>Browser: window.open(url)
        Browser-->>User: Opens in new tab
    end
Loading

3 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link

@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: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 970cd99 and 3019b88.

📒 Files selected for processing (3)
  • frontend/src/components/UnifiedChat.tsx (3 hunks)
  • frontend/src/components/markdown.tsx (2 hunks)
  • frontend/src/utils/openUrl.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/markdown.tsx
  • frontend/src/components/UnifiedChat.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use 2-space indentation, double quotes, and a 100-character line limit for formatting
Use camelCase for variable and function names
Use try/catch with specific error types for error handling

Files:

  • frontend/src/utils/openUrl.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript typing and avoid any when possible

Files:

  • frontend/src/utils/openUrl.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-macos (universal-apple-darwin)
  • GitHub Check: build-linux
  • GitHub Check: build-ios
  • GitHub Check: build-android

@github-actions
Copy link
Contributor

github-actions bot commented Nov 2, 2025

✅ TestFlight deployment completed successfully!

@AnthonyRonning
Copy link
Contributor Author

@TestFlight build

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR successfully implements intelligent link handling for both Tauri and web environments to fix issue #280.

Key Changes:

  • Created openUrl.ts utility with environment detection for proper URL opening
  • Updated Markdown component to use openExternalUrl for external links with error notifications
  • Added HTTPS wildcard (https://*) to all three Tauri capability files (default, mobile-android, mobile-ios)
  • Modified UnifiedChat to render tool outputs as Markdown instead of plain text, enabling clickable links in web search results

Implementation Quality:

  • Proper error handling with user-facing notifications
  • Comprehensive JSDoc documentation
  • Graceful fallback for popup blockers in web environment
  • Consistent security posture across all platforms

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation is well-designed with proper error handling, environment detection, and security considerations. The HTTPS wildcard permission addresses the core issue while the new utility provides a maintainable abstraction. No logical errors or security vulnerabilities were found.
  • No files require special attention

Important Files Changed

File Analysis

Filename Score Overview
frontend/src/utils/openUrl.ts 5/5 New utility for environment-aware URL opening with proper error handling for both Tauri and web
frontend/src/components/markdown.tsx 5/5 Updated link renderer to use openExternalUrl with error notifications for external links
frontend/src-tauri/capabilities/default.json 5/5 Added HTTPS wildcard permission to allow opening arbitrary external URLs

Sequence Diagram

sequenceDiagram
    participant User
    participant Markdown
    participant openExternalUrl
    participant isTauri
    participant TauriOpener
    participant WindowOpen
    participant Browser

    User->>Markdown: Click external link
    Markdown->>Markdown: onClick handler
    Markdown->>Markdown: e.preventDefault()
    Markdown->>openExternalUrl: openExternalUrl(url)
    openExternalUrl->>isTauri: Check environment
    
    alt Tauri Environment
        isTauri-->>openExternalUrl: true
        openExternalUrl->>TauriOpener: import @tauri-apps/plugin-opener
        openExternalUrl->>TauriOpener: await openUrl(url)
        TauriOpener->>Browser: Open in system browser
        Browser-->>User: External URL opens
    else Web Environment
        isTauri-->>openExternalUrl: false
        openExternalUrl->>WindowOpen: window.open(url, "_blank")
        WindowOpen->>Browser: Open in new tab
        alt Success
            Browser-->>User: External URL opens
        else Popup Blocked
            WindowOpen-->>openExternalUrl: null/closed
            openExternalUrl-->>Markdown: throw Error
            Markdown->>User: Show error notification
        end
    end
    
    alt Error in Tauri
        TauriOpener-->>openExternalUrl: throw Error
        openExternalUrl-->>Markdown: throw Error
        Markdown->>User: Show error notification
    end
Loading

5 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@github-actions
Copy link
Contributor

github-actions bot commented Nov 2, 2025

🚀 TestFlight deployment triggered! Check the Actions tab for progress.

@AnthonyRonning AnthonyRonning force-pushed the claude/issue-280-20251022-1553 branch from a509f16 to c5e9e50 Compare November 2, 2025 19:58
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

Added https://* wildcard to Tauri opener capabilities across all platforms (desktop, Android, iOS) to enable opening arbitrary HTTPS URLs in web search results and chat tool outputs.

This completes the fix started in commit 3019b88, which created the openExternalUrl utility and integrated it into the Markdown component. The previous commit addressed the following comment that identified the capabilities whitelist as the blocker.

Key Changes:

  • Appended {"url": "https://*"} to the opener:allow-open-url permission in all three capability files
  • Maintains existing specific domain permissions (localhost, trymaple.ai, stripe.com, etc.) for documentation purposes
  • Enables the Tauri opener plugin to open any HTTPS URL in the system browser

Security Considerations:
The wildcard permission allows opening any HTTPS URL, which could theoretically be exploited if malicious URLs were injected into chat responses. However:

  • URLs are opened in the system browser, not in-app, providing OS-level sandboxing
  • The app already displays untrusted content from AI responses and web searches
  • HTTP (non-encrypted) URLs remain blocked
  • No user confirmation dialog is shown before opening URLs

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk - the changes are straightforward and address the issue correctly
  • The implementation is simple (adding a wildcard to capabilities) and solves the stated problem. The security risk of allowing any HTTPS URL is acceptable for this use case since URLs open in the system browser with OS-level sandboxing. Score is 4 rather than 5 because there's no user confirmation before opening URLs, and malicious URLs in AI responses could be auto-opened if clicked.
  • No files require special attention - all changes are identical JSON additions

Important Files Changed

File Analysis

Filename Score Overview
frontend/src-tauri/capabilities/default.json 4/5 Added https://* wildcard to opener permissions, enabling external link opening for web search results
frontend/src-tauri/capabilities/mobile-android.json 4/5 Added https://* wildcard to opener permissions for Android platform
frontend/src-tauri/capabilities/mobile-ios.json 4/5 Added https://* wildcard to opener permissions for iOS platform

Sequence Diagram

sequenceDiagram
    participant User
    participant Markdown
    participant openExternalUrl
    participant Platform
    participant TauriOpener
    participant SystemBrowser

    User->>Markdown: Clicks external link
    Markdown->>Markdown: onClick preventDefault
    Markdown->>openExternalUrl: openExternalUrl(url)
    openExternalUrl->>Platform: isTauri()?
    
    alt Tauri Environment
        Platform-->>openExternalUrl: true
        openExternalUrl->>TauriOpener: import @tauri-apps/plugin-opener
        TauriOpener->>TauriOpener: Check capabilities (https://*)
        TauriOpener->>SystemBrowser: openUrl(url)
        SystemBrowser-->>User: Opens in system browser
    else Web Environment
        Platform-->>openExternalUrl: false
        openExternalUrl->>SystemBrowser: window.open(url, "_blank")
        SystemBrowser-->>User: Opens in new tab
    end
    
    Note over openExternalUrl,TauriOpener: Fallback to window.open on error
Loading

3 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@github-actions
Copy link
Contributor

github-actions bot commented Nov 2, 2025

✅ TestFlight deployment completed successfully!

Copy link

@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

♻️ Duplicate comments (2)
frontend/src-tauri/capabilities/mobile-android.json (1)

76-79: Consistent with default.json security implications.

This mirrors the same permission expansion in default.json (allowing all HTTPS URLs). Ensure the frontend validation concerns flagged in the default.json review are addressed across all platforms.

frontend/src-tauri/capabilities/mobile-ios.json (1)

33-36: Consistent with default.json security implications.

This mirrors the same permission expansion in default.json (allowing all HTTPS URLs). Ensure the frontend validation concerns flagged in the default.json review are addressed across all iOS platform deployments.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3019b88 and c5e9e50.

📒 Files selected for processing (3)
  • frontend/src-tauri/capabilities/default.json (1 hunks)
  • frontend/src-tauri/capabilities/mobile-android.json (1 hunks)
  • frontend/src-tauri/capabilities/mobile-ios.json (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-android

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR implements intelligent external link handling for both Tauri and web environments, resolving issue #280 where links in web search results and tool outputs weren't opening properly.

Key Changes:

  • Created openUrl utility with environment detection (Tauri vs web) and proper error handling with fallbacks
  • Added user confirmation dialog before opening external URLs for security
  • Updated Markdown component to intercept external link clicks and route through confirmation flow
  • Modified UnifiedChat to render tool outputs as Markdown, enabling proper link parsing
  • Updated Tauri capabilities to allow HTTPS wildcard (https://*) for opening arbitrary external URLs

Implementation Details:

  • Uses module-level callback pattern to connect URL confirmation requests to the dialog handler
  • Confirmation handler mounted at root level for app-wide availability
  • Proper platform detection via existing isTauri() utility
  • Graceful degradation with window.open() fallback if Tauri plugin fails

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • Implementation is well-architected with proper separation of concerns, comprehensive error handling, user confirmation for security, and addresses the previous comment about Tauri capabilities by adding HTTPS wildcard permissions
  • No files require special attention

Important Files Changed

File Analysis

Filename Score Overview
frontend/src/utils/openUrl.ts 5/5 New utility providing environment-aware URL opening with confirmation support, proper error handling and fallbacks
frontend/src/components/ExternalUrlConfirmHandler.tsx 4/5 Handler component managing URL confirmation state via module-level callback pattern
frontend/src/components/markdown.tsx 5/5 Updated to intercept external link clicks and route through confirmation flow

Sequence Diagram

sequenceDiagram
    participant User
    participant Markdown
    participant openUrl
    participant ConfirmHandler
    participant Dialog
    participant Tauri
    participant Browser

    User->>Markdown: Click external link
    Markdown->>openUrl: openExternalUrlWithConfirmation(url)
    
    alt Callback registered
        openUrl->>ConfirmHandler: callback(url)
        ConfirmHandler->>Dialog: Show confirmation
        Dialog->>User: Display URL for approval
        User->>Dialog: Click "Open"
        Dialog->>ConfirmHandler: onConfirm()
        ConfirmHandler->>openUrl: openExternalUrl(url)
    else Callback not set
        openUrl->>openUrl: openExternalUrl(url) directly
    end
    
    alt Running in Tauri
        openUrl->>Tauri: openUrl(url)
        Tauri->>Browser: Open in system browser
    else Running in Web
        openUrl->>Browser: window.open(url)
    end
    
    alt Error occurs
        openUrl->>Browser: Fallback to window.open()
    end
Loading

5 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link

@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: 2

♻️ Duplicate comments (1)
frontend/src/utils/openUrl.ts (1)

52-60: Handle blocked window.open results so clicks don't silently fail

Even when we reach this branch, browsers can return null if a popup blocker intervenes. Right now we treat that as success, so users see nothing when a blocker fires—exactly the scenario the PR is supposed to fix. Check the return value, surface an actionable error, and reuse the same guard in the fallback path.

-      window.open(url, "_blank", "noopener,noreferrer");
+      const popup = window.open(url, "_blank", "noopener,noreferrer");
+      if (!popup) {
+        throw new Error("Failed to open URL; the popup was blocked");
+      }
@@
-      window.open(url, "_blank", "noopener,noreferrer");
+      const popup = window.open(url, "_blank", "noopener,noreferrer");
+      if (!popup) {
+        console.error("Fallback window.open was blocked as well");
+      }

Please also surface a user-visible notification when this happens so the failure isn’t silent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5e9e50 and b8b2f37.

📒 Files selected for processing (5)
  • frontend/src/components/ExternalUrlConfirmDialog.tsx (1 hunks)
  • frontend/src/components/ExternalUrlConfirmHandler.tsx (1 hunks)
  • frontend/src/components/markdown.tsx (2 hunks)
  • frontend/src/routes/__root.tsx (2 hunks)
  • frontend/src/utils/openUrl.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/markdown.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Use 2-space indentation, double quotes, and a 100-character line limit for formatting
Use camelCase for variable and function names
Use try/catch with specific error types for error handling

Files:

  • frontend/src/components/ExternalUrlConfirmDialog.tsx
  • frontend/src/utils/openUrl.ts
  • frontend/src/routes/__root.tsx
  • frontend/src/components/ExternalUrlConfirmHandler.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use strict TypeScript typing and avoid any when possible

Files:

  • frontend/src/components/ExternalUrlConfirmDialog.tsx
  • frontend/src/utils/openUrl.ts
  • frontend/src/routes/__root.tsx
  • frontend/src/components/ExternalUrlConfirmHandler.tsx
🧠 Learnings (1)
📚 Learning: 2025-07-19T21:31:44.925Z
Learnt from: CR
Repo: OpenSecretCloud/Maple PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-19T21:31:44.925Z
Learning: Applies to src/**/*.{tsx} : Use React context for global state management when needed

Applied to files:

  • frontend/src/routes/__root.tsx
🧬 Code graph analysis (3)
frontend/src/components/ExternalUrlConfirmDialog.tsx (1)
frontend/src/components/ui/alert-dialog.tsx (8)
  • AlertDialog (104-104)
  • AlertDialogContent (108-108)
  • AlertDialogHeader (109-109)
  • AlertDialogTitle (111-111)
  • AlertDialogDescription (112-112)
  • AlertDialogFooter (110-110)
  • AlertDialogCancel (114-114)
  • AlertDialogAction (113-113)
frontend/src/routes/__root.tsx (1)
frontend/src/components/ExternalUrlConfirmHandler.tsx (1)
  • ExternalUrlConfirmHandler (5-29)
frontend/src/components/ExternalUrlConfirmHandler.tsx (2)
frontend/src/utils/openUrl.ts (2)
  • setUrlConfirmationCallback (16-18)
  • openExternalUrl (45-64)
frontend/src/components/ExternalUrlConfirmDialog.tsx (1)
  • ExternalUrlConfirmDialog (19-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: build-linux
  • GitHub Check: build-macos (universal-apple-darwin)
  • GitHub Check: build-android
  • GitHub Check: build-ios

@AnthonyRonning AnthonyRonning force-pushed the claude/issue-280-20251022-1553 branch from b8b2f37 to 3962169 Compare November 2, 2025 20:31
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR implements intelligent external link handling for both Tauri and web environments, addressing issue #280 where links in web search results weren't opening in the Tauri app.

Key Changes:

  • Created openUrl utility with environment-aware URL opening (window.open for web, Tauri opener plugin for native)
  • Added confirmation dialog system with global handler mounted at root route
  • Updated Markdown component to intercept external link clicks and show confirmation
  • Modified UnifiedChat to render tool outputs as Markdown for proper link parsing
  • Added HTTPS wildcard (https://*) to Tauri capabilities files to allow opening arbitrary external URLs

Issues Found:

  • Internal link detection regex is too restrictive - only matches /# patterns, will incorrectly treat routes like /chat as external
  • Race condition in confirmation handler - rapid link clicks will lose URLs

Confidence Score: 3/5

  • This PR has critical logic issues that will affect internal navigation and rapid link interactions
  • The implementation solves the core problem but has two significant bugs: the internal link regex will incorrectly treat app routes as external (breaking navigation), and the confirmation handler has a race condition with rapid clicks. The Tauri capabilities wildcard is correct per previous comments.
  • frontend/src/components/markdown.tsx (internal link detection) and frontend/src/components/ExternalUrlConfirmHandler.tsx (race condition)

Important Files Changed

File Analysis

Filename Score Overview
frontend/src/utils/openUrl.ts 4/5 New utility for opening external URLs in both Tauri and web environments with confirmation dialog support
frontend/src/components/ExternalUrlConfirmHandler.tsx 4/5 Global handler for external URL confirmations using React state and callback registration
frontend/src/components/markdown.tsx 4/5 Updated anchor tag renderer to intercept external links and show confirmation before opening

Sequence Diagram

sequenceDiagram
    participant User
    participant Markdown
    participant Handler as ExternalUrlConfirmHandler
    participant Dialog as ExternalUrlConfirmDialog
    participant Util as openUrl
    participant Browser

    User->>Markdown: Click external link
    Markdown->>Markdown: Check if internal (regex test)
    Markdown->>Util: openExternalUrlWithConfirmation(url)
    Util->>Handler: urlConfirmationCallback(url)
    Handler->>Handler: setPendingUrl(url)
    Handler->>Dialog: open=true, url=pendingUrl
    Dialog->>User: Show confirmation dialog
    
    alt User confirms
        User->>Dialog: Click "Open"
        Dialog->>Handler: onConfirm()
        Handler->>Util: openExternalUrl(pendingUrl)
        
        alt Tauri environment
            Util->>Util: import @tauri-apps/plugin-opener
            Util->>Browser: openUrl(url) via Tauri plugin
        else Web environment
            Util->>Browser: window.open(url, "_blank")
        end
        
        Handler->>Handler: setPendingUrl(null)
    else User cancels
        User->>Dialog: Click "Cancel"
        Dialog->>Handler: onOpenChange(false)
        Handler->>Handler: setPendingUrl(null)
    end
Loading

5 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@AnthonyRonning AnthonyRonning force-pushed the claude/issue-280-20251022-1553 branch from 3962169 to ed7ca91 Compare November 2, 2025 22:00
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR implements intelligent link handling for both Tauri and web environments, allowing external URLs (especially from web search results) to open properly in the system browser.

Key Changes:

  • Created openUrl.ts utility with platform-aware URL opening logic
  • Added confirmation dialog system for external links with user consent flow
  • Updated Tauri capabilities to allow HTTPS wildcard (https://*) across all platforms
  • Modified UnifiedChat to render tool outputs as Markdown, enabling clickable links
  • Updated Markdown component to intercept external links and show confirmation

Issues Found:

  • The regex /^\/#/i in markdown.tsx:376 only detects hash-based internal links (/#...), missing absolute internal routes like /chat, /settings, etc. This will incorrectly treat internal navigation as external, triggering unnecessary confirmation dialogs.

Note: The rapid-click issue mentioned in previous comments has already been addressed in ExternalUrlConfirmHandler.tsx:11 with the guard (current !== null ? current : url).

Confidence Score: 4/5

  • This PR is safe to merge with one regex fix needed for internal link detection
  • The implementation is well-structured with proper error handling and platform detection. The Tauri wildcard capability fix addresses the original issue. One regex bug in markdown.tsx will cause incorrect behavior for internal navigation, but it's not a critical security or data issue.
  • Pay attention to frontend/src/components/markdown.tsx - the internal link regex needs fixing to prevent false external link detection

Important Files Changed

File Analysis

Filename Score Overview
frontend/src/utils/openUrl.ts 4/5 New utility module for opening external URLs with platform-specific handling. The Tauri wildcard capability was added. Logic is sound with good error handling and fallbacks.
frontend/src/components/ExternalUrlConfirmHandler.tsx 4/5 React component managing URL confirmation dialog lifecycle. Already has logic to prevent rapid clicks from losing URLs (line 11). Clean implementation.
frontend/src/components/markdown.tsx 3/5 Updated link renderer to use confirmation dialog. Regex for detecting internal links only matches /# patterns, missing other internal routes like /chat or /settings.

Sequence Diagram

sequenceDiagram
    participant User
    participant Markdown
    participant ConfirmHandler
    participant openUrl
    participant Tauri/Browser
    
    User->>Markdown: Click external link
    Markdown->>Markdown: Check if internal (regex test)
    alt External link
        Markdown->>openUrl: openExternalUrlWithConfirmation(url)
        openUrl->>ConfirmHandler: urlConfirmationCallback(url)
        ConfirmHandler->>ConfirmHandler: setPendingUrl(url)
        ConfirmHandler->>User: Show confirmation dialog
        User->>ConfirmHandler: Click "Open"
        ConfirmHandler->>openUrl: openExternalUrl(url)
        alt Tauri environment
            openUrl->>Tauri/Browser: Tauri opener plugin
            Tauri/Browser-->>User: Opens in system browser
        else Web environment
            openUrl->>Tauri/Browser: window.open()
            Tauri/Browser-->>User: Opens in new tab
        end
    else Internal link
        Markdown->>Tauri/Browser: Standard navigation
        Tauri/Browser-->>User: Navigate in-app
    end
Loading

5 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@AnthonyRonning
Copy link
Contributor Author

@claude with the external link popup dialog, please let the user also click outside the dialog to cancel the operation too. not just require hitting cancel or open. right now it does nothing when i click outside the dialog.

@claude
Copy link
Contributor

claude bot commented Nov 5, 2025

Claude finished @AnthonyRonning's task —— View job


Dialog dismiss behavior updated ✓

  • Read ExternalUrlConfirmDialog component
  • Check ExternalUrlConfirmHandler integration
  • Verify and fix click-outside behavior
  • Run formatting and linting
  • Commit and push changes

Summary

Successfully enabled click-outside and Escape key dismissal for the external link confirmation dialog.

Solution: Switched from AlertDialog to regular Dialog component. Radix UI's AlertDialog intentionally blocks outside interactions (requiring explicit user action), but the regular Dialog component supports click-outside-to-dismiss by default.

Changes:

  • Replaced AlertDialog components with Dialog equivalents in ExternalUrlConfirmDialog.tsx
  • Users can now dismiss the dialog via:
    • Cancel button
    • Escape key
    • Clicking outside the dialog
    • X button (built into Dialog component)

The change maintains the same visual appearance and functionality while providing the more flexible dismissal UX you requested.
| Branch: claude/issue-280-20251022-1553

@AnthonyRonning AnthonyRonning force-pushed the claude/issue-280-20251022-1553 branch from 561c79c to a9299b2 Compare November 5, 2025 14:14
github-actions bot and others added 2 commits November 5, 2025 08:14
- Created openUrl utility to intelligently handle URL opening for Tauri and web
- Updated markdown component to use openExternalUrl for external links
- Updated UnifiedChat tool renderer to use Markdown component for proper link parsing
- Links in web search results and tool outputs now work in both Tauri and web

Fixes #280

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Anthony <AnthonyRonning@users.noreply.github.com>
- Add https://* to opener:allow-open-url in all capabilities files (desktop, mobile-android, mobile-ios)
- Allows opening any HTTPS URL in web search results and other external links

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
- Create ExternalUrlConfirmDialog component to show URL before opening
- Add ExternalUrlConfirmHandler to manage dialog state
- Implement openExternalUrlWithConfirmation wrapper function
- Apply confirmation only to markdown-rendered links (tool outputs)
- Existing link openers remain unchanged

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR successfully implements external link handling for both Tauri and web environments, resolving issue #280 where links in web search results weren't working in the Tauri app.

Key Changes:

  • Created openUrl utility with environment-aware URL opening (Tauri plugin vs window.open)
  • Added user confirmation dialog for all external links with proper state management
  • Updated Tauri capabilities to allow all HTTPS URLs via wildcard pattern
  • Switched tool output rendering from plain text to Markdown, enabling proper link parsing in search results

Implementation Quality:

  • Race condition properly handled using functional setState (line 11 in ExternalUrlConfirmHandler.tsx)
  • Clean separation of concerns with dedicated utility, handler, and dialog components
  • Fallback error handling when URL opening fails
  • Applied consistently across all platforms (desktop, iOS, Android)

Minor Issue:
The internal link regex /^\/#/i at markdown.tsx:376 only matches hash-based URLs (/#section). While unlikely to affect chat content, relative paths like /chat would trigger external confirmation dialogs. The existing implementation is acceptable since AI-generated markdown rarely contains internal app links.

Confidence Score: 4/5

  • This PR is safe to merge with minimal risk - the implementation is clean and well-tested
  • Score reflects a solid implementation with proper error handling and state management. The internal link regex issue is minor and unlikely to cause problems in practice since markdown content comes from AI responses and web searches, not internal navigation. All previously identified issues (race conditions, capability wildcards) have been properly addressed.
  • No files require special attention - the implementation is consistent and well-structured

Important Files Changed

File Analysis

Filename Score Overview
frontend/src/utils/openUrl.ts 5/5 New utility for opening external URLs with confirmation dialog support, handles both Tauri and web environments with proper fallbacks
frontend/src/components/ExternalUrlConfirmHandler.tsx 5/5 Dialog handler component that manages URL confirmation state, properly prevents race conditions with functional setState
frontend/src/components/markdown.tsx 3/5 Updated link renderer to use confirmation dialog for external links, but internal link detection regex is too restrictive
frontend/src-tauri/capabilities/default.json 5/5 Added HTTPS wildcard to opener plugin permissions, allowing all external URLs to open

Sequence Diagram

sequenceDiagram
    participant User
    participant Markdown
    participant openUrl
    participant Handler
    participant Dialog
    participant Tauri/Browser

    User->>Markdown: Click external link
    Markdown->>openUrl: openExternalUrlWithConfirmation(url)
    openUrl->>Handler: urlConfirmationCallback(url)
    Handler->>Handler: setPendingUrl(url)
    Handler->>Dialog: open={true}, url={url}
    Dialog->>User: Show confirmation dialog
    User->>Dialog: Click "Open"
    Dialog->>Handler: onConfirm()
    Handler->>openUrl: openExternalUrl(url)
    alt isTauri()
        openUrl->>Tauri/Browser: @tauri-apps/plugin-opener.openUrl()
    else web
        openUrl->>Tauri/Browser: window.open(url, "_blank")
    end
    Handler->>Handler: setPendingUrl(null)
    Dialog->>User: Close dialog
Loading

9 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@AnthonyRonning
Copy link
Contributor Author

@claude oops please try that last one again. switching AlertDialog to Dialog so we can close it by clicking outside the dialog too. I accidentally force pushed

@claude
Copy link
Contributor

claude bot commented Nov 5, 2025

Claude finished @AnthonyRonning's task —— View job


Dialog dismiss behavior updated ✓

  • Read ExternalUrlConfirmDialog component
  • Check ExternalUrlConfirmHandler integration
  • Verify and fix click-outside behavior
  • Run formatting and linting
  • Commit and push changes

Summary

Successfully enabled click-outside and Escape key dismissal for the external link confirmation dialog.

Solution: Switched from AlertDialog to regular Dialog component. Radix UI's AlertDialog intentionally blocks outside interactions (requiring explicit user action), but the regular Dialog component supports click-outside-to-dismiss by default.

Changes:

  • Replaced AlertDialog components with Dialog equivalents in ExternalUrlConfirmDialog.tsx
  • Users can now dismiss the dialog via:
    • Cancel button
    • Escape key
    • Clicking outside the dialog
    • X button (built into Dialog component)

The change maintains the same visual appearance and functionality while providing the more flexible dismissal UX you requested.


Branch: claude/issue-280-20251022-1553 | View job

Replace AlertDialog with regular Dialog component to allow users to
dismiss the confirmation dialog by clicking outside of it or pressing
Escape. AlertDialog blocks these interactions by design, requiring
explicit user action. The regular Dialog provides a more flexible UX
while maintaining the same visual appearance and functionality.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Anthony <AnthonyRonning@users.noreply.github.com>
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR successfully implements external link handling for the Tauri app, addressing issue #280. The implementation adds:

  • Cross-platform URL opening: New openUrl utility that intelligently handles URL opening in both web browsers and Tauri environments, with proper fallback handling
  • User confirmation flow: Global confirmation dialog that prompts users before opening external URLs, preventing unexpected navigation
  • Tauri capability updates: All three platform configs (default, Android, iOS) now allow HTTPS wildcard URLs, removing the previous whitelist restriction
  • Markdown link support: UnifiedChat now renders tool outputs and web search results as Markdown, enabling clickable links

Key changes:

  • Created openUrl.ts with environment-aware URL opening logic
  • Built confirmation dialog system with ExternalUrlConfirmDialog and ExternalUrlConfirmHandler
  • Updated Markdown component to intercept external link clicks and trigger confirmation
  • Switched UnifiedChat from plain text to Markdown rendering for tool outputs

The implementation correctly addresses the root issue and follows React patterns with proper component separation.

Confidence Score: 4/5

  • This PR is safe to merge with minor behavioral considerations around UX
  • The implementation is technically sound with proper error handling and fallbacks. The HTTPS wildcard correctly resolves the whitelist issue. However, there are minor UX considerations: rapid link clicks are silently ignored while the dialog is open (rather than queued), and the internal link regex only matches hash links. These are not critical bugs but could cause minor user confusion in edge cases.
  • frontend/src/components/ExternalUrlConfirmHandler.tsx and frontend/src/components/markdown.tsx have minor UX considerations but are not blocking

Important Files Changed

File Analysis

Filename Score Overview
frontend/src/components/ExternalUrlConfirmHandler.tsx 4/5 Global handler for URL confirmation - addresses rapid clicks but silently ignores subsequent clicks while dialog is open
frontend/src/components/UnifiedChat.tsx 5/5 Replaced plain text rendering with Markdown component for tool outputs to enable link parsing
frontend/src/components/markdown.tsx 4/5 Added external URL confirmation for links - regex only matches hash links, not relative paths
frontend/src/utils/openUrl.ts 5/5 New utility for cross-platform URL opening with fallback handling and confirmation flow

Sequence Diagram

sequenceDiagram
    participant User
    participant Markdown
    participant openUrl
    participant Handler as ExternalUrlConfirmHandler
    participant Dialog as ExternalUrlConfirmDialog
    participant Tauri as Tauri/Browser

    User->>Markdown: Clicks external link in tool output
    Markdown->>openUrl: openExternalUrlWithConfirmation(url)
    openUrl->>Handler: urlConfirmationCallback(url)
    Handler->>Handler: setPendingUrl(url)
    Handler->>Dialog: Show dialog (open=true)
    Dialog->>User: Display confirmation prompt
    
    alt User confirms
        User->>Dialog: Click "Open" button
        Dialog->>Handler: onConfirm()
        Handler->>openUrl: openExternalUrl(url)
        
        alt Tauri environment
            openUrl->>Tauri: tauri-plugin-opener.openUrl()
            Tauri->>User: Opens URL in system browser
        else Web browser
            openUrl->>Tauri: window.open()
            Tauri->>User: Opens URL in new tab
        end
        
        Handler->>Handler: setPendingUrl(null)
        Handler->>Dialog: Close dialog
    else User cancels
        User->>Dialog: Click "Cancel"
        Dialog->>Handler: onOpenChange(false)
        Handler->>Handler: setPendingUrl(null)
        Handler->>Dialog: Close dialog
    end
Loading

9 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@AnthonyRonning
Copy link
Contributor Author

@TestFlight build

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

🚀 TestFlight deployment triggered! Check the Actions tab for progress.

@github-actions
Copy link
Contributor

github-actions bot commented Nov 5, 2025

✅ TestFlight deployment completed successfully!

@AnthonyRonning AnthonyRonning merged commit 57c0e84 into master Nov 5, 2025
8 checks passed
@AnthonyRonning AnthonyRonning deleted the claude/issue-280-20251022-1553 branch November 5, 2025 17:12
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.

Allow opening any links in the tauri app, for web search

1 participant