@@ -173,9 +173,8 @@ export interface RegisterCollabContextRoutesDeps {
173173 context : WorkspaceCollabContext ,
174174 ) => Promise < CollabCloudMemberDirectoryEntry [ ] > ;
175175 /**
176- * Legacy local selection store retained for compatibility wiring. Data-plane
177- * routes do not read or mutate it; each tab carries its exact Workspace and
178- * member identity on the request.
176+ * Client-local restart default. Data-plane routes never use it as authority;
177+ * each tab continues to carry its exact Workspace and member identity.
179178 */
180179 activeWorkspace ?: {
181180 get ( ) : string | null ;
@@ -549,7 +548,21 @@ export function registerCollabContextRoutes(app: Express, deps: RegisterCollabCo
549548 fetchWorkspaceDirectory : async ( ) => directory ,
550549 configuredEnv : configuredEnv ( ) ,
551550 } ) ;
552- const activeWorkspaceId = claimed . ok ? claimed . context . workspaceId : null ;
551+ const savedWorkspaceId = deps . activeWorkspace ?. get ( ) ?. trim ( ) || null ;
552+ const savedWorkspaceIsVisible = Boolean (
553+ savedWorkspaceId
554+ && items . some (
555+ ( item ) =>
556+ item . workspaceId === savedWorkspaceId
557+ && item . memberStatus === 'active'
558+ && item . lifecycleState !== 'deleted' ,
559+ ) ,
560+ ) ;
561+ if ( savedWorkspaceId && ! savedWorkspaceIsVisible ) {
562+ await deps . activeWorkspace ?. clear ( ) . catch ( ( ) => undefined ) ;
563+ }
564+ let activeWorkspaceId = savedWorkspaceIsVisible ? savedWorkspaceId : null ;
565+ if ( claimed . ok ) activeWorkspaceId = claimed . context . workspaceId ;
553566 const body : WorkspaceDirectoryResponse = { items, activeWorkspaceId } ;
554567 res . json ( body ) ;
555568 } ) ;
@@ -588,8 +601,8 @@ export function registerCollabContextRoutes(app: Express, deps: RegisterCollabCo
588601 // Matching on the id alone would let a listed-but-removed membership (or a
589602 // deleted workspace) through, and this entry is also what gets synthesized
590603 // into the response below — so an unfiltered match could describe a
591- // workspace the caller no longer holds. Same predicate the provider's own
592- // `resolvePinnedWorkspace` uses .
604+ // workspace the caller no longer holds. This matches the context provider's
605+ // directory-selection predicate .
593606 const selected = directory . find (
594607 ( item ) =>
595608 item . workspaceId === workspaceId &&
@@ -601,9 +614,10 @@ export function registerCollabContextRoutes(app: Express, deps: RegisterCollabCo
601614 return res . status ( 404 ) . json ( { error : 'workspace_not_visible' } ) ;
602615 }
603616
604- // Choosing a workspace is tab-local. The membership directory above is the
605- // authorization; neither this compatibility endpoint nor any data-plane
606- // route writes a daemon-global active Workspace.
617+ // The membership directory above is the authorization. Persist the choice
618+ // only as this client's next-start default; data-plane routes continue to
619+ // require the exact Workspace/member pair on every request, so another tab
620+ // already operating in a different workspace keeps its own scope.
607621 //
608622 // This used to PUT B's account-level active workspace first and fail the
609623 // user's click (502) when that write did not take. That row is keyed by app
@@ -628,6 +642,16 @@ export function registerCollabContextRoutes(app: Express, deps: RegisterCollabCo
628642 return res . status ( 404 ) . json ( { error : 'workspace_no_longer_available' } ) ;
629643 }
630644 const resolved = context ?? workspaceContextFromDirectoryItem ( selected , configuredEnv ( ) ) ;
645+ try {
646+ await deps . activeWorkspace ?. set ( workspaceId ) ;
647+ } catch {
648+ return sendApiError (
649+ res ,
650+ 500 ,
651+ 'INTERNAL_ERROR' ,
652+ 'failed to persist the selected workspace' ,
653+ ) ;
654+ }
631655 // Warm this exact workspace's cold caches before responding, but never
632656 // await them — a slow upstream must not delay the tab-local selection.
633657 deps . onWorkspaceSwitched ?.( workspaceId ) ;
0 commit comments