Show the terminal's live working directory in the pane header - #209
Conversation
The terminal pane header rendered a bare "Terminal" — the same word the
tab directly above it already shows — so the strip carried no
information at all. It now appends the session's live working directory,
but only when that directory differs from the workspace root.
Design: signal only when it's surprising.
- At the workspace root (the common case, and where every session
starts) the header is unchanged, so it doesn't churn on every pane.
- Inside the workspace it shows the relative path: "Terminal · src-tauri".
- Deep paths elide their head ("…/src/pty_daemon") and out-of-workspace
paths contract against $HOME ("~/dotfiles"); the untrimmed path is on
the hover tooltip.
Full absolute paths are deliberately not rendered. The header is a 28px
strip that CSS-truncates on the right, so a long path degrades to
"/home/user/.codemux/worktr…" and drops the only segment that mattered.
Trimming to a leading "…/" plus the trailing segments keeps the
meaningful tail and still fits a narrow split pane.
Two sources, with osc7 beating proc:
- TerminalPane registers an xterm OSC 7 handler. Shells with integration
push this on every prompt; it is exact, event-driven, and the only
source that works for remote/SSH panes, where the shell's pid lives on
another machine.
- Shells without that integration fall back to a new
`terminal_session_cwds` command that readlinks /proc/<pid>/cwd for the
session's shell pid — so `cd` moves the header but a long-running child
(a build, an agent) does not. Sessions with no local pid are omitted
rather than answered from an unrelated local /proc entry.
Once a session reports via OSC 7 the poller drops it from its request
set, so a fully shell-integrated setup costs zero IPC. A single
app-level 2s interval batches one call for the active workspace's
remaining sessions and pauses while the window is hidden.
State is frontend-only. TerminalSessionSnapshot.cwd stays the spawn
directory: making it live would fan out a full state rebuild plus a
persistence write on every `cd` for a cosmetic label. Pane titles also
stay write-once, and the cwd is appended as a separate element rather
than replacing node.title, because PRESET_TITLE_TO_ICON keys the preset
icon off the exact title string — a regression test pins this.
Tests: 42 new frontend tests covering the formatter rules, the
precedence rule, the OSC 7 payload parser, and the header's render
states; 2 Rust tests over the /proc reader. The dev mock gains an
emitOsc7 helper that pushes a real escape sequence through the pty
stream, so the production handler is exercised in browser dev.
Review — Opus 5 (high effort)The architecture is right: OSC 7 push beating a poll, the poller dropping OSC-7 sessions from its request set so an integrated shell costs zero IPC, One real bug.
|
|
Review by GPT 5.6 Sol xhigh found a Windows OSC 7 parsing bug.
{"parsed":"/C:/Users/z/proj","hint":{"label":"…/z/proj","full":"/C:/Users/z/proj"}}Windows has no |
Keep the leading slash on short absolute paths: a two-segment path outside the workspace (/var/log) was trimmed to var/log, indistinguishable from a workspace-relative label. Normalize Windows drive paths in OSC 7 URIs (file:///C:/Users -> C:/Users) and preserve the // prefix of empty-authority UNC URIs. Make terminal_session_cwds async with spawn_blocking so the readlink loop runs off the GTK main thread. Replace the per-session clearCwd loop in the poller GC with a bulk pruneCwds store write so closing a workspace with N terminals costs one render pass instead of N.
Audited the eleven PRs merged after the v0.15.0 tag (#204, #205, #206, #207, #208, #209, #210, #211, #212, #213, #214) against the docs system and normalized the result. Core docs: - STATUS: relabel the v0.15.0 content as shipped, add an unreleased section covering the eleven post-tag PRs in four clusters, correct the avatar-regression constraint (three row shapes, strip exception). - PLAN: replace the stale "unreleased after v0.14.3" entry, add the v0.15.0 release record, and narrow priority 6 to Archive Project now that avatar customization has been re-homed. - INDEX: correct the Thread Scope and Context Row entries (no trailing slot; no message-count gate), credit #205/#212 alongside #211, mark the inbox shipped, and index the terminal cwd hint. Feature docs: - sidebar: fix pending-row placement, document that parked rows report real agent status, add workspace-reorder.ts to the dead-code list, and defer the avatar mechanism to project-avatars.md. - project-avatars: all three row shapes are entry points, document the dirty-field write-wins merge, and record that the needs-you strip is a color-only surface outside the appearance store. - agent-chat: rewrite the Context Row section (the pane never renders ThreadScopeRow), scope the focusCmdkOnOpen claim to the chat and launch pickers, and document the WebKitGTK hardening — 2.40 floor, clean-exit sentinel disarm, =0 opt-out, sentinel-delete recovery, remote guards. - terminal: leading-slash rule for short outside paths, OSC 7 Windows drive normalization, Linux-only /proc fallback, strip_renderer_env. - workspace-context-bar: drop the pre-inbox state-driven density framing. - presets: useHorizontalWheelScroll replaced the passive onWheel handler. - settings: add the Appearance -> Scrolling toggle. - execution: correct the scope note (one production reach point; the branch_name.rs prepare_agent_command is an unrelated name collision) and the openflow_agent_default parenthetical it contradicted. - FEATURES: add the cwd hint and done-review checkmark persistence. Also moved the shipped agent-run-checkpoint plan to docs/archive/ per the INDEX rule and repointed its four references.
Problem
The terminal pane header rendered a bare
Terminal— the same word the tab directly above it already shows. Two labels stacked 28px apart, zero information between them.What this does
Appends the session's live working directory to the header, only when it differs from the workspace root.
Terminal— unchangedcd src-tauriTerminal src-tauriTerminal …/src/pty_daemonTerminal ~/dotfiles$HOMETerminal …/docker/volumesDesign notes
Signal only when it's surprising. ~80% of sessions sit at the workspace root, and the workspace identity is already on screen twice (sidebar + context bar). Staying quiet at the root means the header doesn't churn and doesn't add a third copy of the same string to every pane in a split.
Full absolute paths are deliberately not rendered. The header is a 28px strip that CSS-truncates on the right, so
/home/user/.codemux/worktrees/codemux/some-branchwould degrade to/home/user/.codemux/worktr…— dropping the only segment that mattered. Eliding the head keeps the meaningful tail and fits a narrow split; the untrimmed path is on the hover tooltip.Foreground command was considered and rejected — showing the running process would flash the header on every
ls.Known tradeoff: absence is ambiguous — an empty hint means "at the root" or "cwd not known yet." This only bites on remote panes whose shell doesn't emit OSC 7. Judged the better trade against always-on noise; it's a one-line flip in
formatCwdHintif we want to revisit.Two sources,
osc7beatsproc/proc/<pid>/cwdOnce a session reports via OSC 7 the poller drops it from its request set, so a fully shell-integrated setup costs zero IPC. A single app-level 2s interval batches one call for the active workspace's remaining sessions and pauses while the window is hidden.
The
/procread targets the session's shell pid, socdmoves the header but a long-running child (a build, an agent) does not. Sessions with no local pid are omitted rather than answered from an unrelated local/procentry.Constraints the codebase imposed
TerminalSessionSnapshot.cwdstays the spawn directory — making it live would fan out a full state rebuild plus a persistence write on everycdfor a cosmetic label.PRESET_TITLE_TO_ICONkeys the preset icon off the exact title string, so overwritingnode.titlewith a path would silently kill the Claude/Codex icons. A regression test pins this.Testing
osc7-wins precedence, the OSC 7 payload parser, and the header's render states./procreader, covering live pid, dead pid, and no-pid (remote) cases.pane-tree-rerender.test.tsx— the hoisted hooks resolve to stable primitives for non-terminal nodes, so there's no re-render fan-out.__codemuxTerminalMock.emitOsc7dev helper, which exercises the production handler rather than a mock shortcut). All five label cases confirmed, plus correct truncation at a 520px viewport, and the hint survived many poll ticks without being clobbered — confirming the precedence rule./procfallback is unit-tested against real/procbut was not exercised end-to-end in the desktop app (tauri:devopens a native window not visible to browser automation). The OSC 7 path is verified end-to-end.