fix(mobile): links tapped in the conversation leave the app instead of opening in-app

Author: vickyshaw29Created Sep 19, 2026Updated Sep 19, 2026

Problem

A web link tapped in the conversation leaves the app. packages/mobile/lib/chat/ChatMarkdown.tsx:126 hands every link to Linking.openURL, so the user lands in Safari or Chrome and has to come back to AO by hand. Reported by @illegalcall in Discord #dev-chat ("On mobile: links don't open in AO browser").

On desktop the same tap opens the page inside the session's AO Browser inspector (frontend/src/renderer/hooks/useSessionBrowserLink.ts, components/AppLink.tsx); only a modifier-click or the context menu goes to the system browser.

Two consequences beyond the context switch:

  • A github.com link in the conversation skips the app's own rule for github.com. lib/openGitHub.ts opens a repo or PR in the GitHub app when it has a screen for it (that is where the user's session lives; a private PR in a browser is a login wall or a 404) and falls back to the browser otherwise. PR cards and worker rows use it; ChatMarkdown does not, so the same URL is routed by two different rules depending on where it sits.
  • A localhost link the agent prints is opened as the phone's own localhost. The preview screen already maps the agent machine's loopback onto the AO host (lib/api.ts, mobileReachablePreviewURL); chat links do not go through it.

What this is not

  • Not a job for app/preview/[id].tsx. That WebView shows the workspace's detected index.html or the session's advertised previewUrl, with AO's auth header. A tapped link is a third-party page that may need a login, cookies, a share sheet or an address bar — a raw WKWebView has none of that, and ATS refuses plain http to anything but local hosts inside it.
  • Not the elicitation "Open link" button (ChatTimeline.tsx): the desktop sends that to the system browser too, and the flow behind it belongs to the desktop machine.
  • Not the "Review is not available" half of the same report. That is a missing feature (no caller of /sessions/{id}/reviews* exists in packages/mobile), not a bug, and needs a product decision first.

Proposed direction

Open links in the platform's in-app browser — SFSafariViewController on iOS, a Chrome Custom Tab on Android — through expo-web-browser, which is what chat apps do with a tapped link: the user stays in the app and gets a real browser with a Done button.

  • ChatMarkdown reports the tap; the conversation screen decides how it opens (it knows the AO host).
  • Map the loopback with mobileReachablePreviewURL first, then go through openGitHub, so github.com keeps its app handoff and a tapped link and a PR card follow one rule. openGitHub's browser fallback becomes the in-app browser; non-web schemes (the iOS bug report's x-safari-https://) still go to the system.
  • New native module, so the runtime version changes: ships in a store build, not over the air.

Alternatives considered

  • Route into preview/[id] with the tapped URL. Works, but is a hand-rolled browser — see above.
  • NSAllowsArbitraryLoadsInWebContent to make the WebView route viable for plain-http links. Widens ATS for the whole app to solve the wrong problem.
  • Leave it. Safari is not broken; it is the wrong place, and it drops the github.com rule and the loopback mapping the app already has.

Before submitting

  • I searched existing issues / discussions for something similar
  • I am willing to help implement this if maintainers agree on scope

Source: Untrivial-ai/agent-orchestrator