Add repository-based thread organization#1318
Conversation
…-rows-option-2 # Conflicts: # packages/app/src/components/left-sidebar.tsx # packages/app/src/components/sidebar-workspace-list.tsx # packages/app/src/components/workspace-shortcut-targets-subscriber.tsx # packages/app/src/screens/workspace/workspace-screen.tsx # packages/app/src/utils/sidebar-shortcuts.test.ts # packages/app/src/utils/sidebar-shortcuts.ts
|
| Filename | Overview |
|---|---|
| packages/app/src/stores/workspace-organization-store.ts | New store: persists mode preference, exports policy factory. Clean design – two concrete policy constants, a normalizer for safe deserialization, and a custom merge for Zustand persist. |
| packages/app/src/workspace-tabs/agent-visibility.ts | Removes autoOpenAgentIds from WorkspaceAgentVisibility; adds deriveProjectAgentVisibility for project-scoped agent inclusion. The fallback-only-when-no-workspaces guard keeps the function safe during initial load. |
| packages/app/src/stores/workspace-layout-actions.ts | autoOpenAgentIds made optional (defaults to []); workspaceId added to snapshots; terminal tab collapse guarded by workspaceId match; allowArchived exemption in collapseStaleEntityTabs; applyPinnedAndHidden guard changed from knownAgentIds to baseAgentIds. |
| packages/app/src/workspace-tabs/identity.ts | workspaceTabTargetsEqual now includes workspaceId for file tabs and allowArchived for agent tabs; buildDeterministicWorkspaceTabId still uses path only for file tabs, creating an asymmetry with the new equality check. |
| packages/app/src/screens/workspace/workspace-screen.tsx | Switches between workspace-scoped and project-scoped persistence keys; computes autoOpenAgentIds externally via useMemo; threads workspaceId context into all tab opens. |
| packages/app/src/stores/workspace-tabs-store/state.ts | Adds workspaceId? to all tab target kinds; new buildWorkspaceProjectTabScopeKey for project-scoped keys; coerce helpers refactored from if-chain to switch. |
| packages/app/src/hooks/sidebar-workspaces-view-model.ts | Adds SidebarAgentEntry, buildSidebarProjectsWithAgents, and normalizeSidebarBranchName. Agent projection and sorting logic is well-isolated. |
| packages/app/src/utils/workspace-navigation.ts | resolvePreparedTabScopeKey reads the organization policy at call time and builds the correct persistence key. |
| packages/app/src/screens/workspace/workspace-bulk-close.ts | archiveAgentTabs flag added; UI copy now differs by mode; correctly skips agent closeItems RPC when not archiving. |
| packages/app/src/utils/sidebar-project-row-model.ts | isSidebarProjectFlattened and buildSidebarProjectRowModel accept organizationMode; collapsibility logic branches correctly between modes. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
Store["useWorkspaceOrganizationStore\n(persisted mode: workspace-first | thread-first)"]
Policy["getWorkspaceOrganizationPolicy(mode)\n→ WorkspaceOrganizationPolicy"]
Store --> Policy
Policy -->|tabScope: workspace| WPK["workspacePersistenceKey\n(serverId:workspaceId)"]
Policy -->|tabScope: project| PPK["projectTabScopeKey\n(serverId:project:projectKey)"]
WPK -->|workspace-first| Reconcile
PPK -->|thread-first| Reconcile
Policy -->|agentVisibilityScope: workspace| WAV["deriveWorkspaceAgentVisibility"]
Policy -->|agentVisibilityScope: project| PAV["deriveProjectAgentVisibility"]
WAV --> AutoOpen
PAV --> AutoOpen
Policy -->|agentTabPopulation: auto-active| AutoOpen["autoOpenAgentIds\n(shouldAutoOpenAgentTab filter)"]
Policy -->|agentTabPopulation: manual-open| EmptySet["EMPTY_SET"]
AutoOpen --> Reconcile["reconcileWorkspaceTabs\n(WorkspaceTabSnapshot)"]
EmptySet --> Reconcile
Reconcile --> CollapseStale["collapseStaleEntityTabs\n(allowArchived exemption)"]
Reconcile --> AddMissing["addMissingEntityTabs\n(workspaceId context on tabs)"]
Policy -->|agentTabClose: archive-root| CloseArchive["resolveCloseAgentTabPolicy\n(archive root, layout-only sub)"]
Policy -->|agentTabClose: layout-only| CloseLayout["layout-only for all tabs"]
Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'upstream/m..." | Re-trigger Greptile
| export interface SidebarAgentWorkspaceSource { | ||
| id: string; | ||
| projectId: string; | ||
| projectRootPath: string; | ||
| workspaceDirectory: string; | ||
| projectKind: WorkspaceDescriptor["projectKind"]; | ||
| workspaceKind: WorkspaceDescriptor["workspaceKind"]; | ||
| name: string; | ||
| gitRuntime?: { currentBranch?: string | null } | null; | ||
| project?: ProjectPlacementPayload; | ||
| } |
There was a problem hiding this comment.
Duplicate
SidebarAgentWorkspaceSource interface
The same interface name is defined independently here (with project?: ProjectPlacementPayload) and in session-store-hooks/selectors.ts (with project?: WorkspaceDescriptor["project"]). Because TypeScript uses structural typing, they compile together today, but the project field types can diverge silently. use-sidebar-workspaces-list.ts re-exports the model version while useSidebarAgentWorkspaces returns the selectors version — callers that care about project already see two different shapes. One canonical definition should own the type; the other should import and re-export it.
Rule Used: # Code Review Pattern Reference: Slop, Tests, Feat... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
…-rows-option-2 # Conflicts: # packages/app/src/components/left-sidebar.tsx # packages/app/src/components/sidebar-workspace-list.tsx # packages/app/src/hooks/sidebar-workspaces-view-model.test.ts # packages/app/src/hooks/sidebar-workspaces-view-model.ts # packages/app/src/hooks/use-sidebar-workspaces-list.ts # packages/app/src/stores/session-store-hooks/selectors.ts
What changes
This adds a workspace organization preference for how the app presents active work.
The existing Workspaces organization remains the default. It keeps the current sidebar behavior: projects expand into workspaces/branches, and workspace tabs continue to represent the active work for that workspace.
The new Threads organization gives users a repo-centered way to work when several unrelated agent tasks are happening in the same checkout. In this mode:
Workspace-specific organization still exists where it is useful. Status grouping and numbered workspace shortcuts remain tied to the default Workspaces organization, where those controls still map directly to visible workspace rows.
Why
Worktrees are not always the right organizing boundary. Users may run multiple unrelated tasks from the same checkout and resolve conflicts later, which makes branch/workspace tabs a weak proxy for the work they are actually switching between.
This keeps the current workflow intact while adding an alternate organization that treats threads as the primary units of work.