Skip to content

fix(desktop): support Escape navigation in plugin details - #3687

Merged
MagicLizi merged 5 commits into
makecindy:mainfrom
qsoyq:add-esc-plugin-market-back-navigation
Sep 9, 2026
Merged

fix(desktop): support Escape navigation in plugin details#3687
MagicLizi merged 5 commits into
makecindy:mainfrom
qsoyq:add-esc-plugin-market-back-navigation

Conversation

@qsoyq

@qsoyq qsoyq commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

这次改了什么

摘要

已安装插件详情与市场插件详情支持无修饰键 Escape 返回列表;从市场列表进入详情后,返回时恢复原滚动位置。

2026-09-07 同步主干 77ccdc239c4c1c9538433b29e5af61a53080537f,解决与 #3992 推荐流程的冲突:保留推荐提示及详情布局,将取消当前推荐、恢复列表位置和使过期详情请求失效统一到返回处理函数。新增页面级回归测试,并扩充键盘避让覆盖。

此前的 maker-pi-manager 1ms idle timeout 修复仍仅调整测试时钟,不改变生产回收策略。

变更类型

  • feat 新功能
  • fix 缺陷修复
  • refactor / perf 重构或性能优化
  • docs / test / chore 文档、测试或工程维护
  • 其他:

范围

  • 关联 Issue / 需求:N/A — explicitly approved quick fix;维护者产品确认见 维护者确认:插件详情 Escape 返回与列表滚动恢复 #3714(不自动关闭)。
  • 本 PR 包含:共享顶栏 Escape 返回、市场列表滚动恢复、推荐取消与返回交互兼容、回归测试与测试时钟稳定性修复。
  • 明确不包含:全局快捷键、路由历史重构、Mobile、插件安装/批准/权限变更、生产 idle recycle 策略变更。
  • 用户可见变化:按 Escape 或点击返回离开详情;市场列表恢复原 scrollTop;从推荐进入详情后返回会取消接续。
  • 是否存在 breaking change:无;公共 API、IPC 与配置保持不变。

UI 变化

  • 引用的设计规范:docs/design-rules/DESIGN.md §14 Interaction Conventions、§10 双模式交付门槛。输入控件、IME、修饰键和已消费的 Escape 不被抢占;滚动恢复在列表布局阶段进行。保留主干推荐提示和原有语义 token,不新增配色或布局方案。
  • macOS 实际 Desktop Renderer 验收:Cindy Light / Dark 均已目检。截图来自隔离开发实例,非静态 HTML 或测试组件。截图已本地保存;gh pr edit --attach 返回 attaching files requires write access to the repository,尚未上传到 PR。

怎么验证的

自动验证(2026-09-07)

验证源码对应 HEAD 3d18b8a4511e9d4addf7ec672e2f4703bd478625;提交树 e6c1c12f6b454561e47477acb733e738155fea94 与测试及 UI 验收时的暂存树一致。

pnpm --filter desktop exec vitest run \
  src/renderer/features/plugin/__tests__/GhostPluginPage.navigation.test.tsx \
  src/renderer/features/plugin/__tests__/PluginDetailTopBar.test.tsx \
  src/renderer/features/plugin/__tests__/MarketPluginDetailView.test.tsx \
  src/renderer/features/plugin/__tests__/usePluginListScrollRestoration.test.tsx \
  src/renderer/features/cc-agent/__tests__/pendingPluginSuggestion.test.ts \
  src/renderer/features/cc-agent/__tests__/pluginHomeSuggestions.test.ts
结果:6 个测试文件、40 tests 通过。

pnpm --filter @cindy/maker-pi-manager exec vitest run src/__tests__/edge-cases.test.ts
结果:60 tests 通过。

pnpm --filter desktop run --if-present typecheck
pnpm --filter @cindy/maker-pi-manager run --if-present typecheck
pnpm --filter @cindy/maker-pi-manager build
结果:通过;maker-pi-manager 没有独立 typecheck script,build 执行 tsc --noEmit。

pnpm --filter desktop exec eslint <本 PR 涉及的 Renderer 源码及测试>
结果:通过。

pnpm test:unit:related
结果:通过。同步主干包含依赖/CI 调整,调度器自动退回全量 unit;所有可运行 workspace 通过。
首轮 Desktop 有两项 agent-island/service.test.ts 模块加载期间的 5s 超时;该文件独立复跑 129 tests 通过。
停止类型检查并发后,完整门禁第二轮通过(未改超时、未跳过失败测试)。

pnpm check:dco
结果:通过;4 个非 merge 提交已签名,1 个 merge 提交按脚本规则豁免,merge 提交自身也带 Signed-off-by。

git diff origin/main...HEAD --check
结果:通过;PR 差异仍仅涉及 7 个 Desktop / maker-pi-manager 文件。

当前 HEAD 的 GitHub CI:client-ci run 34112624381 已完成且结论为 success;Linux 两分片、Windows 两分片及汇总、verify-checks、verify、Desktop Git integration 全部通过,DCO 和设计依据检查通过。该 run 未重跑。PR 已无合并冲突(MERGEABLE),仍为 REVIEW_REQUIRED,等待 #3714 对应的维护者 Approve;未自动合并。

手工验证(macOS,2026-09-07)

  • 隔离 worktree frosty-kolibripnpm restart:desktop:remote -- --isolated=@worktree 启动,返回 DESKTOP_DEV_VERDICT=ready。用户确认协议后进入免登录模式;未复制正式用户数据或授权信息。
  • 市场详情 / Light:滚动至 World Bank Open Data,进入详情前 scrollTop=938.5;真实 Escape 按键返回后仍为 938.5;再次进入并点击返回,仍为 938.5
  • 市场详情 / Dark:通过设置切换深色,进入前 scrollTop=938,Escape 返回后仍为 938。亮暗列表及详情截图已目检。
  • 已安装详情 / Light + Dark:仅在隔离沙箱安装官方 Mermaid 插件;进入已安装详情后按 Escape,均成功返回列表。
  • 推荐入口:首页点击「把这封邮件整理成回复草稿」进入 Gmail 市场详情,显示「取消接续」提示;Escape 返回后提示消失、列表可见。未安装 Gmail、未进行 OAuth、未发送消息或邮件。
  • 截图侧栏显示的是启动时旧 HEAD f5d0578;验收针对合并后的工作树,之后创建的 merge commit 具有同一源码树。

历史验证记录(非当前 HEAD)

  • 原 PR 报告三个 Renderer 文件共 21 tests、定向 ESLint、Desktop typecheck、maker-pi-manager 60 个边界测试及整包 314 tests、build、related unit 和 DCO 通过。
  • 原 PR 报告 Actions run 33407754887 全绿,以及一次 macOS Escape / 滚动恢复手工验证。
  • 后续旧 HEAD f5d05780caef24e61095cad8b8e8846d0a3ee94e 的 run 33456221471 存在 Windows unit 失败。这些均为历史快照,不作为新 HEAD 的 CI 结论。

未执行的验证

Windows 实机 UI 和发布包验证未执行;不把 jsdom 的平台 fixture 当作 Windows 实机证据。

风险

风险分类

  • 无已知风险
  • SQLite / migration
  • system prompt
  • 协议兼容
  • 权限 / 安全 / 用户数据
  • 存量插件兼容(批准状态 / 指纹 / manifest 校验 / 安装布局 / 包格式)
  • 原生层 / fingerprint / OTA
  • 跨平台差异
  • 其他:产品 / UI 人工批准仍待维护者确认

影响与回滚

  • 影响范围:Desktop 两类插件详情的返回交互、市场列表滚动恢复以及推荐接续取消。存量插件影响:无;不更改批准记录、manifest、安装布局、凭证或偏好。
  • 跨平台风险:Windows 实机 UI 未验收;跨平台单测以当前 HEAD CI 为准。
  • 回滚:回退本 PR 的产品改动即可。此次同步主干使用保留双亲历史的 merge commit;不要为撤销此交互而顺带回滚主干其他提交。
  • 维护者确认:插件详情 Escape 返回与列表滚动恢复 #3714 的维护者 Approve 是独立门禁。CI 通过不替代批准;不启用被手动停用的 pr-code-review workflow,不自动合并。

AI Assistance

本 PR 使用 Codex 完成定位、冲突解决、测试、CI 分类与 macOS Desktop 验收;由维护者按最终 diff、证据和 CI 复核。

Reviewer Focus

  • 两类详情均通过共享顶栏获得 Escape 返回,修饰键、IME、编辑控件和嵌套表面优先。
  • 主干推荐提示/布局完整保留;点击返回与 Escape 共用取消推荐及滚动恢复路径。
  • 返回后过期详情刷新不会重新打开详情;恢复是像素位置,不承诺市场刷新后仍对齐同一插件卡片。
  • 本 PR 不修改生产 idle recycle、插件安装或权限策略。

提交前检查

  • 已 review 完整 PR diff
  • 每个 commit 都带 DCO 签名
  • UI 变化已注明设计规范,Light / Dark 均有实机目检
  • 未提交凭证、令牌或授权文件
  • 已更新必要说明
  • 已确认测试结果或说明未执行原因

qsoyq added 2 commits August 31, 2026 19:32
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>
@qsoyq

qsoyq commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up 修正:

  • 根因是详情分支会卸载插件市场列表滚动容器,返回时新容器的 scrollTop 默认回到 0。
  • 新增列表滚动位置恢复 hook;进入市场详情前记录当前位置,ESC/返回按钮回退时在列表重新挂载的 layout phase 恢复,避免顶部闪回。
  • 账号或模式切换会清除待恢复位置,避免跨数据归属复用旧滚动状态。

验证:pnpm test:unit:related、Desktop typecheck、定向 ESLint,以及新增的 3 条滚动生命周期回归测试均通过。

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>
@qsoyq
qsoyq marked this pull request as ready for review August 31, 2026 15:37
@qsoyq
qsoyq requested a review from a team as a code owner August 31, 2026 15:37
@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown

Greptile Summary

本 PR 为桌面端已安装插件和市场插件详情增加 Escape 返回,并在市场列表重新挂载时恢复进入详情前的滚动位置;同时通过冻结单调时钟稳定 maker-pi-manager 的超短 idle timeout 边界测试。

  • 在共享 PluginDetailTopBar 中处理无修饰键 Escape,并避让编辑控件、输入法组合态和已消费事件
  • 为条件挂载的插件市场列表增加滚动位置捕获与恢复
  • 调整测试时钟控制,不改变生产 idle 回收逻辑

Confidence Score: 5/5

当前没有仍需阻止合并的已确认故障,PR 看起来可以安全合并。

没有阻塞性故障仍然存在。

Important Files Changed

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[恢复原滚动位置]
Loading

Reviews (2): Last reviewed commit: "test(maker-pi-manager): stabilize idle t..." | Re-trigger Greptile

@qsoyq
qsoyq marked this pull request as draft August 31, 2026 17:22
@qsoyq
qsoyq marked this pull request as ready for review August 31, 2026 18:04
)

Simulate the catalog list unmounting for detail, receiving a new leading item while hidden, and remounting. The regression guard confirms the saved pixel offset is restored instead of resetting to the top.

Signed-off-by: qsoyq <github@19940731.xyz>
@qsoyq

qsoyq commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

本次推送(commit f5d05780c)补充了“详情期间市场列表内容变化”的回归测试:

  • 模拟插件市场列表停在 scrollTop=640 并进入详情;
  • 详情打开期间市场刷新,在列表前插入一个新插件;
  • 返回列表后验证仍恢复到 scrollTop=640,不会跳回顶部。

测试位于 apps/desktop/src/renderer/features/plugin/__tests__/usePluginListScrollRestoration.test.tsx。本次只增强测试覆盖,未改变生产滚动恢复逻辑,也没有把像素位置恢复表述为始终保持同一插件卡片的语义位置。

@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示) labels Sep 1, 2026
@MagicLizi

Copy link
Copy Markdown
Contributor

这个 PR 会改变插件详情页的返回交互(Escape 返回 + 列表滚动恢复),已进入维护者确认,球不在作者手里。

请维护者直接在 PR 上 Approve;需要修改就 Request Changes。讨论 issue:#3714

@MagicLizi

Copy link
Copy Markdown
Contributor

命中 UI 路径(插件详情顶栏 / 插件列表)但 description 未附界面效果证据——建议补充改动后效果:截图/录屏,或改动后界面的 HTML 页面,便于确认界面符合 DESIGN.md 设计规范。

@MagicLizi MagicLizi added awaiting-discussion 等待维护者讨论(review-pr) and removed awaiting-discussion 等待维护者讨论(review-pr) labels Sep 5, 2026
@MagicLizi

Copy link
Copy Markdown
Contributor

@qsoyq 👋 这个 PR 现在在等维护者确认,确认之前流程不会合并它 —— 不是卡住了,也不是在等你再改一版(你推的改动流程都读到了,判的就是最新一版代码)。

  • 在拦的是:维护者确认门(产品 / UI 变更)。
  • 讨论 issue:维护者确认:插件详情 Escape 返回与列表滚动恢复 #3714
  • 通过方式只有一个:维护者在本 PR 上 Approve。维护者觉得要改会直接 Request Changes,那时候球才回到你手里。
  • 这期间如果还有 review 意见没处理完、CI 没过,照常修就行,不影响这条等待。

这条是流程自动发的状态提醒(同一版代码只发一次),不用回复。

@MagicLizi

Copy link
Copy Markdown
Contributor

@qsoyq 👋 这个 PR 目前与 main 有合并冲突,auto-review 因此暂时跳过、没法继续审查 / 合并。

请在本地 merge 最新的 origin/main 解决冲突后推送;冲突解除后,下一轮 auto-review 会自动重新处理这个 PR。

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 MagicLizi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (selectedId path) 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-ci and pr-design-basis both 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.

@MagicLizi MagicLizi removed the awaiting-discussion 等待维护者讨论(review-pr) label Sep 8, 2026
@MagicLizi
MagicLizi merged commit 4d2cd78 into makecindy:main Sep 9, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

touches:product-ui 改动碰到产品 / UI 面(review-pr 自动维护,仅展示)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants