feat(code): add Option+Space quick entry widget#2307
Conversation
Adds a Claude-Desktop-style quick entry widget. Pressing Option+Space anywhere on macOS — even with PostHog Code minimized or in the background — pops a small floating bar at the bottom of the active display where the user can type a prompt and pick a recent folder. Submitting creates the task and routes the main window to the new task detail.
Implementation:
- New `QuickEntryService` orchestrates show/hide/submit and emits `FocusInput`/`Hide` events. Because services can't import `electron` directly, the BrowserWindow primitives live in `window.ts` (`createQuickEntryWindow`, `showQuickEntryWindow`, `hideQuickEntryWindow`, `destroyQuickEntryWindow`).
- New `quickEntry` tRPC router (`toggle/show/hide/openTaskInMain/getRecentRepos/onFocusInput/onHide`).
- New `OpenTask` event on `UIService` so the QE submit flow can tell the main renderer to navigate to a specific task.
- Frameless, transparent, always-on-top BrowserWindow (720×132) loaded from the same renderer bundle with hash `#quick-entry`. Renderer entry branches on `window.location.hash` to mount `QuickEntryRoot` instead of `App`.
- `QuickEntryView` reuses the existing `PromptInput` and calls `TaskService.createTask` with the user's last-used workspace mode, adapter, and model.
- `globalShortcut.register("Alt+Space")` wired in `index.ts` with cleanup on `will-quit`.
- Blur-to-hide with a 120ms grace period so dropdown popups don't dismiss the widget.
Out of scope / follow-ups: settings UI toggle, customizable hotkey, true double-tap-Option detection (would add `uiohook-napi` and require macOS Accessibility permission), Windows/Linux polish.
Generated-By: PostHog Code
Task-Id: 7ff94cd2-f189-4563-a913-a32e48eaae8d
Reshape the quick-entry view to mirror the real TaskInput: - Drop the orange Lightning icon and inline pill design. - Header row with FolderPicker inside ButtonGroup (same as TaskInput). - Full PromptInput below with the standard toolbar: UnifiedModelSelector, ReasoningLevelSelector, mode selector (via usePreviewConfig), AttachmentMenu, history-aware hints. - Placeholder matches the real one: "What do you want to ship? @ to add files, / for skills, ↑↓ for history". - Loads skills into useDraftStore so `/` for skills works. - Bump window to 680×260 to fit the taller layout. Generated-By: PostHog Code Task-Id: 7ff94cd2-f189-4563-a913-a32e48eaae8d
Remove the rounded/bordered wrapper around the quick entry contents so only the PromptInput's own card surface shows. Widen the BrowserWindow from 680 to 960 to fit the actual TaskInput-style layout. Generated-By: PostHog Code Task-Id: 7ff94cd2-f189-4563-a913-a32e48eaae8d
- Add BranchSelector next to FolderPicker, wired via useGitQueries. Cloud workspace mode is coerced to worktree from quick entry (no cloud repo picker here), so the selected branch is honored on submit. - Fix the main window not seeing the new task after submit: when the onOpenTask event fires with a taskId that isn't in the main window's React Query cache yet, store it as pendingOpenTaskId, invalidate the tasks query, and navigate when the task lands in taskById. - Clear the editor and error message when the quick entry window hides, so reopening starts fresh. - Shrink the window from 260 to 200 tall and switch the outer wrapper to justify-center so the content hugs the top instead of floating mid-window. Generated-By: PostHog Code Task-Id: 7ff94cd2-f189-4563-a913-a32e48eaae8d
…size - When onOpenTask fires in the main window, also invalidate workspace.getAll and folders.getFolders so the task-detail view sees the new workspace/folder created in the quick-entry window. Without this the task opened to the "Select a repository folder" empty state. - Shrink the quick entry window from 200 to 170 tall and switch the outer wrapper to items-start so the inner card hugs the top of the window instead of stretching, removing the empty space below. Generated-By: PostHog Code Task-Id: 7ff94cd2-f189-4563-a913-a32e48eaae8d
Previously the QE renderer ran the full TaskCreationSaga, which writes to renderer-local Zustand stores for session, draft, and folder caches. The main window then navigated to the new task with no local session record, so useSessionConnection bailed out (it doesn't auto-start sessions for brand-new tasks) and the task detail rendered with stale/empty state. New flow: - QE collects the form params (prompt XML, repo, branch, adapter, model, reasoning, executionMode) and calls a new trpc.quickEntry.requestCreateTask mutation. - QuickEntryService hides the QE window, focuses the main window, and emits a CreateTaskRequested event with the params. - The main window subscribes via trpc.quickEntry.onCreateTaskRequested and runs TaskService.createTask in its own renderer, with the onTaskReady callback navigating to the new task. All renderer-local state (session manager, folder cache, sidebar, navigation) is set up in the right window. Removed the now-unused UIService.OpenTask event, UIService.openTask(), uiRouter.onOpenTask, QuickEntryService.openTaskInMain() and the quickEntry.openTaskInMain mutation. Generated-By: PostHog Code Task-Id: 7ff94cd2-f189-4563-a913-a32e48eaae8d
The CreateTaskRequested handler in the main window did not refresh the tasks query, so the new task appeared in the detail view but not in the sidebar until something else triggered a refetch. Call invalidateTasks with the new task inside onTaskReady so it is added optimistically and then refetched. Generated-By: PostHog Code Task-Id: 7ff94cd2-f189-4563-a913-a32e48eaae8d
|
| trpcClient.skills.list.query().then((skills) => { | ||
| if (cancelled) return; | ||
| useDraftStore.getState().actions.setCommands( | ||
| SESSION_ID, | ||
| skills.map((s) => ({ | ||
| name: s.name, | ||
| description: s.description, | ||
| })), | ||
| ); | ||
| }); |
There was a problem hiding this comment.
Missing
.catch() on the skills query — if the call rejects, it silently swallows the error, leaving the command list unpopulated with no feedback.
| trpcClient.skills.list.query().then((skills) => { | |
| if (cancelled) return; | |
| useDraftStore.getState().actions.setCommands( | |
| SESSION_ID, | |
| skills.map((s) => ({ | |
| name: s.name, | |
| description: s.description, | |
| })), | |
| ); | |
| }); | |
| trpcClient.skills.list.query().then((skills) => { | |
| if (cancelled) return; | |
| useDraftStore.getState().actions.setCommands( | |
| SESSION_ID, | |
| skills.map((s) => ({ | |
| name: s.name, | |
| description: s.description, | |
| })), | |
| ); | |
| }).catch((err) => { | |
| log.warn("Failed to load skills for quick entry", { err }); | |
| }); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/quick-entry/QuickEntryView.tsx
Line: 95-104
Comment:
Missing `.catch()` on the skills query — if the call rejects, it silently swallows the error, leaving the command list unpopulated with no feedback.
```suggestion
trpcClient.skills.list.query().then((skills) => {
if (cancelled) return;
useDraftStore.getState().actions.setCommands(
SESSION_ID,
skills.map((s) => ({
name: s.name,
description: s.description,
})),
);
}).catch((err) => {
log.warn("Failed to load skills for quick entry", { err });
});
```
How can I resolve this? If you propose a fix, please make it concise.- Drop the `windowCreated` flag from `QuickEntryService` and call `createQuickEntryWindow` lazily from `show()`. `window.ts` already guards against double-creation; this lets the widget recover if the renderer crashes and the BrowserWindow's `closed` event nulls the module-level handle. Without this, every subsequent toggle silently fails because `createWindow()` was a no-op. - Type `handleCreateTaskFromQuickEntry` against the shared `CreateTaskRequest` schema instead of inline-redeclaring the shape, so schema changes can't silently diverge. - Add a `.catch()` to the `trpcClient.skills.list.query()` call in `QuickEntryView` so a rejected query logs a warning instead of being silently swallowed. Generated-By: PostHog Code Task-Id: 7ff94cd2-f189-4563-a913-a32e48eaae8d
|
this is awesome! I really suggest you add the toggle on/off in settings before pushing this lol |
Summary
Claude-Desktop-style quick entry: pressing Option+Space anywhere on macOS — even with PostHog Code minimized or in the background — pops a small floating bar at the bottom of the active display where the user can type a prompt and pick a recent folder. Submitting creates the task and routes the main window to the new task detail.
Screen.Recording.2026-05-22.at.17.09.04.mov
QuickEntryService+quickEntrytRPC router orchestrate show/hide/submit andgetRecentRepos.BrowserWindow(720×132) loaded from the same renderer bundle with hash#quick-entry; renderer entry branches to mountQuickEntryRoot.QuickEntryViewreuses the existingPromptInputand callsTaskService.createTaskwith the user's last-used workspace mode, adapter, and model.globalShortcut.register(\"Alt+Space\")wired inindex.tswith cleanup on `will-quit`.OpenTaskevent on `UIService` so the QE submit flow can tell the main renderer to navigate to a specific task.Architectural note: because main-process services can't import `electron` directly (biome `noRestrictedImports` rule), the BrowserWindow primitives live in `window.ts` and the service calls them.
Out of scope / follow-ups:
Test plan
Screenshots
The widget mimics the layout shown in the original screenshot from Claude Desktop: a horizontal pill at the bottom of the screen with prompt input + repo dropdown + send button.