Repository navigation
⚙️ feat: Move Chat Actions Into a Header Options Menu - #16775
Conversation
The header's export/share button becomes a chat options menu with Share, Export, Rename, Pin, Change project, Remove from project, Duplicate, Archive and Delete for the open chat. The sidebar row menu keeps Pin, Mark unread, Rename, Archive and Delete.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3246f94f34
ℹ️ 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".
| /** Pin and project changes land in the query cache, not in the chat's own state. */ | ||
| const { data: cached } = useGetConvoIdQuery(conversationId, { enabled: false }); | ||
| const current = cached ?? conversation; | ||
| const isPinned = current?.pinned === true; |
There was a problem hiding this comment.
Update the point cache after toggling a pin
For a normally loaded saved chat, cached contains the point-query snapshot and therefore always wins over the mirrored Recoil conversation here. usePinConversationMutation updates conversation-list and pinned caches but does not update [QueryKeys.conversation, conversationId], while this observer is explicitly disabled from refetching, so after a successful pin or unpin the header continues showing the old action and repeats the same write when clicked again; update the point cache from the mutation result or derive the pin state from a source that the mutation updates.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not reproduced. updateConvoInAllQueries, which the pin mutation calls on success, also updates the [conversation, id] point entry when one exists, and the header only reads that entry when it exists. A test that seeds the point entry, pins, and reads it back passes on the unchanged mutation (pinPointCache.spec.tsx), so I kept it as a guard rather than as a regression test. The open chat's state is also updated on success for the case where no point entry exists. Leaving this open in case you have a sequence that still shows a stale label.
Limit read-only subagent threads to share and export, apply the sidebar's rename gate for running chats, let an unchanged title claim ownership, keep later navigation after a late archive, hide history-only actions for temporary chats, expose dialog semantics on the dialog items, and fix import sorting.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5f02e942ed
ℹ️ 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".
… For ChatView stays mounted across a route change, so dialog flags now carry the conversation id they were opened for and reset when the open chat or the read-only state changes.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a2b5331519
ℹ️ 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".
Read the open route when a delete settles, give the delete dialog the id its menu item controls, return focus to the menu item when a rename saves, and only let a close clear the dialog it belongs to.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 45431d676a
ℹ️ 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".
A rename that settles after its dialog unmounted no longer closes the parent state or moves focus to the reused menu item.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 73a7319efb
ℹ️ 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".
The route moves before the chat state does and the menu and dialogs are portaled outside the hidden pane, so they are now dismissed on a route change and gated on it. A delete that settles after another chat opened no longer closes that chat's menu, and the options read the freshest cached copy of the chat instead of an older point entry.
Summary
The chat header's export/share button only offered Share and Export, while the other things you do to the open chat (rename, pin, move to a project, duplicate, archive, delete) were reachable only from its row in the sidebar. The header button is now a three-dots chat options menu for the open chat: Share and Export first, then Rename, Pin/Unpin, Change project, Remove from project (only when the chat is in a project) and Duplicate, then Archive/Unarchive and Delete. The mobile overflow menu uses the same items. Its label is "Chat options", or "Chat options, link active" when the chat has a shared link, and the blue link dot is kept.
The sidebar row menu keeps the quick actions for any row: Pin, Mark unread, Rename (inline), Archive and Delete. Share, Duplicate, Change project and Remove from project now live only in the header, so those four apply to the open chat only. Project assignment from the sidebar is still available by dragging a row onto a project. The sidebar renames inline in its row and the header has no row, so rename in the header opens a small dialog.
The header menu follows the sidebar's rules where they apply: a read-only subagent thread offers only Share and Export, Rename is disabled for a running chat unless the deployment supports title ownership, saving an unchanged automatic title claims ownership, and a temporary chat drops Pin, Change project, Remove from project and Archive because those lists exclude temporary chats.
How it works
useChatOptionsbuilds the menu items and owns the dialogs, reusing the sidebar's mutations,DeleteButtonandProjectButton, and the Share/Export dialog-item contract (hideOnClick: false, item ref, button render, dialogtriggerRef).ChatViewstays mounted while the route changes, so the hook ties each dialog to the conversation it was opened for, closes the menu and dialogs when the route or the read-only state changes, and checks the open route when a late archive or delete settles. The open chat is read from the freshest cached copy, and a landed pin, project change or archive is also written to the open chat's own state.DeleteButtongained thedelete-conversation-dialogid that the sidebar menu'saria-controlsalready pointed at, and an optionalgetCurrentConversationIdfor callers whose dialog outlives its route.ProjectButtongained an optionalonAssigned.Type of change
Testing
Tested environments/configuration:
Unit tests in jsdom. The header and sidebar menus were also opened in headless Chromium against the dev server (screenshots below) to check which items each menu lists; interactions such as focus return and route changes were not exercised in a browser, and the Playwright specs were not run locally.
Automated tests:
lc teston the changed files:useChatOptions.spec.tsx(item order, dialog-item contract, read-only, rename gate, temporary chats, route changes, late archive and delete, stale cache),Rename.spec.tsx,DeleteButton.test.tsx,pinPointCache.spec.tsx, plus the updatedExportAndShareMenu,HeaderMenu,Header.stackingandConvoOptions.archivespecslc lint, clienttsc --noEmitandnpm run static-checks -- --against origin/devcleanScreenshots / recordings
Captured from the running app (dev server on a mock endpoint, light theme), base revision on the left and this branch on the right. Desktop is 1280 px wide, mobile is 390 px wide. The React Query devtools button is hidden in both.
Light mode only; dark mode was not captured.
Risk / compatibility
Share, Duplicate and Change project can no longer be started from a closed chat's sidebar row. Only English locale keys changed: added
com_ui_chat_optionsandcom_ui_chat_options_link_active, removed the two unused export/share keys.DeleteButtonis shared with the sidebar, the project list and the keyboard delete dialog; the new props are optional.Checklist