fix(desktop): rebind the Files pane cwd when switching sessions
Two defects left the Files pane showing the previous project's tree: - `applyStoredSessionPreviewRuntimeInfo` reset every composer atom EXCEPT cwd, and runs before the `session.resume` RPC. The sidebar row already knows the conversation's workspace (`cwd` is in the compact row projection), so mirror it on the same tick the selection changes. - `if (info.cwd)` was truthy-only, so a detached session reporting `cwd: ''` never cleared and the pane stayed pinned to the last project for the rest of the session — the "not always" in the report. Empty is now authoritative. Empty routes through ownership release rather than a persisted `''`: `setCurrentCwd` writes to localStorage and seeds `$currentCwd` on next boot, so blanking would also wipe the remembered workspace. Only `cwd` is consulted, never `git_repo_root` — the latter is documented null for non-git workspaces and not-yet-backfilled rows, so falling back to it reads as "no workspace" and blanks a pane that was correct. A session outside the loaded sidebar page (no row at all) releases ownership instead of blanking, for the same reason. Also claims ownership on the warm-cache path (its missing-RPC compat branch returns before `applyRuntimeInfo`) and for a center tile, whose Project "+" create left the right rail on the previous session's folder. Co-authored-by: xxxigm <tuancanhnguyen706@gmail.com> Co-authored-by: worlldz <worlldz@users.noreply.github.com> Co-authored-by: ZHJay <ZHJay@users.noreply.github.com>
This commit is contained in:
parent
ae6eb578bb
commit
416e025c46
|
|
@ -54,6 +54,7 @@ import {
|
|||
setSessions,
|
||||
setSessionStartedAt,
|
||||
setTurnStartedAt,
|
||||
setWorkspaceCwdOwner,
|
||||
setYoloActive
|
||||
} from '@/store/session'
|
||||
import {
|
||||
|
|
@ -350,6 +351,11 @@ export function useSessionActions({
|
|||
setCurrentCwd(workspaceTarget)
|
||||
}
|
||||
|
||||
// A fresh draft resolves its own workspace right here, so it owns it. The
|
||||
// selected stored id is null for a draft, and so is the owner — they match,
|
||||
// which keeps workspace surfaces live on a new chat instead of treating the
|
||||
// draft as an un-re-homed switch (#71254).
|
||||
setWorkspaceCwdOwner(null)
|
||||
setCurrentBranch('')
|
||||
// Never clear the composer here — ChatBar's per-thread draft swap owns it.
|
||||
setFreshDraftReady(true)
|
||||
|
|
@ -514,13 +520,23 @@ export function useSessionActions({
|
|||
upsertOptimisticSession(created, stored, null, null)
|
||||
}
|
||||
|
||||
// A tile lives in its OWN worktree — it must not publish its cwd/branch
|
||||
// into the composer atoms the main pane renders from.
|
||||
// A tile lives in its OWN worktree, so it must not run the full
|
||||
// foreground composer publish. A CENTER tile is the focused surface,
|
||||
// though, and the Files pane still keys off the global `$currentCwd` —
|
||||
// so the right rail kept showing the previous session's tree when a
|
||||
// Project "+" created a session while the main chat was occupied
|
||||
// (#76696). Split/side tiles deliberately stay isolated.
|
||||
const runtimeInfo = applyRuntimeInfo(created.info, { foreground: false })
|
||||
updateSessionState(created.session_id, state => (runtimeInfo ? { ...state, ...runtimeInfo } : state), stored)
|
||||
|
||||
openSessionTile(stored, dir)
|
||||
patchSessionTile(stored, { runtimeId: created.session_id })
|
||||
|
||||
if (dir === 'center' && runtimeInfo?.cwd) {
|
||||
setCurrentCwd(runtimeInfo.cwd)
|
||||
setWorkspaceCwdOwner(stored)
|
||||
}
|
||||
|
||||
revealTreePane(`session-tile:${stored}`)
|
||||
|
||||
if (listed) {
|
||||
|
|
@ -701,6 +717,12 @@ export function useSessionActions({
|
|||
activeSessionIdRef.current = cachedRuntimeId
|
||||
syncSessionStateToView(cachedRuntimeId, cachedViewState)
|
||||
setCurrentCwd(cachedViewState.cwd)
|
||||
// The warm cache IS this conversation's own workspace truth, so the
|
||||
// switch is already re-homed here. This claim cannot wait for
|
||||
// `session.activate`: its missing-RPC compat branch returns before
|
||||
// `applyRuntimeInfo` runs, which would leave the workspace marked
|
||||
// un-owned for the life of the session (#71254).
|
||||
setWorkspaceCwdOwner(storedSessionId)
|
||||
setCurrentBranch(cachedViewState.branch)
|
||||
setSessionStartedAt(Date.now())
|
||||
|
||||
|
|
@ -844,7 +866,7 @@ export function useSessionActions({
|
|||
const stored =
|
||||
$sessions.get().find(session => sessionMatchesStoredId(session, storedSessionId)) ?? storedForProfile
|
||||
|
||||
applyStoredSessionPreviewRuntimeInfo(stored)
|
||||
applyStoredSessionPreviewRuntimeInfo(stored, storedSessionId)
|
||||
|
||||
if (stored) {
|
||||
applyStoredUsage(stored)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ import { $activeGatewayProfile, $profiles, normalizeProfileKey } from '@/store/p
|
|||
import {
|
||||
$currentCwd,
|
||||
$sessions,
|
||||
commitWorkspaceCwdForSelectedSession,
|
||||
releaseWorkspaceCwdOwner,
|
||||
sessionMatchesStoredId,
|
||||
setCurrentBranch,
|
||||
setCurrentCwd,
|
||||
|
|
@ -20,6 +22,7 @@ import {
|
|||
setCurrentServiceTier,
|
||||
setCurrentUsage,
|
||||
setSessions,
|
||||
setWorkspaceCwdOwner,
|
||||
setYoloActive
|
||||
} from '@/store/session'
|
||||
|
||||
|
|
@ -959,7 +962,17 @@ function publishRuntimeToComposer(state: SessionRuntimeStatePatch): void {
|
|||
}
|
||||
|
||||
if (state.cwd !== undefined) {
|
||||
setCurrentCwd(state.cwd)
|
||||
if (state.cwd) {
|
||||
// The runtime named a real folder for the session in the main pane, so
|
||||
// that conversation owns the path.
|
||||
commitWorkspaceCwdForSelectedSession(state.cwd)
|
||||
} else {
|
||||
// A detached session: the path on screen is provably still the previous
|
||||
// conversation's. Release rather than write `''` — `setCurrentCwd`
|
||||
// persists, so blanking here would also wipe the remembered workspace
|
||||
// that seeds `$currentCwd` on next boot.
|
||||
releaseWorkspaceCwdOwner()
|
||||
}
|
||||
}
|
||||
|
||||
if (state.branch !== undefined) {
|
||||
|
|
@ -1017,7 +1030,12 @@ export function applyRuntimeInfo(
|
|||
sessionState.provider = info.provider
|
||||
}
|
||||
|
||||
if (info.cwd) {
|
||||
// Empty string is authoritative, not "no opinion": a detached/bare session
|
||||
// reports `cwd: ''`, and the truthy-only test left `$currentCwd` — and so the
|
||||
// Files pane — pinned to the PREVIOUS project for the rest of the session
|
||||
// (#71254). Empty is routed through ownership release below rather than
|
||||
// persisted, so the pane hides a path it no longer owns instead of blanking.
|
||||
if (typeof info.cwd === 'string') {
|
||||
sessionState.cwd = info.cwd
|
||||
}
|
||||
|
||||
|
|
@ -1056,7 +1074,10 @@ export function applyRuntimeInfo(
|
|||
return sessionState
|
||||
}
|
||||
|
||||
export function applyStoredSessionPreviewRuntimeInfo(stored: { model?: null | string } | undefined) {
|
||||
export function applyStoredSessionPreviewRuntimeInfo(
|
||||
stored: { cwd?: null | string; model?: null | string } | undefined,
|
||||
storedSessionId: null | string
|
||||
) {
|
||||
setCurrentModel(stored?.model || '')
|
||||
setCurrentProvider('')
|
||||
setCurrentReasoningEffort('')
|
||||
|
|
@ -1064,6 +1085,35 @@ export function applyStoredSessionPreviewRuntimeInfo(stored: { model?: null | st
|
|||
setCurrentFastMode(false)
|
||||
setYoloActive(false)
|
||||
setCurrentPersonality('')
|
||||
|
||||
// Cold resume paints the transcript before `session.resume` returns, so
|
||||
// without this the Files pane shows the PREVIOUS project's tree for the whole
|
||||
// round-trip (#71254 / #76696). The sidebar row already knows this
|
||||
// conversation's workspace — `cwd` is part of the compact row projection — so
|
||||
// mirror it on the same tick the selection changes.
|
||||
//
|
||||
// Only `cwd` is consulted. `git_repo_root` is documented as null for non-git
|
||||
// workspaces and not-yet-backfilled history rows, so falling back to it would
|
||||
// read as "no workspace" for those sessions and blank a pane that was correct.
|
||||
const storedCwd = stored?.cwd?.trim() || ''
|
||||
|
||||
if (storedCwd) {
|
||||
setCurrentCwd(storedCwd)
|
||||
setWorkspaceCwdOwner(storedSessionId)
|
||||
} else {
|
||||
// Either a genuinely detached session, or a row outside the loaded sidebar
|
||||
// page (`stored` is undefined) — neither says anything about the workspace,
|
||||
// while `$currentCwd` still holds the previous conversation's folder.
|
||||
// Release so workspace-derived surfaces stop trusting it; `applyRuntimeInfo`
|
||||
// publishes the truth a moment later. The path is deliberately left in place
|
||||
// — clearing it collapses the workspace/review panes and drops file-tree
|
||||
// state on every switch.
|
||||
releaseWorkspaceCwdOwner()
|
||||
}
|
||||
|
||||
// Same window, same reasoning: the branch is derived from the workspace, so
|
||||
// carrying the previous conversation's label across a switch is never right.
|
||||
setCurrentBranch('')
|
||||
}
|
||||
|
||||
// A "session genuinely doesn't exist" failure (deleted, or an id from a wiped /
|
||||
|
|
|
|||
Loading…
Reference in New Issue