From f48bdf8f59f90114b41b32f2d7484efede59b078 Mon Sep 17 00:00:00 2001
From: Neil <4138956+nwparker@users.noreply.github.com>
Date: Mon, 17 Aug 2026 21:47:29 -0700
Subject: [PATCH] fix(new-workspace): keep workspace creation reachable with
zero projects (#15234)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The sidebar +, the landing Create button, the board lane +, the palette
create row, and the tour CTA all disabled themselves when repos was
empty — a dead end, since the composer's project field can add the first
project inline and auto-select it.
Drop the repo-count gate from every entry point. Submitting without a
project still shows the inline "Choose or add a project" error.
---
src/renderer/src/components/Landing.tsx | 12 +--
.../src/components/WorktreeJumpPalette.tsx | 8 +-
.../ContextualTourOverlay.tsx | 2 -
.../contextual-tour-step-actions.test.ts | 10 +-
.../contextual-tour-step-actions.ts | 15 ++-
.../components/sidebar/SidebarHeader.test.tsx | 93 +++++++++++++++++++
.../src/components/sidebar/SidebarHeader.tsx | 28 ++----
.../WorkspaceKanbanDrawer.search.test.tsx | 1 -
...paceKanbanDrawer.task-status-sync.test.tsx | 1 -
.../sidebar/WorkspaceKanbanDrawer.tsx | 3 +-
.../sidebar/WorkspaceKanbanLaneGrid.test.tsx | 1 -
.../sidebar/WorkspaceKanbanLaneGrid.tsx | 3 -
.../WorkspaceKanbanStatusLane.test.tsx | 1 -
.../sidebar/WorkspaceKanbanStatusLane.tsx | 8 +-
.../use-workspace-kanban-create-worktree.ts | 4 +-
.../worktree-palette-create-action.test.ts | 10 +-
.../src/lib/worktree-palette-create-action.ts | 9 +-
17 files changed, 124 insertions(+), 85 deletions(-)
create mode 100644 src/renderer/src/components/sidebar/SidebarHeader.test.tsx
diff --git a/src/renderer/src/components/Landing.tsx b/src/renderer/src/components/Landing.tsx
index 701863a1ef9..99ed596b98f 100644
--- a/src/renderer/src/components/Landing.tsx
+++ b/src/renderer/src/components/Landing.tsx
@@ -231,7 +231,7 @@ export default function Landing(): React.JSX.Element {
const createTargetLabel =
repos.length > 0 && repos.every((repo) => isGitRepoKind(repo)) ? 'Worktree' : 'Workspace'
- const canCreateWorktree = repos.length > 0
+ const hasProjects = repos.length > 0
const hasGitHubProject = useMemo(() => hasGitHubBackedProject(repos), [repos])
const showGitHubSupportFooter = repos.length === 0 || hasGitHubProject
@@ -274,7 +274,7 @@ export default function Landing(): React.JSX.Element {
{preflightIssues.length > 0 && }
- {canCreateWorktree
+ {hasProjects
? translate(
'auto.components.Landing.9c00bd4adf',
'Select a workspace from the sidebar to begin.'
@@ -292,13 +292,7 @@ export default function Landing(): React.JSX.Element {
openModal('new-workspace-composer', { telemetrySource: 'unknown' })}
>
diff --git a/src/renderer/src/components/WorktreeJumpPalette.tsx b/src/renderer/src/components/WorktreeJumpPalette.tsx
index c63fe2c921f..6263e53e289 100644
--- a/src/renderer/src/components/WorktreeJumpPalette.tsx
+++ b/src/renderer/src/components/WorktreeJumpPalette.tsx
@@ -848,7 +848,6 @@ function WorktreeJumpPaletteContent({
hostLabelOverrides
]
)
- const canCreateWorktree = repos.length > 0
// Why: host-less repos and worktrees inherit the focused runtime host, exactly
// as the sidebar's host headers do — otherwise the two disagree on bucketing.
@@ -1815,14 +1814,13 @@ function WorktreeJumpPaletteContent({
} = useMemo(
() =>
getWorktreePaletteCreateActionState({
- canCreateWorktree,
query: deferredQuery
}),
- [canCreateWorktree, deferredQuery]
+ [deferredQuery]
)
const createWorktreeName = taskSourceUrl ? query.trim() : deferredCreateWorktreeName
- // Why still gated: a task URL bypasses query deferral, not creation eligibility.
- const showCreateAction = deferredShowCreateAction || (taskSourceUrl !== null && canCreateWorktree)
+ // Why: a task URL bypasses query deferral, so it arms create on its own.
+ const showCreateAction = deferredShowCreateAction || taskSourceUrl !== null
// Why: arm the lookup before Enter can target the newly rendered Linear row.
useLayoutEffect(() => {
diff --git a/src/renderer/src/components/contextual-tours/ContextualTourOverlay.tsx b/src/renderer/src/components/contextual-tours/ContextualTourOverlay.tsx
index 8129a6f20d8..73ac37ad8d2 100644
--- a/src/renderer/src/components/contextual-tours/ContextualTourOverlay.tsx
+++ b/src/renderer/src/components/contextual-tours/ContextualTourOverlay.tsx
@@ -39,7 +39,6 @@ export function ContextualTourOverlay(): JSX.Element | null {
const keybindings = useAppStore((s) => s.keybindings)
const activeTabId = useAppStore((s) => s.activeTabId)
const sidebarOpen = useAppStore((s) => s.sidebarOpen)
- const canCreateWorkspace = useAppStore((s) => s.repos.length > 0)
const markContextualToursSeen = useAppStore((s) => s.markContextualToursSeen)
const advanceContextualTour = useAppStore((s) => s.advanceContextualTour)
const regressContextualTour = useAppStore((s) => s.regressContextualTour)
@@ -316,7 +315,6 @@ export function ContextualTourOverlay(): JSX.Element | null {
setSidebarOpen,
openTaskPage,
openModal,
- canCreateWorkspace,
openWorkspaceComposer: openWorkspaceCreationComposerWithTourHandoff,
dispatchTerminalPaneSplit: requestActiveTerminalPaneSplit,
schedule: (callback) => {
diff --git a/src/renderer/src/components/contextual-tours/contextual-tour-step-actions.test.ts b/src/renderer/src/components/contextual-tours/contextual-tour-step-actions.test.ts
index 4c18f50d3bb..ce350f21659 100644
--- a/src/renderer/src/components/contextual-tours/contextual-tour-step-actions.test.ts
+++ b/src/renderer/src/components/contextual-tours/contextual-tour-step-actions.test.ts
@@ -18,7 +18,6 @@ describe('performContextualTourStepAction', () => {
setSidebarOpen: vi.fn(),
openTaskPage,
openModal: vi.fn(),
- canCreateWorkspace: true,
openWorkspaceComposer: vi.fn(),
dispatchTerminalPaneSplit: vi.fn(),
schedule: vi.fn()
@@ -43,7 +42,6 @@ describe('performContextualTourStepAction', () => {
setSidebarOpen: vi.fn(),
openTaskPage: vi.fn(),
openModal: vi.fn(),
- canCreateWorkspace: true,
openWorkspaceComposer: vi.fn(),
dispatchTerminalPaneSplit,
schedule: vi.fn()
@@ -71,7 +69,6 @@ describe('performContextualTourStepAction', () => {
setSidebarOpen: vi.fn(),
openTaskPage: vi.fn(),
openModal: vi.fn(),
- canCreateWorkspace: true,
openWorkspaceComposer,
dispatchTerminalPaneSplit: vi.fn(),
schedule: vi.fn()
@@ -85,7 +82,7 @@ describe('performContextualTourStepAction', () => {
expect(finishTour).not.toHaveBeenCalled()
})
- it('does not open the workspace composer when workspace creation is unavailable', () => {
+ it('opens the workspace composer with no projects so the first one can be added there', () => {
const detachContextualTourSource = vi.fn()
const openWorkspaceComposer = vi.fn()
@@ -99,13 +96,12 @@ describe('performContextualTourStepAction', () => {
setSidebarOpen: vi.fn(),
openTaskPage: vi.fn(),
openModal: vi.fn(),
- canCreateWorkspace: false,
openWorkspaceComposer,
dispatchTerminalPaneSplit: vi.fn(),
schedule: vi.fn()
})
- expect(detachContextualTourSource).not.toHaveBeenCalled()
- expect(openWorkspaceComposer).not.toHaveBeenCalled()
+ expect(detachContextualTourSource).toHaveBeenCalledTimes(1)
+ expect(openWorkspaceComposer).toHaveBeenCalledTimes(1)
})
})
diff --git a/src/renderer/src/components/contextual-tours/contextual-tour-step-actions.ts b/src/renderer/src/components/contextual-tours/contextual-tour-step-actions.ts
index c68a797b8ed..d0961066968 100644
--- a/src/renderer/src/components/contextual-tours/contextual-tour-step-actions.ts
+++ b/src/renderer/src/components/contextual-tours/contextual-tour-step-actions.ts
@@ -11,7 +11,6 @@ export function performContextualTourStepAction(args: {
setSidebarOpen: (open: boolean) => void
openTaskPage: () => void
openModal: (modal: 'setup-guide', data?: Record) => void
- canCreateWorkspace: boolean
openWorkspaceComposer: () => void
dispatchTerminalPaneSplit: (detail: RequestActiveTerminalPaneSplitDetail) => void
schedule: (callback: () => void) => void
@@ -37,14 +36,12 @@ export function performContextualTourStepAction(args: {
}
return
case 'create-worktree':
- if (args.canCreateWorkspace) {
- // Why: opening the composer cancels this tour (it isn't allowed over the
- // modal) and hands off to the workspace-creation tour. Detach first so the
- // terminal source's unmount cleanup can't record a stray suppression.
- args.detachContextualTourSource()
- args.setSidebarOpen(true)
- args.openWorkspaceComposer()
- }
+ // Why: opening the composer cancels this tour (it isn't allowed over the
+ // modal) and hands off to the workspace-creation tour. Detach first so the
+ // terminal source's unmount cleanup can't record a stray suppression.
+ args.detachContextualTourSource()
+ args.setSidebarOpen(true)
+ args.openWorkspaceComposer()
return
case 'show-worktrees':
args.setSidebarOpen(true)
diff --git a/src/renderer/src/components/sidebar/SidebarHeader.test.tsx b/src/renderer/src/components/sidebar/SidebarHeader.test.tsx
new file mode 100644
index 00000000000..19af555be04
--- /dev/null
+++ b/src/renderer/src/components/sidebar/SidebarHeader.test.tsx
@@ -0,0 +1,93 @@
+// @vitest-environment happy-dom
+
+import React, { act } from 'react'
+import { createRoot, type Root } from 'react-dom/client'
+import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
+import SidebarHeader from './SidebarHeader'
+
+;(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true
+
+const mocks = vi.hoisted(() => ({
+ openWorkspaceCreationComposerWithTourHandoff: vi.fn()
+}))
+
+type MockState = {
+ repos: { id: string }[]
+ groupBy: string
+ openModal: (modal: string, data?: unknown) => void
+}
+
+let mockState: MockState
+
+vi.mock('@/store', () => ({
+ useAppStore: (selector: (state: MockState) => unknown) => selector(mockState)
+}))
+
+vi.mock('./SidebarWorkspaceOptionsMenu', () => ({ default: () => null }))
+
+vi.mock('@/hooks/useShortcutLabel', () => ({ useShortcutLabel: () => '⌘N' }))
+
+vi.mock('@/components/ui/tooltip', () => ({
+ Tooltip: ({ children }: { children: React.ReactNode }) => <>{children}>,
+ TooltipTrigger: ({ children }: { children: React.ReactNode }) => <>{children}>,
+ TooltipContent: ({ children }: { children: React.ReactNode }) => <>{children}>
+}))
+
+vi.mock('../contextual-tours/workspace-creation-tour-handoff', () => ({
+ openWorkspaceCreationComposerWithTourHandoff: mocks.openWorkspaceCreationComposerWithTourHandoff
+}))
+
+let container: HTMLDivElement
+let root: Root
+
+function newWorkspaceButton(): HTMLButtonElement {
+ const button = container.querySelector('[aria-label="New workspace"]')
+ if (!button) {
+ throw new Error('New workspace button not rendered')
+ }
+ return button
+}
+
+beforeEach(() => {
+ mocks.openWorkspaceCreationComposerWithTourHandoff.mockClear()
+ mockState = { repos: [], groupBy: 'repo', openModal: vi.fn() }
+ container = document.createElement('div')
+ document.body.append(container)
+ root = createRoot(container)
+})
+
+afterEach(() => {
+ act(() => root.unmount())
+ container.remove()
+})
+
+describe('SidebarHeader', () => {
+ it('keeps New workspace clickable with zero projects, since the composer adds the first one', () => {
+ act(() => {
+ root.render( )
+ })
+
+ const button = newWorkspaceButton()
+ expect(button.disabled).toBe(false)
+
+ act(() => {
+ button.click()
+ })
+
+ expect(mocks.openWorkspaceCreationComposerWithTourHandoff).toHaveBeenCalledTimes(1)
+ })
+
+ it('opens the composer the same way once projects exist', () => {
+ mockState.repos = [{ id: 'repo-a' }]
+ act(() => {
+ root.render( )
+ })
+
+ act(() => {
+ newWorkspaceButton().click()
+ })
+
+ expect(newWorkspaceButton().disabled).toBe(false)
+ expect(mocks.openWorkspaceCreationComposerWithTourHandoff).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/src/renderer/src/components/sidebar/SidebarHeader.tsx b/src/renderer/src/components/sidebar/SidebarHeader.tsx
index f456fc25f0f..b31eb5d378b 100644
--- a/src/renderer/src/components/sidebar/SidebarHeader.tsx
+++ b/src/renderer/src/components/sidebar/SidebarHeader.tsx
@@ -18,7 +18,6 @@ const SidebarHeader = React.memo(function SidebarHeader({
const openModal = useAppStore((s) => s.openModal)
const newWorktreeShortcutLabel = useShortcutLabel('workspace.create')
const groupBy = useAppStore((s) => s.groupBy)
- const canCreateWorkspace = useAppStore((s) => s.repos.length > 0)
const sidebarTitle = groupBy === 'repo' ? 'Projects' : 'Workspaces'
return (
@@ -62,35 +61,24 @@ const SidebarHeader = React.memo(function SidebarHeader({
{
- if (!canCreateWorkspace) {
- return
- }
- // Why: the parallel-work tour must click the real sidebar
- // control so it can hand off to the workspace-creation tour.
- openWorkspaceCreationComposerWithTourHandoff()
- }}
+ // Why: the parallel-work tour must click the real sidebar
+ // control so it can hand off to the workspace-creation tour.
+ onClick={openWorkspaceCreationComposerWithTourHandoff}
aria-label={translate(
'auto.components.sidebar.SidebarHeader.92154beb7e',
'New workspace'
)}
- disabled={!canCreateWorkspace}
data-contextual-tour-target="workspace-create-control"
>
- {canCreateWorkspace
- ? translate(
- 'auto.components.sidebar.SidebarHeader.ca6f729da2',
- 'New workspace ({{value0}})',
- { value0: newWorktreeShortcutLabel }
- )
- : translate(
- 'auto.components.sidebar.SidebarHeader.5c9c7c16aa',
- 'Add a project to create workspaces'
- )}
+ {translate(
+ 'auto.components.sidebar.SidebarHeader.ca6f729da2',
+ 'New workspace ({{value0}})',
+ { value0: newWorktreeShortcutLabel }
+ )}
diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.search.test.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.search.test.tsx
index 50052ce6fde..7cb3081474e 100644
--- a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.search.test.tsx
+++ b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.search.test.tsx
@@ -130,7 +130,6 @@ vi.mock('./use-workspace-kanban-column-resize', () => ({
vi.mock('./use-workspace-kanban-create-worktree', () => ({
useWorkspaceKanbanCreateWorktree: () => ({
- canCreateWorktree: true,
createWorktreeForStatus: vi.fn()
})
}))
diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.task-status-sync.test.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.task-status-sync.test.tsx
index cf56ebe275f..8f3b86dd119 100644
--- a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.task-status-sync.test.tsx
+++ b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.task-status-sync.test.tsx
@@ -138,7 +138,6 @@ vi.mock('./use-workspace-kanban-column-resize', () => ({
vi.mock('./use-workspace-kanban-create-worktree', () => ({
useWorkspaceKanbanCreateWorktree: () => ({
- canCreateWorktree: true,
createWorktreeForStatus: vi.fn()
})
}))
diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx
index 0ddf23f0ed7..eb9614f28d8 100644
--- a/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx
+++ b/src/renderer/src/components/sidebar/WorkspaceKanbanDrawer.tsx
@@ -203,7 +203,7 @@ function WorkspaceKanbanDrawerContent({
const activeWorktreeIdentity = activeWorktreeId
? composeWorktreeHostIdentity(activeWorkspaceExecutionHostId ?? undefined, activeWorktreeId)
: null
- const { canCreateWorktree, createWorktreeForStatus } = useWorkspaceKanbanCreateWorktree()
+ const { createWorktreeForStatus } = useWorkspaceKanbanCreateWorktree()
const visibleWorktreeIdSet = useVisibleWorkspaceKanbanWorktreeIds({
allWorktrees,
repoMap
@@ -944,7 +944,6 @@ function WorkspaceKanbanDrawerContent({
columnWidth={columnWidth}
isResizingColumn={isResizingColumn}
dragOverStatus={dragOverStatus}
- canCreateWorktree={canCreateWorktree}
renderCards={renderCards}
selectedWorktreeIds={selectedWorktreeIds}
selectedWorktrees={renderedSelectedWorktrees}
diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanLaneGrid.test.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanLaneGrid.test.tsx
index 8e613d2187b..b6d59e3284d 100644
--- a/src/renderer/src/components/sidebar/WorkspaceKanbanLaneGrid.test.tsx
+++ b/src/renderer/src/components/sidebar/WorkspaceKanbanLaneGrid.test.tsx
@@ -108,7 +108,6 @@ function makeGrid(activeWorktreeIdentity: string | null = null): React.JSX.Eleme
columnWidth={308}
isResizingColumn={false}
dragOverStatus={null}
- canCreateWorktree={true}
renderCards={true}
selectedWorktreeIds={new Set()}
selectedWorktrees={[]}
diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanLaneGrid.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanLaneGrid.tsx
index 2e4a1754657..63a82779a60 100644
--- a/src/renderer/src/components/sidebar/WorkspaceKanbanLaneGrid.tsx
+++ b/src/renderer/src/components/sidebar/WorkspaceKanbanLaneGrid.tsx
@@ -35,7 +35,6 @@ type WorkspaceKanbanLaneGridProps = {
columnWidth: number
isResizingColumn: boolean
dragOverStatus: WorkspaceStatus | null
- canCreateWorktree: boolean
renderCards: boolean
selectedWorktreeIds: ReadonlySet
selectedWorktrees: readonly Worktree[]
@@ -65,7 +64,6 @@ export default function WorkspaceKanbanLaneGrid({
columnWidth,
isResizingColumn,
dragOverStatus,
- canCreateWorktree,
renderCards,
selectedWorktreeIds,
selectedWorktrees,
@@ -210,7 +208,6 @@ export default function WorkspaceKanbanLaneGrid({
columnWidth={columnWidth}
isResizingColumn={isResizingColumn}
isDragTarget={dragOverStatus === status.id}
- canCreateWorktree={canCreateWorktree}
renderCards={renderCards && renderedLaneIds.has(status.id)}
selectedWorktreeIds={selectedWorktreeIds}
selectedWorktrees={selectedWorktrees}
diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.test.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.test.tsx
index 884313aa7c4..4545f0a66ab 100644
--- a/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.test.tsx
+++ b/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.test.tsx
@@ -62,7 +62,6 @@ function renderLane(props: {
columnWidth={308}
isResizingColumn={false}
isDragTarget={false}
- canCreateWorktree={true}
renderCards={true}
selectedWorktreeIds={new Set()}
selectedWorktrees={[]}
diff --git a/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.tsx b/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.tsx
index d82ab75014c..15f6ae41715 100644
--- a/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.tsx
+++ b/src/renderer/src/components/sidebar/WorkspaceKanbanStatusLane.tsx
@@ -30,7 +30,6 @@ type WorkspaceKanbanStatusLaneProps = {
columnWidth: number
isResizingColumn: boolean
isDragTarget: boolean
- canCreateWorktree: boolean
nativeDragEnabled?: boolean
renderCards: boolean
selectedWorktreeIds: ReadonlySet
@@ -61,7 +60,6 @@ function WorkspaceKanbanStatusLane({
columnWidth,
isResizingColumn,
isDragTarget,
- canCreateWorktree,
nativeDragEnabled = true,
renderCards,
selectedWorktreeIds,
@@ -94,9 +92,7 @@ function WorkspaceKanbanStatusLane({
undefined
)
}, [fullWorktreeIds, hasQuery, items])
- const createTooltip = canCreateWorktree
- ? `New workspace in ${status.label}`
- : 'Add a project to create workspaces'
+ const createTooltip = `New workspace in ${status.label}`
const createButton = (
onCreateWorktree(status.id)}
>
@@ -224,7 +219,6 @@ function WorkspaceKanbanStatusLane({
'group-hover/lane:opacity-100 group-focus-within/lane:opacity-100'
)}
aria-label={createTooltip}
- disabled={!canCreateWorktree}
onClick={() => onCreateWorktree(status.id)}
>
diff --git a/src/renderer/src/components/sidebar/use-workspace-kanban-create-worktree.ts b/src/renderer/src/components/sidebar/use-workspace-kanban-create-worktree.ts
index bd218580cb9..32fc3d98fd7 100644
--- a/src/renderer/src/components/sidebar/use-workspace-kanban-create-worktree.ts
+++ b/src/renderer/src/components/sidebar/use-workspace-kanban-create-worktree.ts
@@ -3,11 +3,9 @@ import { useAppStore } from '@/store'
import type { WorkspaceStatus } from '../../../../shared/worktree/types'
export function useWorkspaceKanbanCreateWorktree(): {
- canCreateWorktree: boolean
createWorktreeForStatus: (workspaceStatus: WorkspaceStatus) => void
} {
const openModal = useAppStore((s) => s.openModal)
- const canCreateWorktree = useAppStore((s) => s.repos.length > 0)
const createWorktreeForStatus = useCallback(
(workspaceStatus: WorkspaceStatus) => {
@@ -19,5 +17,5 @@ export function useWorkspaceKanbanCreateWorktree(): {
[openModal]
)
- return { canCreateWorktree, createWorktreeForStatus }
+ return { createWorktreeForStatus }
}
diff --git a/src/renderer/src/lib/worktree-palette-create-action.test.ts b/src/renderer/src/lib/worktree-palette-create-action.test.ts
index 2cddebf6bee..60f23867e46 100644
--- a/src/renderer/src/lib/worktree-palette-create-action.test.ts
+++ b/src/renderer/src/lib/worktree-palette-create-action.test.ts
@@ -13,7 +13,6 @@ import { WORKTREE_PALETTE_QUERY_MAX_BYTES } from './worktree-palette-query-bound
describe('worktree-palette-create-action', () => {
it('shows create for typed queries with workspace matches but selects the first workspace row', () => {
const state = getWorktreePaletteCreateActionState({
- canCreateWorktree: true,
query: 'feature'
})
@@ -33,7 +32,6 @@ describe('worktree-palette-create-action', () => {
it('skips create for free text even when it is listed before every other row', () => {
const state = getWorktreePaletteCreateActionState({
- canCreateWorktree: true,
query: 'opencode-issue'
})
@@ -77,7 +75,6 @@ describe('worktree-palette-create-action', () => {
it('leaves Enter unarmed for typed queries with no real matches', () => {
const state = getWorktreePaletteCreateActionState({
- canCreateWorktree: true,
query: 'new-workspace'
})
@@ -156,19 +153,17 @@ describe('worktree-palette-create-action', () => {
).toBe(CREATE_WORKTREE_ITEM_ID)
})
- it('hides create when no project is available to create a workspace in', () => {
+ it('offers create with no projects, since the composer adds the first one inline', () => {
expect(
getWorktreePaletteCreateActionState({
- canCreateWorktree: false,
query: 'new-workspace'
})
- ).toEqual({ createWorktreeName: 'new-workspace', showCreateAction: false })
+ ).toEqual({ createWorktreeName: 'new-workspace', showCreateAction: true })
})
it('hides create for an empty query', () => {
expect(
getWorktreePaletteCreateActionState({
- canCreateWorktree: true,
query: ' '
}).showCreateAction
).toBe(false)
@@ -179,7 +174,6 @@ describe('worktree-palette-create-action', () => {
expect(
getWorktreePaletteCreateActionState({
- canCreateWorktree: true,
query: oversizedQuery
})
).toEqual({
diff --git a/src/renderer/src/lib/worktree-palette-create-action.ts b/src/renderer/src/lib/worktree-palette-create-action.ts
index ae58a248d8f..6b6afbc0256 100644
--- a/src/renderer/src/lib/worktree-palette-create-action.ts
+++ b/src/renderer/src/lib/worktree-palette-create-action.ts
@@ -8,10 +8,8 @@ export type WorktreePaletteCreateActionState = {
}
export function getWorktreePaletteCreateActionState({
- canCreateWorktree,
query
}: {
- canCreateWorktree: boolean
query: string
}): WorktreePaletteCreateActionState {
const createWorktreeName = query.trim()
@@ -21,12 +19,11 @@ export function getWorktreePaletteCreateActionState({
showCreateAction: false
}
}
- // Why gate on eligibility: creation must not be offered — or reachable by Enter —
- // when there is no repo to create a workspace in.
- const showCreateAction = canCreateWorktree && createWorktreeName.length > 0
+ // Why no project gate: the composer can add the first project inline, so
+ // creation stays offered with zero projects.
return {
createWorktreeName,
- showCreateAction
+ showCreateAction: createWorktreeName.length > 0
}
}