diff --git a/components/mail/mail-app.tsx b/components/mail/mail-app.tsx index 33d2ced23..b4c8be3b5 100644 --- a/components/mail/mail-app.tsx +++ b/components/mail/mail-app.tsx @@ -157,7 +157,7 @@ import { import { consumePendingDeepLink, subscribePendingDeepLink } from "@/lib/deep-link-handoff"; import { useProInterfaceActive } from "@/components/pro/pro-interface-redirect"; import type { QuoteHeader } from "@/lib/plugin-types"; -import { useLiteLinkSegments } from "@/hooks/use-lite-link-segments"; +import { initialLinkSearch, useLiteLinkSegments } from "@/hooks/use-lite-link-segments"; export interface MailAppProps { /** @@ -1893,7 +1893,7 @@ export function MailApp({ linkSegments: routeSegments }: MailAppProps = {}) { // actually opened with on first client render. const initialSearchRef = useRef(null); if (initialSearchRef.current === null && typeof window !== "undefined") { - initialSearchRef.current = window.location.search; + initialSearchRef.current = initialLinkSearch('mail'); } useEffect(() => { diff --git a/hooks/__tests__/use-lite-link-segments.test.ts b/hooks/__tests__/use-lite-link-segments.test.ts index c3210bcd3..c4e6137d5 100644 --- a/hooks/__tests__/use-lite-link-segments.test.ts +++ b/hooks/__tests__/use-lite-link-segments.test.ts @@ -14,9 +14,41 @@ vi.mock('@/lib/lite', async (importOriginal) => { }; }); -import { useLiteLinkSegments } from '@/hooks/use-lite-link-segments'; +import { initialLinkSearch, useLiteLinkSegments } from '@/hooks/use-lite-link-segments'; import { LITE_PENDING_PATH_KEY } from '@/lib/lite'; +// Mail reads the query it was opened with on its first render, while the +// address bar still shows the surface root the 404 shim jumped to (#1352). +describe('initialLinkSearch', () => { + beforeEach(() => sessionStorage.clear()); + + it('takes the query of a parked deep link in the first render', () => { + liteState.IS_LITE = true; + window.history.replaceState({}, '', '/en/mail/'); + sessionStorage.setItem(LITE_PENDING_PATH_KEY, '/en/mail/message/m1/?account=bob%40example.org#x'); + + const seen: string[] = []; + renderHook(() => { + useLiteLinkSegments('mail', []); + seen.push(initialLinkSearch('mail')); + }); + + expect(seen[0]).toBe('?account=bob%40example.org'); + // The restored URL carries it from then on. + expect(window.location.search).toBe('?account=bob%40example.org'); + }); + + it('reads the address bar otherwise', () => { + liteState.IS_LITE = true; + window.history.replaceState({}, '', '/en/mail/message/m1?account=a'); + sessionStorage.setItem(LITE_PENDING_PATH_KEY, '/en/calendar/week/2026-09-17?view=1'); + expect(initialLinkSearch('mail')).toBe('?account=a'); + liteState.IS_LITE = false; + sessionStorage.setItem(LITE_PENDING_PATH_KEY, '/en/mail/message/m2?account=b'); + expect(initialLinkSearch('mail')).toBe('?account=a'); + }); +}); + describe('useLiteLinkSegments', () => { beforeEach(() => { sessionStorage.clear(); diff --git a/hooks/use-lite-link-segments.ts b/hooks/use-lite-link-segments.ts index fcda099f4..a899252c4 100644 --- a/hooks/use-lite-link-segments.ts +++ b/hooks/use-lite-link-segments.ts @@ -53,3 +53,16 @@ export function useLiteLinkSegments(surface: AppSurface, linkSegments?: string[] return segments; } + +/** + * The query a surface was opened with, for a caller that reads it during its + * first render: until the effect above restores a parked deep link, the + * address bar shows the surface root without the link's query (#1352). + */ +export function initialLinkSearch(surface: AppSurface): string { + const pending = IS_LITE ? peekPendingLitePath(surface, getPathPrefix()) : null; + if (pending === null) return window.location.search; + const query = pending.split('#')[0]; + const start = query.indexOf('?'); + return start === -1 ? '' : query.slice(start); +}