@@ -69,8 +69,8 @@ const SessionRoute = Object.assign(
6969 createEffect ( ( ) => {
7070 if ( ! settings . general . newLayoutDesigns ( ) ) return
7171 if ( params . id || search . draftId ) return
72- if ( ! tabs . ready ( ) || ! sdk . directory ) return
73- tabs . newDraft ( { server : server . key , directory : sdk . directory } , search . prompt )
72+ if ( ! tabs . ready ( ) || ! sdk ( ) . directory ) return
73+ tabs . newDraft ( { server : server . key , directory : sdk ( ) . directory } , search . prompt )
7474 } )
7575
7676 return (
@@ -82,6 +82,45 @@ const SessionRoute = Object.assign(
8282 { preload : Session . preload } ,
8383)
8484
85+ // Wraps the non-draft routes. They are gated on (and keyed to) the globally selected
86+ // server via ServerKey, then provide the server-scoped shell (Permission/Layout/
87+ // Notification/Models + the visual Layout) for that server.
88+ function SelectedServerLayout ( props : ParentProps ) {
89+ return (
90+ < ServerKey >
91+ < ServerSDKProvider >
92+ < ServerSyncProvider >
93+ < ServerScopedShell > { props . children } </ ServerScopedShell >
94+ </ ServerSyncProvider >
95+ </ ServerSDKProvider >
96+ </ ServerKey >
97+ )
98+ }
99+
100+ // Wraps /new-session. It resolves the draft's target server and provides the
101+ // server-scoped shell for that server — without ServerKey, so the page never depends
102+ // on the globally "selected" server.
103+ function DraftServerLayout ( props : ParentProps ) {
104+ const server = useServer ( )
105+ const tabs = useTabs ( )
106+ const [ search ] = useSearchParams < { draftId ?: string } > ( )
107+ const conn = createMemo ( ( ) => {
108+ const id = search . draftId
109+ if ( ! id ) return undefined
110+ const draft = tabs . store . find ( ( tab ) : tab is DraftTab => tab . type === "draft" && tab . draftID === id )
111+ if ( ! draft ) return undefined
112+ return server . list . find ( ( c ) => ServerConnection . key ( c ) === draft . server )
113+ } )
114+
115+ return (
116+ < ServerSDKProvider server = { conn } >
117+ < ServerSyncProvider server = { conn } >
118+ < ServerScopedShell > { props . children } </ ServerScopedShell >
119+ </ ServerSyncProvider >
120+ </ ServerSDKProvider >
121+ )
122+ }
123+
85124function DraftRoute ( ) {
86125 const [ search ] = useSearchParams < { draftId ?: string } > ( )
87126 const tabs = useTabs ( )
@@ -95,19 +134,15 @@ function DraftRoute() {
95134}
96135
97136function ResolvedDraftRoute ( props : { draftID : string } ) {
98- const server = useServer ( )
99137 const tabs = useTabs ( )
100138 const draft = createMemo ( ( ) =>
101139 tabs . store . find ( ( tab ) : tab is DraftTab => tab . type === "draft" && tab . draftID === props . draftID ) ,
102140 )
103141
104- createEffect ( ( ) => {
105- const current = draft ( )
106- if ( current && current . server !== server . key ) server . setActive ( current . server )
107- } )
108-
109142 // Key on the directory so retargeting the draft's project re-instantiates the
110- // SDK/data providers for the new directory while keeping the same draft id.
143+ // directory-scoped providers while keeping the same draft id. The draft's target
144+ // server is provided by DraftServerLayout, so changing only the server updates the
145+ // SDK/sync hooks without remounting the composer.
111146 const directory = ( ) => draft ( ) ?. directory
112147
113148 return (
@@ -171,27 +206,36 @@ function BodyDesignClass() {
171206 return null
172207}
173208
174- function AppShellProviders ( props : ParentProps ) {
209+ // Server-agnostic providers shared across every route. These live in the shared
210+ // shell (router root) so they stay mounted regardless of the active server/route.
211+ function SharedProviders ( props : ParentProps ) {
175212 return (
176213 < SettingsProvider >
177214 < BodyDesignClass />
178- < PermissionProvider >
179- < LayoutProvider >
180- < NotificationProvider >
181- < ModelsProvider >
182- < CommandProvider >
183- < HighlightsProvider >
184- < Layout > { props . children } </ Layout >
185- </ HighlightsProvider >
186- </ CommandProvider >
187- </ ModelsProvider >
188- </ NotificationProvider >
189- </ LayoutProvider >
190- </ PermissionProvider >
215+ < CommandProvider >
216+ < HighlightsProvider > { props . children } </ HighlightsProvider >
217+ </ CommandProvider >
191218 </ SettingsProvider >
192219 )
193220}
194221
222+ // Server-scoped providers plus the visual Layout (tabs/sidebar). These live inside
223+ // each per-route server layout so they resolve to that route's server (selected vs
224+ // draft). The Layout remounts when crossing between those groups.
225+ function ServerScopedShell ( props : ParentProps ) {
226+ return (
227+ < PermissionProvider >
228+ < LayoutProvider >
229+ < NotificationProvider >
230+ < ModelsProvider >
231+ < Layout > { props . children } </ Layout >
232+ </ ModelsProvider >
233+ </ NotificationProvider >
234+ </ LayoutProvider >
235+ </ PermissionProvider >
236+ )
237+ }
238+
195239function SessionProviders ( props : ParentProps ) {
196240 return (
197241 < TerminalProvider >
@@ -216,17 +260,6 @@ function DraftProviders(props: ParentProps) {
216260 )
217261}
218262
219- function RouterRoot ( props : ParentProps < { appChildren ?: JSX . Element } > ) {
220- return (
221- < AppShellProviders >
222- { /*<Suspense fallback={<Loading />}>*/ }
223- { props . appChildren }
224- { props . children }
225- { /*</Suspense>*/ }
226- </ AppShellProviders >
227- )
228- }
229-
230263export function AppBaseProviders ( props : ParentProps < { locale ?: Locale } > ) {
231264 return (
232265 < MetaProvider >
@@ -385,6 +418,20 @@ export function AppInterface(props: {
385418 router ?: Component < BaseRouterProps >
386419 disableHealthCheck ?: boolean
387420} ) {
421+ // The shared shell holds only server-agnostic providers (QueryClient + Settings/
422+ // Command/Highlights) and stays mounted across every route. The server-scoped
423+ // providers and the visual Layout live in the per-route layouts below, so they
424+ // resolve to that route's server (selected for most routes, the draft's server for
425+ // /new-session). appChildren is server-agnostic, so it renders here once.
426+ const ServerShell = ( shellProps : ParentProps ) => (
427+ < QueryProvider >
428+ < SharedProviders >
429+ { props . children }
430+ { shellProps . children }
431+ </ SharedProviders >
432+ </ QueryProvider >
433+ )
434+
388435 return (
389436 < ServerProvider
390437 defaultServer = { props . defaultServer }
@@ -397,23 +444,19 @@ export function AppInterface(props: {
397444 component = { props . router ?? Router }
398445 root = { ( routerProps ) => (
399446 < TabsProvider >
400- < ServerKey >
401- < QueryProvider >
402- < ServerSDKProvider >
403- < ServerSyncProvider >
404- < RouterRoot appChildren = { props . children } > { routerProps . children } </ RouterRoot >
405- </ ServerSyncProvider >
406- </ ServerSDKProvider >
407- </ QueryProvider >
408- </ ServerKey >
447+ < ServerShell > { routerProps . children } </ ServerShell >
409448 </ TabsProvider >
410449 ) }
411450 >
412- < Route path = "/" component = { HomeRoute } />
413- < Route path = "/new-session" component = { DraftRoute } />
414- < Route path = "/:dir" component = { DirectoryLayout } >
415- < Route path = "/" component = { ( ) => < Navigate href = "session" /> } />
416- < Route path = "/session/:id?" component = { SessionRoute } />
451+ < Route component = { SelectedServerLayout } >
452+ < Route path = "/" component = { HomeRoute } />
453+ < Route path = "/:dir" component = { DirectoryLayout } >
454+ < Route path = "/" component = { ( ) => < Navigate href = "session" /> } />
455+ < Route path = "/session/:id?" component = { SessionRoute } />
456+ </ Route >
457+ </ Route >
458+ < Route component = { DraftServerLayout } >
459+ < Route path = "/new-session" component = { DraftRoute } />
417460 </ Route >
418461 </ Dynamic >
419462 </ ConnectionGate >
0 commit comments