Skip to content

🗂️ feat: Chat List Properties Menu and Project-Scoped Chats - #16246

Merged
berry-13 merged 22 commits into
canaryfrom
berry-13/chat-filter-menu
Sep 27, 2026
Merged

berry-13 merged 22 commits into
canaryfrom
berry-13/chat-filter-menu

Conversation

@berry-13

@berry-13 berry-13 commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Part 4 of 6 of the quieter-layout stack. The parts below it (#16243, #16244, #16245) have merged, so this now sits directly on canary.

The chat list filter listed every option at once. It now opens on three rows (Show, Sort, Filter), each with its current value and a submenu holding the choices. Filter holds the five facets from the previous PR plus a search field that looks inside them. Reset sits beside the title, and the active facet count rides on the trigger. The facets are one derived atom, so the query key and the request come from the same description, and date cutoffs snap to local midnight so the list does not refetch on every render.

A chat assigned to a project was listed twice in the sidebar: under the project and again in Chats. Chats now asks only for chats that belong to no project. Search and the archived view still ask for everything, and so does a sidebar whose project list failed to load, so no chat is left with no way back to it. Cache writers only place a row in a filtered list when the row provably matches it; the attachment and sharing filters are refetched from the server instead.

Type of change

  • Feature

Testing

Tested environments/configuration:

  • Linux, Node 24, local build of the stack branch on top of canary
  • Checked in the browser in light and dark mode against a seeded dev account (a project with chats, memories, prompts)

Automated tests:

  • npx tsc --noEmit in client and packages/client
  • npx eslint on every changed file, including the shadcn/* design rules
  • npx jest --findRelatedTests over the changed client files, passing
  • Playwright mock scenarios in e2e/specs/mock/scenarios/chat-filter-menu.spec.ts (8 tests: keyboard filtering, reset, date window, project scoping, a live reply under a server-only filter, reset on sign-out, a role without bookmarks, a failed project list), passing on desktop light, desktop dark and mobile

Screenshots / recordings

Before is canary; after is the top of this stack, so an image can also show changes from later PRs in the chain. Chromium, 1440x900 desktop and 390x844 mobile.

Before After
Filter menu, light filter before, light filter after, light
Filter menu, dark filter before, dark filter after, dark
Chats with a project expanded, dark sidebar before, dark sidebar after, dark

Risk / compatibility

Uses the list filters from #16245, which is on canary.

Checklist

  • I reviewed my own changes
  • Existing relevant tests pass
  • The change does not introduce new warnings or errors
  • Required documentation PR: N/A

@berry-13
berry-13 added this pull request to stack #16249 September 23, 2026 15:15
@berry-13
berry-13 marked this pull request as ready for review September 23, 2026 16:42
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-23T17:04:36.742069Z 71f2c0e Draft marked ready
🔒 Security Review ✅ Completed 2026-09-23T16:51:35.438648Z 71f2c0e Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 71f2c0e8ad

ℹ️ 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".

Comment on lines +116 to +120
updatedAfter,
createdAfter,
endpoints,
hasFiles,
sharedOnly,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Reconcile facet-filtered caches before inserting rows

Adding these facets to allConversations query variants also makes every existing cache writer facet-sensitive, but conversationInsertVerdict and conversationBelongsToListQuery in client/src/utils/convos.ts still consider only project, archive, tags, search, and sort. Consequently, while an endpoint/files/shared/date filter is active, upsertConvoInAllQueries, duplication/fork mutations, or SSE updates can insert or retain a conversation that the server excluded—for example, an unshared conversation appears in a sharedOnly list after an SSE update. Treat these facets as server-reconciled predicates or teach the writers how to evaluate them.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in eb5842a: the insert verdict decides endpoint and date facets from the row and refetches attachment/sharing variants instead of inserting. Verified by the live-chat-respects-active-facet scenario on f142342 (it holds the facet refetch and fails with the verdict change reverted).

Comment on lines +94 to +95
const updatedAfter = rangeCutoff(get(updatedRangeAtom));
const createdAfter = rangeCutoff(get(createdRangeAtom));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh date cutoffs when the local day changes

When either date range is selected, this derived atom computes the cutoff only when one of its source atoms changes; Jotai caches the derived value rather than reevaluating it on every render. If the app remains open across local midnight, “Today” continues using yesterday's midnight and the other windows likewise grow by a day, even when React Query refetches on focus, until the user changes or resets the facet. Schedule an update at the next local midnight or derive the cutoff from a day-valued atom that advances then.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in eb5842a: cutoffs derive from a day anchor that useFreshLocalDay advances at local midnight while the list is mounted; covered by facets.spec.

};
});

export const resetFacetsAtom = atom(null, (_get, set) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear facet atoms at the authentication boundary

These new atoms are global session state, but endSessionClientState in client/src/hooks/AuthContext.tsx resets only resetChatFilterSessionAtom; resetFacetsAtom is currently invoked only from filter UI actions. After logout or an in-app account switch, the next user therefore inherits the prior user's date, endpoint, attachment, and sharing filters and can land on an unexpectedly empty chat list. Wire this reset into the existing authentication-boundary cleanup.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in eb5842a: endSessionClientState resets the facet atoms with the rest of the session state. Verified by the facets-cleared-after-sign-out scenario on f142342.

/** Which dead end this is decides both the line and the glyph above it: a search
* that found nothing, a filter that matched nothing, an empty archive, and an
* account with no chats yet are four different situations wearing one sentence. */
let emptyLabel: TranslationKeys = 'com_ui_no_chats';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Distinguish an empty unassigned list from an empty account

Once the default request is scoped to projectId: 'unassigned', an empty response no longer proves that the account has no chats: it also occurs whenever every chat belongs to a project. In that common case the project rows are rendered above this state while the Chats section still displays com_ui_no_chats, contradicting the branch's stated “account is empty” meaning. Suppress this account-level state for the scoped list or use copy that explicitly describes the absence of unassigned chats.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in eb5842a (unassigned-specific empty state) and fff951c (the account-level copy now needs a loaded, empty project list). Covered by the ConversationsSection spec.


/** Searching reaches into the categories, so both lists have to be here rather than
* inside the submenu that normally owns them. Both are cached queries. */
const { data: bookmarkData } = useGetConversationTags();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Disable the bookmark query when access is denied

For users whose role lacks BOOKMARKS:USE, showBookmarks is false and all bookmark UI is hidden, but opening the Filter submenu still runs this query unconditionally. The /api/tags router applies checkBookmarkAccess to every request, so these users receive a 403 (with React Query's default retries) merely for opening an otherwise permitted filter menu. Pass an enabled: showBookmarks option or mount the query only in the authorized branch.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in eb5842a: the Filter submenu's tags query is enabled only with BOOKMARKS:USE. Verified by bookmark-filter-quiet-without-access on f142342, which fails with the gate removed.

@danny-avila danny-avila added the 🗺️ Chat UI Shell codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9) label Sep 24, 2026
Base automatically changed from berry-13/convo-list-filters-api to canary September 26, 2026 15:09
@berry-13
berry-13 force-pushed the berry-13/chat-filter-menu branch from 71f2c0e to fff951c Compare September 26, 2026 15:49

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fff951c2f1

ℹ️ 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".

Comment on lines +393 to +397
if (typeof conversation.endpoint === 'string') {
if (!endpoints.includes(conversation.endpoint)) {
return 'skip';
}
} else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Evict existing rows that no longer match endpoint facets

When an already-cached conversation changes provider—for example, after the endpoint selector updates conversation.endpoint and an SSE sidebar update calls upsertConvoInAllQueries—this new skip result governs only insertion when the row is absent. The existing-row paths still check conversationBelongsToListQuery, which considers only project and archive state, while queryNeedsServerReconciliation also excludes endpoint filters; consequently, the conversation is rewritten but remains visible in its old endpoint-only list until a later refetch. This re-raises the earlier cache-writer concern with fresh evidence: the current patch added the endpoint verdict without applying it to existing rows. Evict an existing row on skip, or invalidate endpoint-filtered variants after these writes. CLAUDE.mdL338-L342

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in b8e576a: list membership for existing rows now includes the endpoint, so upsert and in-place updates evict a row whose known endpoint left the facet (an endpoint-less row stays). convos.spec cases fail without it.

* Search and the archived view stay whole: both are places the user goes to
* find something, and a project chat that appears in neither list nor result
* would have no way back. */
projectId: isArchivedView || search.debouncedQuery ? undefined : 'unassigned',

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve project chats when the project list fails

When the initial projects request fails while the conversations request succeeds, this unconditional unassigned scope removes every project-owned active chat from Chats. ProjectsSection currently consumes only data and isLoading, so an error without cached data is rendered as an empty project list with a “New project” action rather than an error or retry; there is then no project row or fallback chat row through which the affected conversations can be opened. Gate this scope on usable project data, or expose the project failure and recovery path before hiding the fallback rows.

AGENTS.md reference: AGENTS.md:L31-L34

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 3435b17: a projects query that failed without data leaves Chats unscoped, so project chats stay reachable. Verified by project-chats-reachable-when-projects-fail on f142342 (GET /api/projects answered 500).

Comment thread client/src/components/UnifiedSidebar/ConversationsSection.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f142342799

ℹ️ 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".

? localize('com_ui_none')
: localize('com_ui_active_count', { count: narrowingCount });

const totalCount = activeCount + facetCount;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Count the bookmark group once in the trigger

When two or more bookmarks are selected, chatFilterCountAtom contributes every tag to activeCount, while the Filter row deliberately counts the entire bookmark group as one facet and multi-selected endpoints also count once. The trigger therefore announces values such as “Filters active: 3” while its only active Filter row says “1 active,” giving screen-reader users a contradictory filter count. Derive the trigger total from the same grouped bookmark/facet count used by filterValue, plus the view and sort deviations.

AGENTS.md reference: AGENTS.md:L31-L34

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in a94b76e: bookmarks now count once in chatFilterCountAtom, like the endpoint facet, so the trigger and the Filter row agree. Covered by the new chatFilters.spec (fails without it); all 8 scenarios pass on a94b76e.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a94b76e1e9

ℹ️ 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".

Comment thread client/src/components/Conversations/ChatFilterMenu.tsx

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2aa610c100

ℹ️ 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".

Comment thread api/server/routes/config.js Outdated
Comment on lines +165 to +166
maxEndpointFilters: resolveConversationListLimits(appConfig?.conversationList)
.maxEndpointFilters,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Publish the same endpoint limit the list route enforces

When a principal-scoped config override changes conversationList.maxEndpointFilters, this payload publishes the merged per-user value because appConfig comes from getAppConfigOptionsFromUser, while resolveConversationListFilters explicitly reads the deployment's baseOnly config. If an override raises the limit, the UI permits a selection that the list route rejects with 400; if it lowers the limit, valid selections are unnecessarily disabled. Resolve this startup field from the same base configuration used by the request parser.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 34fc4e2: both list-filter limits now come from loadConversationListLimits in packages/api, which reads the base config the same way the list route does (the route calls it too), and the startup config publishes them as conversationListLimits. config.spec covers the configured, default and principal-override cases. endpoint-filter-stops-at-limit passes on c421468.

Comment on lines +162 to +164
const served = Object.keys(endpointsConfig ?? {}).filter(
(endpoint) => endpointsConfig?.[endpoint] != null,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Prevent selecting endpoint names the server rejects

When a served custom endpoint's name exceeds conversationList.maxEndpointNameLength—which can also be configured below the 128-character default—this includes it as a selectable facet even though parseEndpoints rejects that same value. Selecting an otherwise usable endpoint therefore turns the Chats request into a 400 and displays the list error state. Publish and enforce the name limit in this control, or ensure configured endpoint names are exempt from the request-input limit.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 34fc4e2: maxEndpointNameLength is published with maxEndpointFilters, and the Endpoint facet leaves out a served name longer than it (a selected one stays listed so it can be turned off). Covered by facets.spec (selectableEndpoints); scenarios green on c421468.

@github-actions

Copy link
Copy Markdown
Contributor

Lighthouse CI failed. The last 80 log lines contain the measured budgets and assertion failures.

│ 21      │ 'http://localhost:3080/api/convos?pinned=true&limit=100'                                                        │ 2655.7870000000694 │ 3418.2780000000494 │ 200    │
│ 22      │ 'http://localhost:3080/api/mcp/servers'                                                                         │ 2987.1590000000433 │ 4255.270000000019  │ 200    │
│ 23      │ 'http://localhost:3080/api/permissions/mcpServer/effective/all'                                                 │ 2988.2990000000573 │ 3749.917000000074  │ 200    │
│ 24      │ 'http://localhost:3080/api/prompts/groups?limit=10'                                                             │ 2989.6630000000005 │ 4254.7660000000615 │ 200    │
│ 25      │ 'http://localhost:3080/api/keys?name=openAI'                                                                    │ 3242.4150000000955 │ 3910.14300000004   │ 200    │
│ 26      │ 'http://localhost:3080/api/presets'                                                                             │ 3242.7200000000303 │ 3924.789000000048  │ 200    │
│ 27      │ 'http://localhost:3080/api/tags'                                                                                │ 3242.9280000000144 │ 3925.2450000000536 │ 200    │
│ 28      │ 'http://localhost:3080/api/share/link/16390000-0000-4000-8000-000000000001'                                     │ 3245.1870000000345 │ 4259.085000000021  │ 200    │
│ 29      │ 'http://localhost:3080/api/messages/16390000-0000-4000-8000-000000000001'                                       │ 3245.3850000000093 │ 4416.083000000042  │ 200    │
│ 30      │ 'http://localhost:3080/api/files/config'                                                                        │ 3245.5620000000345 │ 4180.060000000056  │ 200    │
│ 31      │ 'http://localhost:3080/api/agents/tools/web_search/auth'                                                        │ 3247.2390000000596 │ 6944.75            │ 200    │
│ 32      │ 'http://localhost:3080/api/endpoints/token-config'                                                              │ 3247.6470000000554 │ 4435.47100000002   │ 200    │
│ 33      │ 'http://localhost:3080/api/agents/tools/calls?conversationId=16390000-0000-4000-8000-000000000001'              │ 3248.051000000036  │ 4763.121000000043  │ 200    │
│ 34      │ 'http://localhost:3080/api/agents/chat/status/16390000-0000-4000-8000-000000000001?generationProtocolVersion=2' │ 4515.833000000042  │ 4770.396000000008  │ 200    │
└─────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴────────────────────┴────────────────────┴────────┘

Inspect .lighthouse HTML/JSON and e2e/lighthouse/README.md. Reuse loaded user/config data; overlap independent reads without bypassing authorization.

┌─────────┬────────────────────────────┬──────────────────────┬───────┐
│ (index) │ audit                      │ median               │ limit │
├─────────┼────────────────────────────┼──────────────────────┼───────┤
│ 0       │ 'largest-contentful-paint' │ 4543.745             │ 4500  │
│ 1       │ 'cumulative-layout-shift'  │ 0.018450450449464428 │ 0.1   │
│ 2       │ 'total-blocking-time'      │ 253.26500000000078   │ 500   │
└─────────┴────────────────────────────┴──────────────────────┴───────┘

  1) [chrome] › e2e/lighthouse/load.spec.ts:10:5 › serial database latency stays within web-vitals budgets 

    Error: Median largest-contentful-paint must stay within 4500

    expect(received).toBeLessThanOrEqual(expected)

    Expected: <= 4500
    Received:    4543.745

       at audit.ts:159

      157 |   console.table(measured);
      158 |   for (const { audit, median, limit } of measured) {
    > 159 |     expect(median, `Median ${audit} must stay within ${limit}`).toBeLessThanOrEqual(limit);
          |                                                                 ^
      160 |   }
      161 |   return results;
      162 | }
        at auditPage (/home/runner/work/LibreChat/LibreChat/e2e/lighthouse/audit.ts:159:65)
        at /home/runner/work/LibreChat/LibreChat/e2e/lighthouse/load.spec.ts:33:19

    attachment #1: screenshot (image/png) ──────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/test-failed-1.png
    ────────────────────────────────────────────────────────────────────────────────────────────────

    Error Context: e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/error-context.md

    attachment #3: trace (application/zip) ─────────────────────────────────────────────────────────
    e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip
    Usage:

        npx playwright show-trace e2e/lighthouse/.test-results/load-serial-database-latency-stays-within-web-vitals-budgets-chrome/trace.zip

    ────────────────────────────────────────────────────────────────────────────────────────────────


🤖: global teardown has been started
2026-09-27 13:55:38 �[32minfo�[39m: �[32mMongo Connection options�[39m
2026-09-27 13:55:38 �[32minfo�[39m: �[32m{�[39m
�[32m  "bufferCommands": false�[39m
�[32m}�[39m
🤖:  ✅  Connected to Database
🤖:  ✅  Found user in Database
🤖:  ✅  Deleted 1 convos & 2 messages
🤖:  ✅  Deleted user from Database
🤖: global teardown has been started
2026-09-27 13:55:39 �[32minfo�[39m: �[32mMongo Connection options�[39m
2026-09-27 13:55:39 �[32minfo�[39m: �[32m{�[39m
�[32m  "bufferCommands": false�[39m
�[32m}�[39m
🤖:  ✅  Connected to Database
🤖:  ⚠️  User not found in Database
  1 failed
    [chrome] › e2e/lighthouse/load.spec.ts:10:5 › serial database latency stays within web-vitals budgets 

Open the full run

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4214685da

ℹ️ 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".

Comment on lines +75 to +76
const scopeToUnassigned =
!isArchivedView && !search.debouncedQuery && !(projectsFailed && projectsData == null);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle failures from each project chat query

When the top-level projects request succeeds but ProjectChatsInline fails to load an expanded project's conversations, this condition still scopes the main list to unassigned chats. The child query ignores isError and treats missing data as com_ui_no_project_chats, so the project's unpinned chats disappear from the sidebar behind a false empty state with no retry. Keep fallback chats visible or add an error/retry path to each project list before relying on this exclusive scope.

AGENTS.md reference: AGENTS.md:L31-L34

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 61a0a73 (ProjectsSection.tsx): a project whose own chat request failed with no rows now shows an alert with Retry instead of the empty state, so its chats stay reachable while Chats is scoped to unassigned. Verified by project-chats-failure-offers-retry (fails that project's /api/convos request, asserts the alert, retries, sees the chat) on desktop light, dark and mobile.

The old menu listed every option at once. It now opens on three rows that
state where the list stands, Show, Sort and Filter, each with its current
value and a submenu holding the choices. Reading the list's state no longer
means reading the whole menu.

Filter holds the five facets and a search field that looks inside them, so
typing openai finds the endpoint rather than the category that contains it,
and typing a category name offers everything under it. Reset sits in the
header next to the title, and the active facet count rides on the trigger.

The facets are one derived atom, so the query key and the request are built
from the same description of the filter. Date cutoffs snap to local
midnight: taken from the current instant they would differ on every render
and refetch the list each time.

The panels also fade their content at the foot when a list runs past the
fold, and the chat list's loading label shimmers the way every other
in-flight label in the app does.
A chat assigned to a project was listed twice in one sidebar: once under
the project and once in Chats. The chats list now asks for the chats that
belong to no project, which the list query and its cache helpers already
knew how to answer.

Search and the archived view keep asking for everything. Projects is not
rendered while a search is on, and an archived chat has no project row to
appear under, so excluding them from either would leave a chat with no way
back to it.
…-scoped

Cache writers judged list membership only by project, archive, tags and
sort, so an SSE update or a fork could insert a chat the server's filter
had excluded. The endpoint and date facets are decided from the row
itself, and the attachment and sharing facets mark their variants for a
refetch, since only the server can place a row in them.

The date cutoffs now derive from a day anchor a mounted consumer
advances at local midnight, so a tab left open stops serving
yesterday's today. Facets reset at the authentication boundary with the
rest of the session state, the bookmark query stops firing for roles
without BOOKMARKS:USE, an empty Chats section under existing projects
says so instead of claiming the account is empty, and the design-rule
violations this branch had suppressed in Conversations and
ChatFilterMenu are fixed and their entries pruned.
An empty Chats list read as "No chats yet" whenever the projects query
had not succeeded, so a slow or failed projects request claimed an
account was empty when its chats may all live under projects. The
account-level wording now needs a loaded, empty project list; otherwise
the section says only that nothing sits outside a project.
…matches

The endpoint facet only guarded insertion, so a chat already in an
endpoint-filtered list stayed there after switching provider until the
next refetch. List membership now includes the endpoint for existing
rows as well; a row with no endpoint stays until the server decides.
Scoping Chats to unassigned chats hid every project chat behind the
Projects section, which shows nothing when its request fails. A failed
project list without cached data now leaves Chats unscoped, so those
chats stay reachable, and an empty unscoped list reads as an empty
account.
The trigger added one per selected bookmark while the Filter row counts
the whole bookmark group as one, so a screen reader heard two different
totals for the same state. Bookmarks now count once, like the endpoint
facet.
The Endpoint facet let a user pick more endpoints than
conversationList.maxEndpointFilters allows, and the list request past it
answered 400, leaving Chats in its error state. The post-login startup
config now publishes the limit, resolved by the same helper the list
parser uses, and the facet disables further endpoints once the
selection reaches it, with a note saying why.
The sidebar renders before the post-login startup config resolves, and
until then the Endpoint facet had no limit to enforce, so a selection
made in that window could pass conversationList.maxEndpointFilters.
Endpoints are now offered disabled until that config has loaded.
…ocked

A selected bookmark whose last chat dropped it fell out of both bookmark
lists, so only Reset could clear it; a selected tag now stays listed.
The Endpoint facet waits only while the startup config is loading, so a
config that failed no longer locks every endpoint without saying why.
…ps listing it

An endpoint that left the endpoints config, or a config that failed to
load, dropped out of both endpoint lists while it stayed selected, so it
kept narrowing the chats and could not be turned off. Both lists now come
from one helper that keeps the selection listed.
The startup config published maxEndpointFilters from the caller's merged
config while the list route reads the deployment's base config, so a
principal override could let the sidebar allow a selection the route
refused, or disable one it accepted; and the endpoint name limit was not
published at all, so a long custom endpoint name could be selected and
turn the list into a 400. Both limits now come from one loader the list
route also uses and are published together as conversationListLimits;
the Endpoint facet leaves out names past the length limit.
A selected bookmark that was deleted no longer came back from the tags
query, so neither bookmark list could show it and only Reset cleared it.
Both lists now come from one helper that keeps every selected bookmark.
Chats lists only chats outside any project, so a project whose own chat
request failed hid its chats behind the empty state. The project now says
its chats could not load and offers a retry.
@berry-13
berry-13 force-pushed the berry-13/chat-filter-menu branch from c421468 to 61a0a73 Compare September 27, 2026 14:37
@berry-13

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review the current PR head 61a0a73. State the exact reviewed commit and ignore findings that apply only to earlier heads.

Purpose supplied by the requester: 1 fix since reviewed head c421468: project chat list shows an error with retry when its own request fails (finding 4115607380); head also rebased onto canary

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: 61a0a73e2b

ℹ️ 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".

@berry-13
berry-13 merged commit 1160c80 into canary Sep 27, 2026
39 checks passed
@berry-13
berry-13 deleted the berry-13/chat-filter-menu branch September 27, 2026 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🗺️ Chat UI Shell codegraph: the taxonomy area this belongs to (classifier, confidence ≥ 0.9)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants