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";
2222import { appAtomRegistry } from "./state/atom-registry";
2323import { OverlayPortalHost } from "./components/OverlayPortal";
2424import { appBlurTargetRef } from "./lib/appBlurTarget";
25import { shouldHandleAppLink } from "./lib/appLinking";
2526import { useMobileNavigationTheme } from "./lib/useMobileNavigationTheme";
2627
2728import { SubscriptionUsageCoordinator } from "./widgets/SubscriptionUsageCoordinator";
......@@ -40,14 +41,7 @@ const appLinking = {
4041 prefixes: [Linking.createURL("/"), "t3code://", "t3code-dev://", "t3code-preview://"],
4142 // Keep the compact thread list available beneath a directly opened thread.
4243 config: { initialRouteName: "Home" },
43 // The Expo dev client launches the app via
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"),
44 filter: shouldHandleAppLink,
5145};
5246
5347const 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}