🗄️ feat: Archive All Chats From Data Controls - #14885
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c4bc35c6dc
ℹ️ 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
Adds a bulk “Archive all chats” action to the Data Controls settings UI and wires it through the client data layer to a new backend route and data-schemas method that archives all visible conversations for the authenticated user in a single DB pass.
Changes:
- Backend: add
archiveAllConvos(user)and expose it viaPOST /api/convos/archive/all, archiving viaupdateMany({ timestamps: false })and refreshing affected project stats. - Client: add a Settings entry + dialog UI to trigger the bulk archive and start a new chat, plus a cache fix for the single-chat archive flow to clear the new-chat message alias.
- Data-provider: add endpoint/type/service/mutation plumbing for
archiveAllConversations().
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/data-schemas/src/methods/conversation.ts | Implements archiveAllConvos with retention visibility filtering, timestamps: false, and best-effort project stats refresh. |
| packages/data-schemas/src/methods/conversation.spec.ts | Adds unit tests for archiveAllConvos (scope, no-op, retention/temporary skip, updatedAt preservation, project stats refresh, empty). |
| packages/data-provider/src/types/mutations.ts | Adds mutation option typing for the new archive-all mutation. |
| packages/data-provider/src/types.ts | Adds TArchiveAllConversationsResponse type. |
| packages/data-provider/src/data-service.ts | Adds archiveAllConversations() data-service call. |
| packages/data-provider/src/api-endpoints.ts | Adds /api/convos/archive/all endpoint helper. |
| client/src/utils/messages.ts | Refactors alias detection and adds clearArchivedConversationMessagesCache to fix the “new chat still shows archived convo” issue. |
| client/src/utils/tests/messages.test.ts | Adds tests for clearArchivedConversationMessagesCache. |
| client/src/locales/en/translation.json | Adds i18n strings for the Archive All UI and messaging. |
| client/src/data-provider/mutations.ts | Adds useArchiveAllConversationsMutation and updates single-archive flow to clear the archived message alias. |
| client/src/components/Nav/SettingsTabs/Data/ArchiveAllChats.tsx | Adds the Settings UI row + confirmation dialog to trigger the bulk archive and start a new chat. |
| client/src/components/Nav/Settings/registry.tsx | Registers the new Settings entry. |
| api/server/routes/convos.js | Adds POST /archive/all route calling db.archiveAllConvos. |
| api/server/routes/tests/convos.spec.js | Adds route tests for the new endpoint (success + 500). |
| api/server/routes/test-utils/convos-route-mocks.js | Adds archiveAllConvos to route mocks. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! 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". |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 61aeb23a03
ℹ️ 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.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 27d93703bc
ℹ️ 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".
| await Promise.all( | ||
| [...projectIds].map((projectId) => | ||
| refreshChatProjectStatsForUser(mongoose, user, projectId), | ||
| ), | ||
| ); |
There was a problem hiding this comment.
Prevent older project refreshes from overwriting newer stats
If another tab creates or assigns a visible conversation in one of these projects after refreshChatProjectStatsForUser reads its count/latest conversation but before that helper writes the result, this archive refresh can overwrite the newer operation's project update with the earlier count and lastConversationId, leaving the project stale indefinitely. Fresh evidence in this revision is the post-update refresh here: snapshot-scoping the archive prevents the concurrent chat from being archived, but does not make the subsequent read-then-write stats reconciliation safe against newer project changes.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is a pre-existing property of refreshChatProjectStatsForUser, whose read-then-write implementation is also used by deleteConvos, save/import, and project assignment. Fixing it correctly belongs in that shared helper across all six callers, not as an archive-only workaround in this PR.
Adds an "Archive all chats" row under Data controls > Your data, next to Shared links, with a confirmation dialog. It calls a new POST /api/convos/archive/all endpoint backed by archiveAllConvos, which archives every conversation currently visible to the user in a single updateMany and refreshes the stats of every chat project the archived conversations belonged to. Temporary and retention-expired conversations are skipped: they are already hidden from the chat list, so archiving them would only surface them in the archived view. The update runs with timestamps disabled so each conversation keeps its own updatedAt and the archived list stays sorted by real activity. Archiving a conversation now also drops the new-chat message cache alias for it. A chat's first turn writes the same message array under both the conversation key and the new-chat key, so without this the messages of a just-archived chat kept rendering on the new chat screen until a reload. Deleting already handled this; archiving did not.
27d9370 to
94b3613
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 94b3613de4
ℹ️ 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".
| router.post('/archive/all', async (req, res) => { | ||
| try { | ||
| const dbResponse = await db.archiveAllConvos(req.user.id); |
There was a problem hiding this comment.
Move the archive-all handler into packages/api
This adds the new endpoint implementation directly to the legacy /api workspace and calls the data-schema method there. Move the handler into the TypeScript /packages/api workspace and leave only the minimal JS route wrapper here; otherwise this new backend path bypasses the repository's required backend boundary and continues expanding the legacy server layer. CLAUDE.mdL20-L24
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The route is already thin transport glue: it only calls db.archiveAllConvos, returns the result, and maps errors, matching neighboring archive and pin handlers. The rule places database-specific shared logic in data-schemas, where this implementation already lives; a packages/api pass-through would add no policy or business logic.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 07217b4957
ℹ️ 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.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dd2d6d92cb
ℹ️ 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".
| const idRange = lastConversationId | ||
| ? { $gt: lastConversationId, $lte: snapshotBoundary._id } | ||
| : { $lte: snapshotBoundary._id }; | ||
| const conversations = await Conversation.find({ ...filter, _id: idRange }) |
There was a problem hiding this comment.
Freeze archive membership instead of only its upper ID
When another tab unarchives an older conversation after snapshotBoundary is read but before its batch is queried, that conversation now satisfies the live filter and falls below the high-water mark, so this operation archives it even though it was absent from the initial visible set, reversing the concurrent user's action. The current high-water revision therefore still does not provide the fixed-snapshot semantics expected here; persist or otherwise identify membership at submission time rather than re-evaluating eligibility for every batch.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Leaving this one open for maintainer judgement rather than acting on it, because the remedy conflicts with an earlier finding on the same code. The previous revision did materialise full membership up front, and the P2 at conversation.ts:942 asked for that to be replaced precisely because an unbounded hydrated read plus a large $in risks the BSON command size limit on big histories. Restoring fixed-snapshot membership reintroduces exactly that. The window here is also narrow: it needs a second tab to unarchive a conversation during the sweep, and the outcome is that one chat is archived again rather than any data loss. Happy to take either side, but it is a trade-off call rather than a defect to patch silently.
Summary
Adds an Archive all chats option under Settings > Data controls > Your data, next to Shared links. It confirms first, then archives every conversation currently visible in the chat list in one pass and starts a new chat.
Backend:
POST /api/convos/archive/allis backed by a newarchiveAllConvosmethod inpackages/data-schemas. It archives in a singleupdateManyand then refreshes the stats of every chat project the archived conversations belonged to, mirroring howdeleteConvoshandles project stats.Two deliberate calls in that method:
timestamps: false, so each conversation keeps its ownupdatedAtand the Archived chats table stays sorted by real activity instead of collapsing every row onto the archive time.This also fixes a pre-existing wart in the single-chat archive flow. A chat's first turn writes the same message array under both
[messages, <conversationId>]and[messages, "new"], and the messages query returns the new-chat cache verbatim. Archiving never reset that alias, so the messages of a just-archived chat kept rendering on the new chat screen until a reload. Deleting already guarded against this withclearDeletedConversationMessagesCache; archiving now does too, viaclearArchivedConversationMessagesCache, which drops the alias but keeps the conversation's own history cached because an archived chat can still be reopened.Change Type
Testing
Six new tests for
archiveAllConvosagainst a real in-memory MongoDB (per-user scoping, already-archived no-op, temporary and expired chats skipped,updatedAtpreserved, project stats refreshed, empty case), two new route tests forPOST /archive/all, and four new tests forclearArchivedConversationMessagesCache.Manual verification in the browser, light and dark mode: archived two chats from the settings row, confirmed the sidebar emptied, the view moved to a new chat with no leftover messages, and both chats appeared under Archived chats and still opened with their history. Also confirmed Cancel archives nothing and the single-chat archive from the sidebar menu clears the chat pane the same way.
Test Configuration:
Node 24.16.0, MongoDB via mongodb-memory-server for the data-schemas suite, Ollama (
gemma4:12b-it-qat) for the manual browser pass.Checklist