diff --git a/apps/desktop/electron/main/bootstrap/app-lifecycle.ts b/apps/desktop/electron/main/bootstrap/app-lifecycle.ts index 5eea389b2..e6fb161e9 100644 --- a/apps/desktop/electron/main/bootstrap/app-lifecycle.ts +++ b/apps/desktop/electron/main/bootstrap/app-lifecycle.ts @@ -265,11 +265,14 @@ export function createApplicationLifecycle({ state.tray = new Tray(icon); state.tray.setToolTip(APP_NAME); // macOS single-click opens its attached menu without focusing/reading a conversation. - if (process.platform !== "darwin") state.tray.on("click", restoreMainWindow); + // mouse-enter/move/leave replace the native NSStatusItem with a custom view, + // which hides the menu-bar extra. Keep hover retry on Windows/Linux only. + if (process.platform !== "darwin") { + state.tray.on("click", restoreMainWindow); + state.tray.on("mouse-enter", () => { void traySessions.refresh(); }); + state.tray.on("right-click", () => { void traySessions.refresh(); }); + } state.tray.on("double-click", restoreMainWindow); - // User access retries a transient Host read failure without a polling timer. - state.tray.on("mouse-enter", () => { void traySessions.refresh(); }); - state.tray.on("right-click", () => { void traySessions.refresh(); }); updateTrayMenu(); void traySessions.refresh(); } diff --git a/apps/desktop/test/close-behavior-tray.test.mjs b/apps/desktop/test/close-behavior-tray.test.mjs index f92cebb36..e66724761 100644 --- a/apps/desktop/test/close-behavior-tray.test.mjs +++ b/apps/desktop/test/close-behavior-tray.test.mjs @@ -146,3 +146,14 @@ test("explicit quit asks for confirmation except probes and update restarts", () /already chose to quit in the close-behavior dialog[\s\S]*?windowState\.quitConfirmed = true/, ); }); + +test("macOS tray keeps the native status item instead of mouse-enter tracking", async () => { + const lifecycle = await readMainModule("bootstrap/app-lifecycle.ts"); + const create = lifecycle.slice(lifecycle.indexOf("function createTray()")); + const body = create.slice(0, create.indexOf("function resetMenuRendererReady")); + assert.match( + body, + /if \(process\.platform !== "darwin"\) \{[\s\S]*on\("mouse-enter"[\s\S]*on\("right-click"/, + ); + assert.equal((body.match(/on\("mouse-enter"/g) || []).length, 1); +}); diff --git a/docs/adr/tray-session-shortcuts.md b/docs/adr/tray-session-shortcuts.md index 82d1046d6..28c0d1b51 100644 --- a/docs/adr/tray-session-shortcuts.md +++ b/docs/adr/tray-session-shortcuts.md @@ -31,8 +31,9 @@ or closed, so a renderer-only menu snapshot would become stale. (the existing 200-record inbox), consumes root agent lifecycle/status events, and refreshes after session/inbox mutations. It coalesces pending reads, rejects obsolete host generations, and clears shortcuts on a failed - refresh. Tray hover/right-click retries a failed read. It continues to - refresh with no renderer attached; deleting a + refresh. Windows/Linux hover/right-click retries a failed read. macOS does + not subscribe to mouse-enter, which would replace the native status item. + It continues to refresh with no renderer attached; deleting a session or archiving it or its project removes the shortcut. 4. A session click restores/focuses the main window and sends `tray/event/sessionActivated { sessionId }` (`null` means View more) only after the renderer finishes diff --git a/docs/spec/04-ux/09-interaction-patterns.md b/docs/spec/04-ux/09-interaction-patterns.md index 4109cf8f7..bec69db32 100644 --- a/docs/spec/04-ux/09-interaction-patterns.md +++ b/docs/spec/04-ux/09-interaction-patterns.md @@ -200,6 +200,10 @@ recency only breaks ties between equally relevant matches. - Start/finish, read, pin, rename, archive, delete, and backend restart update the menu. The menu remains available when the main window is hidden or closed, without creating another window until an explicit activation. +- macOS does not listen for tray mouse-enter: that event replaces the native + status item and hides the extra. Windows/Linux still retry a failed Host + read on hover/right-click; macOS retries from the next session or inbox event. + ### 1.6 Sidebar project and conversation organization diff --git a/docs/spec/06-delivery/04-e2e-test-plan.md b/docs/spec/06-delivery/04-e2e-test-plan.md index f47b38993..c40911066 100644 --- a/docs/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/spec/06-delivery/04-e2e-test-plan.md @@ -13031,7 +13031,7 @@ plugin-form fixtures in an isolated temporary directory at runtime. Close the macOS window while a task runs and let it finish, then activate its tray row while the new renderer bootstraps a pending plan. Delay a Host read while a newer preference update, delete, or Host restart arrives. - Retry a transient read failure by hovering/right-clicking the tray. + Retry a transient read failure by hovering/right-clicking the tray on Windows/Linux; macOS retries from the next session or inbox event instead of mouse-enter. Repeat after clearing all group memberships and changing shipped locales. Choose Quit then Cancel, then Quit and confirm. - **Expected**: Running → Unread → Pinned; at most nine rows in total. Each diff --git a/docs/zh-CN/spec/04-ux/09-interaction-patterns.md b/docs/zh-CN/spec/04-ux/09-interaction-patterns.md index 43c1075b3..a328ae08a 100644 --- a/docs/zh-CN/spec/04-ux/09-interaction-patterns.md +++ b/docs/zh-CN/spec/04-ux/09-interaction-patterns.md @@ -163,6 +163,8 @@ - Start/finish, read, pin, rename, archive, delete, and backend restart update the menu. The menu remains available when the main window is hidden or closed, without creating another window until an explicit activation. +- macOS 不监听托盘 mouse-enter:该事件会替换原生 status item 并让菜单栏图标消失。 + Windows/Linux 仍可在悬停/右键时重试失败的 Host 读取;macOS 改由下一次会话或收件箱事件刷新。 ### 1. 6 侧边栏项目和对话组织 diff --git a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md index 0bcbdc370..c19e9d571 100644 --- a/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md +++ b/docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md @@ -7855,7 +7855,7 @@ runner 会在运行时的隔离临时目录中生成六个插件形态 fixture Close the macOS window while a task runs and let it finish, then activate its tray row while the new renderer bootstraps a pending plan. Delay a Host read while a newer preference update, delete, or Host restart arrives. - Retry a transient read failure by hovering/right-clicking the tray. + Retry a transient read failure by hovering/right-clicking the tray on Windows/Linux; macOS retries from the next session or inbox event instead of mouse-enter. Repeat after clearing all group memberships and changing shipped locales. Choose Quit then Cancel, then Quit and confirm. - **Expected**: Running → Unread → Pinned; at most nine rows in total. Each