Skip to content

feat(web): reach projects from a folded sidebar - #13943

Merged
raunakab merged 22 commits into
mainfrom
feat/projects-viz
Aug 22, 2026
Merged

feat(web): reach projects from a folded sidebar#13943
raunakab merged 22 commits into
mainfrom
feat/projects-viz

Conversation

@raunakab

@raunakab raunakab commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

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: projectId is dropped once a chat opens (PARAMS_TO_SKIP in app/app/services/lib.tsx), so useActiveProject resolves it by finding the project whose chat_sessions contains the open chat. Chats only — the project page already names itself in ProjectContextPanel.

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 selected while 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.

ProjectFolderButton moves from sections/sidebar/ into lib/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 SidebarTab lay an absolute z-99 control over the whole row whenever the tab has an onClick — previously that overlay existed only for href. Only rightChildren is 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 way rightChildren already is.

Worth checking whether main has the same latent issue — its ProjectFolderButton renders the icon as an Opal Button, equally unpositioned, under the same overlay.

LineItemButton no longer forces a 36px row. A fixed h-9 clipped the two-line rows that pass a description (NotificationsPopover, ModelSelectorContent among them) and contradicted the component's own doc comment promising a content-driven height.

Screenshots + Videos

Project Buttons spacings have been updated:

image

There's a "project breadcrumb" in the top-left corner when inside of a project-chat:

image

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

image

Folded sidebar projects-menu:

image

Additional Options

  • [Optional] Please cherry-pick this PR to the latest release version.
  • [Optional] Override Linear Check

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.

  • Folded sidebar: adds a Projects popover with search over project and chat names, a New Project button, and per‑project folders that expand and list matching chats. A project‑name hit keeps the project folded with all chats; a chat‑only hit opens the project and shows only matches. The tab stays selected while open. Selecting a chat navigates to /app and closes the popover; toggling a folder does not close it.
  • Header: adds an active‑project label in chats via ActiveProjectBreadcrumb (hidden on the project page). useActiveProject resolves context when projectId is absent from the URL, and useChatSessions falls back to the owning project’s sessions so header actions, document title, and agent lookup work for project chats.
  • Folder icon: is now a real button with an accessible name and expanded state, sits above the SidebarTab click 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).
  • Routing: buildChatUrl and the chat search menu now route to /app and use useAppRouter.
  • Structure: moves ProjectFolderButton to web/src/lib/projects/components.tsx and adds FoldedProjectsPopover, ActiveProjectBreadcrumb, useProjectSearch, and a ProjectSearchMatch type. The popover’s search field focuses on open without page‑load focus theft.
  • UI tweaks in @opal: updates folder‑open and partial‑open icons; restores content‑driven LineItemButton height and adds a padding prop (default 0.5, typed as 0 | 0.5 | 1 | 2); input padding is p-1.5.
  • Tests: adds an e2e spec for the folded Projects popover and a helper to move chat sessions into projects.

Written for commit 37e7e28. Summary will update on new commits.

Review in cubic

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.
@raunakab
raunakab requested a review from a team as a code owner August 13, 2026 16:50
@raunakab
raunakab requested a review from jmelahman August 13, 2026 16:50
@greptile-apps

greptile-apps Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds project discovery and navigation to the folded sidebar, identifies the active project in chat headers, and aligns chat navigation with the /app route.

  • Adds a searchable folded-sidebar Projects popover with project and chat rows.
  • Adds active-project resolution for project chats and exposes it in the app header.
  • Moves project row components into the projects feature and updates folder interactions.
  • Adds folded-sidebar E2E coverage and related page-object/API helpers.
  • Updates Opal row spacing, input spacing, sidebar nesting, and folder icons.

Confidence Score: 4/5

The 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

Filename Overview
web/src/lib/projects/components.tsx Adds the folded Projects popover and shared project rows, but leaves chat-match auto-expansion disabled.
web/src/lib/projects/hooks.ts Adds active-project lookup and in-memory project/chat search over the projects payload.
web/src/hooks/useChatSessions.ts Resolves project chats from the active project's session list when Recents excludes them.
web/src/app/app/services/lib.tsx Corrects generated non-search chat navigation from the obsolete /chat path to /app.
web/src/sections/sidebar/AppSidebar.tsx Replaces the folded New Project entry with the new Projects popover and uses moved project components.
web/tests/e2e/chat/folded_projects_popover.spec.ts Covers folded project interactions, but part of the navigation flow bypasses the page-object and retrying-matcher conventions.
web/lib/opal/src/icons/folder-open.tsx Replaces the folder-open SVG but declares the component with a disallowed arrow-function pattern.
web/lib/opal/src/icons/folder-partial-open.tsx Replaces the partial-open SVG and repeats the component declaration convention violation.
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

Comment thread web/src/lib/projects/components.tsx Outdated
Comment thread web/tests/e2e/chat/folded_projects_popover.spec.ts Outdated
Comment thread web/lib/opal/src/icons/folder-open.tsx
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Status Preview Commit Updated
https://onyx-preview-kwhjwpkgd-danswer.vercel.app 37e7e28 2026-08-22 00:21:04 UTC

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 17 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread web/src/lib/projects/components.tsx Outdated
Comment thread web/src/lib/projects/hooks.ts Outdated
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.
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🖼️ Visual Regression Report

Project Changed Added Removed Unchanged Report
admin 80 0 0 107 View Report
exclusive 5 0 0 5 View 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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 3 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread web/lib/opal/src/components/buttons/line-item-button/README.md Outdated
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.
@github-actions

Copy link
Copy Markdown
Contributor

Full-stack Preview (frontend + backend)

Status Preview Commit Updated
https://37e7e28-onyx.preview.onyxcorp.dev/ 37e7e28 2026-08-22 00:19:46 UTC

Sign in with GitHub as an onyx-dot-app member to view it.

@raunakab
raunakab added this pull request to the merge queue Aug 22, 2026
Merged via the queue into main with commit d721efe Aug 22, 2026
55 checks passed
@raunakab
raunakab deleted the feat/projects-viz branch August 22, 2026 00:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants