11/// <reference types="vite/client" />
22/// <reference lib="dom" />
33
4- import type { DockPanelStorage } from '@vitejs/devtools-kit/client'
4+ import type { DevToolsDockConfig } from '@vitejs/devtools-kit'
5+ import type { DevToolsRpcClient , DockPanelStorage } from '@vitejs/devtools-kit/client'
6+ import type { Ref } from 'vue'
57import { CLIENT_CONTEXT_KEY , getDevToolsRpcClient } from '@vitejs/devtools-kit/client'
68import { DEVTOOLS_MOUNT_PATH } from '@vitejs/devtools-kit/constants'
79import { useLocalStorage } from '@vueuse/core'
810import { DEVTOOLS_HIDE_EVENT , DEVTOOLS_MODE_FILENAME } from '../../constants'
911import { createDocksContext } from '../webcomponents/state/context'
1012
13+ const DOCK_STATE_STORAGE_KEY = 'vite-devtools-dock-state'
14+
1115export type InjectMode = 'passive' | 'normal' | 'hidden'
1216
1317// Persistence endpoint the node middleware serves next to `__connection.json`.
@@ -33,6 +37,40 @@ function matchesActivation(event: KeyboardEvent): boolean {
3337 && ( event . code === 'KeyD' || event . key === 'd' || event . key === 'D' )
3438}
3539
40+ /**
41+ * Seeds `state.mode`/`.position` from the host's declared `defaultMode`/
42+ * `defaultPosition`, once, for a developer with no stored preference yet.
43+ *
44+ * The client's `sharedState.get()` resolves immediately with just
45+ * `initialValue` while the connection is still untrusted — the normal
46+ * first-load case — so the declared config usually lands after `state` (and
47+ * the dock) already exist. Applying it retroactively here, exactly once,
48+ * avoids depending on `useLocalStorage`'s `mergeDefaults`, which would have
49+ * already persisted the fallback `float`/`left` and made the seed a no-op on
50+ * every later load too.
51+ */
52+ export async function seedWindowDefaultsOnce ( rpc : DevToolsRpcClient , state : Ref < DockPanelStorage > ) : Promise < void > {
53+ const configState = await rpc . sharedState . get < DevToolsDockConfig > ( 'devtools:dock-config' , { initialValue : { } } )
54+
55+ const applyOnce = ( config : DevToolsDockConfig ) : boolean => {
56+ if ( config . defaultMode == null && config . defaultPosition == null )
57+ return false
58+ if ( config . defaultMode != null )
59+ state . value . mode = config . defaultMode
60+ if ( config . defaultPosition != null )
61+ state . value . position = config . defaultPosition
62+ return true
63+ }
64+
65+ if ( applyOnce ( configState . value ( ) ) )
66+ return
67+
68+ const unsubscribe = configState . on ( 'updated' , ( config ) => {
69+ if ( applyOnce ( config ) )
70+ unsubscribe ( )
71+ } )
72+ }
73+
3674async function persistNormalMode ( enabled : boolean ) : Promise < void > {
3775 try {
3876 await fetch ( MODE_URL , {
@@ -60,23 +98,26 @@ async function mountDock(): Promise<void> {
6098 ] ,
6199 } )
62100
63- // Plugin-declared mode/position only seed the defaults — `mergeDefaults` never
64- // overwrites a developer's stored preference.
65- const dockConfig = rpc . connectionMeta . dockConfig
101+ // Read before `useLocalStorage` creates/touches the entry below — the write
102+ // it does on every load would otherwise erase the "nothing stored yet"
103+ // signal `seedWindowDefaultsOnce` needs.
104+ const hasStoredDockState = localStorage . getItem ( DOCK_STATE_STORAGE_KEY ) !== null
66105 const state = useLocalStorage < DockPanelStorage > (
67- 'vite-devtools-dock-state' ,
106+ DOCK_STATE_STORAGE_KEY ,
68107 {
69- mode : dockConfig ?. defaultMode ?? 'float' ,
108+ mode : 'float' ,
70109 width : 80 ,
71110 height : 80 ,
72111 top : 0 ,
73112 left : 0 ,
74- position : dockConfig ?. defaultPosition ?? 'left' ,
113+ position : 'left' ,
75114 open : false ,
76115 inactiveTimeout : 3_000 ,
77116 } ,
78117 { mergeDefaults : true } ,
79118 )
119+ if ( ! hasStoredDockState )
120+ void seedWindowDefaultsOnce ( rpc , state )
80121
81122 const context = await createDocksContext (
82123 'embedded' ,
0 commit comments