diff --git a/packages/ui/src/components/desktop/OpenInAppButton.tsx b/packages/ui/src/components/desktop/OpenInAppButton.tsx index 6241a89a..e8f52660 100644 --- a/packages/ui/src/components/desktop/OpenInAppButton.tsx +++ b/packages/ui/src/components/desktop/OpenInAppButton.tsx @@ -47,7 +47,7 @@ const OPEN_IN_APPS: OpenInAppOption[] = [ { id: 'trae', label: 'Trae', appName: 'Trae' }, ]; -const DEFAULT_APP_ID = 'vscode'; +const DEFAULT_APP_ID = 'finder'; const ALWAYS_AVAILABLE_APP_IDS = new Set(['finder', 'terminal']); const getAlwaysAvailableApps = () => OPEN_IN_APPS.filter((app) => ALWAYS_AVAILABLE_APP_IDS.has(app.id)); @@ -121,9 +121,17 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps) if (typeof window === 'undefined') return; const handler = (event: Event) => { const detail = (event as CustomEvent).detail; - if (detail && typeof detail.openInAppId === 'string' && detail.openInAppId.length > 0) { - setSelectedAppId(detail.openInAppId); + const nextId = detail + && typeof detail.openInAppId === 'string' + && detail.openInAppId.length > 0 + && OPEN_IN_APPS.some((app) => app.id === detail.openInAppId) + ? detail.openInAppId + : null; + if (!nextId) { + return; } + window.localStorage.setItem('openInAppId', nextId); + setSelectedAppId(nextId); }; window.addEventListener('openchamber:settings-synced', handler); return () => window.removeEventListener('openchamber:settings-synced', handler); @@ -174,10 +182,7 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps) retryTimeoutRef.current = null; } if (force) { - console.info('[open-in] manual refresh requested'); setLoadedState(false); - } else { - console.info('[open-in] load installed apps'); } isLoadingRef.current = true; setIsScanning(true); @@ -191,7 +196,6 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps) } = await fetchDesktopInstalledApps(appNames, force); if (!isMountedRef.current) return; setIsCacheStale(hasCache ? nextCacheStale : false); - console.info('[open-in] installed apps returned', installed.map((app) => app.name)); applyInstalledApps(installed); if (success) { if (!hasCache && installed.length === 0 && retryAttemptRef.current < 3) { @@ -230,14 +234,12 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps) if (typeof window === 'undefined') return; void loadInstalledApps(); const handler = () => { - console.info('[open-in] app ready, starting installed app scan'); void loadInstalledApps(); }; window.addEventListener('openchamber:app-ready', handler); const updateHandler = (event: Event) => { const detail = (event as CustomEvent).detail; if (Array.isArray(detail)) { - console.info('[open-in] received installed app update', detail.length); retryAttemptRef.current = 3; keepScanningRef.current = false; setIsScanning(false); @@ -248,7 +250,6 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps) window.addEventListener('openchamber:installed-apps-updated', updateHandler); const flag = (window as unknown as { __openchamberAppReady?: boolean }).__openchamberAppReady; if (flag) { - console.info('[open-in] app ready flag already set'); void loadInstalledApps(); } return () => { @@ -262,22 +263,22 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps) if (typeof window === 'undefined') return; const fallbackTimer = window.setTimeout(() => { if (!hasLoadedAppsRef.current) { - console.info('[open-in] fallback scan triggered'); void loadInstalledApps(); } }, 5000); return () => window.clearTimeout(fallbackTimer); }, [isDesktopLocal, loadInstalledApps]); - const selectedApp = availableApps.find((app) => app.id === selectedAppId) ?? availableApps[0]; - - React.useEffect(() => { - if (!selectedApp) return; - if (selectedAppId !== selectedApp.id) { - setSelectedAppId(selectedApp.id); - void updateDesktopSettings({ openInAppId: selectedApp.id }); + const selectedApp = React.useMemo(() => { + const known = OPEN_IN_APPS.find((app) => app.id === selectedAppId) + ?? OPEN_IN_APPS.find((app) => app.id === DEFAULT_APP_ID) + ?? OPEN_IN_APPS[0]; + if (known) { + const iconDataUrl = availableApps.find((app) => app.appName === known.appName)?.iconDataUrl; + return iconDataUrl ? { ...known, iconDataUrl } : known; } - }, [selectedApp, selectedAppId]); + return availableApps[0]; + }, [availableApps, selectedAppId]); if (!isDesktopLocal || !directory) { return null; @@ -293,6 +294,9 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps) const handleSelect = async (app: OpenInAppOption) => { setSelectedAppId(app.id); + if (typeof window !== 'undefined') { + window.localStorage.setItem('openInAppId', app.id); + } await updateDesktopSettings({ openInAppId: app.id }); await handleOpen(app); }; @@ -348,7 +352,9 @@ export const OpenInAppButton = ({ directory, className }: OpenInAppButtonProps) iconDataUrl={selectedApp.iconDataUrl} fallbackIconDataUrl={selectedApp.fallbackIconDataUrl} /> - Open + + Open + diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index 371bdc58..80259766 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -237,9 +237,16 @@ export const Header: React.FC = () => { return normalize(raw || ''); }, [currentSession?.directory]); + const draftDirectory = useSessionStore((state) => { + if (!state.newSessionDraft?.open) { + return ''; + } + return normalize(state.newSessionDraft.directoryOverride ?? ''); + }); + const openDirectory = React.useMemo(() => { - return worktreeDirectory || sessionDirectory; - }, [sessionDirectory, worktreeDirectory]); + return worktreeDirectory || sessionDirectory || draftDirectory; + }, [draftDirectory, sessionDirectory, worktreeDirectory]); const [planTabAvailable, setPlanTabAvailable] = React.useState(false); diff --git a/packages/ui/src/index.css b/packages/ui/src/index.css index 4d31ca7a..47967652 100644 --- a/packages/ui/src/index.css +++ b/packages/ui/src/index.css @@ -463,11 +463,17 @@ html:not(.dark) .chat-scroll { :root:not(.mobile-pointer):not(.vscode-runtime) .header-tab-label { display: inline; } +:root:not(.mobile-pointer):not(.vscode-runtime) .header-open-label { + display: inline; +} @media (max-width: 940px) { :root:not(.mobile-pointer):not(.vscode-runtime) .header-tab-label { display: none; } + :root:not(.mobile-pointer):not(.vscode-runtime) .header-open-label { + display: none; + } :root:not(.mobile-pointer):not(.vscode-runtime) .header-tab-badge { margin-left: -0.125rem; } diff --git a/packages/ui/src/lib/persistence.ts b/packages/ui/src/lib/persistence.ts index 2ab26988..33c68537 100644 --- a/packages/ui/src/lib/persistence.ts +++ b/packages/ui/src/lib/persistence.ts @@ -73,8 +73,6 @@ const persistToLocalStorage = (settings: DesktopSettings) => { } if (typeof settings.openInAppId === 'string' && settings.openInAppId.length > 0) { localStorage.setItem('openInAppId', settings.openInAppId); - } else { - localStorage.removeItem('openInAppId'); } }; diff --git a/packages/web/server/index.js b/packages/web/server/index.js index b3281bee..72f0f660 100644 --- a/packages/web/server/index.js +++ b/packages/web/server/index.js @@ -1094,6 +1094,12 @@ const sanitizeSettingsUpdate = (payload) => { if (typeof candidate.filesViewShowGitignored === 'boolean') { result.filesViewShowGitignored = candidate.filesViewShowGitignored; } + if (typeof candidate.openInAppId === 'string') { + const trimmed = candidate.openInAppId.trim(); + if (trimmed.length > 0) { + result.openInAppId = trimmed; + } + } // Memory limits for message viewport management if (typeof candidate.memoryLimitHistorical === 'number' && Number.isFinite(candidate.memoryLimitHistorical)) {