diff --git a/CHANGELOG.md b/CHANGELOG.md index 356099e89..1f65dae91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **The dock now reviews the directory the session is actually working in.** A + session that moved — spawned in one checkout and `cd`-ed into another + repository — moved its card and the footer with it: both read the live working + directory, showing that repository's branch and its uncommitted counts. The + dock did not. It stayed on the directory the session started in, so **Review** + answered "No uncommitted changes" and the **Code** tree listed the wrong + checkout while the footer, one row below, counted 35 changed files. The dock's + three tabs and the pull-request screen now follow the same directory the card + and footer show. + ## [0.26.1] - 2026-08-05 ### Fixed diff --git a/frontend/src/components/FooterBar.tsx b/frontend/src/components/FooterBar.tsx index 29ca7a497..07c067484 100644 --- a/frontend/src/components/FooterBar.tsx +++ b/frontend/src/components/FooterBar.tsx @@ -6,7 +6,6 @@ import { Code, FileText, GitBranch, Folder, Plus, Diff, GitPullRequestArrow } fr import { ProjectService, Terminal as TerminalService } from "@/lib/rpc" import type { DockTab } from "@/components/dock/RightDock" import { useActiveSession } from "@/lib/session/use-active-session" -import { useSessionCwd } from "@/lib/session/use-session-cwd" import { useSessionUsage } from "@/lib/session/use-session-usage" import { useCostReadout } from "@/lib/use-cost-readout" import { formatCost } from "@/lib/session/session-cost" @@ -95,11 +94,7 @@ interface FooterBarProps { // shows its checkout's path, branch and diff. export function FooterBar({ dock, onDock }: FooterBarProps) { const navigate = useNavigate() - const { projectId, sessionId, path: basePath } = useActiveSession() - // Overlay the backend's live cwd so a `cd` in the terminal moves the footer - // with it — same source the session card follows. Falls back to the session's - // static start path until the watcher reports. - const path = useSessionCwd(sessionId) || basePath + const { projectId, sessionId, path, checkout } = useActiveSession() // Context-window occupancy of the active session, read off its transcript at // each turn's end (null until the first turn of a Claude session lands). const usage = useSessionUsage(sessionId) @@ -218,7 +213,7 @@ export function FooterBar({ dock, onDock }: FooterBarProps) { { - openPulls(basePath) + openPulls(checkout) navigate(`/projects/${projectId}/pulls`) }} wide diff --git a/frontend/src/components/pulls/Pulls.tsx b/frontend/src/components/pulls/Pulls.tsx index fd6525e5e..5b4de269e 100644 --- a/frontend/src/components/pulls/Pulls.tsx +++ b/frontend/src/components/pulls/Pulls.tsx @@ -58,7 +58,7 @@ export function Pulls({ list = false }: PullsProps) { activateSession, } = useProjects() const projectPath = projects.find((p) => p.id === projectId)?.path ?? "" - const { sessionId, path } = useActiveSession() + const { sessionId, path, checkout } = useActiveSession() const status = useGitStatus(path) const branch = status?.branch ?? "" const head = status?.head ?? "" @@ -119,7 +119,7 @@ export function Pulls({ list = false }: PullsProps) { void Store.PurgeWorktreeSessions(projectId, wtPath) closePulls(wtPath) // Only leave the screen when the checkout under it is the one going away. - if (wtPath === path) { + if (wtPath === checkout) { navigate(`/projects/${projectId}`) } try { diff --git a/frontend/src/lib/session/use-active-session.ts b/frontend/src/lib/session/use-active-session.ts index 13b354b3d..c0397cbe3 100644 --- a/frontend/src/lib/session/use-active-session.ts +++ b/frontend/src/lib/session/use-active-session.ts @@ -1,12 +1,18 @@ import { useMatch } from "react-router-dom" import { useProjects } from "@/providers/projects" import { activeTarget } from "./sessions" +import { useSessionCwd } from "./use-session-cwd" // useActiveSession resolves what is currently in focus: the routed project, its -// active session and the working path that session lives in — a worktree -// session resolves to its checkout, everything else to the project root. The -// footer and the review panel follow this triple; projectId is null on -// project-less screens (Home), where sessionId and path are empty. +// active session and the working path that session lives in — the session's +// live cwd, falling back to its checkout (a worktree) and then the project +// root. The footer and the review panel follow this triple; projectId is null +// on project-less screens (Home), where sessionId and path are empty. +// +// The cwd overlay is what keeps the dock on the same tree the session card and +// the footer show: a session spawned in one checkout and `cd`-ed into another +// reviews where it is working, not where it started — the static path can be a +// clean worktree while every count on screen comes from somewhere else. // // The match is the project's whole subtree, not the bare route: Settings and // the pull-request screen are that project's screens too, and the chrome around @@ -17,10 +23,20 @@ export function useActiveSession(): { projectId: string | null sessionId: string path: string + /** The session's static checkout — what the sidebar keys a worktree's pull + * request card on, so a `cd` cannot open a card no group can show. */ + checkout: string } { const { projects, sessions } = useProjects() const match = useMatch({ path: "/projects/:projectId", end: false }) const projectId = match?.params.projectId ?? null const projectPath = projects.find((p) => p.id === projectId)?.path ?? "" - return { projectId, ...activeTarget(sessions, projectId, projectPath) } + const target = activeTarget(sessions, projectId, projectPath) + const cwd = useSessionCwd(target.sessionId) + return { + projectId, + sessionId: target.sessionId, + path: cwd || target.path, + checkout: target.path, + } }