Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 2 additions & 7 deletions frontend/src/components/FooterBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -218,7 +213,7 @@ export function FooterBar({ dock, onDock }: FooterBarProps) {
<FooterButton
label="View pull request"
onClick={() => {
openPulls(basePath)
openPulls(checkout)
navigate(`/projects/${projectId}/pulls`)
}}
wide
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/pulls/Pulls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?? ""
Expand Down Expand Up @@ -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 {
Expand Down
26 changes: 21 additions & 5 deletions frontend/src/lib/session/use-active-session.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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,
}
}