@@ -11,7 +11,7 @@ import { BUILTIN_ENTRIES, BUILTIN_ENTRY_SETTINGS, DEFAULT_CATEGORIES_ORDER, HUB_
1111import { useBranding } from './branding'
1212import { createCommandsContext } from './commands'
1313import { docksGroupByCategories , getCategoryLabel , getGroupMembers , getGroupMembersGrouped , getRegisteredGroupIds , resolveCommandIcon , resolveGroupDefaultChild } from './dock-settings'
14- import { createDockEntryState , DEFAULT_DOCK_PANEL_STORE , DEFAULT_DOCK_SESSION_STORE , sharedStateToRef , useDocksEntries } from './docks'
14+ import { createDockEntryState , DEFAULT_DOCK_PANEL_STORE , DEFAULT_DOCK_SESSION_STORE , sharedStateToRef , useDocksEntries , waitForInitialSharedStateSync } from './docks'
1515import { createClientMessagesClient } from './messages-client'
1616import { registerMainFrameDockActionHandler , triggerMainFrameDockAction , useIsDockPopupOpen } from './popup'
1717import { executeSetupScript } from './setup-script'
@@ -27,16 +27,22 @@ export async function createDocksContext(
2727 return docksContextByRpc . get ( rpc ) !
2828 }
2929
30- const dockEntries = await useDocksEntries ( rpc )
30+ const { entries : dockEntries , initialSyncComplete : dockEntriesInitialSyncComplete } = await useDocksEntries ( rpc )
3131
3232 // The hub's renderer manifest (`initHub({ renderers })`): dock type →
3333 // prebuilt renderer-module entry. The registry below lazy-imports a module
3434 // the first time a dock of its type mounts; locally-registered renderers win.
35+ /** Identity marker replaced by the first server response, including an empty manifest. */
36+ const pendingRendererManifest : DockRendererManifest = { }
3537 const rendererManifestState = await rpc . sharedState . get < DockRendererManifest > (
3638 DOCK_RENDERERS_STATE_KEY ,
37- { initialValue : { } } ,
39+ { initialValue : pendingRendererManifest } ,
3840 )
3941 const rendererManifest = sharedStateToRef ( rendererManifestState )
42+ const rendererManifestInitialSyncComplete = waitForInitialSharedStateSync (
43+ rendererManifestState ,
44+ pendingRendererManifest ,
45+ )
4046
4147 // Client-only dock registry (0.7.10 `DocksEntriesContext` API). Docks
4248 // registered here live in this page only, merged over the server-provided
@@ -85,6 +91,10 @@ export async function createDocksContext(
8591 const restoreIntent = {
8692 ...sessionStore . value ,
8793 }
94+ /** Keep the persisted view unmounted until its server-backed registries are ready. */
95+ const initialRestorePending = ref (
96+ restoreIntent . open && restoreIntent . selectedDockId != null ,
97+ )
8898
8999 // `selectedDockId` is backed by the session store so the current selection both
90100 // drives the UI and persists across reloads through one source of truth.
@@ -101,11 +111,13 @@ export async function createDocksContext(
101111 } ,
102112 } )
103113
104- const selected = computed (
105- ( ) => entries . value . find ( entry => entry . id === selectedDockId . value )
114+ const selected = computed ( ( ) => {
115+ if ( initialRestorePending . value )
116+ return null
117+ return entries . value . find ( entry => entry . id === selectedDockId . value )
106118 ?? BUILTIN_ENTRIES . find ( entry => entry . id === selectedDockId . value )
107- ?? null ,
108- )
119+ ?? null
120+ } )
109121
110122 const dockEntryStateMap : Map < string , DockEntryState > = reactive ( new Map ( ) )
111123 watchEffect ( ( ) => {
@@ -200,12 +212,14 @@ export async function createDocksContext(
200212
201213 const switchEntry = async ( id : string | null = null ) => {
202214 if ( id == null ) {
215+ initialRestorePending . value = false
203216 selectedDockId . value = null
204217 sessionStore . value . open = false
205218 sessionStore . value . selectedDockRoute = null
206219 return true
207220 }
208221 if ( id === '~client-auth-notice' ) {
222+ initialRestorePending . value = false
209223 selectedDockId . value = id
210224 sessionStore . value . open = true
211225 return true
@@ -273,6 +287,7 @@ export async function createDocksContext(
273287 if ( entry . type === 'iframe' && entry . frameId && ! entry . subTabs )
274288 frameNavCurrentMember . set ( entry . frameId , entry . id )
275289
290+ initialRestorePending . value = false
276291 selectedDockId . value = entry . id
277292 sessionStore . value . open = true
278293 // Only an iframe dock owns an address-bar route; ViewIframe keeps
@@ -602,29 +617,48 @@ export async function createDocksContext(
602617 return switchEntry ( entry . id )
603618 } )
604619
605- // Restore the persisted selection once the RPC is trusted. A reload starts
606- // untrusted, and Dock.vue force-closes the panel during that window (and a
607- // revocation clears the selection), so the durable intent captured in
608- // `restoreIntent` is re-applied here after the handshake — re-running the
609- // dock's setup script and re-opening the panel on the dock the developer left
610- // open. `switchEntry` reads `session.selectedDockRoute` back through `consumeBootRoute`
611- // when the restored iframe boots.
612- const applyRestore = ( ) : void => {
613- if ( restoreIntent . open && restoreIntent . selectedDockId != null )
614- void switchEntry ( restoreIntent . selectedDockId )
615- }
616- if ( rpc . isTrusted ) {
617- applyRestore ( )
618- }
619- else {
620- const off = rpc . events . on ( 'rpc:is-trusted:updated' , ( isTrusted ) => {
621- if ( ! isTrusted )
622- return
623- off ( )
624- applyRestore ( )
620+ const waitUntilTrusted = async ( ) : Promise < void > => {
621+ if ( rpc . isTrusted )
622+ return
623+ await new Promise < void > ( ( resolve ) => {
624+ const stopListening = rpc . events . on ( 'rpc:is-trusted:updated' , ( isTrusted ) => {
625+ if ( ! isTrusted )
626+ return
627+ stopListening ( )
628+ resolve ( )
629+ } )
625630 } )
626631 }
627632
633+ // A reload starts untrusted, and Dock.vue temporarily closes the panel during
634+ // that window. The trust event precedes the asynchronous `devframe:docks`
635+ // and renderer-manifest responses, so wait for all three before re-applying
636+ // the captured session intent.
637+ // `switchEntry` then consumes the persisted iframe route when the view boots.
638+ const restoreAfterInitialization = async ( ) : Promise < void > => {
639+ const restoreDockId = restoreIntent . selectedDockId
640+ if ( ! restoreIntent . open || restoreDockId == null )
641+ return
642+
643+ await Promise . all ( [
644+ waitUntilTrusted ( ) ,
645+ dockEntriesInitialSyncComplete ,
646+ rendererManifestInitialSyncComplete ,
647+ ] )
648+
649+ if ( ! initialRestorePending . value )
650+ return
651+
652+ if ( selectedDockId . value !== restoreDockId ) {
653+ initialRestorePending . value = false
654+ return
655+ }
656+
657+ initialRestorePending . value = false
658+ await switchEntry ( restoreDockId )
659+ }
660+ void restoreAfterInitialization ( )
661+
628662 docksContextByRpc . set ( rpc , docksContext )
629663 return docksContext
630664}
0 commit comments