Symptom. An in-app link worked on the web (new tab) but tapping it in the iOS app did nothing: no navigation, no console error, no Sentry event.
Mechanism (read from source, @capacitor/ios 8.5.3 and @sveltejs/kit 2.70.3):
- SvelteKit's client router bails before
beforeNavigateon any anchor whosetargetis not_self/_parent/_top(runtime/client/client.js,if (target && target !== '_self') return;). So the click reaches WKWebView as a plain new-window request. - WKWebView calls the WKUIDelegate
webView(_:createWebViewWith:for:windowFeatures:). Capacitor'sWebViewDelegationHandler.swiftimplements it asif let url = navigationAction.request.url { UIApplication.shared.open(url, options: [:], completionHandler: nil) } return nil - A relative href resolves against the shell origin, so the URL is
capacitor://localhost/new?.... Nothing registers thecapacitorscheme inCFBundleURLSchemes(Capacitor apps only register their deep-link schemes), soopenfails and the completion handler is nil. Silent.
The same handler is why target="_blank" to an https URL does work in Capacitor (it opens Safari): the behaviour is per-scheme, not per-attribute.
Fix. Render target/rel only off the native shell, e.g. in Svelte target={IS_APP ? null : '_blank'} rel={IS_APP ? null : 'noopener'} (a null attribute is omitted), and drop the external-link glyph in the app. With no target, the SPA router handles the tap as a same-document navigation.
Audit. grep -rn '_blank' src and classify each hit: external https (fine: Safari), internal path (broken in the shell), already platform-gated. Only the internal ones need the gate.
Verification status: chain read from the installed sources plus Info.plist; the in-place navigation is covered by a Playwright test against the MOBILE bundle, not yet re-run on a device.