fix(app): make sidebar "Remove project" actually close the working directory#1177
Conversation
…rectory The sidebar Remove action was wired to a soft-hide (pawworkProjectHidden) that only filtered the sidebar's session list. It never touched the canonical open-project list (server.projects), so the removed directory stayed in the input-bar workspace chip, and any navigation — including the new-session button — silently un-hid it (a behavior pinned by tests). The old hard-remove (closeProject) was orphaned when the legacy SidebarPanel was dropped in 57ee5b4, leaving soft-hide as the only path. Collapse to a single source of truth (server.projects): - Remove resolves the group key to its project root and closes it via server.projects, with an undo toast that reopens it. - The sidebar's global session window is filtered by open-project membership (filterPawworkRowsByOpenProjects) instead of the soft-hide map, so a removed project disappears from the sidebar as well. - Delete the soft-hide subsystem entirely: pawworkProjectHidden state, hide/unhideProject, the three route unhide triggers, and the orphaned pawworkSessionRouteUnhideKeys helper. Removal now persists across new sessions and is reflected in the workspace chip because both read the one list. Copy updated from "hidden from the sidebar" to "removed from your workspace". Why it recurred: the prior fix lived in the legacy SidebarPanel; when that was deleted the new sidebar substituted a soft-hide that never reached server.projects, so the regression was a wrong-seam reimplementation. Verify: app typecheck; 222 unit tests (incl. new filterPawworkRowsByOpenProjects cases + rewritten routing-actions expectations); 3 sidebar-project-actions e2e (remove decreases groups; remove → new session → project stays gone, empty state shown).
📝 WalkthroughWalkthroughThis PR replaces the sidebar project "hide" feature with full project removal and undo. It removes the ChangesRemove Hidden-Project Model and Implement Removal with Undo
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Suggested priority: P2 (includes user-path files (packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts, packages/app/src/pages/layout.tsx, packages/app/src/pages/layout/layout-page-store.ts, packages/app/src/pages/layout/pawwork-project-controls.ts, packages/app/src/pages/layout/pawwork-routing-actions.test.ts, packages/app/src/pages/layout/pawwork-routing-actions.ts, packages/app/src/pages/layout/pawwork-session-controller.ts, packages/app/src/pages/layout/pawwork-session-source.ts, packages/app/src/pages/layout/pawwork-sidebar-session-rows.test.ts)).
P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.
There was a problem hiding this comment.
Code Review
This pull request refactors project removal in the sidebar to close the project from the workspace rather than just hiding it. This change simplifies the codebase by removing the pawworkProjectHidden state, its associated routing/unhiding logic, and updating translations and tests accordingly. The review feedback suggests a UX improvement for the "Undo" action: if the removed project was active, undoing should restore focus and navigate back to it instead of just reopening it in the background.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Address Codex review on the open-project filter: a session.get backfill (an active or pinned session outside the first page of the global list) returns a plain Session.Info, which has no `project` field — only the list endpoint's GlobalInfo carries `project`. For a session opened in a subfolder of an open project (e.g. /repo/packages/app), the old fallback matched the bare directory against the open worktree keys and wrongly filtered the row out, hiding the current or pinned session from the sidebar. Resolve the owning worktree from executionContext.ownerDirectory first (always present and canonical: Instance.worktree === executionContext.ownerDirectory), then project.worktree, then the bare directory. Adds a regression test for the project-less subfolder case. Verify: app typecheck; 23 unit tests in the two touched files; 3 sidebar-project-actions e2e still pass.
|
Codex review (xhigh, diff-scoped) flagged one P2: the open-project filter resolved a session's owner from Fixed in e8e0e5d: resolve the owning worktree from |
Integrate dev's direct-start sessions feature (6fc2c9a) with the workspace-removal fix. Conflict: pawwork-sidebar-session-rows.test.ts imports — kept filterPawworkRowsByOpenProjects (ours) and PAWWORK_DIRECT_START_PROJECT_KEY (theirs), dropped the deleted pawworkSessionRouteUnhideKeys. Semantic fix: filterPawworkRowsByOpenProjects now exempts direct-start rows (projectKey === PAWWORK_DIRECT_START_PROJECT_KEY). Direct-start sessions live in the server cwd and belong to no managed project, so the open-project membership filter would otherwise hide them. Added a regression test. Verified: typecheck, 656 layout/prompt-input unit tests, and the 3 sidebar project-action e2e specs (including "removed project stays removed when starting a new session") all pass.
Code review flagged the 0-project empty state as hiding direct-start sessions (the filter exempts them). This is intentional: the sidebar lists only open projects, and direct-start rows stay reachable once any project is open or by reopening. Document the choice inline so it is not re-flagged.
The sidebar session list is fetched as a global, activity-sorted, paginated window and filtered to open projects client-side. When the most recent page is dominated by closed-project sessions it filters to empty, and the Show more / search-history entries were gated behind a non-empty list — so an open project with older sessions showed a blank sidebar with no way to load deeper. - Auto-expand the window one page at a time (up to the cap) when the open-project list is empty but more pages exist, via shouldAutoExpandPawworkSessionWindow. Gated on loadedLimit === limit so it only advances after the current page has settled: it steps 30 -> 60 -> 90, never fires mid-load, and never retries a failed load. Skipped at zero open projects (the empty state is deliberate). - Keep the nav (with Show more / search-history) mounted whenever the window can still load or has hit the cap, so the load-deeper entry never vanishes with an empty filtered list. Unit-tests the auto-expand predicate; sidebar project-action e2e still passes.
Removing the active project navigates away (closeProject routes to "/" or the next project), but Undo only reopened it in the background, leaving the user where the close sent them. Undo now navigates back to the project when it was the active one, and keeps the plain background reopen otherwise.
Summary
The sidebar Remove project action was wired to a soft-hide (
pawworkProjectHidden) that only filtered the sidebar's session list. It never touched the canonical open-project list (server.projects), so the removed directory stayed in the input-bar workspace chip and any navigation (including the new-session button) silently un-hid it.This collapses everything to a single source of truth (
server.projects):server.projects, with an undo toast that reopens it (and restores focus when the removed project was the active one).filterPawworkRowsByOpenProjects) instead of the soft-hide map, so a removed project also disappears from the sidebar.pawworkProjectHiddenstate,hide/unhideProject, the three route unhide triggers, and the orphanedpawworkSessionRouteUnhideKeyshelper.Net effect: two parallel, drifting states become one. Removal now persists across new sessions and is reflected in the workspace chip because both read the one list.
Why
Regression: the left-sidebar Remove button no longer removed a working directory — after clicking New session the removed directory reappeared, and it was always still listed in the input-bar workspace chip.
Root cause: the original hard-remove (
closeProject→server.projects.close) lived in the legacySidebarPanel. When that panel was dropped in57ee5b4b91, the new sidebar substituted a soft-hide that never reachesserver.projects— a wrong-seam reimplementation.closeProjectwas left orphaned.Related Issue
No tracking issue; reported directly as a regression. Behavior boundary confirmed with the maintainer: "Remove" closes the working directory, and the sidebar shows only open projects (session data is kept and recoverable by reopening the project).
Follow-ups spun off from review (deliberately out of scope here):
Human Review Status
Pending
Review Focus
filterPawworkRowsByOpenProjectsmembership logic inpawwork-session-source.ts— a row is kept when its session's owning worktree (resolved viaexecutionContext.ownerDirectory, thenproject.worktree, then its own directory) matches an open worktree/sandbox. Direct-start rows (PAWWORK_DIRECT_START_PROJECT_KEY, from dev's merged direct-start feature) are exempt and always survive.shouldAutoExpandPawworkSessionWindow+ the controller effect — the window is fetched globally (activity-sorted, paginated, capped at 90) then filtered to open projects client-side, so a page of closed-project sessions can filter to empty. When the open-project list is empty but more pages exist, the window auto-expands one page at a time (gated onloadedLimit === limit: only after the current page settled, never mid-load, never retrying a failed load). The Show more / search-history entries were also moved out of the non-empty-list gate so the load-deeper entry never vanishes with an empty filtered list.removeProjectinlayout.tsx— resolves the group key to the project root before closing; the undo toast navigates back to the project when it was active, otherwise reopens in the background.Risk Notes
pawworkProjectHiddenmap (the old action never closedserver.projects). On upgrade those projects reappear in the sidebar once; removing them again now sticks. No migration is included — the pain is one-time and self-correcting.server.projectsentry. Its "Remove" maps to the parent project (or no-ops if the root can't be resolved). Removal stays project-level in this PR; precise per-group removal is tracked in [Feature] Make subfolder session groups first-class, independently removable units #1184.project.remove.*strings updated (en + zh).How To Verify
Screenshots or Recordings
Verified via the e2e real-renderer run: after Remove + New session the sidebar renders the "No projects open" empty state rather than re-listing the project.
Checklist
bug,enhancement,task,documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.app,ui,platform,harness,ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.P0,P1,P2,P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.Pending,Approved by @<reviewer>, orNot required: <reason>(default isPending; "not required" is restricted to bot-authored low-risk PRs).dev, and my PR title and commit messages use Conventional Commits in English.