feat(app): filter the sidebar by project (#3563)

The sidebar can be pinned to a set of hosts or to a set of labels, but
not to a set of projects. If you keep many projects on one host, the
project is the filter you want.

Add a Project page to the sidebar display menu, next to Host. It
multi-selects projects, and an empty selection means every project.

Apply the filter at two points. Project grouping reads the projects
array, but Status grouping builds its rows from the workspace entries.
A filter applied to only one of the two leaves the other mode
unfiltered.

Apply nothing when a stored selection names no visible project. The
project list is narrowed by the host filter, and it is empty before any
host connects, so dropping keys that do not match would destroy the
selection on every cold start.

Count the project filter in the sidebar header gate. Pinning every chat
in a project drops that project from the unpinned list. Filtering to
that project then removed the header, and the header carries the display
menu, which is the only way back to the Project page.

Generalise the label filter empty state to all sidebar filters, and move
its strings out of the labels namespace.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Christoph Leiter
2026-08-20 23:51:35 +02:00
committed by GitHub
co-authored by Claude Opus 5
parent 33224ace47
commit c3d37eba4b
23 changed files with 747 additions and 78 deletions
@@ -174,6 +174,27 @@ export async function openSidebarDisplayPage(page: Page, branchTestID: string):
await page.getByTestId(branchTestID).click();
}
// Project filters live a page below the display-preferences root, like the host filters.
export async function openSidebarProjectFilter(page: Page): Promise<void> {
await openSidebarDisplayPage(page, "sidebar-display-project-filter");
}
// The filter rows are keyed by the project's sidebar view key. A project seeded from its own temp
// repo has a `projectKey` unique on the host, so the view key IS that key — see
// `buildWorkspaceStructureProjects`.
export async function toggleProjectFilter(page: Page, projectViewKey: string): Promise<void> {
await page.getByTestId(`sidebar-project-filter-${projectViewKey}`).click();
}
export async function selectAllProjectsFilter(page: Page): Promise<void> {
await page.getByTestId("sidebar-project-filter-all").click();
}
export async function closeSidebarDisplayPreferences(page: Page): Promise<void> {
await page.keyboard.press("Escape");
await expect(page.getByTestId("sidebar-display-preferences-content")).toHaveCount(0);
}
export async function selectSidebarStatusGrouping(page: Page): Promise<void> {
await openSidebarDisplayPage(page, "sidebar-display-grouping");
await page.getByTestId("sidebar-grouping-status").click();