Skip to content

Commit f0bdefd

Browse files
committed
feat(dashboard): project / session breadcrumb in the session toolbar
Prefix the session name in the action bar with its project as a breadcrumb — "project / session" — so the toolbar says which project the session belongs to. The project part is muted and gives up width first, so a long project name truncates before the session name does. Threaded projectName from the page (already computed there) down through RunView and RunActionBar to GitStatusBar.
1 parent e3f15c4 commit f0bdefd

4 files changed

Lines changed: 31 additions & 5 deletions

File tree

packages/framework-dashboard/components/GitStatusBar.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export function GitStatusBar({
2525
runId,
2626
inline = false,
2727
label,
28+
projectName,
2829
summary,
2930
expanded = false,
3031
onToggle,
@@ -36,6 +37,9 @@ export function GitStatusBar({
3637
/** The session's name (#1030). Given, it leads as the bold identity and the branch drops to
3738
* muted git context beside it; absent (the project home), the branch stays the identity. */
3839
label?: string | undefined
40+
/** The project the session belongs to. Given alongside a label, it prefixes the name as a
41+
* `project / session` breadcrumb; it gives up width first, so the session name truncates last. */
42+
projectName?: string | null | undefined
3943
/** What the branch holds, in a phrase — rendered beside the branch's own status. */
4044
summary?: ReactNode
4145
expanded?: boolean
@@ -85,8 +89,22 @@ export function GitStatusBar({
8589
change under you, unlike the branch, which the agent renames near the end (#736). It is
8690
the one element that shrinks, so it truncates last and the identity never disappears. */}
8791
{label && (
88-
<span className="min-w-0 truncate font-medium text-foreground" title={label}>
89-
{label}
92+
<span className="flex min-w-0 items-center gap-1.5 overflow-hidden">
93+
{/* The project, as a breadcrumb parent: muted, and the first to give up width (shrink-999)
94+
so a long project name truncates before the session name does. */}
95+
{projectName && (
96+
<span className="flex min-w-0 shrink-[999] items-center gap-1.5 overflow-hidden text-muted-foreground">
97+
<span className="truncate" title={projectName}>
98+
{projectName}
99+
</span>
100+
<span className="shrink-0 text-muted-foreground/60" aria-hidden>
101+
/
102+
</span>
103+
</span>
104+
)}
105+
<span className="min-w-0 truncate font-medium text-foreground" title={label}>
106+
{label}
107+
</span>
90108
</span>
91109
)}
92110
{/* The branch: the identity when there is no session label (the project home), otherwise

packages/framework-dashboard/components/RunActionBar.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export function RunActionBar({
1717
onWorktreeRemoved,
1818
onDeleted,
1919
label,
20+
projectName,
2021
summary,
2122
expanded = false,
2223
onToggle,
@@ -28,6 +29,8 @@ export function RunActionBar({
2829
events: FrameworkEvent[]
2930
/** The session's name — leads the bar, so the branch is git context, not the identity (#1030). */
3031
label?: string | undefined
32+
/** The session's project, shown as a `project / session` breadcrumb before the name. */
33+
projectName?: string | null | undefined
3134
/** True when this finished run still has a worktree on disk, so it can be removed (#737). */
3235
retainedWorktree?: boolean
3336
/** Told after that worktree is removed, so the menu item goes. */
@@ -49,7 +52,7 @@ export function RunActionBar({
4952
<div className="@container flex items-center gap-2 overflow-hidden border-b border-border px-4 py-2">
5053
{/* Where this session is working (#798/#809): its branch, whether it holds uncommitted work,
5154
its size on disk, and the PR its branch has — read from this session's own worktree. */}
52-
<GitStatusBar projectId={projectId} runId={runId} inline label={label} summary={summary} expanded={expanded} onToggle={onToggle} />
55+
<GitStatusBar projectId={projectId} runId={runId} inline label={label} projectName={projectName} summary={summary} expanded={expanded} onToggle={onToggle} />
5356
{/* What the session IS sits at the start; what you can DO to it sits at the end. The spacer
5457
grows but never shrinks (#1030), so a tight row takes its width from the label. */}
5558
<div className="grow shrink-0" />

packages/framework-dashboard/components/RunView.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function RunView({
3232
events,
3333
live,
3434
label,
35+
projectName,
3536
target,
3637
remoteLabel,
3738
files,
@@ -52,6 +53,8 @@ export function RunView({
5253
* the stable identity, so the branch renaming itself near the end of a run (#736) reads as a
5354
* detail changing rather than the whole view changing. */
5455
label?: string | undefined
56+
/** The session's project, shown as a `project / session` breadcrumb in the action bar. */
57+
projectName?: string | null | undefined
5558
/** Where the run executes (#1053): `actions` swaps the live feed for a burst-mode affordance; `remote` is relayed to a device (#1067). */
5659
target?: 'local' | 'actions' | 'remote' | undefined
5760
/** The device this run executes on (#1067), when it is relayed to a connected one. Set only for a
@@ -112,6 +115,7 @@ export function RunView({
112115
runId={runId}
113116
events={shown}
114117
label={label ?? progress.sessionName}
118+
projectName={projectName}
115119
retainedWorktree={hasWorktree}
116120
onWorktreeRemoved={onWorktreeRemoved}
117121
onDeleted={onDeleted}

packages/framework-dashboard/pages/index/+Page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ export default function Page() {
241241
if (runId === null) {
242242
// Just pressed Start on a project with no worktree: follow the live output until the poll
243243
// surfaces the run and the effect above adopts its id.
244-
if (adopting) return <RunView projectId={projectId} runId={null} events={events} live label={runStart.intent || undefined} remoteLabel={runStart.runsOn} files={files} addContext={addContext} removeContext={removeContext} lost={lost} onRunStarted={onRunStarted} />
244+
if (adopting) return <RunView projectId={projectId} runId={null} events={events} live label={runStart.intent || undefined} projectName={projectName} remoteLabel={runStart.runsOn} files={files} addContext={addContext} removeContext={removeContext} lost={lost} onRunStarted={onRunStarted} />
245245
return (
246246
<ProjectHome
247247
projectId={projectId}
@@ -260,7 +260,7 @@ export default function Page() {
260260
// list we have not read yet. Both are live views; only a session that is genuinely absent
261261
// from a list we did read is gone.
262262
if (runId === runStart.id || !runsLoaded)
263-
return <RunView projectId={projectId} runId={runId} events={events} live label={runStart.intent || undefined} remoteLabel={runId === runStart.id ? runStart.runsOn : undefined} files={files} addContext={addContext} removeContext={removeContext} lost={lost} onRunStarted={onRunStarted} />
263+
return <RunView projectId={projectId} runId={runId} events={events} live label={runStart.intent || undefined} projectName={projectName} remoteLabel={runId === runStart.id ? runStart.runsOn : undefined} files={files} addContext={addContext} removeContext={removeContext} lost={lost} onRunStarted={onRunStarted} />
264264
return (
265265
<NotFound
266266
title="This session is gone"
@@ -279,6 +279,7 @@ export default function Page() {
279279
events={events}
280280
live={selectedRun.status === 'running'}
281281
label={runLabel(selectedRun)}
282+
projectName={projectName}
282283
files={files}
283284
addContext={addContext}
284285
removeContext={removeContext}

0 commit comments

Comments
 (0)