fix(desktop): keep the slash picker stable across same-content skill refreshes - #2768
Conversation
Astro-Han
left a comment
There was a problem hiding this comment.
Reviewed against the slash-picker flicker.
Same-key skill reloads no longer clear the list, and a same-content refresh keeps the array identity so the menu-replay effect stays quiet. The four-field InvocableSkillEntry comparison matches the type today. A stale picker entry still goes through resolveSkillInvocations, so a withdrawn skill is not executed.
Non-blocking: the effect still lists skills and the new-session model as fail-closed keys, even though neither enters the invocable projection. Pinning or refreshing Skills can therefore replay the same collapse. Astryx light-dismiss hides it from a normal click. [...next] is also an extra copy; the IPC payload is already a new array.
Approve.
AI-assisted review: Grok 4.6, opencode-go/deepseek-v4-flash:max, and ark-coding-plan/glm-5.3 each reviewed the PR independently. I checked the identity path from useComposerMentions through composer.tsx and the e2e that counts menu-node removals. Unverified by me: I did not rerun the desktop e2e locally.
|
/agentic_review |
Code Review by Qodo
1. Failed refresh keeps stale skills
|
| const refresh = (options?: { failClosed?: boolean }) => { | ||
| const version = ++requestVersion; | ||
| setMentionSkills([]); | ||
| if (options?.failClosed) setMentionSkills([]); |
There was a problem hiding this comment.
1. Failed refresh keeps stale skills 🐞 Bug ≡ Correctness
A same-key listInvocable rejection now leaves the previous skill projection visible indefinitely, rather than limiting staleness to one IPC round trip or leaving / with no suggestions as documented. The stale entries remain until another successful refresh or key change, although selecting one may subsequently fail in Runtime.
Agent Prompt
## Issue description
Same-key refreshes preserve the current skill list while loading, but a rejected authoritative refresh never clears that list. Clear skills when the latest refresh fails so the temporary stale window cannot become indefinite.
## Issue Context
The rejection handler must apply only when the effect is still active and the failed request remains the latest `requestVersion`; otherwise an older failure could erase a newer successful projection.
## Fix Focus Areas
- apps/desktop/src/renderer/use-composer-mentions.ts[63-86]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
|
This PR changes the visible slash-picker behavior during Skill refreshes. Could you please add a screenshot showing the stable open picker after a same-content refresh? A short recording would also be helpful for the no-collapse behavior, but the screenshot is the required UI evidence. Thanks! Posted by Codex on behalf of Astro-Han. |
|
Hi — this PR conflicts with current I tested a rebase onto current
These are real source conflicts, so they need your judgement rather than a mechanical rebase — please rebase onto current Thanks for the contribution — happy to help if any conflict is unclear. AI-assisted maintenance note, not a review. It does not count as the required human review under |
|
This branch is now 388 commits behind main, and it cannot be rebased mechanically: That said, I checked and the problem this PR set out to fix is still present on main: The current Suggestion: write a small fresh patch against current main that preserves the previous 简体中文这个分支已经落后 main 388 个提交,而且没法直接 rebase: 不过我核对了一下,这个 PR 要修的问题在 main 上还在:现在的 建议在当前 main 上重写一个小补丁:同 context 刷新时保留上一份 |
An open `/` menu alternated between its commands-only and commands-plus-skills geometries on every session or MCP event (apache#2667): each refresh cleared the invocable-Skill catalog fail-closed, so the popup lost and regained its Skills group for the length of one IPC round trip. Fail closed only when the context key actually changes. A same-context refresh keeps the Skills already on screen, and a settled refresh that returned an identical list keeps the previous array identity, so the composer's trigger memo and menu-replay effect stay quiet. A Skill withdrawn inside that stale window still fails safely, because selection resolves through the Runtime resolver that no longer knows it. The + menu's separate fail-closed path is unchanged: a Plan toggle moves the context key, so it still clears and still reads `settled`.
9ef3e4f to
ee92622
Compare
|
I rebased this onto current The bug is still live on What changed since you opened this. How I re-expressed the fix. Rather than a
Your No regression to the + menu. Your regression spec applied unchanged; I verified its fixture and selectors ( Checks: Biome clean on both files. Renderer typecheck produced an identical error set to a clean Since the code moved, the existing approval no longer describes this head. I will re-review at 简体中文我把这个 PR rebase 到了当前 这个 bug 在 这期间的变化: 我怎么重新表达这个修复:不再给
你写的 没有回退 + 菜单的修复: 你的回归用例原样适用;我核对过它用到的 fixture 和选择器( 检查结果:两个文件 Biome 通过。渲染层 typecheck 与同环境下干净 由于代码已经变动,原有的 approve 不再描述这个 head。我会在 |
Astro-Han
left a comment
There was a problem hiding this comment.
Approving at ee926225b. CI is terminal green on this exact head (run 32714136676), including the Desktop e2e job that carries your regression spec.
Since I rebased this branch and re-expressed the fix against main's rewritten use-composer-mentions.ts, I re-checked it rather than relying on the earlier approval, which was bound to 9ef3e4f86 and no longer describes this code.
The open review comment about a failed refresh is addressed. The concern was that a same-key listInvocable rejection would leave the previous projection visible indefinitely. That was accurate against the original patch, but it does not survive the rebase: main's rejection handler already fails soft by clearing, and I kept it unchanged, so a rejected refresh still lands on { loading: false, settled: 'empty', skills: [] } under the same cancelled || version !== requestVersion guard the comment asked for. The stale window stays bounded by one IPC round trip.
What else I verified:
- Merged against current
main— which moved tod77e854f7after #3573 landed — not just against the PR head. The merge is clean, Biome is clean on both files, and the renderer typecheck produces an error set identical to a cleanmaincheckout in the same environment (235 files either way), so nothing is attributable to this change. - The + menu's separate fail-closed path is intact. A Plan toggle moves
newSessionCollaborationMode, which is part ofcontextKey, so that path still clears and still readssettled.composer-plus-menu-stability.spec.tsand your new spec cover complementary cases rather than competing ones. - The bug was still live on
mainbefore this:refresh()clearedskillson every call andmentionSkillsread straight from it.
Thank you for the original diagnosis and for the regression spec — the spec applied unchanged and is what makes this safe to land. Merging now.
简体中文
在 ee926225b 上给出 approve。这个 exact head 的 CI 是终态绿(运行记录 32714136676),其中包含承载你那条回归用例的 Desktop e2e 任务。
由于这个分支是我 rebase 的,并且我把修复按 main 重写后的 use-composer-mentions.ts 重新表达了一遍,所以我重新核对了结果,而没有沿用之前那条 approve:它绑在 9ef3e4f86 上,已经不描述当前代码了。
关于「刷新失败」的那条未解决评论已经不成立了。 它担心的是:同 key 的 listInvocable 被拒绝后,之前的投影会无限期地留在界面上。这一点对原始补丁是准确的,但 rebase 之后不再成立:main 原有的拒绝分支本来就是失败即清空,我原样保留了它,因此被拒绝的刷新仍然落到 { loading: false, settled: 'empty', skills: [] },并且带着评论所要求的那个 cancelled || version !== requestVersion 守卫。过期窗口仍然被限制在一次 IPC 往返之内。
我另外核对的内容:
- 是对当前
main做的合并验证——#3573 合入后 main 已经变成d77e854f7——而不只是看 PR head。合并干净,两个文件 Biome 通过,渲染层 typecheck 的报错集合与同环境下干净main检出完全一致(都是 235 个文件),因此没有可归因于本次改动的问题。 - + 菜单那条独立的失败关闭路径没有被破坏。 Plan 切换会改变
newSessionCollaborationMode,它属于contextKey,因此那条路径仍然清空、仍然读settled。composer-plus-menu-stability.spec.ts和你新增的用例覆盖的是互补情形,而不是互相竞争。 - 这个 bug 在
main上此前确实仍然存在:refresh()每次调用都会清空skills,而mentionSkills直接读它。
感谢你最初的定位和那条回归用例——用例原样适用,也正是它让这次落地是安全的。现在合并。
Summary
Typing
/rand waiting made the slash picker alternate between two popup geometries with a replayed open transition. Every sessionupdated/turn-status-change/reboundevent and every MCP change reloads the invocable Skill projection, and the reload cleared the list before repopulating it: the open menu collapsed to its commands-only shape, then sprang back one IPC round trip later, each time replaying the trigger-menu transition because the projection's identity had changed twice.Two changes in
useComposerMentions:Fixes #2667
Verification
/, three same-content projection refreshes (thinking-level updates publishing the session'supdatedevent) must remove neither the listbox nor its groups. Without the fix the watch counts 15 removals; with it, zero, and the Skills group stays visible throughout.Checklist
Does this PR entail a change in behavior?