🗂️ feat: Add Private Chat Projects#13467
Conversation
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
Pull request overview
This PR introduces a first-class “private chat projects” feature that lets users organize conversations into user-owned projects end-to-end (DB schema/model/methods, API routes, data-provider types/hooks, and client UI + sidebar organization modes).
Changes:
- Added
ChatProjectpersistence (schema/model/methods) with per-user CRUD, assignment/unassignment, and derived stats (conversation count + last activity). - Extended conversation listing/pagination to filter by
projectId(including an"unassigned"sentinel) and surfacedchatProjectIdin conversation payloads. - Added
/api/projectsendpoints and client UI surfaces (/projects,/projects/:projectId) plus sidebar “by project / recent projects” views and convo menu actions.
Reviewed changes
Copilot reviewed 48 out of 48 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/data-schemas/src/types/index.ts | Exposes new chatProject types via barrel export. |
| packages/data-schemas/src/types/convo.ts | Adds chatProjectId field to conversation type. |
| packages/data-schemas/src/types/chatProject.ts | New ChatProject TS types. |
| packages/data-schemas/src/schema/index.ts | Exports new chatProjectSchema. |
| packages/data-schemas/src/schema/convo.ts | Adds chatProjectId field + supporting indexes to conversations. |
| packages/data-schemas/src/schema/chatProject.ts | New Mongoose schema for chatprojects. |
| packages/data-schemas/src/models/index.ts | Registers the ChatProject model. |
| packages/data-schemas/src/models/chatProject.ts | Creates tenant-isolated ChatProject model. |
| packages/data-schemas/src/methods/index.ts | Registers ChatProject methods in data-schemas methods bundle. |
| packages/data-schemas/src/methods/conversation.ts | Adds projectId filtering + project stat refresh hooks. |
| packages/data-schemas/src/methods/chatProject.ts | Implements CRUD, assignment, cursor pagination, and stat refresh. |
| packages/data-schemas/src/methods/chatProject.spec.ts | Adds Mongo-memory tests for ChatProject behavior. |
| packages/data-provider/src/types/queries.ts | Adds projectId to convo list params + project list response types. |
| packages/data-provider/src/types.ts | Adds ChatProject API types + includes chatProjectId in endpoint options. |
| packages/data-provider/src/schemas.ts | Adds chatProjectId to conversation zod schema and derived schemas. |
| packages/data-provider/src/schemas.spec.ts | Adds schema test ensuring chatProjectId survives compact parsing. |
| packages/data-provider/src/keys.ts | Adds React Query keys/mutation keys for projects. |
| packages/data-provider/src/data-service.ts | Adds project CRUD + assignment API calls. |
| packages/data-provider/src/bedrock.ts | Preserves chatProjectId through Bedrock input parsing. |
| packages/data-provider/src/api-endpoints.ts | Adds /api/projects endpoints. |
| packages/api/src/db/utils.ts | Ensures chatprojects collection exists for migrations/compat. |
| client/src/utils/convos.ts | Adds project-aware query-cache update helpers + dateField grouping. |
| client/src/routes/index.tsx | Adds /projects and /projects/:projectId routes. |
| client/src/routes/ChatRoute.tsx | Supports seeding new chats with chatProjectId via query param. |
| client/src/locales/en/translation.json | Adds UI strings for projects and sidebar organization. |
| client/src/data-provider/queries.ts | Passes projectId through conversation infinite query key/fetch. |
| client/src/data-provider/Projects/queries.ts | New project list + project detail queries. |
| client/src/data-provider/Projects/mutations.ts | New project create/update/delete + assignment mutations. |
| client/src/data-provider/Projects/index.ts | Exports Projects query/mutation hooks. |
| client/src/data-provider/mutations.ts | Invalidates projects on convo archive/delete/duplicate/fork. |
| client/src/data-provider/index.ts | Re-exports Projects hooks. |
| client/src/components/UnifiedSidebar/ConversationsSection.tsx | Adds sidebar organization modes + “new project” affordance. |
| client/src/components/Projects/ProjectWorkspace.tsx | New project-scoped chat listing + “new chat in project” UX. |
| client/src/components/Projects/ProjectsView.tsx | New projects grid view + create flow + search/sort. |
| client/src/components/Projects/ProjectChatList.tsx | Virtualized project chat list grouped by date. |
| client/src/components/Projects/index.ts | Exports Projects UI components. |
| client/src/components/Conversations/ProjectConversations.tsx | New sidebar project/grouped conversations virtualized view. |
| client/src/components/Conversations/ConvoOptions/ProjectButton.tsx | New dialog for assigning a convo to a project. |
| client/src/components/Conversations/ConvoOptions/index.ts | Exports ProjectButton (TS barrel). |
| client/src/components/Conversations/ConvoOptions/index.js | Exports ProjectButton (JS barrel). |
| client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx | Adds “Change project” / “Remove from project” menu actions. |
| client/src/components/Conversations/Convo.tsx | Passes chatProjectId into convo options. |
| client/src/components/Conversations/Conversations.tsx | Adds dateField option for grouping/sorting display. |
| api/server/routes/projects.js | New authenticated /api/projects CRUD + assignment routes. |
| api/server/routes/index.js | Registers new projects router module. |
| api/server/routes/convos.js | Adds projectId query param passthrough to convo cursor listing. |
| api/server/index.js | Mounts /api/projects. |
| api/server/experimental.js | Mounts /api/projects in experimental server. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return null; | ||
| } | ||
|
|
||
| const op = sortDirection === 'asc' ? '$gt' : '$lt'; | ||
| const id = new mongoose.Types.ObjectId(cursor.id); | ||
| const primary = cursorPrimaryValue(cursor.primary, sortBy); | ||
|
|
||
| return { | ||
| $or: [{ [sortBy]: { [op]: primary } }, { [sortBy]: primary, _id: { [op]: id } }], | ||
| } as FilterQuery<IChatProjectDocument>; |
|
|
||
| const VALID_SORT_FIELDS = new Set<ChatProjectSortBy>(['name', 'createdAt', 'lastConversationAt']); | ||
|
|
| if (conversation.chatProjectId) { | ||
| await refreshChatProjectStatsForUser(mongoose, userId, conversation.chatProjectId); | ||
| } |
| }; | ||
|
|
||
| return ( | ||
| <OGDialogContent className="w-11/12 max-w-md" showCloseButton={false}> |
| parseCursor(options.cursor), | ||
| sortBy, | ||
| sortDirection, | ||
| ); | ||
| if (cursorFilter) { |
| const handleCreate = async (event: FormEvent<HTMLFormElement>) => { | ||
| event.preventDefault(); | ||
| const trimmedName = name.trim(); | ||
| if (!trimmedName) { | ||
| return; | ||
| } | ||
| const project = await createProject.mutateAsync({ name: trimmedName }); | ||
| setName(''); | ||
| setIsCreating(false); | ||
| navigate(`/projects/${project._id}`); | ||
| }; |
| const index = 0; | ||
| const [searchParams] = useSearchParams(); | ||
| const { conversationId = '' } = useParams(); | ||
| const chatProjectId = searchParams.get('projectId'); |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1df7035aa1
ℹ️ 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".
GitNexus: 🚀 deployedThe |
|
@codex review |
GitNexus: 🚀 deployedThe |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a27e88d441
ℹ️ 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".
|
@codex review |
GitNexus: 🚀 deployedThe |
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 ℹ️ 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". |
358fd09 to
77e9c0c
Compare
|
@codex review |
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! ℹ️ 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". |
GitNexus: 🚀 deployedThe |
…delete redirect, project-detail invalidation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5b258d374a
ℹ️ 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".
GitNexus: 🚀 deployedThe |
…o to avoid stale conversation state
…ted chat's project for detail invalidation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 90dd1af803
ℹ️ 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".
GitNexus: 🚀 deployedThe |
…a chat between them
Codex triage (as of
|
|
@codex review |
GitNexus: 🚀 deployedThe |
There was a problem hiding this comment.
💡 Codex Review
With the new Projects section rendering project-scoped conversations separately, this main Chats query still omits projectId: 'unassigned', so the backend returns every non-archived conversation, including ones assigned to projects. In the sidebar, any project chat that is shown under ProjectsSection will also remain in the general Chats list (unless a search is active), defeating the separate project organization and making project moves look duplicated. Use the new unassigned filter for the default Chats list while preserving all-chat search if that is desired.
ℹ️ 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".
# Conflicts: # client/src/components/Conversations/ConvoOptions/ConvoOptions.tsx
GitNexus: 🚀 deployedThe |
…press Request cleanly
…n, upward combobox; sort en translations
GitNexus: 🚀 deployedThe |
* feat: Add private chat projects
* fix: Format project files
* fix: Address project review findings
* fix: Resolve project review follow-ups
* fix: Handle project stats and cache edge cases
* style: align projects UI with sidebar patterns
* fix: resolve projects UI lint issues
* style: Align project menus and composer
* fix: Avoid project placeholder shadowing
* fix: Handle project search and stale ids
* fix: Polish project sidebar behavior
* fix: Preserve new chat stream after creation
* fix: Stabilize project sidebar sections
* fix: Smooth project sidebar organization
* fix: stabilize project chat entry
* fix: keep project workspace outside chat context
* fix: show default model on project workspace
* fix: fallback project workspace model label
* fix: preserve project scope during draft hydration
* fix: include route project in new chat submission
* fix: persist project id in agent chat saves
* fix: refine project sidebar and creation UX
* fix: export chat project method types
* fix: polish project landing context
* fix: refine project navigation affordances
* feat: rework projects UX — coexisting sidebar sections + URL-driven scope
Sidebar
- Replace the chronological/by-project mode toggle with coexisting
Projects + Chats sections (both always visible)
- Remove ProjectConversations (927 lines), the org-mode Header, and types
- Add ProjectsSection: collapsible project rows that unfurl chats inline
(full-size rows), with per-project new chat and an open/rename/delete menu
- Lift the marketplace/favorites shortcuts above the Projects section
Chat scope
- Derive a new chat's project strictly from the URL ?projectId, so the
global New Chat no longer stays stuck in a project after a project chat
Surfaces
- Chat landing: subtle, clickable project chip instead of the floating badge
- Project workspace: modest header, composer-style entry, chats list
- All-projects grid: Claude-style cards with pluralized chat counts
* chore: prune unused i18n keys; fix project chat-count pluralization
* fix: project new-chat keeps model spec; sidebar header + row polish
- newConversation: ignore a chatProjectId-only template when deciding to
apply the default model spec, so starting a chat in a project no longer
strips the conversation `spec`
- useSelectMention: the Model Selector and @ command now retain the active
project across endpoint/spec/preset switches; other new-chat paths still
clear it
- Chats header now matches the Projects header (inline chevron + a new-chat
icon button) and starts a non-project chat
- Project rows: use the new-chat icon for the per-project add button, render
at text-sm to match the chat list, and align the row actions + hover color
with conversation rows
* fix: read project scope from router params; align sidebar header icons
- useSelectMention now reads the active project from React Router's search
params instead of window.location, which can drift out of sync because
new-chat params are written to the URL via raw history.pushState; the
Model Selector and @ command now reliably keep the project on switch
- Move the Chats section header out of the virtualized list so it renders
in the same context as the Projects header and isn't shifted by the
list scrollbar
- Inset header action icons (pr-2) so Projects/Chats header icons line up
with the project-row and conversation-row trailing actions
- Extract getRouteChatProjectId into utils for the submit path
* fix: preserve chatProjectId through the new-chat template reduction
The param-endpoint guard in newConversation reduced a new chat's template to
{ endpoint } only, dropping the chatProjectId injected by the Model Selector /
@ switch — so switching models cleared the project scope. Keep chatProjectId
in the reduced template.
* style: align chat-history panel top padding; improve projects page contrast
- Add pt-2 to the chat-history panel so its top spacing matches the other
side panels (agent builder, skills, files, etc.)
- Projects grid + workspace now use the darkest surface for the page
(surface-primary) with cards, inputs, and the composer one step lighter
(surface-secondary) and tertiary on hover, so cards read as elevated
rather than darker than the background
* feat: interactive project landing chip + gallery icon for all-projects
- All-projects sidebar button uses the gallery-vertical-end icon
- The project landing chip is now interactive: click it to switch projects
via a searchable combobox (ControlCombobox), or the trailing × to drop the
project scope. Both update the draft conversation and the ?projectId search
param in place, so the typed message and selected model are preserved
* test: fix Conversations unit test for refactored sidebar; add projects e2e
- Update Conversations.test.tsx mocks for the inline Chats header
(useNewConvo, useQueryClient, conversation atom, NewChatIcon, TooltipAnchor),
drop the removed chatsHeaderControls prop, and remove the mock for the
deleted ../Header module — fixes the failing frontend Jest job
- Add e2e/specs/mock/projects.spec.ts covering project creation, the
project-scoped new-chat landing + interactive chip (switch/remove), and
listing projects on /projects
- Give the landing chip combobox a stable selectId for reliable targeting
* fix: refresh project stats after project-chat activity; stabilize e2e
- useEventHandlers: when a project chat is created/updated, invalidate the
live [projects] query (gated on chatProjectId) instead of the now-unused
projectConversations key, so the sidebar + all-projects stats refresh
after a streamed reply (addresses a Codex finding)
- projects e2e: assert the reliable project-landing behavior (chip, scoped
composer, accepted send) rather than the /c/:id transition, which the
mock LLM harness doesn't complete
* test: verify a project chat saves and is filed under its project (e2e)
- Switch to a mock endpoint before sending so the message streams without a
real API key (the default model failed with "No key found", so no chat was
saved and the page never left /c/new); this also asserts the project chip
survives the model switch
- Restore the reply + /c/:id transition assertions and add a check that the
chat is listed under the expanded project in the sidebar
- Add data-testid="project-chats-<id>" to the inline project chat list
* fix: address Codex review findings (project scope edge cases)
- useSelectMention: fall back to the conversation's chatProjectId when the
URL has no projectId, so switching model/spec inside an existing project
chat (/c/:id) keeps the project assignment
- Conversations: include chatProjectId in the MemoizedConvo comparator so a
sidebar row's project menu doesn't stay stale after a reassignment
- useDeleteProjectMutation: clear the active conversation's chatProjectId
when its project is deleted (mirrors the assignment mutation); drop the
now-dead projectConversations invalidation
- useQueryParams: carry the project into the new conversation when applying
URL settings, so /c/new?projectId=...&<settings> stays scoped
* fix: project stats pagination + archived-chat edge cases (data-schemas)
- listChatProjects: include the null lastConversationAt bucket in the desc
cursor so empty projects paginate (a $lt:<date> predicate excluded nulls,
hiding chat-less projects from "Load more")
- saveConvo: recompute project stats instead of the incremental fast path
when the saved conversation is itself archived/temporary/expired, so a
project's lastConversationAt/Id no longer points at a hidden chat
* test: cover chat-less project pagination across the dated→null boundary
* fix: validate project ownership in bulkSaveConvos
Bulk paths (import/duplicate/fork) persisted whatever chatProjectId the
payload carried; an id that does not belong to the user created an orphan
assignment hidden from both the project and the unassigned sidebar. Validate
ownership like saveConvo and strip un-owned project ids before persisting,
refreshing stats only for owned projects.
* fix(projects): preserve chatProjectId on continuation, basename-safe delete redirect, project-detail invalidation
* fix(projects): navigate project workspace chats via useNavigateToConvo to avoid stale conversation state
* fix(projects): include projectConversations cache when resolving deleted chat's project for detail invalidation
* fix(projects): refresh both projects when a save or bulk write moves a chat between them
* style(projects): use Folders icon for the sidebar Projects header
* fix(projects): require id on ProjectUser so ProjectRequest extends Express Request cleanly
* style(projects): taller project chip with hover-revealed remove button, upward combobox; sort en translations
* style(projects): show endpoint/agent icon for project workspace chat rows
…ted (#13525) * fix(projects): clear landing scope when the selected project is deleted When a project-scoped new-chat landing (/c/new?projectId=...) was open and the project got deleted, the chip kept showing the dead project and sends targeted it (saving unscoped with a visual glitch). - ChatRoute: only trust the scope when the project query succeeds (isSuccess), so React Query's retained-on-error data can't keep a deleted project's chip alive; strip ?projectId once the query settles to not-found so the landing reverts to a normal unscoped chat. - useDeleteProjectMutation: invalidate the project-detail query instead of removing it, so active observers refetch and settle into an error state (removing left them stuck loading under refetchOnMount: false). - e2e: regression test for delete-while-scoped. Fixes a follow-up issue to the projects feature (#13467). * fix(projects): only drop scope on definitive not-found; clear inactive deleted detail Address Codex review on #13525: - ChatRoute: gate scope removal on a 404 (isNotFoundError) or a success that resolves to a different/empty project, so a transient (non-404) failure under retry:false no longer unscopes a valid project; keep the chip through transient errors via retained data. - useDeleteProjectMutation: also removeQueries({ type: 'inactive' }) so a deleted project's inactive cached detail is dropped and a later visit refetches into a not-found state instead of rendering stale cache within cacheTime.
…ted (danny-avila#13525) * fix(projects): clear landing scope when the selected project is deleted When a project-scoped new-chat landing (/c/new?projectId=...) was open and the project got deleted, the chip kept showing the dead project and sends targeted it (saving unscoped with a visual glitch). - ChatRoute: only trust the scope when the project query succeeds (isSuccess), so React Query's retained-on-error data can't keep a deleted project's chip alive; strip ?projectId once the query settles to not-found so the landing reverts to a normal unscoped chat. - useDeleteProjectMutation: invalidate the project-detail query instead of removing it, so active observers refetch and settle into an error state (removing left them stuck loading under refetchOnMount: false). - e2e: regression test for delete-while-scoped. Fixes a follow-up issue to the projects feature (danny-avila#13467). * fix(projects): only drop scope on definitive not-found; clear inactive deleted detail Address Codex review on danny-avila#13525: - ChatRoute: gate scope removal on a 404 (isNotFoundError) or a success that resolves to a different/empty project, so a transient (non-404) failure under retry:false no longer unscopes a valid project; keep the chip through transient errors via retained data. - useDeleteProjectMutation: also removeQueries({ type: 'inactive' }) so a deleted project's inactive cached detail is dropped and a later visit refetches into a not-found state instead of rendering stale cache within cacheTime.
Summary
I implemented private, user-owned chat projects as a V1 organizing layer for LibreChat conversations.
ChatProjectdata-schemas module backed by thechatprojectscollection, with per-user CRUD, one-project-per-chat assignment, stats maintenance, delete-unassign behavior, and Mongo memory tests.chatProjectIdto conversations and extended cursor-paginated conversation listing to filter by project orunassignedwithout grouping all chats client-side./api/projectsCRUD and conversation assignment routes, with shared data-provider endpoints, types, query keys, services, schemas, and React Query hooks./projectsand/projects/:projectIdUI surfaces for project grid browsing, project-scoped chat listing, and project-scoped new chat creation.Change Type
Testing
npm run build:data-providernpm run build:data-schemasnpm run build:client-packagenpm run build:clientnpm run build:apiexits 0; it still reports existing unrelated TypeScript warnings in API config/auth/share filescd packages/data-provider && npx jest src/schemas.spec.ts --runInBandcd packages/data-schemas && npx jest src/methods/chatProject.spec.ts --runInBandnode -c api/server/routes/projects.jsnpm run frontend:devand smoke-checked/projects; unauthenticated routing landed on login with no console errorsTest Configuration:
packages/data-schemastestsChecklist