feat(web): reach projects from a folded sidebar - #13943
Conversation
A chat inside a project gave no sign of which project it belonged to. `projectId` is dropped from the URL once a chat opens (PARAMS_TO_SKIP), so nothing on screen carried the context. Add `ActiveProjectBreadcrumb`, which resolves the active project itself and renders "Projects > <name>" in the header. It covers both the project page and any chat the project owns. Also fix two problems found on the way: - Project chats lost the header actions. Share, full width, and the overflow menu are gated on `currentChatSession`, which resolves out of the Recents feed — and that feed excludes project chats on purpose. Fall back to the owning project's session list. This also repairs the document title and the agent lookup, which read the same value. - `buildChatUrl` and the chat search menu pointed at `/chat`, which redirects to `/app`. Every new chat paid for a redirect. Point them at `/app` and route the search menu through `useAppRouter`.
Drop the "Projects >" crumb and show a folder icon plus the project name. The header states where you are; it does not need a path. Also restore the header's wider horizontal padding at the sm breakpoint.
The project page already names itself in ProjectContextPanel, so the header label only belongs in chats. Guard in the component rather than in `useActiveProject`: the hook means "the project you are in", which still includes the project page, and the chat-session fallback in `useChatSessions` relies on that.
Co-locate the project sidebar row with the rest of the project components. Drop the memo wrapper: the only prop is the project object, which the sidebar rebuilds on each render, so the equality check never hit.
Convert the new folder-open artwork. It is an expanded outline, not a centreline path, so it must be filled: stroking it drew the outline of the outline and the glyph went solid. Reframe folder-partial-open from its 20-wide Figma frame. Shift the geometry 2 units left so the glyph fits a 16x16 viewBox instead of being clipped on the right.
Hover now resolves to the same icon in every state. Open or closed, the folder shows its partial-open form while the pointer is on it. Render the icon in a plain container instead of a Button, so the folder keeps its own size and colour.
A click sets allowHoverEffect false, so the partial-open preview stays off until the pointer leaves the icon and comes back. Without this the icon previews the state the user just left.
Pass the project name to SidebarTab as a string. SidebarTab routes string children through ContentAction, which is the path ChatButton already takes, so a project row and its chats now render identically. The label keeps its one-line truncation and picks up the tab's interactive colour instead of a fixed one. Narrow the nested spacer to match. The folder icon container is 20px, so a 24px spacer pushed every project chat 4px past its project.
Folded, the sidebar had no path to a project. It showed only New Project, and project chats appear nowhere else — Recents excludes them. Replace that tab with a Projects tab that opens a popover holding the whole tree: a search field, New Project, every project, and each project's chats. The tab stays marked while its popover is open. Search matches project names and chat names. A project listed because a chat matched opens itself and shows only the matching chats, so the reason it is listed is never hidden. Navigating closes the popover; folding a project's chats does not, because it never touches the URL. Add ProjectPopoverRow for the popover: a navigator, not a manager, so it carries no drop target and no rename or delete. Both rows now share useFolderIcon, so the folder glyph cannot drift between them. The unfolded sidebar is unchanged.
Swap the popover rows from SidebarTab to LineItemButton, and give the search field the new-project button so the row above it can go. Work in progress. The chat rows carry no onClick or href yet, so they do not navigate, and the e2e spec still expects the old markup.
# Conflicts: # web/src/layouts/chromes/AppChrome.tsx # web/src/sections/sidebar/AppSidebar.tsx # web/src/sections/sidebar/ProjectFolderButton.tsx
The folder icon container loses its padding, so it is the icon's own 16px rather than 20px. The nested spacer follows to match, keeping a project's chats aligned under the project label rather than 4px past it.
SidebarTab now lays an absolute z-99 control over the whole row whenever the tab has an onClick, so the folder icon sat underneath it. A statically positioned element can never paint above a positioned one, so the click landed on the row: it navigated into the project, and the arrival effect opened the folder. The fold looked like it expanded and could never close, because nothing was ever calling the toggle. Raising the icon is what rightChildren already does for the same reason.
The fixed h-9 capped every row at 36px, which clips the two-line rows that pass a description — NotificationsPopover and ModelSelectorContent among them. It also contradicted the component's own doc comment, which promises the height is content-driven. The flex centring goes with it: ContentAction is full width, so it was doing nothing.
The rows rendered but did nothing. They now carry an href in the same shape ChatButton uses, close the popover on the way out, and mark the chat you are already in. An unnamed chat shows the label the sidebar shows rather than an empty row. The new-project control regains a test id and a tooltip, both lost when it became an icon inside the search field. Card and Divider go, unused since the rows became line items.
Greptile SummaryThe PR adds project discovery and navigation to the folded sidebar, identifies the active project in chat headers, and aligns chat navigation with the
Confidence Score: 4/5The PR needs the chat-search expansion defect fixed before merging; the remaining findings are non-blocking test and component-convention issues. Chat-name searches return the owning project but cannot expose the matching chat because the only state synchronization that opens matched projects is commented out. Files Needing Attention: web/src/lib/projects/components.tsx, web/tests/e2e/chat/folded_projects_popover.spec.ts, web/lib/opal/src/icons/folder-open.tsx, web/lib/opal/src/icons/folder-partial-open.tsx Important Files Changed
Prompt To Fix All With AI### Issue 1
web/src/lib/projects/components.tsx:311-313
**Chat matches remain hidden**
When a query matches a chat inside a closed project, `chatMatched` never updates the row's `open` state because this effect is commented out, causing the matching chat to remain hidden and unselectable.
```suggestion
useEffect(() => {
if (isActiveProject || match.chatMatched) setOpen(true);
}, [isActiveProject, match.chatMatched]);
```
### Issue 2
web/tests/e2e/chat/folded_projects_popover.spec.ts:124-131
**Navigation bypasses page object**
The spec clicks the chat locator and checks the URL directly with `page.waitForFunction`, leaking navigation details outside the page object and bypassing the repository's auto-retrying matcher convention. Move the complete chat-selection and navigation assertion into `AppSidebarPage`.
### Issue 3
web/lib/opal/src/icons/folder-open.tsx:2
**Icons use arrow components**
`SvgFolderOpen` and the corresponding `SvgFolderPartialOpen` rewrite use arrow-function component declarations, contrary to the frontend convention requiring regular function declarations for React components.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "feat(web): make the folded popover's cha..." | Re-trigger Greptile |
|
Preview Deployment
|
There was a problem hiding this comment.
All reported issues were addressed across 17 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The inner ContentAction's padding was fixed, so a caller who wanted a denser or roomier row had no way to ask. It is now a prop, still defaulting to 0 so existing rows are unchanged. Corrects the README while there: it claimed the padding was hardcoded to "lg", later carried through the numeric migration as 2, when the code had always passed "fit" — so the documented value was wrong before and after that conversion.
🖼️ Visual Regression Report
|
The popover lists the same things the sidebar does, so its rows should be the sidebar's row. SidebarTab also carries what the line items were reproducing by hand: `nested` supplies the indent that lines a chat up under its project, and `selected` marks both the project you are in and the chat you are reading. The project row regains the sidebar's rule — while its chats are hidden, the folder carries the you-are-here mark for them.
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
The prop is inherited from ContentActionProps, which narrows it to 0 | 0.5 | 1 | 2 — documenting it as an open Spacing invited callers to try values that do not type-check. The default was also stale: it moved to 0.5 and the table still said 0.
The effect that opens a project whose chat matched was commented out, so a search could list a project while hiding the very chat that put it there. It was a workaround for the folder icon losing its clicks to the row overlay; that is fixed at the root now, so the effect comes back. A query matching both a project name and one of its chats also narrowed to the matching chats, which contradicted the rule stated right above it. The project is what matched, so it stays whole and stays folded — only a chat-only hit narrows and opens. The chat-selection test now goes through the page object and asserts the URL with an auto-retrying matcher, per the e2e rules.
Merging main brought in stricter jsx-a11y rules, which the projects sidebar was already breaking. The folder toggle was a div with a click handler, so it was unreachable by keyboard and had no role. A button gives it both, plus a name and its fold state, which a bare glyph cannot carry on its own. It keeps the classes it had, so nothing moves. The popover's search field used autoFocus. useFocusOnMount focuses it the same way without the page-load focus theft the attribute also allows.
|
Full-stack Preview (frontend + backend)
Sign in with GitHub as an |
Description
Makes projects reachable from a folded sidebar, and surfaces which project the current chat belongs to.
Folded, the sidebar previously showed only a "New Project" tab. There was no way to see, search, or open a project without unfolding — and project chats were unreachable entirely, since Recents excludes them.
Three pieces:
1. An active-project label in the app header. A chat inside a project now says which one. This is not derivable from the URL:
projectIdis dropped once a chat opens (PARAMS_TO_SKIPinapp/app/services/lib.tsx), souseActiveProjectresolves it by finding the project whosechat_sessionscontains the open chat. Chats only — the project page already names itself inProjectContextPanel.2. A Projects popover in the folded sidebar. The folded tab opens a panel holding the whole tree: search, new project, every project, and each project's chats. The tab stays
selectedwhile open. Search matches project and chat names; a project listed because one of its chats matched opens itself and shows only the matches, since a hit you cannot see is no hit at all.3. Fold-state fixes on the existing project rows. The folder icon previews the partial-open folder on hover, holds that preview off after a click until the pointer leaves, and the row carries the "you are here" mark while its chats are hidden — a project chat appears nowhere else in the sidebar.
ProjectFolderButtonmoves fromsections/sidebar/intolib/projects/components.tsx, alongside the rest of the project feature code.Two fixes worth calling out
The folder icon had stopped receiving its own clicks. #13921 made
SidebarTablay an absolutez-99control over the whole row whenever the tab has anonClick— previously that overlay existed only forhref. OnlyrightChildrenis raised above it, so the icon sat underneath, and a statically positioned element can never paint above a positioned one. Clicks landed on the row: it navigated into the project, and the arrival effect opened the folder. The fold appeared to expand and could never close, because nothing was calling the toggle. The icon is now raised the wayrightChildrenalready is.Worth checking whether
mainhas the same latent issue — itsProjectFolderButtonrenders the icon as an OpalButton, equally unpositioned, under the same overlay.LineItemButtonno longer forces a 36px row. A fixedh-9clipped the two-line rows that pass adescription(NotificationsPopover,ModelSelectorContentamong them) and contradicted the component's own doc comment promising a content-driven height.Screenshots + Videos
Project Buttons spacings have been updated:
There's a "project breadcrumb" in the top-left corner when inside of a project-chat:
If a project-folder is closed and you're inside of a project-chat, the project-folder is highlighted instead (to indicate to the user where in the application they are):
Folded sidebar projects-menu:
Additional Options
Summary by cubic
Reach projects and their chats from a folded sidebar, and show the active project in chat headers. Previously the folded sidebar only showed “New Project” and project chats were unreachable; now a Projects popover lists projects and chats, and chat headers show their project.
/appand closes the popover; toggling a folder does not close it.ActiveProjectBreadcrumb(hidden on the project page).useActiveProjectresolves context whenprojectIdis absent from the URL, anduseChatSessionsfalls back to the owning project’s sessions so header actions, document title, and agent lookup work for project chats.SidebarTabclick overlay so clicks toggle without navigating, shows partial‑open on hover in both states, holds off the hover preview until the pointer leaves after a click, and tightens the nested indent (w-4).buildChatUrland the chat search menu now route to/appand useuseAppRouter.ProjectFolderButtontoweb/src/lib/projects/components.tsxand addsFoldedProjectsPopover,ActiveProjectBreadcrumb,useProjectSearch, and aProjectSearchMatchtype. The popover’s search field focuses on open without page‑load focus theft.@opal: updates folder‑open and partial‑open icons; restores content‑drivenLineItemButtonheight and adds apaddingprop (default0.5, typed as0 | 0.5 | 1 | 2); input padding isp-1.5.Written for commit 37e7e28. Summary will update on new commits.