🗂️ feat: Rework the Projects Dashboard, Sidebar and Scoped Composer - #14866
🗂️ feat: Rework the Projects Dashboard, Sidebar and Scoped Composer#14866berry-13 wants to merge 20 commits into
Conversation
Give /projects a sticky navbar, quieter search/sort toolbar, and folder-style cards. Drop the create-dialog close control, restyle the sidebar Projects row, and align the workspace with the same layout language.
Add a shared edit dialog so a project can be renamed and given a description from the workspace or the sidebar menu. The create flow already stored descriptions; this is the matching update path.
Extract the project delete confirmation into a shared dialog and expose it on the workspace header so deleting no longer requires the sidebar menu.
Give dashboard cards a more-options menu that opens the same edit and delete dialogs as the workspace, so those actions are not workspace-only.
Project search only matched the name, so a project found by its description was invisible in the sidebar and the projects dashboard. Match the escaped search term against name or description.
The popover always matched the trigger width, sat 4px from it and used the same enter animation, which is wrong for a pill-shaped trigger in a composer and for a full-width field in a dialog. Add popoverClassName, matchTriggerWidth, gutter and portal so a consumer can opt out of each. All four keep the current behaviour by default, so existing comboboxes are unchanged; portal in particular stays true, as turning it off inside a scrollable dialog would clip the list.
areConversationListItemFieldsEqual left pinned out of its comparison, so a row memoised on it kept rendering the stale pin state until some other tracked field changed.
Starting a chat from the project workspace set chatProjectId on the draft but left the URL on the unscoped route, so a reload or a refresh of the route dropped the project. Navigate to the project-scoped new chat URL alongside the draft.
The chip floated above the composer as a separate row, which read as an unrelated control and pushed the conversation starters down. Render it inside the composer border as the first row instead, and pass the project through ChatForm so the memoised form still controls it. The remove button no longer fades in on hover, since a control that only appears on hover is unreachable by touch. Its popover opens upward with a matching bottom-origin animation that honours reduced motion.
The dialog used a bare select, so picking a project meant scrolling an unsearchable native list capped at the default page of 25. Use the searchable ControlCombobox, request the full first page, and disable Save until the selection actually differs from the current project. The combobox opts out of portalling so its search field sits inside the dialog's focus trap and can be typed in, and the dialog is overflow-visible so the list is not clipped. Unassigning now lives on the menu's own Remove From Project action, so the dialog no longer needs an empty option. Returning focus to the menu button rather than the menu item fixes focus being lost on close, since the item unmounts with the menu.
Chats in a project workspace could only be opened. Managing one meant finding it again in the sidebar, so add the same actions to the row: change project, remove from project, and delete. The row becomes a card matching the project cards, and the endpoint icon is rendered at landing size rather than in a tinted tile. Its memo comparison now uses areConversationListItemFieldsEqual, since the render props comparison ignored fields the row displays.
The section header duplicated the projects count and the New Project action already on the dashboard, and spent a row on a chevron button separate from its label. Collapse it to a single label toggle with an All Projects action, and drop the per-project count that was hidden on hover anyway. The new chat action becomes a real link to the project-scoped URL, so it can be middle-clicked and opened in a new tab, and modified clicks fall through to the browser. On the new chat route it commits ?projectId synchronously, because a deferred search param update lets ChatRoute see a project-scoped draft on an unscoped URL and wipe it. Row actions stay visible on devices without hover, where an action that appears on hover cannot be reached.
The dashboard and the workspace sat on bg-surface-primary at different max widths, so moving between them shifted the content and the shade did not match the rest of the app. Put both on bg-presentation at max-w-6xl. Project and chat cards gain a border, since colour alone separated them from the background and that separation is thin in light mode. The translucent blurred headers become opaque, and the scale-on-press transforms are dropped. In the workspace the edit and delete actions move out of the heading row into their own group, so a long project name no longer pushes them around.
Closing the Ariakit menu in the same handler that opens the dialog made the menu's own dismissal land on the freshly mounted Radix dialog, which closed it again before paint, so Change project did nothing. Leave the menu close to the dialog, which already receives setMenuOpen and closes it once the assignment succeeds, matching the share and delete handlers beside it.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee8931ba03
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR reworks the Projects feature surfaces (dashboard, workspace, sidebar section, and project-scoped composer) so they behave and look like a cohesive flow, while addressing project-scoping/navigation and project assignment UX limitations across the app.
Changes:
- Backend: project search now matches both project name and description, with updated schema-method test coverage.
- UI/UX: Projects dashboard/workspace layouts are unified (presentation background + max width), cards/headers/actions are reworked, and project-scoped “new chat” navigation now correctly scopes the URL.
- Components:
ControlComboboxgains configurable popover behaviors (portal/sizing/gutter/classes) with tests; project CRUD dialogs and project chat overflow menu are introduced/expanded.
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/data-schemas/src/methods/chatProject.ts | Extends project search filtering to include description via $or. |
| packages/data-schemas/src/methods/chatProject.spec.ts | Adds coverage for name/description search behavior. |
| packages/client/src/components/ControlCombobox.tsx | Adds popover configuration props (portal, sizing, gutter, custom class). |
| packages/client/src/components/ControlCombobox.spec.tsx | Adds tests for search filtering and portal-in-dialog behavior. |
| client/src/style.css | Adds a bottom-origin popover animation class with reduced-motion handling. |
| client/src/locales/en/translation.json | Adds new UI strings for projects (counts, actions, empty states, etc.). |
| client/src/components/Projects/ProjectWorkspace.tsx | Unifies workspace layout/styling, adds edit/delete actions, and fixes project-scoped new-chat URL navigation. |
| client/src/components/Projects/ProjectsView.tsx | Redesigns the projects dashboard: navbar, cards, skeletons, empty/search states, sorting UI. |
| client/src/components/Projects/ProjectsNavBar.tsx | New sticky navbar for the projects dashboard with responsive sidebar trigger. |
| client/src/components/Projects/ProjectEditDialog.tsx | New project edit dialog (name + description) using shared dialog primitives. |
| client/src/components/Projects/ProjectDeleteDialog.tsx | New project delete confirmation dialog with redirect handling. |
| client/src/components/Projects/ProjectCreateDialog.tsx | Adds optional description on create and updates dialog structure/styling. |
| client/src/components/Projects/ProjectChatOptions.tsx | New overflow menu for project workspace chat rows (change/remove/delete). |
| client/src/components/Projects/ProjectChatList.tsx | Updates project chat list row UI and integrates the new overflow menu. |
| client/src/components/Conversations/utils.ts | Expands conversation equality comparison to include pinned. |
| client/src/components/Conversations/ProjectsSection.tsx | Reworks sidebar Projects section: simplified header/actions, accessible always-available row actions, project-scoped new chat link behavior. |
| client/src/components/Conversations/ConvoOptions/ProjectButton.tsx | Replaces native select with searchable ControlCombobox, increases initial project page size, and disables save when unchanged. |
| client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx | Adjusts dialog trigger refs for the “Change project” dialog flow. |
| client/src/components/Chat/ProjectLandingChip.tsx | Moves project scope control into the composer and customizes combobox popover presentation. |
| client/src/components/Chat/Input/ChatForm.tsx | Renders the project scope chip inside the composer when on a project-scoped landing. |
| client/src/components/Chat/ChatView.tsx | Passes project context down into ChatForm instead of rendering the chip above it. |
| client/src/components/Chat/tests/ChatView.spec.tsx | Updates mocks to reflect chip relocation into ChatForm. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Hiding the Ariakit menu in the same handler that opens Change project or Delete restores focus to the menu trigger, which the dialog mounting alongside it reads as an outside interaction and closes on, so the action could do nothing. Both dialogs already receive setIsMenuOpen and close the menu once they finish, so leave the close to them, as the conversation menu does.
Collapse hides its children with CSS and inert rather than unmounting them, so every project row's chat query ran on sidebar load, up to one request per project, even with the whole section collapsed. Gate the query on the row's expanded state. React Query keeps what it already fetched, so reopening a row is still instant.
ControlCombobox owns its popover animations in AnimatePopover.css, so the upward variant it needs belongs there rather than in the application stylesheet, where the control's appearance would diverge from the package that ships it. The app already loads the package stylesheet, so the animation resolves the same way the existing variants do.
The dialogs accepted any length and reported success, while the persistence layer trimmed names to 100 and descriptions to 1000 characters, so reopening a project revealed text had been dropped with no warning. Share both limits from data-provider and cap the inputs at them, so the fields stop where the server would have cut them and the rule has one definition instead of the three it had.
The dashboard counted the projects fetched so far, so an account with more than one page read as exactly one page's worth and the supposed total grew with each Load more. Show the loaded count as a lower bound while another page exists.
|
@codex review |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Sort the delete dialog's imports to the repository order, and drop the three English keys this branch orphaned: the sidebar menu now says Edit project, the dashboard labels its own sort control, and the change project dialog no longer offers an Unassigned option now that removal lives on the menu.
Summary
Reworks the Projects surfaces so the dashboard, the workspace, the sidebar section and the project-scoped composer read as one feature, and fixes the bugs that showed up while going through them.
Dashboard and workspace. Both sat on
bg-surface-primaryat different max widths, so moving between them shifted the content and the shade did not match the rest of the app. Both are nowbg-presentationatmax-w-6xl. Project and chat cards gain a border, since colour alone separated them from the background and that separation is thin in light mode. The translucent blurred headers become opaque, and the scale-on-press transforms are dropped. In the workspace the edit and delete actions move out of the heading row into their own group, so a long project name no longer pushes them around.Sidebar section. The header duplicated the project count and the New Project action already on the dashboard, and spent a row on a chevron button separate from its label. It collapses to a single label toggle plus an All Projects action. The per-project new chat action becomes a real link to the project-scoped URL, so it can be middle-clicked and opened in a new tab, and row actions stay visible on devices without hover, where an action that only appears on hover cannot be reached.
Project scope. Starting a chat from a project set
chatProjectIdon the draft but left the URL unscoped, so a reload dropped the project. Both entry points now navigate to the project-scoped URL. The scope chip moves from a floating row above the composer to the first row inside the composer border, where it reads as part of the input rather than an unrelated control.Change project dialog. It used a bare select, so picking a project meant scrolling an unsearchable native list capped at the default page of 25. It now uses the searchable
ControlCombobox, requests the full first page, and disables Save until the selection actually differs. Project search also matches descriptions, not just names.Project workspace chats gain the overflow menu the sidebar already has: change project, remove from project, delete.
The commits are split so each is reviewable on its own; the two
fix:commits at the end are defects found during browser verification.Change Type
Testing
Verified in the browser against a real backend, in both light and dark mode: created a project, started a project-scoped chat and confirmed
?projectIdlands in the URL and survives a reload, opened the composer chip's project picker, assigned a conversation through the Change project dialog (including typing in its search field, which is the case theportalprop exists for), searched a project by a term that only appears in its description, and exercised the new chat-row overflow menu in the workspace.Two defects were found and fixed this way: starting a project chat did not put the project in the URL, and closing the Ariakit menu in the same handler that opened the Change project dialog dismissed the dialog before paint, so the action did nothing.
ControlComboboxgainedpopoverClassName,matchTriggerWidth,gutterandportal. All four default to current behaviour, so the five existing comboboxes are untouched;portalin particular staystrue, because turning it off inside a scrollable dialog would clip the list for the Anthropic and Bedrock preset settings. New tests pin both the preserved default and the opt-in.Test Configuration:
Checklist