Guide workspace creation for empty repositories (#1926)

* Guide empty repository workspace creation

* fix: address CI failures
This commit is contained in:
Jinjing 2026-05-15 17:14:02 -07:00 committed by GitHub
parent be76195159
commit 82f99ee6ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 116 additions and 41 deletions

View File

@ -28,6 +28,7 @@ import SparseCheckoutPresetSelect from '@/components/sparse/SparseCheckoutPreset
import SmartWorkspaceNameField, {
type SmartWorkspaceNameSelection
} from '@/components/new-workspace/SmartWorkspaceNameField'
import type { WorkspaceCreateErrorDisplay } from '@/lib/workspace-create-error-format'
const isMac = typeof navigator !== 'undefined' && navigator.userAgent.includes('Mac')
@ -65,7 +66,7 @@ type NewWorkspaceComposerCardProps = {
onSetupDecisionChange: (value: 'run' | 'skip') => void
shouldWaitForSetupCheck: boolean
resolvedSetupDecision: 'run' | 'skip' | null
createError: string | null
createError: WorkspaceCreateErrorDisplay | null
canUseSparseCheckout: boolean
sparsePresets: SparsePreset[]
sparseSelectedPresetId: string | null
@ -549,8 +550,19 @@ export default function NewWorkspaceComposerCard({
</div>
{createError ? (
<div className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
{createError}
<div
role="alert"
className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive"
>
{createError.help ? (
<div className="space-y-1">
<p className="font-medium">{createError.title}</p>
<p>{createError.message}</p>
<p className="text-destructive/85">{createError.help}</p>
</div>
) : (
createError.message
)}
</div>
) : null}

View File

@ -16,23 +16,10 @@ import { getRuntimeGitBranchDiff, getRuntimeGitDiff } from '@/runtime/runtime-gi
import '@/lib/monaco-setup'
import { Button } from '@/components/ui/button'
import type { OpenFile } from '@/store/slices/editor'
import type { GitBranchChangeEntry, GitDiffResult, GitStatusEntry } from '../../../../shared/types'
import type { GitBranchChangeEntry, GitDiffResult } from '../../../../shared/types'
import { DiffSectionItem } from './DiffSectionItem'
import { getCombinedUncommittedEntries } from './combined-diff-entries'
type DiffSection = {
key: string
path: string
status: string
area?: GitStatusEntry['area']
oldPath?: string
originalContent: string
modifiedContent: string
collapsed: boolean
loading: boolean
dirty: boolean
diffResult: GitDiffResult | null
}
import type { DiffSection } from './diff-section-types'
type CachedCombinedDiffViewState = {
entrySignature: string

View File

@ -23,25 +23,12 @@ import { DiffCommentPopover } from '../diff-comments/DiffCommentPopover'
import { getDiffCommentPopoverTop } from '../diff-comments/diff-comment-popover-position'
import { applyDiffEditorLineNumberOptions } from './diff-editor-line-number-options'
import { computeLineStats } from './diff-line-stats'
import type { DiffComment, GitDiffResult } from '../../../../shared/types'
import type { DiffSection } from './diff-section-types'
import type { DiffComment } from '../../../../shared/types'
import { isDiffComment } from '@/lib/diff-comment-compat'
const ImageDiffViewer = lazy(() => import('./ImageDiffViewer'))
type DiffSection = {
key: string
path: string
status: string
area?: 'staged' | 'unstaged' | 'untracked'
oldPath?: string
originalContent: string
modifiedContent: string
collapsed: boolean
loading: boolean
dirty: boolean
diffResult: GitDiffResult | null
}
export function DiffSectionItem({
section,
index,

View File

@ -0,0 +1,15 @@
import type { GitDiffResult, GitStatusEntry } from '../../../../shared/types'
export type DiffSection = {
key: string
path: string
status: string
area?: GitStatusEntry['area']
oldPath?: string
originalContent: string
modifiedContent: string
collapsed: boolean
loading: boolean
dirty: boolean
diffResult: GitDiffResult | null
}

View File

@ -61,6 +61,11 @@ import {
readRuntimeIssueCommand,
type HookCheckResult
} from '@/runtime/runtime-hooks-client'
import {
formatWorkspaceCreateError,
getWorkspaceCreateErrorToastMessage,
type WorkspaceCreateErrorDisplay
} from '@/lib/workspace-create-error-format'
export type UseComposerStateOptions = {
initialRepoId?: string
@ -167,7 +172,7 @@ export type ComposerCardProps = {
onSetupDecisionChange: (value: 'run' | 'skip') => void
shouldWaitForSetupCheck: boolean
resolvedSetupDecision: 'run' | 'skip' | null
createError: string | null
createError: WorkspaceCreateErrorDisplay | null
canUseSparseCheckout: boolean
/** Saved presets for the currently-selected repo. Empty array when no
* presets exist or when the repo is remote. */
@ -375,7 +380,7 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
const [hasLoadedIssueCommand, setHasLoadedIssueCommand] = useState(false)
const [setupDecision, setSetupDecision] = useState<'run' | 'skip' | null>(null)
const [creating, setCreating] = useState(false)
const [createError, setCreateError] = useState<string | null>(null)
const [createError, setCreateError] = useState<WorkspaceCreateErrorDisplay | null>(null)
const [advancedOpen, setAdvancedOpen] = useState(
persistDraft ? Boolean((newWorkspaceDraft?.note ?? '').trim()) : false
)
@ -1664,9 +1669,9 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
}
onCreated?.()
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create worktree.'
setCreateError(message)
toast.error(message)
const formattedError = formatWorkspaceCreateError(error)
setCreateError(formattedError)
toast.error(getWorkspaceCreateErrorToastMessage(formattedError))
} finally {
setCreating(false)
}
@ -1896,9 +1901,9 @@ export function useComposerState(options: UseComposerStateOptions): UseComposerS
}
onCreated?.()
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to create worktree.'
setCreateError(message)
toast.error(message)
const formattedError = formatWorkspaceCreateError(error)
setCreateError(formattedError)
toast.error(getWorkspaceCreateErrorToastMessage(formattedError))
} finally {
setCreating(false)
}

View File

@ -0,0 +1,41 @@
import { describe, expect, it } from 'vitest'
import {
formatWorkspaceCreateError,
getWorkspaceCreateErrorToastMessage
} from './workspace-create-error-format'
describe('formatWorkspaceCreateError', () => {
it('returns guidance for missing default base ref failures', () => {
const error = new Error(
'Could not resolve a default base ref for this repo. Pick a base branch explicitly and try again.'
)
const formatted = formatWorkspaceCreateError(error)
expect(formatted).toEqual({
title: 'No base branch found',
message: 'Orca could not resolve a usable base ref for this workspace.',
help: 'Create an initial commit (for example on main), or select an existing branch in Create From, then try again.'
})
expect(getWorkspaceCreateErrorToastMessage(formatted)).toBe('No base branch found')
})
it('matches missing base ref failures case-insensitively', () => {
const formatted = formatWorkspaceCreateError(
new Error('COULD NOT RESOLVE A DEFAULT BASE REF from remote provider')
)
expect(formatted.title).toBe('No base branch found')
expect(formatted.help).toBeDefined()
})
it('passes unknown errors through unchanged', () => {
const formatted = formatWorkspaceCreateError(new Error('fatal: not a git repository'))
expect(formatted).toEqual({
title: 'fatal: not a git repository',
message: 'fatal: not a git repository'
})
expect(getWorkspaceCreateErrorToastMessage(formatted)).toBe('fatal: not a git repository')
})
})

View File

@ -0,0 +1,28 @@
export type WorkspaceCreateErrorDisplay = {
title: string
message: string
help?: string
}
const MISSING_BASE_REF_ANCHOR = 'could not resolve a default base ref'
export function formatWorkspaceCreateError(error: unknown): WorkspaceCreateErrorDisplay {
const message = error instanceof Error ? error.message : 'Failed to create worktree.'
if (message.toLowerCase().includes(MISSING_BASE_REF_ANCHOR)) {
return {
title: 'No base branch found',
message: 'Orca could not resolve a usable base ref for this workspace.',
help: 'Create an initial commit (for example on main), or select an existing branch in Create From, then try again.'
}
}
return {
title: message,
message
}
}
export function getWorkspaceCreateErrorToastMessage(error: WorkspaceCreateErrorDisplay): string {
return error.help ? error.title : error.message
}