diff --git a/src/browser/App.tsx b/src/browser/App.tsx index 8ab4af27d..0532df137 100644 --- a/src/browser/App.tsx +++ b/src/browser/App.tsx @@ -486,6 +486,14 @@ function AppInner() { // Handle keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { + // DEBUG: Log Cmd+Q presses to diagnose menu accelerator issues + if (e.metaKey && e.key.toLowerCase() === "q") { + console.log("[App] Cmd+Q detected in renderer", { + key: e.key, + metaKey: e.metaKey, + defaultPrevented: e.defaultPrevented, + }); + } if (matchesKeybind(e, KEYBINDS.NEXT_WORKSPACE)) { e.preventDefault(); handleNavigateWorkspace("next"); diff --git a/src/browser/contexts/ProviderOptionsContext.tsx b/src/browser/contexts/ProviderOptionsContext.tsx index 148bb09aa..fbd02bb67 100644 --- a/src/browser/contexts/ProviderOptionsContext.tsx +++ b/src/browser/contexts/ProviderOptionsContext.tsx @@ -1,5 +1,9 @@ -import React, { createContext, useContext } from "react"; -import { usePersistedState } from "@/browser/hooks/usePersistedState"; +import React, { createContext, useContext, useLayoutEffect } from "react"; +import { + readPersistedState, + updatePersistedState, + usePersistedState, +} from "@/browser/hooks/usePersistedState"; import type { MuxProviderOptions } from "@/common/types/providerOptions"; interface ProviderOptionsContextType { @@ -11,6 +15,10 @@ interface ProviderOptionsContextType { const ProviderOptionsContext = createContext(undefined); +const OPENAI_OPTIONS_KEY = "provider_options_openai"; +// One-time migration key: force disableAutoTruncation to true for existing users +const OPENAI_TRUNCATION_MIGRATION_KEY = "provider_options_openai_truncation_migrated"; + export function ProviderOptionsProvider({ children }: { children: React.ReactNode }) { const [anthropicOptions, setAnthropicOptions] = usePersistedState< MuxProviderOptions["anthropic"] @@ -19,12 +27,20 @@ export function ProviderOptionsProvider({ children }: { children: React.ReactNod }); const [openaiOptions, setOpenAIOptions] = usePersistedState( - "provider_options_openai", - { - disableAutoTruncation: false, - } + OPENAI_OPTIONS_KEY, + { disableAutoTruncation: true } ); + // One-time migration: force disableAutoTruncation to true for existing users + useLayoutEffect(() => { + const alreadyMigrated = readPersistedState(OPENAI_TRUNCATION_MIGRATION_KEY, false); + if (alreadyMigrated) { + return; + } + updatePersistedState(OPENAI_OPTIONS_KEY, { disableAutoTruncation: true }); + updatePersistedState(OPENAI_TRUNCATION_MIGRATION_KEY, true); + }, []); + const [googleOptions, setGoogleOptions] = usePersistedState( "provider_options_google", {} diff --git a/src/browser/utils/messages/sendOptions.ts b/src/browser/utils/messages/sendOptions.ts index 30e6c1b0b..685952ce8 100644 --- a/src/browser/utils/messages/sendOptions.ts +++ b/src/browser/utils/messages/sendOptions.ts @@ -26,7 +26,7 @@ function getProviderOptions(): MuxProviderOptions { { use1MContext: false } ); const openai = readPersistedState("provider_options_openai", { - disableAutoTruncation: false, + disableAutoTruncation: true, }); const google = readPersistedState("provider_options_google", {}); diff --git a/src/desktop/main.ts b/src/desktop/main.ts index 34505e0d0..9155ecc48 100644 --- a/src/desktop/main.ts +++ b/src/desktop/main.ts @@ -143,6 +143,13 @@ function timestamp(): string { function createMenu() { const template: MenuItemConstructorOptions[] = [ + { + label: "File", + submenu: + process.platform === "darwin" + ? [{ role: "close" }] // macOS: Cmd+W to close window + : [{ role: "quit" }], // Windows/Linux: Quit in File menu + }, { label: "Edit", submenu: [ @@ -182,7 +189,14 @@ function createMenu() { }, { label: "Window", - submenu: [{ role: "minimize" }, { role: "close" }], + submenu: + process.platform === "darwin" + ? [ + // macOS Window menu - close is in File menu to avoid Cmd+Q/Cmd+W conflicts + { role: "minimize" }, + { role: "zoom" }, + ] + : [{ role: "minimize" }, { role: "close" }], }, ]; @@ -206,7 +220,12 @@ function createMenu() { { role: "hideOthers" }, { role: "unhide" }, { type: "separator" }, - { role: "quit" }, + { + role: "quit", + click: () => { + console.log(`[${timestamp()}] Quit menu item clicked`); + }, + }, ], }); } @@ -504,7 +523,13 @@ function createWindow() { // First token count will use approximation, accurate count caches in background. }); + // Log when window is about to close (can be prevented) + mainWindow.on("close", (event) => { + console.log(`[${timestamp()}] mainWindow 'close' event fired`); + }); + mainWindow.on("closed", () => { + console.log(`[${timestamp()}] mainWindow 'closed' event fired`); mainWindow = null; }); } @@ -573,12 +598,15 @@ if (gotTheLock) { let isDisposing = false; app.on("before-quit", (event) => { + console.log(`[${timestamp()}] app 'before-quit' event fired (isDisposing=${isDisposing}, hasServices=${!!services})`); // Skip if already disposing or no services to clean up if (isDisposing || !services) { + console.log(`[${timestamp()}] before-quit: skipping (already disposing or no services)`); return; } // Prevent quit, clean up, then quit again + console.log(`[${timestamp()}] before-quit: preventing quit, starting disposal`); event.preventDefault(); isDisposing = true; @@ -594,6 +622,7 @@ if (gotTheLock) { }); app.on("window-all-closed", () => { + console.log(`[${timestamp()}] app 'window-all-closed' event fired (platform=${process.platform})`); if (process.platform !== "darwin") { app.quit(); } diff --git a/tests/ipc/sendMessage.heavy.test.ts b/tests/ipc/sendMessage.heavy.test.ts index dcf9d8363..41228daca 100644 --- a/tests/ipc/sendMessage.heavy.test.ts +++ b/tests/ipc/sendMessage.heavy.test.ts @@ -41,13 +41,21 @@ describeIntegration("sendMessage heavy/load tests", () => { await withSharedWorkspace(provider, async ({ env, workspaceId, collector }) => { // Build up large conversation history to exceed context limit // This approach is model-agnostic - it keeps sending until we've built up enough history + // Use auto-truncation enabled (disableAutoTruncation: false) so history builds up successfully const largeMessage = "x".repeat(50_000); for (let i = 0; i < 10; i++) { await sendMessageWithModel( env, workspaceId, `Message ${i}: ${largeMessage}`, - modelString(provider, model) + modelString(provider, model), + { + providerOptions: { + openai: { + disableAutoTruncation: false, + }, + }, + } ); await collector.waitForEvent("stream-end", 30000); collector.clear(); @@ -93,8 +101,14 @@ describeIntegration("sendMessage heavy/load tests", () => { env, workspaceId, "This should succeed with auto-truncation", - modelString(provider, model) - // disableAutoTruncation defaults to false (auto-truncation enabled) + modelString(provider, model), + { + providerOptions: { + openai: { + disableAutoTruncation: false, + }, + }, + } ); expect(successResult.success).toBe(true);