Skip to content

fix(app): make sidebar "Remove project" actually close the working directory#1177

Merged
Astro-Han merged 6 commits into
devfrom
claude/remove-workspace-fix
Jun 5, 2026
Merged

fix(app): make sidebar "Remove project" actually close the working directory#1177
Astro-Han merged 6 commits into
devfrom
claude/remove-workspace-fix

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jun 5, 2026

Copy link
Copy Markdown
Owner

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):

  • Remove resolves the group key to its project root and closes it via server.projects, with an undo toast that reopens it (and restores focus when the removed project was the active one).
  • The sidebar's global session window is filtered by open-project membership (filterPawworkRowsByOpenProjects) instead of the soft-hide map, so a removed project also disappears from the sidebar.
  • The soft-hide subsystem is deleted entirely: pawworkProjectHidden state, hide/unhideProject, the three route unhide triggers, and the orphaned pawworkSessionRouteUnhideKeys helper.
  • Copy updated from "hidden from the sidebar" to "removed from your workspace" (en + zh).

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 (closeProjectserver.projects.close) lived in the legacy SidebarPanel. When that panel was dropped in 57ee5b4b91, the new sidebar substituted a soft-hide that never reaches server.projects — a wrong-seam reimplementation. closeProject was 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

  • filterPawworkRowsByOpenProjects membership logic in pawwork-session-source.ts — a row is kept when its session's owning worktree (resolved via executionContext.ownerDirectory, then project.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 on loadedLimit === 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.
  • removeProject in layout.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.
  • Deletion of the soft-hide subsystem and the rewritten routing-actions expectations.

Risk Notes

  • Behavior change (intended): the sidebar now lists only open projects. A project with backend sessions that is not open will not appear until reopened. Confirmed with maintainer.
  • Upgrade behavior (accepted, self-correcting): users who "removed" projects on an older build only have that choice in the now-deleted pawworkProjectHidden map (the old action never closed server.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.
  • Subfolder groups (deferred — [Feature] Make subfolder session groups first-class, independently removable units #1184): a session started inside a subfolder shows as its own group but is not a separate server.projects entry. 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.
  • Zero open projects + direct-start: the empty state is keyed on open-project count only, so it intentionally shows even when direct-start rows survive the filter (documented inline). Auto-expand is skipped at zero open projects.
  • Copy change: project.remove.* strings updated (en + zh).
  • No platform/packaging/updater/signing surface touched (renderer logic only).

How To Verify

typecheck (packages/app, tsgo -b): clean
unit (bun test src/pages/layout src/components/prompt-input): 663 passed, 0 failed
  - filterPawworkRowsByOpenProjects (open/closed/subfolder/sandbox/empty/executionContext/direct-start)
  - shouldAutoExpandPawworkSessionWindow (7 guard-branch cases)
  - rewritten pawwork-routing-actions expectations (no unhide on navigate/new-session)
e2e (playwright e2e/sidebar/sidebar-project-actions.spec.ts): 3 passed
  - remove decreases project group count
  - remove -> click New session -> project stays gone; sidebar shows "No projects open"
eslint (changed source files): 0 errors

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

  • Type label — this PR carries exactly one of 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.
  • Routing labels — this PR carries at least one of 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.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

…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).
@Astro-Han Astro-Han added bug Something isn't working P2 Medium priority app Application behavior and product flows labels Jun 5, 2026
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR replaces the sidebar project "hide" feature with full project removal and undo. It removes the pawworkProjectHidden persisted state, deletes hide/unhide handlers, introduces removeProject with undo support, switches session filtering from hidden-projects to open-projects, and adds auto-expand pagination based on open project count.

Changes

Remove Hidden-Project Model and Implement Removal with Undo

Layer / File(s) Summary
Remove pawworkProjectHidden persisted state
packages/app/src/pages/layout/layout-page-store.ts, packages/app/e2e/snap/sidebar-pinned.snap.ts
pawworkProjectHidden field removed from default state, all migration branches, and field-compatibility detection; snapshot test omits the field from seeded localStorage.
Remove hideProject/unhideProject handlers and routing
packages/app/src/pages/layout/pawwork-project-controls.ts, packages/app/src/pages/layout.tsx, packages/app/src/pages/layout/pawwork-routing-actions.ts
hideProject/unhideProject implementations deleted; projectKeyForSession and unhideProject removed from routing inputs; sidebar wiring and session controller updated to remove hidden-project filtering.
Add removeProject handler with undo and toast
packages/app/src/pages/layout.tsx, packages/app/src/i18n/en.ts, packages/app/src/i18n/zh.ts
New removeProject(projectKey) handler closes the project and displays a toast with Undo action; sidebar callback switched from hideProject to removeProject; i18n strings updated to describe workspace removal instead of sidebar hiding.
Switch to open-projects-based session filtering
packages/app/src/pages/layout/pawwork-session-source.ts, packages/app/src/pages/layout/pawwork-session-controller.ts, packages/app/src/pages/layout/pawwork-sidebar-session-rows.test.ts
pawworkSessionRouteUnhideKeys removed; new filterPawworkRowsByOpenProjects filters sidebar rows by matching session worktrees/sandboxes against open projects; session controller uses the new filter; comprehensive test coverage added for filtering logic.
Add session window auto-expand behavior
packages/app/src/pages/layout/pawwork-session-window.ts, packages/app/src/pages/layout/pawwork-session-window.test.ts, packages/app/src/pages/layout/pawwork-session-controller.ts
shouldAutoExpandPawworkSessionWindow helper added to determine when to auto-advance pagination; loadedLimit field tracks last successful load for gating; auto-expand effect added to controller; test suite validates guard conditions (zero open projects, visible sessions, loading state, pagination limits).
Update sidebar rendering and test coverage
packages/app/src/pages/layout/pawwork-sidebar.tsx, packages/app/e2e/sidebar/sidebar-project-actions.spec.ts, packages/app/src/pages/layout/pawwork-routing-actions.test.ts
Sidebar navigation block now renders when sessions exist or pagination can continue; e2e test replaced to verify removed projects persist removed across sessions; routing-action tests simplified by removing unhide sequencing assertions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Astro-Han/pawwork#1060: Earlier extraction of the hideProject/unhideProject handlers that this PR now removes and replaces with removeProject.
  • Astro-Han/pawwork#517: Introduced the sidebar project hide/restore functionality and pawworkProjectHidden persistence that this PR removes.
  • Astro-Han/pawwork#386: Earlier work on session-window-driven sidebar rendering that aligns with this PR's enhancements to sidebar navigation visibility and auto-expand logic.

Poem

🐰 Projects removed now, not just hidden away,
Undo magic whispers "come back, please stay!"
Filters bloom brighter by what's open and true,
Auto-expand wings—the sidebar's breakthrough!
Hopping through state, we cleanup the view. 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: replacing sidebar Remove's soft-hide behavior with actual project closure via server.projects.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description comprehensively covers all required sections: Summary explains the change, Why provides context and root cause, Related Issue is stated, Human Review Status is set, Review Focus identifies key areas, Risk Notes detail behavior and upgrade impacts, How To Verify includes verification steps with results, and all checklist items are addressed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/remove-workspace-fix

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the ui Design system and user interface label Jun 5, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/app/src/pages/layout.tsx
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.
@Astro-Han

Copy link
Copy Markdown
Owner Author

Codex review (xhigh, diff-scoped) flagged one P2: the open-project filter resolved a session's owner from project.worktree ?? directory, but a session.get backfill (active/pinned sessions off the first page of the global list) returns a plain Session.Info with no project field. A session opened in a subfolder of an open project (e.g. /repo/packages/app) would then match the bare directory against open worktree keys and be wrongly filtered out, hiding the current/pinned session.

Fixed in e8e0e5d: resolve the owning worktree from executionContext.ownerDirectory first (always present; Instance.worktree === executionContext.ownerDirectory), then project.worktree, then the bare directory. Added a regression test for the project-less subfolder case. Re-ran typecheck + unit + the 3 sidebar e2e — all green.

Astro-Han added 2 commits June 5, 2026 13:45
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.
Astro-Han added 2 commits June 5, 2026 17:11
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.
@Astro-Han Astro-Han merged commit 171760c into dev Jun 5, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows bug Something isn't working P2 Medium priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant