@@ -62,7 +62,7 @@ export function useRouter(): RouterContext {
6262 return ctx ;
6363}
6464
65- const STANDALONE_PWA_SESSION_KEY = "muxStandaloneSessionInitialized" ;
65+ type StartupNavigationType = "navigate" | "reload" | "back_forward" | "prerender" | null ;
6666
6767function isStandalonePwa ( ) : boolean {
6868 return (
@@ -71,20 +71,52 @@ function isStandalonePwa(): boolean {
7171 ) ;
7272}
7373
74- function hasStandalonePwaSessionInitialized ( ) : boolean {
75- try {
76- return window . sessionStorage . getItem ( STANDALONE_PWA_SESSION_KEY ) === "1" ;
77- } catch {
78- return false ;
74+ function getStartupNavigationType ( ) : StartupNavigationType {
75+ const entries = window . performance ?. getEntriesByType ?.( "navigation" ) ;
76+ const firstEntry = entries ?. [ 0 ] ;
77+ const entryType =
78+ firstEntry && typeof firstEntry === "object" && "type" in firstEntry ? firstEntry . type : null ;
79+
80+ if (
81+ entryType === "navigate" ||
82+ entryType === "reload" ||
83+ entryType === "back_forward" ||
84+ entryType === "prerender"
85+ ) {
86+ return entryType ;
87+ }
88+
89+ const legacyType = window . performance ?. navigation ?. type ;
90+ if ( legacyType === 1 ) {
91+ return "reload" ;
7992 }
93+ if ( legacyType === 2 ) {
94+ return "back_forward" ;
95+ }
96+ if ( legacyType === 0 ) {
97+ return "navigate" ;
98+ }
99+
100+ return null ;
80101}
81102
82- function markStandalonePwaSessionInitialized ( ) : void {
83- try {
84- window . sessionStorage . setItem ( STANDALONE_PWA_SESSION_KEY , "1" ) ;
85- } catch {
86- // If sessionStorage is unavailable, fall back to treating each load as a fresh launch.
103+ function isRouteRestoringNavigationType ( type : StartupNavigationType ) : boolean {
104+ return type === "reload" || type === "back_forward" ;
105+ }
106+
107+ function shouldRestoreWorkspaceUrlOnStartup ( options : {
108+ isStandalone : boolean ;
109+ launchBehavior : LaunchBehavior | null ;
110+ navigationType : StartupNavigationType ;
111+ } ) : boolean {
112+ if ( options . isStandalone ) {
113+ return isRouteRestoringNavigationType ( options . navigationType ) ;
87114 }
115+
116+ return (
117+ options . launchBehavior === "last-workspace" ||
118+ isRouteRestoringNavigationType ( options . navigationType )
119+ ) ;
88120}
89121
90122function hasValidEncodedPathSegment ( encodedValue : string ) : boolean {
@@ -147,12 +179,10 @@ function isRestorableRoute(route: unknown): route is string {
147179function getInitialRoute ( ) : string {
148180 const isStorybook = window . location . pathname . endsWith ( "iframe.html" ) ;
149181 const isStandalone = isStandalonePwa ( ) ;
150- const hasStandaloneSession = hasStandalonePwaSessionInitialized ( ) ;
182+ const navigationType = getStartupNavigationType ( ) ;
151183 const launchBehavior = ! isStandalone
152184 ? readPersistedState < LaunchBehavior > ( LAUNCH_BEHAVIOR_KEY , "dashboard" )
153185 : null ;
154- const shouldIgnoreStandaloneWorkspaceUrl =
155- isStandalone && ! hasStandaloneSession && window . location . pathname . startsWith ( "/workspace/" ) ;
156186
157187 if ( window . location . protocol === "file:" ) {
158188 const persistedRoute = readPersistedState < string | null > ( LAST_VISITED_ROUTE_KEY , null ) ;
@@ -161,21 +191,24 @@ function getInitialRoute(): string {
161191 }
162192 }
163193
164- // In browser mode (not Storybook), read route directly from URL (enables refresh restoration).
165- // Standalone PWAs intentionally ignore stale workspace URLs on cold launch so opening the app
166- // lands on the default root entrypoint, while preserving explicit deep links like /settings
167- // or /project.
168- if ( window . location . protocol !== "file:" && ! isStorybook && ! shouldIgnoreStandaloneWorkspaceUrl ) {
194+ // In browser mode (not Storybook), read route directly from the current URL. Workspace
195+ // routes are special: fresh launches may ignore them, but explicit restore-style navigations
196+ // such as hard reload/back-forward should reopen the same chat.
197+ if ( window . location . protocol !== "file:" && ! isStorybook ) {
169198 const url = window . location . pathname + window . location . search ;
170199 // Only use URL if it's a valid route (starts with /, not just "/" or empty)
171200 if ( url . startsWith ( "/" ) && url !== "/" ) {
172201 if ( ! url . startsWith ( "/workspace/" ) ) {
173202 return url ;
174203 }
175204
176- // Respect dashboard/new-chat launch preferences in browser mode so stale workspace URLs
177- // do not silently override the user's chosen startup destination on a fresh launch.
178- if ( isStandalone || launchBehavior === "last-workspace" ) {
205+ if (
206+ shouldRestoreWorkspaceUrlOnStartup ( {
207+ isStandalone,
208+ launchBehavior,
209+ navigationType,
210+ } )
211+ ) {
179212 return url ;
180213 }
181214 }
@@ -427,13 +460,6 @@ function RouterContextInner(props: { children: ReactNode }) {
427460// causing a flash of stale UI between normal-priority updates (e.g.
428461// setIsSending(false)) and the deferred route change.
429462export function RouterProvider ( props : { children : ReactNode } ) {
430- useEffect ( ( ) => {
431- if ( ! isStandalonePwa ( ) ) return ;
432- // Mark the standalone session after commit so StrictMode's throwaway renders cannot
433- // flip a cold launch into the reload path before the first real paint.
434- markStandalonePwaSessionInitialized ( ) ;
435- } , [ ] ) ;
436-
437463 return (
438464 < MemoryRouter initialEntries = { [ getInitialRoute ( ) ] } unstable_useTransitions = { false } >
439465 < RouterContextInner > { props . children } </ RouterContextInner >
0 commit comments