Repository navigation
feat(projects): stop collapsing a nested workspace into its parent repo - #8490
mfattakhov wants to merge 1 commit into
Conversation
A project is identified by its workspace root, but default sidebar grouping collapsed every project sharing a git remote into one row. Adding ~/delta/commerce-pricing next to ~/delta produced a single "owner/delta" row, the new-thread picker offered one entry, and typing the package name found nothing — so an agent could not be aimed at the subdirectory on purpose. Default `repository` grouping now keys a nested workspace separately from its parent while sibling clones of one remote still share a row, which is the whole change on the grouping seam. Rows that would collide on the git name gain the repo-relative path as a disambiguator, project search terms grow to cover the git owner/name, the full workspace root, and every path segment, and a new default-off setting flips the primary label from the git name to the path. Wired through the web sidebar, both command-palette project sections, Settings, and the mobile home list, thread sidebar, and grouping screen.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
There was a problem hiding this comment.
🟠 High
For repositories rooted at / or C:\, deriveRepositoryRelativeProjectPath returns null for nested workspaces, so they receive the unscoped canonicalKey and collapse into the repository-root group. This happens because line 66 appends another separator, testing // or c:\\; preserve an existing trailing separator when building rootPrefix.
| const rootPrefix = normalizedRootPath.endsWith(separator) | |
| ? normalizedRootPath | |
| : `${normalizedRootPath}${separator}`; |
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/projectGrouping.ts around line 66:
For repositories rooted at `/` or `C:\`, `deriveRepositoryRelativeProjectPath` returns `null` for nested workspaces, so they receive the unscoped `canonicalKey` and collapse into the repository-root group. This happens because line 66 appends another separator, testing `//` or `c:\\`; preserve an existing trailing separator when building `rootPrefix`.
| projectSortOrder: "updated_at", | ||
| }), | ||
| [groupingSettings.sidebarProjectGroupingMode, projects, threads], | ||
| [groupingSettings, projects, threads], |
There was a problem hiding this comment.
🟡 Medium threads/new-task-flow-provider.tsx:210
projectScopes is recomputed and activity-sorted on every draft update, including each keystroke, which can make composing a new task janky for larger project/thread sets. useMobileProjectGroupingSettings() returns a new groupingSettings object on each successful render, so this dependency invalidates the useMemo even when the grouping settings have not changed; memoize the resolved settings or depend on stable setting values.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/mobile/src/features/threads/new-task-flow-provider.tsx around line 210:
`projectScopes` is recomputed and activity-sorted on every draft update, including each keystroke, which can make composing a new task janky for larger project/thread sets. `useMobileProjectGroupingSettings()` returns a new `groupingSettings` object on each successful render, so this dependency invalidates the `useMemo` even when the grouping settings have not changed; memoize the resolved settings or depend on stable setting values.
| {project.displayName} | ||
| </span> | ||
| {project.pathLabel ? ( | ||
| <span className="shrink-0 truncate text-secondary-label text-[11px]"> |
There was a problem hiding this comment.
🟡 Medium components/LegacySidebar.tsx:2307
Long pathLabel values consume their full intrinsic width, causing the project title to disappear and the disambiguator to be clipped at the sidebar edge instead of ellipsized. Replace shrink-0 with a shrinkable constrained flex item so truncate can render an ellipsis.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/LegacySidebar.tsx around line 2307:
Long `pathLabel` values consume their full intrinsic width, causing the project title to disappear and the disambiguator to be clipped at the sidebar edge instead of ellipsized. Replace `shrink-0` with a shrinkable constrained flex item so `truncate` can render an ellipsis.
| if (relativePath === null) { | ||
| return lastPathSegment(project.workspaceRoot); | ||
| } | ||
| return relativePath.length === 0 ? "." : relativePath; |
There was a problem hiding this comment.
🟡 Medium state/projectGrouping.ts:222
Repository-root projects receive the identical disambiguator `
- return relativePath.length === 0 ? "." : relativePath;
+ return relativePath.length === 0 ? project.workspaceRoot : relativePath;🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/projectGrouping.ts around line 222:
Repository-root projects receive the identical disambiguator `
There was a problem hiding this comment.
Reviewed the changed web UI surfaces for the nested-project labelling work. The primitive usage is consistent (the new General setting reuses SettingsRow + Switch + searchableSetting, and every group label now routes through formatSidebarProjectLabel). Two follow-ups below: one layout ownership issue on the new sidebar path label, and one accessible-name gap where the disambiguated label is not applied to adjacent controls.
Posted via Macroscope — UI Consistency
| {project.pathLabel ? ( | ||
| <span className="shrink-0 truncate text-secondary-label text-[11px]"> | ||
| {project.pathLabel} | ||
| </span> | ||
| ) : null} |
There was a problem hiding this comment.
The new path label is shrink-0, so it never yields width inside the flex row while the name span (truncate, shrink allowed) absorbs all the shrinkage. Unlike the neighbouring {count} projects badge, this text is unbounded (packages/integrations/pricing), so on a narrow sidebar the project name can truncate to nothing while the path renders in full — and truncate on a shrink-0 item never actually triggers. Making the label shrinkable keeps both parts readable and lets the path be the thing that ellipsises.
| {project.pathLabel ? ( | |
| <span className="shrink-0 truncate text-secondary-label text-[11px]"> | |
| {project.pathLabel} | |
| </span> | |
| ) : null} | |
| {project.pathLabel ? ( | |
| <span className="min-w-0 shrink truncate text-secondary-label text-[11px]"> | |
| {project.pathLabel} | |
| </span> | |
| ) : null} |
Posted via Macroscope — UI Consistency
| <span className="min-w-0 truncate text-sm"> | ||
| {formatSidebarProjectLabel(project)} | ||
| </span> |
There was a problem hiding this comment.
The visible row label is now disambiguated, but the settings button immediately below still derives its accessible name and tooltip from project.displayName. For a parent workspace and a workspace nested inside it, both menu items expose the identical name (Project settings for kosyanmedia/delta), so keyboard and screen-reader users lose the distinction the new label restores. Same pattern applies to the Create new thread in ${project.displayName} label in LegacySidebar.tsx (~line 2354).
<Button
- aria-label={`Project settings for ${project.displayName}`}
- title={`Project settings for ${project.displayName}`}
+ aria-label={`Project settings for ${formatSidebarProjectLabel(project)}`}
+ title={`Project settings for ${formatSidebarProjectLabel(project)}`}Posted via Macroscope — UI Consistency
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This 32-file change alters default project grouping and labels across shared runtime, web, and mobile navigation, while adding persisted settings and new search behavior; it is not a small mechanical or off-by-default change. An unresolved root-path grouping edge case and concrete accessibility/UI concerns further warrant human review. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
A project is an environment-local workspace record keyed by
workspaceRoot, and git identity is correlation and labelling — not routing. Default sidebar grouping does not respect that: it collapses every project sharing a git remote into one row named after the repo. Adding~/delta/commerce-pricingnext to~/deltaproduces a singleowner/deltarow, ⌘N / "New thread in…" offers one entry, and typingcommerce-pricingfinds nothing. The server will happily create both projects — uniqueness is the exact path — but you can never aim an agent at the subdirectory on purpose.Default
repositorygrouping now keys a nested workspace separately from its parent, while sibling clones of one remote still share a row — that is the whole change on the grouping seam, and the local+remote checkout feature is untouched. Rows that would collide on the git name gain the repo-relative path as a disambiguator. Project search terms grow to cover the git display name,owner/name, the full workspace root, and every path segment, socommerce-pricingmatches even when the row readsowner/delta. A new default-off setting flips the primary label from the git name to the path. Wired through the web sidebar, both command-palette project sections, Settings → Projects, and the mobile home list, thread sidebar, and grouping screen.How I use it: I work in a GitButler workspace at
~/deltaand I also need a project whose cwd is~/delta/commerce-pricing, so file search, scripts,t3.json, and the agent session all start in the package instead of the repo root. Before this, the two collapsed into one row and one picker entry, so every thread I started for the package silently started at the repo root — and I had no way to find the package by the name I actually call it. Git operations still resolve the real toplevel, so status, PRs, and commits work unchanged from the nested cwd.UI change note: this alters sidebar rows and adds a setting; I can attach before/after screenshots on request.
Model: Claude Opus 5 (1M context), harness: Claude Code.
Note
Medium Risk
Changes default sidebar/project picker grouping and labels across web, mobile, and shared runtime; behavior is well-tested but affects how users see and find projects daily.
Overview
Nested workspaces no longer collapse into one sidebar row. Default
repositorygrouping now keys projects by git remote plus repo-relative path, so~/deltaand~/delta/commerce-pricingstay distinct while sibling clones at different roots still merge. Shared client-runtime logic adds path disambiguators (owner/repo · commerce-pricing),formatProjectGroupLabel, andderiveProjectSearchTermsso web sidebar, command palette, Settings → Projects, and mobile home/thread lists stay aligned.Adds
sidebarProjectNamesUsePath(default off) on desktop/web client settings and mobileprojectNamesUsePathpreference, with UI toggles in General settings and mobile Project Grouping. Mobile list code now passes fullProjectGroupingSettingsinstead of a single grouping mode.Documents nested projects in user docs and internals; extends tests across
client-runtime, web, mobile, and contracts. Server tests assertrequireActiveProjectWorkspaceRootAbsentallows creating a project nested under an existing root while still rejecting duplicate exact paths.Reviewed by Cursor Bugbot for commit 9dc6632. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Stop collapsing nested workspaces into their parent repo across grouping modes
deriveRepositoryScopedKeyalways appends the repo-relative path instead of short-circuiting for'repository'mode.sidebarProjectNamesUsePathsetting (defaultfalse) to web and mobile settings, letting users label projects by workspace path rather than git repo name.formatProjectGroupLabelandderiveProjectSearchTermsto disambiguate colliding labels with repo-relative paths and to make nested workspaces searchable by path segments.ProjectGroupingSettingsobject and render formatted labels.buildProjectGroupsnow yields separate groups for nested workspaces in repository modes;deriveRepositoryScopedKeyandderiveLogicalProjectKeysignatures changed (removedgroupingModeparam). Consumers still passing the oldprojectGroupingModeenum or expecting parent/nested collapse will break.📊 Macroscope summarized 9dc6632. 25 files reviewed, 4 issues evaluated, 0 issues filtered, 4 comments posted
🗂️ Filtered Issues