Skip to content

Commit e9a8191

Browse files
committed
🤖 fix: add logging to debug Cmd+Q behavior on macOS
Adding detailed logging to understand why Cmd+Q closes the window instead of quitting the app on macOS: - Quit menu item click handler - mainWindow 'close' and 'closed' events - app 'before-quit' event with disposal state - app 'window-all-closed' event - Cmd+Q detection in renderer (to see if keypress reaches renderer) Run the app and press Cmd+Q, then check the console output. Fixes #1100
1 parent cac3cb0 commit e9a8191

File tree

5 files changed

+79
-12
lines changed

5 files changed

+79
-12
lines changed

src/browser/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,14 @@ function AppInner() {
486486
// Handle keyboard shortcuts
487487
useEffect(() => {
488488
const handleKeyDown = (e: KeyboardEvent) => {
489+
// DEBUG: Log Cmd+Q presses to diagnose menu accelerator issues
490+
if (e.metaKey && e.key.toLowerCase() === "q") {
491+
console.log("[App] Cmd+Q detected in renderer", {
492+
key: e.key,
493+
metaKey: e.metaKey,
494+
defaultPrevented: e.defaultPrevented,
495+
});
496+
}
489497
if (matchesKeybind(e, KEYBINDS.NEXT_WORKSPACE)) {
490498
e.preventDefault();
491499
handleNavigateWorkspace("next");

src/browser/contexts/ProviderOptionsContext.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
import React, { createContext, useContext } from "react";
2-
import { usePersistedState } from "@/browser/hooks/usePersistedState";
1+
import React, { createContext, useContext, useLayoutEffect } from "react";
2+
import {
3+
readPersistedState,
4+
updatePersistedState,
5+
usePersistedState,
6+
} from "@/browser/hooks/usePersistedState";
37
import type { MuxProviderOptions } from "@/common/types/providerOptions";
48

59
interface ProviderOptionsContextType {
@@ -11,6 +15,10 @@ interface ProviderOptionsContextType {
1115

1216
const ProviderOptionsContext = createContext<ProviderOptionsContextType | undefined>(undefined);
1317

18+
const OPENAI_OPTIONS_KEY = "provider_options_openai";
19+
// One-time migration key: force disableAutoTruncation to true for existing users
20+
const OPENAI_TRUNCATION_MIGRATION_KEY = "provider_options_openai_truncation_migrated";
21+
1422
export function ProviderOptionsProvider({ children }: { children: React.ReactNode }) {
1523
const [anthropicOptions, setAnthropicOptions] = usePersistedState<
1624
MuxProviderOptions["anthropic"]
@@ -19,12 +27,20 @@ export function ProviderOptionsProvider({ children }: { children: React.ReactNod
1927
});
2028

2129
const [openaiOptions, setOpenAIOptions] = usePersistedState<MuxProviderOptions["openai"]>(
22-
"provider_options_openai",
23-
{
24-
disableAutoTruncation: false,
25-
}
30+
OPENAI_OPTIONS_KEY,
31+
{ disableAutoTruncation: true }
2632
);
2733

34+
// One-time migration: force disableAutoTruncation to true for existing users
35+
useLayoutEffect(() => {
36+
const alreadyMigrated = readPersistedState<boolean>(OPENAI_TRUNCATION_MIGRATION_KEY, false);
37+
if (alreadyMigrated) {
38+
return;
39+
}
40+
updatePersistedState(OPENAI_OPTIONS_KEY, { disableAutoTruncation: true });
41+
updatePersistedState(OPENAI_TRUNCATION_MIGRATION_KEY, true);
42+
}, []);
43+
2844
const [googleOptions, setGoogleOptions] = usePersistedState<MuxProviderOptions["google"]>(
2945
"provider_options_google",
3046
{}

src/browser/utils/messages/sendOptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ function getProviderOptions(): MuxProviderOptions {
2626
{ use1MContext: false }
2727
);
2828
const openai = readPersistedState<MuxProviderOptions["openai"]>("provider_options_openai", {
29-
disableAutoTruncation: false,
29+
disableAutoTruncation: true,
3030
});
3131
const google = readPersistedState<MuxProviderOptions["google"]>("provider_options_google", {});
3232

src/desktop/main.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ function timestamp(): string {
143143

144144
function createMenu() {
145145
const template: MenuItemConstructorOptions[] = [
146+
{
147+
label: "File",
148+
submenu:
149+
process.platform === "darwin"
150+
? [{ role: "close" }] // macOS: Cmd+W to close window
151+
: [{ role: "quit" }], // Windows/Linux: Quit in File menu
152+
},
146153
{
147154
label: "Edit",
148155
submenu: [
@@ -182,7 +189,14 @@ function createMenu() {
182189
},
183190
{
184191
label: "Window",
185-
submenu: [{ role: "minimize" }, { role: "close" }],
192+
submenu:
193+
process.platform === "darwin"
194+
? [
195+
// macOS Window menu - close is in File menu to avoid Cmd+Q/Cmd+W conflicts
196+
{ role: "minimize" },
197+
{ role: "zoom" },
198+
]
199+
: [{ role: "minimize" }, { role: "close" }],
186200
},
187201
];
188202

@@ -206,7 +220,12 @@ function createMenu() {
206220
{ role: "hideOthers" },
207221
{ role: "unhide" },
208222
{ type: "separator" },
209-
{ role: "quit" },
223+
{
224+
role: "quit",
225+
click: () => {
226+
console.log(`[${timestamp()}] Quit menu item clicked`);
227+
},
228+
},
210229
],
211230
});
212231
}
@@ -504,7 +523,13 @@ function createWindow() {
504523
// First token count will use approximation, accurate count caches in background.
505524
});
506525

526+
// Log when window is about to close (can be prevented)
527+
mainWindow.on("close", (event) => {
528+
console.log(`[${timestamp()}] mainWindow 'close' event fired`);
529+
});
530+
507531
mainWindow.on("closed", () => {
532+
console.log(`[${timestamp()}] mainWindow 'closed' event fired`);
508533
mainWindow = null;
509534
});
510535
}
@@ -573,12 +598,15 @@ if (gotTheLock) {
573598
let isDisposing = false;
574599

575600
app.on("before-quit", (event) => {
601+
console.log(`[${timestamp()}] app 'before-quit' event fired (isDisposing=${isDisposing}, hasServices=${!!services})`);
576602
// Skip if already disposing or no services to clean up
577603
if (isDisposing || !services) {
604+
console.log(`[${timestamp()}] before-quit: skipping (already disposing or no services)`);
578605
return;
579606
}
580607

581608
// Prevent quit, clean up, then quit again
609+
console.log(`[${timestamp()}] before-quit: preventing quit, starting disposal`);
582610
event.preventDefault();
583611
isDisposing = true;
584612

@@ -594,6 +622,7 @@ if (gotTheLock) {
594622
});
595623

596624
app.on("window-all-closed", () => {
625+
console.log(`[${timestamp()}] app 'window-all-closed' event fired (platform=${process.platform})`);
597626
if (process.platform !== "darwin") {
598627
app.quit();
599628
}

tests/ipc/sendMessage.heavy.test.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,21 @@ describeIntegration("sendMessage heavy/load tests", () => {
4141
await withSharedWorkspace(provider, async ({ env, workspaceId, collector }) => {
4242
// Build up large conversation history to exceed context limit
4343
// This approach is model-agnostic - it keeps sending until we've built up enough history
44+
// Use auto-truncation enabled (disableAutoTruncation: false) so history builds up successfully
4445
const largeMessage = "x".repeat(50_000);
4546
for (let i = 0; i < 10; i++) {
4647
await sendMessageWithModel(
4748
env,
4849
workspaceId,
4950
`Message ${i}: ${largeMessage}`,
50-
modelString(provider, model)
51+
modelString(provider, model),
52+
{
53+
providerOptions: {
54+
openai: {
55+
disableAutoTruncation: false,
56+
},
57+
},
58+
}
5159
);
5260
await collector.waitForEvent("stream-end", 30000);
5361
collector.clear();
@@ -93,8 +101,14 @@ describeIntegration("sendMessage heavy/load tests", () => {
93101
env,
94102
workspaceId,
95103
"This should succeed with auto-truncation",
96-
modelString(provider, model)
97-
// disableAutoTruncation defaults to false (auto-truncation enabled)
104+
modelString(provider, model),
105+
{
106+
providerOptions: {
107+
openai: {
108+
disableAutoTruncation: false,
109+
},
110+
},
111+
}
98112
);
99113

100114
expect(successResult.success).toBe(true);

0 commit comments

Comments
 (0)