Fix: Allow opening external links in Tauri app#293
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughReplaces 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
Comment |
There was a problem hiding this comment.
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 viaaProps.onClick(e.g., footnote handling, analytics) no longer fires. Call the original handler first and respectevent.defaultPreventedbefore invokingopenExternalUrl.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
📒 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.tsfrontend/src/components/UnifiedChat.tsxfrontend/src/components/markdown.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use strict TypeScript typing and avoid
anywhen possible
Files:
frontend/src/utils/openUrl.tsfrontend/src/components/UnifiedChat.tsxfrontend/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
There was a problem hiding this comment.
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.tsutility 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-openerto 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 theRemarkBreaksplugin and CSS handling. - Test
frontend/src/components/UnifiedChat.tsxwith 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
3 files reviewed, 1 comment
| <div className="pl-6 text-foreground/80"> | ||
| <Markdown content={isExpanded ? output : preview} fontSize={14} /> |
There was a problem hiding this 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.
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.28f3c61 to
970cd99
Compare
Deploying maple with
|
| 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 |
There was a problem hiding this comment.
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.tsutility that detects the environment (isTauri()) and uses either the Tauri opener plugin orwindow.open() - Updated
markdown.tsxto intercept external link clicks and callopenExternalUrl()instead of default browser behavior - Modified
UnifiedChat.tsxto 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.tsand 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
3 files reviewed, 1 comment
970cd99 to
3019b88
Compare
|
@TestFlight build |
|
🚀 TestFlight deployment triggered! Check the Actions tab for progress. |
There was a problem hiding this comment.
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.tsutility with Tauri/web detection and fallback handling - Updated
frontend/src/components/markdown.tsxto useopenExternalUrlfor external links via onClick - Modified
frontend/src/components/UnifiedChat.tsxto 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.tsand 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
3 files reviewed, no comments
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 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
anywhen 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
|
✅ TestFlight deployment completed successfully! |
|
@TestFlight build |
There was a problem hiding this comment.
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.tsutility with environment detection for proper URL opening - Updated
Markdowncomponent to useopenExternalUrlfor external links with error notifications - Added HTTPS wildcard (
https://*) to all three Tauri capability files (default, mobile-android, mobile-ios) - Modified
UnifiedChatto 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
5 files reviewed, no comments
|
🚀 TestFlight deployment triggered! Check the Actions tab for progress. |
a509f16 to
c5e9e50
Compare
There was a problem hiding this comment.
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 theopener:allow-open-urlpermission 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
3 files reviewed, no comments
|
✅ TestFlight deployment completed successfully! |
There was a problem hiding this comment.
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
📒 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
There was a problem hiding this comment.
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
openUrlutility 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
5 files reviewed, no comments
There was a problem hiding this comment.
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 failEven when we reach this branch, browsers can return
nullif 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
📒 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.tsxfrontend/src/utils/openUrl.tsfrontend/src/routes/__root.tsxfrontend/src/components/ExternalUrlConfirmHandler.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Use strict TypeScript typing and avoid
anywhen possible
Files:
frontend/src/components/ExternalUrlConfirmDialog.tsxfrontend/src/utils/openUrl.tsfrontend/src/routes/__root.tsxfrontend/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
b8b2f37 to
3962169
Compare
There was a problem hiding this comment.
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
openUrlutility with environment-aware URL opening (window.openfor 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/chatas 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) andfrontend/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
5 files reviewed, 2 comments
3962169 to
ed7ca91
Compare
There was a problem hiding this comment.
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.tsutility 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
UnifiedChatto render tool outputs as Markdown, enabling clickable links - Updated Markdown component to intercept external links and show confirmation
Issues Found:
- The regex
/^\/#/iinmarkdown.tsx:376only 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
5 files reviewed, no comments
|
@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 finished @AnthonyRonning's task —— View job Dialog dismiss behavior updated ✓
SummarySuccessfully enabled click-outside and Escape key dismissal for the external link confirmation dialog. Solution: Switched from Changes:
The change maintains the same visual appearance and functionality while providing the more flexible dismissal UX you requested. |
561c79c to
a9299b2
Compare
- 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>
There was a problem hiding this comment.
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
openUrlutility with environment-aware URL opening (Tauri plugin vswindow.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
9 files reviewed, no comments
|
@claude oops please try that last one again. switching |
|
Claude finished @AnthonyRonning's task —— View job Dialog dismiss behavior updated ✓
SummarySuccessfully enabled click-outside and Escape key dismissal for the external link confirmation dialog. Solution: Switched from Changes:
The change maintains the same visual appearance and functionality while providing the more flexible dismissal UX you requested. |
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>
There was a problem hiding this comment.
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
openUrlutility 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.tswith environment-aware URL opening logic - Built confirmation dialog system with
ExternalUrlConfirmDialogandExternalUrlConfirmHandler - 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
9 files reviewed, no comments
|
@TestFlight build |
|
🚀 TestFlight deployment triggered! Check the Actions tab for progress. |
|
✅ TestFlight deployment completed successfully! |
Fixes #280
This PR implements intelligent link handling for both Tauri and web environments.
Changes
openUrlutility to intelligently handle URL opening for Tauri and webopenExternalUrlfor external links🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Chores