Skip to content

Commit 3482bf0

Browse files
abhiaiyer91Mastra Code (anthropic/claude-fable-5)
andcommitted
feat(mastracode-web): single conversation per worktree in the sidebar
Multiple threads inside one worktree don't fit the workspace model — a worktree is one unit of work. Nesting the multi-thread list (titles, rename, clone, new-thread) under every workspace row added noise and made it easy to scatter runs across stray threads. The thread list now only nests under the main-branch workspace; feature worktrees show no thread list, and the workspace row itself is the entry point (selecting it resumes the worktree's latest thread or creates its single one). Local projects keep the flat multi-thread list. Co-Authored-By: Mastra Code (anthropic/claude-fable-5) <noreply@mastra.ai>
1 parent 407b838 commit 3482bf0

3 files changed

Lines changed: 25 additions & 7 deletions

File tree

mastracode/web/src/web/ui/Sidebar.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ import { useOverlays } from './lib/overlays';
1616
* (`useActiveProjectContext`, focused chat hooks, `useOverlays`), so nothing is
1717
* wired through props here.
1818
*
19-
* Threads are scoped to the worktree they run in, so GitHub projects nest the
20-
* thread list under the active worktree inside the Workspaces section; local
21-
* projects (no worktrees) keep the flat list.
19+
* Threads are scoped to the worktree they run in. GitHub projects nest the
20+
* thread list under the main-branch workspace inside the Workspaces section —
21+
* feature worktrees hold a single conversation, so they show no thread list;
22+
* local projects (no worktrees) keep the flat list.
2223
*/
2324
export function Sidebar() {
2425
const overlays = useOverlays();

mastracode/web/src/web/ui/domains/workspaces/components/WorkspacesSection.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ import type { Worktree } from '../services/projects';
2929
* Sidebar section listing a GitHub project's worktrees.
3030
*
3131
* Threads are scoped to the worktree they run in, so callers can pass the
32-
* thread list as `children`: it renders nested under the active worktree row
33-
* (or after the list when no worktree is selected yet).
32+
* thread list as `children`. It only renders nested under the main-branch
33+
* workspace (or after the list when no worktree is selected yet, which falls
34+
* back to the main scope): feature worktrees hold a single conversation, so
35+
* the workspace row itself is the entry point and no thread list (titles,
36+
* rename, new-thread) is shown for them.
3437
*/
3538
export function WorkspacesSection({ children }: { children?: ReactNode }) {
3639
const { baseUrl } = useApiConfig();
@@ -199,7 +202,10 @@ export function WorkspacesSection({ children }: { children?: ReactNode }) {
199202
<div className="flex flex-col gap-1">
200203
{worktrees.map(worktree => {
201204
const active = worktree.worktreePath === selectedPath;
202-
const nested = active && Boolean(children);
205+
// Feature worktrees hold a single conversation, so only the
206+
// main-branch workspace nests the multi-thread list.
207+
const isMainWorkspace = worktree.worktreePath === activeProject.sandboxWorkdir;
208+
const nested = active && isMainWorkspace && Boolean(children);
203209
return (
204210
<div key={worktree.worktreePath} className="flex flex-col gap-1">
205211
<WorkspaceRow

mastracode/web/src/web/ui/domains/workspaces/components/__tests__/WorkspacesSection.msw.test.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ describe('WorkspacesSection', () => {
141141
expect(screen.getByRole('button', { name: 'feat-ui' })).not.toHaveAttribute('aria-current');
142142
});
143143

144-
it('nests children under the active worktree row', async () => {
144+
it('nests children under the active main-branch workspace row', async () => {
145145
seedActiveProject(githubProject);
146146
useAgentControllerHandlers();
147147

@@ -156,6 +156,17 @@ describe('WorkspacesSection', () => {
156156
expect(inactiveRow.parentElement?.parentElement).not.toContainElement(nested);
157157
});
158158

159+
it('given a feature worktree is active, then no thread list renders (worktrees hold a single conversation)', async () => {
160+
seedActiveProject({ ...githubProject, selectedWorktreePath: '/sandbox/mastra-worktrees/feat-ui' });
161+
useAgentControllerHandlers();
162+
163+
renderSection(<div data-testid="nested-threads">Threads</div>);
164+
165+
const activeRow = await screen.findByRole('button', { name: 'feat-ui' });
166+
expect(activeRow).toHaveAttribute('aria-current', 'true');
167+
expect(screen.queryByTestId('nested-threads')).not.toBeInTheDocument();
168+
});
169+
159170
it('does not render for local projects', async () => {
160171
seedActiveProject(localProject);
161172
useAgentControllerHandlers();

0 commit comments

Comments
 (0)