@@ -44,6 +44,20 @@ export function primaryFolder(ctx: FolderServerCtx) {
4444 return ctx . sync . data . path . directory || ctx . sync . data . path . worktree || undefined
4545}
4646
47+ // The folder a server's strip scopes to when neither the screen nor the
48+ // sidebar picks one. A sandbox owns the folder it was created with; a Code
49+ // server has no such folder, so its last-used project stands in, then its
50+ // first. The last-used worktree is resolved through the folder list because
51+ // it can name a project that has since been closed.
52+ function defaultFolder (
53+ side : WorkspaceSide ,
54+ ctx : FolderServerCtx & { projects : { last : ( ) => string | undefined } } ,
55+ folders : string [ ] ,
56+ ) {
57+ if ( side === "work" ) return primaryFolder ( ctx )
58+ return folderForDirectory ( ctx . projects . last ( ) , folders ) ?? folders [ 0 ]
59+ }
60+
4761// The side everything should actually render, as opposed to the side that
4862// happens to be persisted. Read this rather than layout.home.workspace().
4963export function useWorkspaceSide ( ) {
@@ -77,7 +91,7 @@ export function useWorkspaceFolder() {
7791 lastSession : layout . home . folderSession ( server , directory ) ,
7892 serverExists : ! ! conn ,
7993 openDraft : findOpenDraft ( tabs . store , { server, directory } ) ,
80- openSessions : conn && sideForServer ( conn ) === "work" ? openSessionsIn ( conn , server , directory ) : undefined ,
94+ openSessions : conn ? openSessionsIn ( conn , server , directory ) : undefined ,
8195 exists : ( sessionId ) => ( conn ? sessionAlive ( conn , sessionId ) : Promise . resolve ( false ) ) ,
8296 recentSession : ( ) => ( conn ? recentFolderSession ( conn , directory ) : Promise . resolve ( undefined ) ) ,
8397 } )
@@ -124,7 +138,7 @@ export function useWorkspaceFolder() {
124138 // The session tabs that resolve to exactly this folder, in strip order.
125139 // Resolved through the same folders list the strip uses, so the tab landed on
126140 // is always one the strip will be showing. Deliberately skips tabs whose
127- // folder is unknown — those are visible in every folder of the sandbox , so
141+ // folder is unknown — those are visible in every folder of their server , so
128142 // picking one could send you to a tab that belongs elsewhere the moment it
129143 // resolves.
130144 const openSessionsIn = ( conn : ServerConnection . Any , server : ServerConnection . Key , directory : string ) => {
@@ -141,45 +155,50 @@ export function useWorkspaceFolder() {
141155 return { enterFolder, sessionAlive }
142156}
143157
144- // What the tab strip shows. Code is the whole side, unchanged. Work narrows to
145- // one mounted folder of one sandbox, resolved from the folder on screen first,
146- // so the strip always contains the session you are looking at: a deep link, a
158+ // What the tab strip shows. Each side narrows to one folder of the sidebar's
159+ // selected server — a mounted folder of one sandbox on Work, a project of one
160+ // server on Code — resolved from the folder on screen first, so the strip
161+ // always contains the session you are looking at: a deep link, a
147162// command-palette jump or a reopened tab pulls the strip to its own folder
148163// instead of leaving the active tab out of view. Only when nothing is on screen
149- // does the sidebar's selected project decide, and failing that the sandbox's
150- // primary folder — the strip is never unscoped.
164+ // does the sidebar's selected project decide, and failing that the side's
165+ // default folder — the strip is never unscoped. On web, which has no Code/Work
166+ // split, the strip stays upstream's one list for everything.
151167export function useWorkspaceScope ( routeDirectory : ( ) => string | undefined ) : Accessor < TabScope > {
152168 const layout = useLayout ( )
153169 const global = useGlobal ( )
154170 const server = useServer ( )
171+ const platform = usePlatform ( )
155172 const side = useWorkspaceSide ( )
156173
157- const sandbox = createMemo ( ( ) => {
158- if ( side ( ) !== "work" ) return
174+ const selected = createMemo ( ( ) => {
175+ // Sandboxes are desktop-only; without them there is no Code/Work split and
176+ // the strip keeps upstream's behaviour, one list for every server.
177+ if ( ! platform . sandboxes ) return
159178 const key = layout . home . selection ( ) . server
160179 const conn = global . servers . list ( ) . find ( ( item ) => ServerConnection . key ( item ) === key )
161- if ( ! conn || sideForServer ( conn ) !== "work" ) return
180+ if ( ! conn || sideForServer ( conn ) !== side ( ) ) return
162181 return { key : ServerConnection . key ( conn ) , ctx : global . ensureServerCtx ( conn ) }
163182 } )
164183
165184 // The decision lives in nextTabScope (side.ts) with the tests: it holds a
166- // restarting sandbox's folder scope still, but only while the side is still
167- // Work — sandbox () is also empty after a switch to Code , and holding there
168- // kept Work's tabs on the strip while every Code tab was filtered out.
185+ // restarting sandbox's folder scope still, but only while its side is still
186+ // showing — selected () is also empty right after a side switch , and holding
187+ // there kept Work's tabs on the strip while every Code tab was filtered out.
169188 const scope = createMemo < TabScope > ( ( previous ) => {
170- const current = sandbox ( )
189+ const current = selected ( )
171190 const folders = current ? current . ctx . projects . list ( ) . map ( ( project ) => project . worktree ) : [ ]
172191 return nextTabScope ( {
173192 side : side ( ) ,
174193 previous,
175194 retained : server . retained ,
176- sandbox : current && {
195+ selected : current && {
177196 server : current . key ,
178197 folders,
179198 directory :
180199 folderForDirectory ( routeDirectory ( ) , folders ) ??
181200 folderForDirectory ( layout . home . selection ( ) . directory , folders ) ??
182- primaryFolder ( current . ctx ) ,
201+ defaultFolder ( side ( ) , current . ctx , folders ) ,
183202 } ,
184203 } )
185204 } )
@@ -246,16 +265,16 @@ export function useWorkspaceSwitch() {
246265 // neither, the home screen is the only honest destination — that side has
247266 // nothing to open yet.
248267 //
249- // Work resolves the sandbox's own project folder, so switching to Work
250- // lands on the same folder its strip will be scoped to. Code keeps its
251- // first-project fallback .
252- // Unless that folder is a placeholder the app made up, which is hidden
253- // everywhere else and would be a strange place to land: fall back to a
254- // mounted folder, and to the home screen — where the prompt to mount one
255- // lives — when the user has not mounted any yet.
268+ // Work resolves the sandbox's own project folder and Code its last-used
269+ // project, so switching sides lands on the same folder the strip will be
270+ // scoped to (defaultFolder, one level up the same chain) .
271+ // Unless Work's own folder is a placeholder the app made up, which is
272+ // hidden everywhere else and would be a strange place to land: fall back
273+ // to a mounted folder, and to the home screen — where the prompt to mount
274+ // one lives — when the user has not mounted any yet.
256275 const ctx = global . ensureServerCtx ( target )
257- const own = side === "work" && ! isPlaceholderSandbox ( target )
258- const directory = own ? primaryFolder ( ctx ) : ctx . projects . list ( ) [ 0 ] ?. worktree
276+ const folders = ctx . projects . list ( ) . map ( ( project ) => project . worktree )
277+ const directory = side === "work" && isPlaceholderSandbox ( target ) ? folders [ 0 ] : defaultFolder ( side , ctx , folders )
259278 if ( ! directory ) {
260279 navigate ( "/" )
261280 return
0 commit comments