| ... | @@ -131,6 +131,7 @@ import { | ... | @@ -131,6 +131,7 @@ import { |
| 131 | resolveSettledTimestamp, | 131 | resolveSettledTimestamp, |
| 132 | resolveSidebarThreadStatus, | 132 | resolveSidebarThreadStatus, |
| 133 | searchSidebarThreadsByTitle, | 133 | searchSidebarThreadsByTitle, |
| | 134 | shouldCreateNewThreadInCurrentProject, |
| 134 | resolveWorkingStartedAt, | 135 | resolveWorkingStartedAt, |
| 135 | sortLogicalProjectsForSidebar, | 136 | sortLogicalProjectsForSidebar, |
| 136 | sortPinnedThreadsForSidebar, | 137 | sortPinnedThreadsForSidebar, |
| ... | @@ -3164,29 +3165,39 @@ export default function Sidebar() { | ... | @@ -3164,29 +3165,39 @@ export default function Sidebar() { |
| 3164 | // falling back to the top project) — same resolution the command palette | 3165 | // falling back to the top project) — same resolution the command palette |
| 3165 | // uses. The command palette already offers a "New thread in..." submenu | 3166 | // uses. The command palette already offers a "New thread in..." submenu |
| 3166 | // for multi-project setups. | 3167 | // for multi-project setups. |
| 3167 | const handleNewThreadClick = useCallback(() => { | 3168 | const handleNewThreadClick = useCallback( |
| 3168 | // One project: nothing to pick, create immediately. | 3169 | (event?: ReactMouseEvent) => { |
| 3169 | if (projectGroups.length <= 1) { | 3170 | // One project: nothing to pick, create immediately. Shift+click creates |
| | 3171 | // directly in the current project even with several projects, skipping |
| | 3172 | // the palette picker. |
| | 3173 | if (shouldCreateNewThreadInCurrentProject(event?.shiftKey ?? false, projectGroups.length)) { |
| | 3174 | if (isMobile) setOpenMobile(false); |
| | 3175 | void startNewThreadFromContext({ |
| | 3176 | activeDraftThread: newThreadContext.activeDraftThread, |
| | 3177 | activeThread: newThreadContext.activeThread ?? undefined, |
| | 3178 | defaultProjectRef: newThreadContext.defaultProjectRef, |
| | 3179 | handleNewThread: newThreadContext.handleNewThread, |
| | 3180 | }); |
| | 3181 | return; |
| | 3182 | } |
| 3170 | if (isMobile) setOpenMobile(false); | 3183 | if (isMobile) setOpenMobile(false); |
| 3171 | void startNewThreadFromContext({ | 3184 | openCommandPalette({ open: "new-thread-in" }); |
| 3172 | activeDraftThread: newThreadContext.activeDraftThread, | 3185 | }, |
| 3173 | activeThread: newThreadContext.activeThread ?? undefined, | 3186 | [isMobile, newThreadContext, projectGroups.length, setOpenMobile], |
| 3174 | defaultProjectRef: newThreadContext.defaultProjectRef, | 3187 | ); |
| 3175 | handleNewThread: newThreadContext.handleNewThread, | | |
| 3176 | }); | | |
| 3177 | return; | | |
| 3178 | } | | |
| 3179 | if (isMobile) setOpenMobile(false); | | |
| 3180 | openCommandPalette({ open: "new-thread-in" }); | | |
| 3181 | }, [isMobile, newThreadContext, projectGroups.length, setOpenMobile]); | | |
| 3182 | | 3188 | |
| 3183 | // The button mirrors chat.new: in multi-project setups both route through | 3189 | // The button mirrors chat.new: in multi-project setups both route through |
| 3184 | // the command palette's "New thread in..." picker, and in single-project | 3190 | // the command palette's "New thread in..." picker, and in single-project |
| 3185 | // setups both create immediately. chat.newLocal always creates directly, so | 3191 | // setups both create immediately. In multi-project setups the label is only |
| 3186 | // it is only a correct label when chat.new is unbound. | 3192 | // the picker's shortcut: falling back to chat.newLocal would advertise the |
| | 3193 | // same shortcut for both the picker and direct create. In single-project |
| | 3194 | // setups both commands create directly, so chat.newLocal is a valid |
| | 3195 | // fallback. The second tooltip line (multi-project only) advertises |
| | 3196 | // shift+click and its keyboard twin chat.newLocal for direct create. |
| 3187 | const newThreadShortcutLabel = | 3197 | const newThreadShortcutLabel = |
| 3188 | shortcutLabelForCommand(keybindings, "chat.new") ?? | 3198 | shortcutLabelForCommand(keybindings, "chat.new") ?? |
| 3189 | shortcutLabelForCommand(keybindings, "chat.newLocal"); | 3199 | (projectGroups.length <= 1 ? shortcutLabelForCommand(keybindings, "chat.newLocal") : undefined); |
| | 3200 | const newThreadInProjectShortcutLabel = shortcutLabelForCommand(keybindings, "chat.newLocal"); |
| 3190 | return ( | 3201 | return ( |
| 3191 | <> | 3202 | <> |
| 3192 | <SidebarChromeHeader isElectron={isElectron} /> | 3203 | <SidebarChromeHeader isElectron={isElectron} /> |
| ... | @@ -3264,9 +3275,25 @@ export default function Sidebar() { | ... | @@ -3264,9 +3275,25 @@ export default function Sidebar() { |
| 3264 | /> | 3275 | /> |
| 3265 | </TooltipTrigger> | 3276 | </TooltipTrigger> |
| 3266 | <TooltipPopup side="right"> | 3277 | <TooltipPopup side="right"> |
| 3267 | {newThreadShortcutLabel | 3278 | {projectGroups.length > 1 ? ( |
| 3268 | ? `New thread (${newThreadShortcutLabel})` | 3279 | <span className="flex flex-col gap-0.5"> |
| 3269 | : "New thread"} | 3280 | <span> |
| | 3281 | {newThreadShortcutLabel |
| | 3282 | ? `New thread (${newThreadShortcutLabel})` |
| | 3283 | : "New thread"} |
| | 3284 | </span> |
| | 3285 | <span className="text-muted-foreground"> |
| | 3286 | New thread in current project: Shift+click |
| | 3287 | {newThreadInProjectShortcutLabel |
| | 3288 | ? ` (${newThreadInProjectShortcutLabel})` |
| | 3289 | : ""} |
| | 3290 | </span> |
| | 3291 | </span> |
| | 3292 | ) : newThreadShortcutLabel ? ( |
| | 3293 | `New thread (${newThreadShortcutLabel})` |
| | 3294 | ) : ( |
| | 3295 | "New thread" |
| | 3296 | )} |
| 3270 | </TooltipPopup> | 3297 | </TooltipPopup> |
| 3271 | </Tooltip> | 3298 | </Tooltip> |
| 3272 | </div> | 3299 | </div> |