fix(desktop): support Escape navigation in plugin details - #3687
Conversation
Signed-off-by: qsoyq <github@19940731.xyz>
The detail branch unmounts the catalog list, which reset scrollTop to zero on return. Persist the offset across the conditional remount and restore it before paint so Escape returns to the prior catalog position. Signed-off-by: qsoyq <github@19940731.xyz>
|
Follow-up 修正:
验证: |
The Windows shard can spend more than 1ms between the test's activity timestamp and recycleIdle, making a valid recent session look expired. Freeze the monotonic clock only around this synchronous boundary assertion so the test checks its intended state without changing production recycling behavior. Signed-off-by: qsoyq <github@19940731.xyz>
|
| Filename | Overview |
|---|---|
| apps/desktop/src/renderer/features/plugin/GhostPluginPage.tsx | 将市场详情的进入、返回和列表滚动容器接入滚动位置恢复流程。 |
| apps/desktop/src/renderer/features/plugin/PluginDetailTopBar.tsx | 为已安装插件与市场插件共享顶栏增加受约束的 Escape 返回处理。 |
| apps/desktop/src/renderer/features/plugin/lib/usePluginListScrollRestoration.ts | 新增基于布局副作用的条件挂载列表滚动恢复 hook。 |
| packages/maker-pi-manager/src/tests/edge-cases.test.ts | 在 1ms idle timeout 边界测试中冻结单调时钟,以消除平台调度抖动造成的测试失败。 |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[插件市场列表] -->|打开插件详情并捕获滚动位置| B[插件详情]
B -->|点击返回或按 Escape| C[请求恢复]
C --> D[列表重新挂载]
D --> E[恢复原滚动位置]
Reviews (2): Last reviewed commit: "test(maker-pi-manager): stabilize idle t..." | Re-trigger Greptile
|
本次推送(commit
测试位于 |
|
这个 PR 会改变插件详情页的返回交互(Escape 返回 + 列表滚动恢复),已进入维护者确认,球不在作者手里。 请维护者直接在 PR 上 Approve;需要修改就 Request Changes。讨论 issue:#3714 |
|
命中 UI 路径(插件详情顶栏 / 插件列表)但 description 未附界面效果证据——建议补充改动后效果:截图/录屏,或改动后界面的 HTML 页面,便于确认界面符合 DESIGN.md 设计规范。 |
|
@qsoyq 👋 这个 PR 现在在等维护者确认,确认之前流程不会合并它 —— 不是卡住了,也不是在等你再改一版(你推的改动流程都读到了,判的就是最新一版代码)。
这条是流程自动发的状态提醒(同一版代码只发一次),不用回复。 |
|
@qsoyq 👋 这个 PR 目前与 请在本地 merge 最新的 |
Merge main while retaining its recommendation notice and cancellation behavior in the shared market return handler. Cover page-level Escape/button returns, scroll restoration, stale refreshes, IME and modifier arbitration. Signed-off-by: qsoyq <github@19940731.xyz>
MagicLizi
left a comment
There was a problem hiding this comment.
Code Review: PR #3687
fix(desktop): support Escape navigation in plugin details
HEAD: 3d18b8a4511e9d4addf7ec672e2f4703bd478625
Scope
Adds unmodified Escape key navigation to return from both installed and market plugin detail views, and restores the market catalog scroll position on return. Also stabilizes a flaky maker-pi-manager idle timeout boundary test by freezing the monotonic clock.
Signoff fallback
Product trigger (UI path) evaluated as semantic fallback: all three production files are supplements to existing features. The back button already exists on both detail surfaces; Escape adds keyboard support for the same action. Scroll restoration supplements the existing list-to-detail-to-list flow. Proceeding with code review per auto.fallback.action=review.
Security and credentials
No secrets, tokens, API keys, or personally identifiable information found in the diff.
Files reviewed
| File | Verdict |
|---|---|
PluginDetailTopBar.tsx |
Clean. Escape handler guards are comprehensive (modifiers, composing, editable target, already-prevented). useEffect with window.addEventListener properly cleaned up. |
GhostPluginPage.tsx |
Clean. Scroll restoration hook integrated correctly. capturePluginCatalogScroll called before entering detail; requestRestore called on return. clearPendingRestore on account/mode switch prevents cross-owner leaks. handleMarketBack factored from inline callback with correct deps. |
usePluginListScrollRestoration.ts |
Clean. useLayoutEffect for pre-paint restoration is correct. Transition guard (!listVisible || wasListVisible || !pendingRestoreRef.current) prevents spurious restores. All callbacks are stable (useCallback with empty deps, using refs). |
GhostPluginPage.navigation.test.tsx |
Page-level regression tests covering button/Escape return, recommendation cancellation, scroll restoration, and locale-refresh race. |
PluginDetailTopBar.test.tsx |
Unit tests for Escape handler including modifier keys, composing, editable target, consumed events, and nested surface stopPropagation. |
usePluginListScrollRestoration.test.tsx |
Hook lifecycle tests: restore on remount, preserve through data change, no-op without request, clear pending. |
edge-cases.test.ts |
Clock freeze around 1ms idle boundary correctly isolates the test from scheduler jitter. mockRestore() in finally. |
Findings
0 P0, 0 P1.
Observations (non-blocking):
- Scroll restoration covers market detail navigation only; installed detail navigation (
selectedIdpath) does not capture/restore scroll. This is explicitly scoped per the PR description and can be addressed in a follow-up if desired. - The installed detail
onBack(line 1561) remains an inline arrow function (pre-existing pattern, not introduced by this PR).
Verification
- CI:
client-ciandpr-design-basisboth pass on current HEAD. - No stale rebase (behind by 0).
- No unresolved review threads.
- DCO: all commits signed per PR description and CI check.
Conclusion
Clean review. Ready for Approve and merge.
这次改了什么
摘要
已安装插件详情与市场插件详情支持无修饰键
Escape返回列表;从市场列表进入详情后,返回时恢复原滚动位置。2026-09-07 同步主干
77ccdc239c4c1c9538433b29e5af61a53080537f,解决与 #3992 推荐流程的冲突:保留推荐提示及详情布局,将取消当前推荐、恢复列表位置和使过期详情请求失效统一到返回处理函数。新增页面级回归测试,并扩充键盘避让覆盖。此前的
maker-pi-manager1ms idle timeout 修复仍仅调整测试时钟,不改变生产回收策略。变更类型
feat新功能fix缺陷修复refactor/perf重构或性能优化docs/test/chore文档、测试或工程维护范围
scrollTop;从推荐进入详情后返回会取消接续。UI 变化
docs/design-rules/DESIGN.md §14 Interaction Conventions、§10 双模式交付门槛。输入控件、IME、修饰键和已消费的 Escape 不被抢占;滚动恢复在列表布局阶段进行。保留主干推荐提示和原有语义 token,不新增配色或布局方案。gh pr edit --attach返回attaching files requires write access to the repository,尚未上传到 PR。怎么验证的
自动验证(2026-09-07)
验证源码对应 HEAD
3d18b8a4511e9d4addf7ec672e2f4703bd478625;提交树e6c1c12f6b454561e47477acb733e738155fea94与测试及 UI 验收时的暂存树一致。当前 HEAD 的 GitHub CI:
client-cirun34112624381已完成且结论为success;Linux 两分片、Windows 两分片及汇总、verify-checks、verify、Desktop Git integration 全部通过,DCO 和设计依据检查通过。该 run 未重跑。PR 已无合并冲突(MERGEABLE),仍为 REVIEW_REQUIRED,等待 #3714 对应的维护者 Approve;未自动合并。手工验证(macOS,2026-09-07)
frosty-kolibri,pnpm restart:desktop:remote -- --isolated=@worktree启动,返回DESKTOP_DEV_VERDICT=ready。用户确认协议后进入免登录模式;未复制正式用户数据或授权信息。scrollTop=938.5;真实 Escape 按键返回后仍为938.5;再次进入并点击返回,仍为938.5。scrollTop=938,Escape 返回后仍为938。亮暗列表及详情截图已目检。f5d0578;验收针对合并后的工作树,之后创建的 merge commit 具有同一源码树。历史验证记录(非当前 HEAD)
33407754887全绿,以及一次 macOS Escape / 滚动恢复手工验证。f5d05780caef24e61095cad8b8e8846d0a3ee94e的 run33456221471存在 Windows unit 失败。这些均为历史快照,不作为新 HEAD 的 CI 结论。未执行的验证
Windows 实机 UI 和发布包验证未执行;不把 jsdom 的平台 fixture 当作 Windows 实机证据。
风险
风险分类
影响与回滚
pr-code-reviewworkflow,不自动合并。AI Assistance
本 PR 使用 Codex 完成定位、冲突解决、测试、CI 分类与 macOS Desktop 验收;由维护者按最终 diff、证据和 CI 复核。
Reviewer Focus
提交前检查