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.
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.
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)