@@ -6,12 +6,13 @@ import type { Ref } from 'vue'
66import type { DevframeDocksUserSettings } from './dock-settings'
77import { attachFrameNavClient , createDockRenderersContext } from '@devframes/hub/client'
88import { DEFAULT_STATE_USER_SETTINGS , DOCK_RENDERERS_STATE_KEY , HUB_EVENTS } from '@devframes/hub/constants'
9+ import { DEVFRAME_EVENTS } from 'devframe/constants'
910import { computed , markRaw , reactive , ref , toRefs , watch , watchEffect } from 'vue'
1011import { BUILTIN_ENTRIES , BUILTIN_ENTRY_SETTINGS , DEFAULT_CATEGORIES_ORDER , HUB_UI_HIDE_EVENT } from '../constants'
1112import { useBranding } from './branding'
1213import { createCommandsContext } from './commands'
1314import { 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'
15+ import { createDockEntryState , DEFAULT_DOCK_PANEL_STORE , DEFAULT_DOCK_SESSION_STORE , sharedStateToRef , useDocksEntries , waitForInitialSharedStateSync } from './docks'
1516import { createClientMessagesClient } from './messages-client'
1617import { registerMainFrameDockActionHandler , triggerMainFrameDockAction , useIsDockPopupOpen } from './popup'
1718import { executeSetupScript } from './setup-script'
@@ -27,16 +28,22 @@ export async function createDocksContext(
2728 return docksContextByRpc . get ( rpc ) !
2829 }
2930
30- const dockEntries = await useDocksEntries ( rpc )
31+ const { entries : dockEntries , initialSyncComplete : dockEntriesInitialSyncComplete } = await useDocksEntries ( rpc )
3132
3233 // The hub's renderer manifest (`initHub({ renderers })`): dock type →
3334 // prebuilt renderer-module entry. The registry below lazy-imports a module
3435 // the first time a dock of its type mounts; locally-registered renderers win.
36+ /** Identity marker replaced by the first server response, including an empty manifest. */
37+ const pendingRendererManifest : DockRendererManifest = { }
3538 const rendererManifestState = await rpc . sharedState . get < DockRendererManifest > (
3639 DOCK_RENDERERS_STATE_KEY ,
37- { initialValue : { } } ,
40+ { initialValue : pendingRendererManifest } ,
3841 )
3942 const rendererManifest = sharedStateToRef ( rendererManifestState )
43+ const rendererManifestInitialSyncComplete = waitForInitialSharedStateSync (
44+ rendererManifestState ,
45+ pendingRendererManifest ,
46+ )
4047
4148 // Client-only dock registry (0.7.10 `DocksEntriesContext` API). Docks
4249 // registered here live in this page only, merged over the server-provided
@@ -85,6 +92,10 @@ export async function createDocksContext(
8592 const restoreIntent = {
8693 ...sessionStore . value ,
8794 }
95+ /** Keep the persisted view unmounted until its server-backed registries are ready. */
96+ const initialRestorePending = ref (
97+ restoreIntent . open && restoreIntent . selectedDockId != null ,
98+ )
8899
89100 // `selectedDockId` is backed by the session store so the current selection both
90101 // drives the UI and persists across reloads through one source of truth.
@@ -101,11 +112,13 @@ export async function createDocksContext(
101112 } ,
102113 } )
103114
104- const selected = computed (
105- ( ) => entries . value . find ( entry => entry . id === selectedDockId . value )
115+ const selected = computed ( ( ) => {
116+ if ( initialRestorePending . value )
117+ return null
118+ return entries . value . find ( entry => entry . id === selectedDockId . value )
106119 ?? BUILTIN_ENTRIES . find ( entry => entry . id === selectedDockId . value )
107- ?? null ,
108- )
120+ ?? null
121+ } )
109122
110123 const dockEntryStateMap : Map < string , DockEntryState > = reactive ( new Map ( ) )
111124 watchEffect ( ( ) => {
@@ -200,12 +213,14 @@ export async function createDocksContext(
200213
201214 const switchEntry = async ( id : string | null = null ) => {
202215 if ( id == null ) {
216+ initialRestorePending . value = false
203217 selectedDockId . value = null
204218 sessionStore . value . open = false
205219 sessionStore . value . selectedDockRoute = null
206220 return true
207221 }
208222 if ( id === '~client-auth-notice' ) {
223+ initialRestorePending . value = false
209224 selectedDockId . value = id
210225 sessionStore . value . open = true
211226 return true
@@ -273,6 +288,7 @@ export async function createDocksContext(
273288 if ( entry . type === 'iframe' && entry . frameId && ! entry . subTabs )
274289 frameNavCurrentMember . set ( entry . frameId , entry . id )
275290
291+ initialRestorePending . value = false
276292 selectedDockId . value = entry . id
277293 sessionStore . value . open = true
278294 // Only an iframe dock owns an address-bar route; ViewIframe keeps
@@ -602,29 +618,48 @@ export async function createDocksContext(
602618 return switchEntry ( entry . id )
603619 } )
604620
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 ( )
621+ const waitUntilTrusted = async ( ) : Promise < void > => {
622+ if ( rpc . isTrusted )
623+ return
624+ await new Promise < void > ( ( resolve ) => {
625+ const stopListening = rpc . events . on ( DEVFRAME_EVENTS . client . isTrustedUpdated , ( isTrusted ) => {
626+ if ( ! isTrusted )
627+ return
628+ stopListening ( )
629+ resolve ( )
630+ } )
625631 } )
626632 }
627633
634+ // A reload starts untrusted, and Dock.vue temporarily closes the panel during
635+ // that window. The trust event precedes the asynchronous `devframe:docks`
636+ // and renderer-manifest responses, so wait for all three before re-applying
637+ // the captured session intent.
638+ // `switchEntry` then consumes the persisted iframe route when the view boots.
639+ const restoreAfterInitialization = async ( ) : Promise < void > => {
640+ const restoreDockId = restoreIntent . selectedDockId
641+ if ( ! restoreIntent . open || restoreDockId == null )
642+ return
643+
644+ await Promise . all ( [
645+ waitUntilTrusted ( ) ,
646+ dockEntriesInitialSyncComplete ,
647+ rendererManifestInitialSyncComplete ,
648+ ] )
649+
650+ if ( ! initialRestorePending . value )
651+ return
652+
653+ if ( selectedDockId . value !== restoreDockId ) {
654+ initialRestorePending . value = false
655+ return
656+ }
657+
658+ initialRestorePending . value = false
659+ await switchEntry ( restoreDockId )
660+ }
661+ void restoreAfterInitialization ( )
662+
628663 docksContextByRpc . set ( rpc , docksContext )
629664 return docksContext
630665}
0 commit comments