🗄️ feat: Record When a Conversation Was Archived - #14863
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 14d373a341
ℹ️ 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".
14d373a to
da1cebf
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! 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". |
da1cebf to
72d5ccb
Compare
There was a problem hiding this comment.
Pull request overview
Adds first-class support for tracking when a conversation was archived (archivedAt) across the data schema, API, cursor pagination, and the Archived Chats UI, so “Date Archived” reflects archive time (with a safe fallback for legacy archived chats).
Changes:
- Introduces
archivedAton conversations; stamps it on archive and clears it on unarchive. - Updates archived-conversation cursor sorting/pagination to support sorting by
archivedAt(with null/missing-aware paging) and adds targeted tests. - Switches the Archived Chats table default sort and displayed date column to use
archivedAt ?? createdAt.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/data-schemas/src/types/convo.ts | Adds archivedAt to the conversation document type. |
| packages/data-schemas/src/schema/convo.ts | Persists archivedAt and adds an archive-list index aligned to the new sort. |
| packages/data-schemas/src/methods/conversation.ts | Adds archivedAt as a valid sort field; updates cursor paging and projection to include it. |
| packages/data-schemas/src/methods/conversation.spec.ts | Adds tests covering archivedAt cursor paging across stamped/unstamped rows. |
| packages/data-provider/src/types/queries.ts | Extends list sort options and minimal conversation shape to include archivedAt. |
| packages/data-provider/src/schemas.ts | Extends Zod schema so API payloads can include nullable/optional archivedAt. |
| packages/data-provider/src/config.ts | Excludes archivedAt from persisted conversation data (consistent with other metadata fields). |
| client/src/components/Nav/SettingsTabs/General/ArchivedChatsTable.tsx | Defaults sorting to archivedAt and renders archivedAt ?? createdAt in the “Date Archived” column. |
| api/server/routes/convos.js | Stamps archivedAt on archive and clears it on unarchive while preserving updatedAt. |
| api/server/routes/tests/convos.spec.js | Updates archive route expectations and adds tests for stamping/clearing archivedAt. |
💡 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. Keep it up! 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". |
72d5ccb to
ba3c93e
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 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". |
ba3c93e to
67313df
Compare
|
@codex review |
|
Codex Review: Didn't find any major issues. Breezy! 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". |
|
@codex review |
|
Codex Review: Didn't find any major issues. Bravo. 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". |
67313df to
780ee4f
Compare
d449471 to
d428faf
Compare
d428faf to
92e45a7
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92e45a76f3
ℹ️ 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".
92e45a7 to
821ca9c
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 821ca9c4c3
ℹ️ 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: 458c0cc814
ℹ️ 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".
458c0cc to
d627c86
Compare
The archived chats dialog has a "Date Archived" column that was bound to createdAt, so it showed when the chat was created rather than when it was filed away. Nothing recorded the latter. Conversations now carry archivedAt, set on archive and cleared on unarchive, and the column reads it. Chats archived before the field existed have no stamp and fall back to createdAt, which is exactly what that column already showed for them. The archive view sorts on the new field. archivedAt is absent on every previously archived chat, so the missing-value group is the common case here rather than an edge case: the cursor's null handling, written for titles, now covers both, and an absent stamp survives the cursor as null instead of collapsing to the epoch and replaying the whole archive.
- Protect `archivedAt` from saveMessageToDatabase's unset sweep. Any persisted field missing from endpointOptions is unset, so sending a message in an archived chat cleared the stamp while leaving isArchived true, silently dropping it into the legacy fallback group. - Order the legacy group by the createdAt the dialog displays rather than by last activity. The cursor's secondary key is now chosen per sort field, so the fallback the cell renders and the order the server returns cannot disagree. - Put that secondary key in the archive index too, so paging the legacy group does not fall back to a blocking sort.
Opening an archived chat and hitting the archive shortcut, or retrying the POST, sent isArchived: true again and replaced Date Archived with now. saveConvo now stamps only on the unarchived-to-archived transition and still clears the field on unarchive.
d627c86 to
eed576f
Compare
Summary
The archived chats dialog has a "Date Archived" column that was bound to
createdAt, so it showed when the chat was created rather than when it was filed away. Nothing recorded the latter.Conversations now carry
archivedAt, set on archive and cleared on unarchive, and the column reads it. Clearing matters: a stale stamp would resurface the chat in archive ordering the next time it was filed away. The archive view sorts on the new field.Chats archived before the field existed have no stamp and fall back to
createdAt, which is exactly what that column already showed for them, so no existing row changes appearance.Stacked on #14862.
The cursor
archivedAtis absent on every previously archived chat, so in the pagination cursor the missing-value group is the common case here rather than an edge case. Two things had to be right:The cursor already had null-aware paging clauses written for
title. BSON orders a missing field before every string and every date identically, so that logic generalises; the twosortBy === 'title'checks became a namedsortFieldIsNullablecovering both rather than a second special case.The encode side did
new Date(primaryValue ?? 0).toISOString(), which would turn an absent stamp into 1970 and page from the epoch, replaying the whole archive. A null primary now survives as null, which is the signal the decode side uses to know it is inside the missing-value group.Deployment note
This adds
{ user, isArchived, archivedAt, _id }to the conversation schema's indexes, which builds on startup.I deliberately did not backfill
archivedAtfor existing archived chats. There is no record of when those were archived, so a backfill would not recover the data, it would manufacture it, and nothing would then distinguish a real stamp from a fabricated one. The fallback is honest about not knowing, and the legacy set only shrinks: every archive from now on writes a real stamp.Change Type
Testing
Four cursor tests cover the paging risk: descending and ascending across a mixed set of stamped and unstamped rows, asserting every chat appears exactly once (the set of ids seen equals the expected set and the count matches, so a repeat fails), plus null ordering and the rejected-sort-field guard.
Verified against a real database: archived a chat with
updatedAt2026-03-05, it gotarchivedAt= today whileupdatedAtwas unchanged, and the dialog showed today's date for it while the two pre-existing archived chats still showed their unchanged fallback dates. Toggling the column header re-sorted ascending through the real backend. Unarchiving cleared the stamp and removed it from the archive view.Test Configuration:
Local dev server, MongoDB, Chromium.
Checklist