From cd6d84c847dc5ff4a98cf2464cece37786bf00a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?George=C2=B7Dong?= <98630204+GeorgeDong32@users.noreply.github.com> Date: Mon, 20 Apr 2026 21:38:22 +0800 Subject: [PATCH] fix(quick-panel): reset stale state on panel close to prevent model deselection (#14378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### What this PR does Before this PR: When multiple models are selected for simultaneous calling and a message containing "@" is pasted (e.g., `@Reed grandmom said yes`), pressing Enter deselects the last selected model instead of sending the message to all selected models. PixPin_2026-04-18_22-36-23 After this PR: Pressing Enter after pasting a message containing "@" correctly sends the message to all selected models without any model deselection. The QuickPanel state (`prevSymbolRef`, `prevSearchTextRef`, `index`) is properly reset when the panel fully closes, preventing stale state from affecting the next panel session. PixPin_2026-04-18_22-38-49 Fixes #14085 ### Why we need it and why it was done in this way The following tradeoffs were made: - Resetting state inside `useMemo` is technically a side effect (calling `setIndex`), but this pattern was already pre-existing in the codebase and works correctly due to React's batched updates. A more proper solution would move `index` management to `useEffect`, but that's beyond the scope of this hotfix. The following alternatives were considered: - Reset in provider's `close()` function — rejected: would require updating shared component - Reset in `useEffect` on visibility change — rejected: timing issues, `useMemo` runs before effects - Track close action in separate state — rejected: over-engineered for a simple reset Links to places where the discussion took place: Issue #14085 ### Breaking changes None. ### Special notes for your reviewer This is a minimal hotfix targeting a regression bug in v1.8.4. The fix adds state reset logic in the `useMemo` entry guard when QuickPanel fully closes (`isVisible=false` and `symbol` cleared). ### Checklist - [x] PR: The PR description is expressive enough and will help future contributors - [x] Code: [Write code that humans can understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans) and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle) - [ ] Refactor: You have [left the code cleaner than you found it (Boy Scout Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html) - [ ] Upgrade: Impact of this change on upgrade flows was considered and addressed if required - [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com) was considered and is present (link) or not required. Check this only when the PR introduces or changes a user-facing feature or behavior. - [x] Self-review: I have reviewed my own code (e.g., via [`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`, or GitHub UI) before requesting review from others ### Release note ```release-note Fixed a bug where pasting a message containing "@" and pressing Enter would deselect the last selected model in multi-model calling mode instead of sending the message. ``` Signed-off-by: George·Dong Co-authored-by: SuYao Co-authored-by: 亢奋猫 --- src/renderer/src/components/QuickPanel/view.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/components/QuickPanel/view.tsx b/src/renderer/src/components/QuickPanel/view.tsx index 9ed9b966df..c95d33d579 100644 --- a/src/renderer/src/components/QuickPanel/view.tsx +++ b/src/renderer/src/components/QuickPanel/view.tsx @@ -79,7 +79,13 @@ export const QuickPanelView: React.FC = ({ setInputText }) => { const sortFn = ctx.sortFn || defaultSortFn // 处理搜索,过滤列表(始终保留 alwaysVisible 项在顶部) const list = useMemo(() => { - if (!ctx.isVisible && !ctx.symbol) return [] + // Reset stale state when panel fully closes (both isVisible false AND symbol cleared) + if (!ctx.isVisible && !ctx.symbol) { + prevSymbolRef.current = '' + prevSearchTextRef.current = '' + setIndex(-1) + return [] + } const baseList = (ctx.list || []).filter((item) => !item.hidden)