authorgravatar for 76637177+SunkenInTime@users.noreply.github.comDara Adedeji <76637177+SunkenInTime@users.noreply.github.com> 2026-09-15 19:27:01-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-15 23:27:01-03:00
log0ec2b08a9a1db6fa35fab8d505b5bdc2d3709a8a
tree5cecd8fc10abd8a9d1045c0c4430061b07cf5a9e
parent8c18b5bb21a5349fbadba8c77456ea34c41b7339
signature Signed by PGP key B5690EEEBB952194

fix(mobile): bare t3code:// links no longer reset navigation to Home (#12002)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

3 files changed, 46 insertions(+), 8 deletions(-)

apps/mobile/src/App.tsx+2-8
...@@ -22,6 +22,7 @@ import { RootStack } from "./Stack";...@@ -22,6 +22,7 @@ import { RootStack } from "./Stack";
22import { appAtomRegistry } from "./state/atom-registry";22import { appAtomRegistry } from "./state/atom-registry";
23import { OverlayPortalHost } from "./components/OverlayPortal";23import { OverlayPortalHost } from "./components/OverlayPortal";
24import { appBlurTargetRef } from "./lib/appBlurTarget";24import { appBlurTargetRef } from "./lib/appBlurTarget";
25import { shouldHandleAppLink } from "./lib/appLinking";
25import { useMobileNavigationTheme } from "./lib/useMobileNavigationTheme";26import { useMobileNavigationTheme } from "./lib/useMobileNavigationTheme";
2627
27import { SubscriptionUsageCoordinator } from "./widgets/SubscriptionUsageCoordinator";28import { SubscriptionUsageCoordinator } from "./widgets/SubscriptionUsageCoordinator";
...@@ -40,14 +41,7 @@ const appLinking = {...@@ -40,14 +41,7 @@ const appLinking = {
40 prefixes: [Linking.createURL("/"), "t3code://", "t3code-dev://", "t3code-preview://"],41 prefixes: [Linking.createURL("/"), "t3code://", "t3code-dev://", "t3code-preview://"],
41 // Keep the compact thread list available beneath a directly opened thread.42 // Keep the compact thread list available beneath a directly opened thread.
42 config: { initialRouteName: "Home" },43 config: { initialRouteName: "Home" },
43 // The Expo dev client launches the app via44 filter: shouldHandleAppLink,
44 // <scheme>://expo-development-client/?url=<packager> — that URL addresses
45 // the launcher, not app navigation. Without this filter it falls through
46 // to the NotFound wildcard route on every dev launch.
47 // expo-sharing uses a private lifecycle URL only to wake the app. The
48 // persisted share inbox below owns navigation once the payload is durable.
49 filter: (url: string) =>
50 !url.includes("expo-development-client") && !url.includes("://expo-sharing"),
51};45};
5246
53const Navigation = createStaticNavigation(RootStack);47const Navigation = createStaticNavigation(RootStack);
apps/mobile/src/lib/appLinking.test.ts created+27
...@@ -0,0 +1,27 @@
1import { describe, expect, it } from "vite-plus/test";
2
3import { shouldHandleAppLink } from "./appLinking";
4
5describe("shouldHandleAppLink", () => {
6 it.each(["t3code://", "t3code:///", "t3code-dev://", "t3code-preview://"])(
7 "ignores scheme-only URL %s",
8 (url) => {
9 expect(shouldHandleAppLink(url)).toBe(false);
10 },
11 );
12
13 it.each([
14 "t3code://threads/env-1/thread-1",
15 "t3code://pair?pairingUrl=x",
16 "t3code-dev://settings/usage?tab=limits",
17 ])("handles path-bearing URL %s", (url) => {
18 expect(shouldHandleAppLink(url)).toBe(true);
19 });
20
21 it.each(["t3code://expo-development-client/?url=x", "t3code://expo-sharing/anything"])(
22 "ignores lifecycle URL %s",
23 (url) => {
24 expect(shouldHandleAppLink(url)).toBe(false);
25 },
26 );
27});
apps/mobile/src/lib/appLinking.ts created+17
...@@ -0,0 +1,17 @@
1/**
2 * The Expo dev client launches the app via
3 * <scheme>://expo-development-client/?url=<packager> — that URL addresses
4 * the launcher, not app navigation. Without this filter it falls through
5 * to the NotFound wildcard route on every dev launch.
6 * expo-sharing uses a private lifecycle URL only to wake the app. The
7 * persisted share inbox in App.tsx owns navigation once the payload is durable.
8 * A scheme-only URL, as sent by iOS dictation keyboards returning to the app,
9 * only wakes the app and must not reset navigation to Home.
10 */
11export function shouldHandleAppLink(url: string): boolean {
12 return (
13 !url.includes("expo-development-client") &&
14 !url.includes("://expo-sharing") &&
15 !/^t3code(-dev|-preview)?:\/*$/.test(url)
16 );
17}