-
Notifications
You must be signed in to change notification settings - Fork 2.7k
feat(ui): bring back quick launcher #5144
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,44 @@ | ||||||
| import { useRef, useState } from 'react'; | ||||||
|
|
||||||
| export default function LauncherView() { | ||||||
| const [query, setQuery] = useState(''); | ||||||
| const inputRef = useRef<HTMLInputElement>(null); | ||||||
|
|
||||||
| const handleSubmit = (e: React.FormEvent) => { | ||||||
| e.preventDefault(); | ||||||
| if (query.trim()) { | ||||||
| // Create a new chat window with the query | ||||||
| const workingDir = window.appConfig?.get('GOOSE_WORKING_DIR') as string; | ||||||
|
||||||
| const workingDir = window.appConfig?.get('GOOSE_WORKING_DIR') as string; | |
| const workingDir = (window.appConfig?.get('GOOSE_WORKING_DIR') ?? '') as string; |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Corrected capitalization of 'goose' to 'Goose' for consistency with product branding.
| placeholder="Ask goose anything..." | |
| placeholder="Ask Goose anything..." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@alexhancock, should we consider this suggestion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no - we want goose as a product name to always be lowercase
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,7 @@ import { | |||||||||||||||||||
| MenuItem, | ||||||||||||||||||||
| Notification, | ||||||||||||||||||||
| powerSaveBlocker, | ||||||||||||||||||||
| screen, | ||||||||||||||||||||
| session, | ||||||||||||||||||||
| shell, | ||||||||||||||||||||
| Tray, | ||||||||||||||||||||
|
|
@@ -483,11 +484,15 @@ let appConfig = { | |||||||||||||||||||
|
|
||||||||||||||||||||
| const windowMap = new Map<number, BrowserWindow>(); | ||||||||||||||||||||
| const goosedClients = new Map<number, Client>(); | ||||||||||||||||||||
| const windowPowerSaveBlockers = new Map<number, number>(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Track power save blockers per window | ||||||||||||||||||||
| const windowPowerSaveBlockers = new Map<number, number>(); // windowId -> blockerId | ||||||||||||||||||||
| // Track pending initial messages per window | ||||||||||||||||||||
| const pendingInitialMessages = new Map<number, string>(); // windowId -> initialMessage | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const createChat = async ( | ||||||||||||||||||||
| app: App, | ||||||||||||||||||||
| _query?: string, | ||||||||||||||||||||
| initialMessage?: string, | ||||||||||||||||||||
| dir?: string, | ||||||||||||||||||||
| _version?: string, | ||||||||||||||||||||
| resumeSessionId?: string, | ||||||||||||||||||||
|
|
@@ -676,7 +681,7 @@ const createChat = async ( | |||||||||||||||||||
| } | ||||||||||||||||||||
| if ( | ||||||||||||||||||||
| appPath === '/' && | ||||||||||||||||||||
| (recipe !== undefined || recipeDeeplink !== undefined || recipeId !== undefined) | ||||||||||||||||||||
| (recipe !== undefined || recipeDeeplink !== undefined || recipeId !== undefined || initialMessage) | ||||||||||||||||||||
| ) { | ||||||||||||||||||||
| appPath = '/pair'; | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
@@ -695,6 +700,11 @@ const createChat = async ( | |||||||||||||||||||
| log.info('Opening URL: ', formattedUrl); | ||||||||||||||||||||
| mainWindow.loadURL(formattedUrl); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // If we have an initial message, store it to send after React is ready | ||||||||||||||||||||
| if (initialMessage) { | ||||||||||||||||||||
| pendingInitialMessages.set(mainWindow.id, initialMessage); | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Set up local keyboard shortcuts that only work when the window is focused | ||||||||||||||||||||
| mainWindow.webContents.on('before-input-event', (event, input) => { | ||||||||||||||||||||
| if (input.key === 'r' && input.meta) { | ||||||||||||||||||||
|
|
@@ -731,6 +741,9 @@ const createChat = async ( | |||||||||||||||||||
| mainWindow.on('closed', () => { | ||||||||||||||||||||
| windowMap.delete(windowId); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Clean up pending initial message | ||||||||||||||||||||
| pendingInitialMessages.delete(windowId); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if (windowPowerSaveBlockers.has(windowId)) { | ||||||||||||||||||||
| const blockerId = windowPowerSaveBlockers.get(windowId)!; | ||||||||||||||||||||
| try { | ||||||||||||||||||||
|
|
@@ -754,6 +767,65 @@ const createChat = async ( | |||||||||||||||||||
| return mainWindow; | ||||||||||||||||||||
| }; | ||||||||||||||||||||
|
|
||||||||||||||||||||
| const createLauncher = () => { | ||||||||||||||||||||
| const launcherWindow = new BrowserWindow({ | ||||||||||||||||||||
| width: 600, | ||||||||||||||||||||
| height: 80, | ||||||||||||||||||||
| frame: false, | ||||||||||||||||||||
| transparent: process.platform === 'darwin', | ||||||||||||||||||||
| backgroundColor: process.platform === 'darwin' ? '#00000000' : '#ffffff', | ||||||||||||||||||||
| webPreferences: { | ||||||||||||||||||||
| preload: path.join(__dirname, 'preload.js'), | ||||||||||||||||||||
| nodeIntegration: false, | ||||||||||||||||||||
| contextIsolation: true, | ||||||||||||||||||||
| additionalArguments: [JSON.stringify(appConfig)], | ||||||||||||||||||||
| partition: 'persist:goose', | ||||||||||||||||||||
| }, | ||||||||||||||||||||
| skipTaskbar: true, | ||||||||||||||||||||
| alwaysOnTop: true, | ||||||||||||||||||||
| resizable: false, | ||||||||||||||||||||
| movable: true, | ||||||||||||||||||||
| minimizable: false, | ||||||||||||||||||||
| maximizable: false, | ||||||||||||||||||||
| fullscreenable: false, | ||||||||||||||||||||
| hasShadow: true, | ||||||||||||||||||||
| vibrancy: process.platform === 'darwin' ? 'window' : undefined, | ||||||||||||||||||||
| }); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| // Center on screen | ||||||||||||||||||||
| const primaryDisplay = screen.getPrimaryDisplay(); | ||||||||||||||||||||
| const { width, height } = primaryDisplay.workAreaSize; | ||||||||||||||||||||
| const windowBounds = launcherWindow.getBounds(); | ||||||||||||||||||||
|
|
||||||||||||||||||||
| launcherWindow.setPosition( | ||||||||||||||||||||
| Math.round(width / 2 - windowBounds.width / 2), | ||||||||||||||||||||
| Math.round(height / 3 - windowBounds.height / 2) | ||||||||||||||||||||
|
Comment on lines
+800
to
+802
|
||||||||||||||||||||
| launcherWindow.setPosition( | |
| Math.round(width / 2 - windowBounds.width / 2), | |
| Math.round(height / 3 - windowBounds.height / 2) | |
| // Position the launcher window at one-third of the screen height for optimal visibility and accessibility. | |
| const LAUNCHER_VERTICAL_POSITION_RATIO = 1 / 3; | |
| launcherWindow.setPosition( | |
| Math.round(width / 2 - windowBounds.width / 2), | |
| Math.round(height * LAUNCHER_VERTICAL_POSITION_RATIO - windowBounds.height / 2) |
Copilot
AI
Nov 6, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code retrieves window from event.sender but doesn't check if it's null before accessing window.id on line 1071. Although the optional chaining on line 1071 prevents an error, if window is null, the subsequent code on lines 1074-1078 should not execute. Consider adding an early return: if (!window) return; after line 1070.
| const windowId = window?.id; | |
| if (!window) return; | |
| const windowId = window.id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
initialMessageis now directly assigned fromrouteState.initialMessageinstead of being stored in state viauseState. This means changes torouteState.initialMessageafter the initial render won't be reflected. If the route state can change during the component's lifecycle, this could lead to stale data. Consider whether this behavior is intentional or if the originaluseStatewas necessary to preserve the value.