diff --git a/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.add-project.test.tsx b/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.add-project.test.tsx
index 02639b9a238..3adbcbc692d 100644
--- a/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.add-project.test.tsx
+++ b/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.add-project.test.tsx
@@ -25,6 +25,7 @@ function cloudOnly(
picked: null,
folderProject: false,
needsGitHubPick: false,
+ remoteServerProject: false,
githubStep: null,
pick: () => {},
clear: () => {},
diff --git a/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.fixture.tsx b/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.fixture.tsx
index 89cd74fef6c..e54d3b3b8be 100644
--- a/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.fixture.tsx
+++ b/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.fixture.tsx
@@ -99,6 +99,7 @@ const handoffCloudStart = (githubHandoff: 'waiting' | 'timed-out'): CloudStartVi
picked: null,
folderProject: false,
needsGitHubPick: false,
+ remoteServerProject: false,
githubStep: null,
pick: () => {},
clear: () => {},
diff --git a/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.tsx b/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.tsx
index 948b0a9839d..551bfef169a 100644
--- a/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.tsx
+++ b/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.tsx
@@ -236,6 +236,8 @@ export function NewWorkspaceComposerProjectSection({
}
triggerClassName="h-9 w-full border-input text-sm focus-visible:border-focus-ring focus-visible:ring-1 focus-visible:ring-focus-ring"
invalid={Boolean(projectError) || Boolean(needsRepo)}
+ // Waiting for a GitHub repository is a fixable precondition: amber, not red.
+ invalidTone={!projectError && needsRepo ? 'warning' : 'error'}
describedBy={
!projectError && needsRepo ? ORCA_CLOUD_NEEDS_REPO_ID : projectDescriptionId
}
@@ -255,6 +257,8 @@ export function NewWorkspaceComposerProjectSection({
) : projectOptions.length === 0 ? (
diff --git a/src/renderer/src/components/new-workspace/ProjectCombobox.tsx b/src/renderer/src/components/new-workspace/ProjectCombobox.tsx
index 5927bfae436..52e4b34c265 100644
--- a/src/renderer/src/components/new-workspace/ProjectCombobox.tsx
+++ b/src/renderer/src/components/new-workspace/ProjectCombobox.tsx
@@ -23,7 +23,7 @@ import {
import { ProjectOptionMark, ProjectOptionRow } from './ProjectComboboxRow'
import { useRecentProjectIds } from './use-recent-project-ids'
import { isWithinComboboxRoot, useTypeAheadCombobox } from './use-type-ahead-combobox'
-import type { ProjectComboboxProps } from './project-combobox-props'
+import { projectFieldInvalidClass, type ProjectComboboxProps } from './project-combobox-props'
import { COMBOBOX_FIELD_SHELL, COMBOBOX_POPOVER_SURFACE } from './type-ahead-combobox-styles'
import {
cloudRepoKey,
@@ -48,6 +48,7 @@ export default function ProjectCombobox({
placeholder = 'Choose project',
triggerClassName,
invalid = false,
+ invalidTone = 'error',
describedBy,
cloudRepos = null,
cloudRepoValue = null,
@@ -218,9 +219,8 @@ export default function ProjectCombobox({
}}
className={cn(
COMBOBOX_FIELD_SHELL,
- // Invalid stays red while focused, so the field and its error read as one.
- invalid &&
- 'border-destructive ring-destructive/20 focus-within:border-destructive focus-within:ring-destructive/40 dark:ring-destructive/40',
+ // Invalid stays marked while focused, so the field and its line read as one.
+ invalid && projectFieldInvalidClass(invalidTone),
triggerClassName
)}
>
diff --git a/src/renderer/src/components/new-workspace/project-combobox-props.ts b/src/renderer/src/components/new-workspace/project-combobox-props.ts
index 72401f1d6fe..0b37d12577e 100644
--- a/src/renderer/src/components/new-workspace/project-combobox-props.ts
+++ b/src/renderer/src/components/new-workspace/project-combobox-props.ts
@@ -13,6 +13,11 @@ export type ProjectComboboxProps = {
placeholder?: string
triggerClassName?: string
invalid?: boolean
+ /**
+ * `warning`: a fixable precondition (Orca Cloud waiting for a GitHub repository), ringed amber
+ * rather than red; still aria-invalid for screen readers. Defaults to `error`.
+ */
+ invalidTone?: 'error' | 'warning'
describedBy?: string
/** D4: GitHub repositories with no clone here, listed while Orca Cloud is the Run on target. */
cloudRepos?: readonly OrcaCloudGitHubRepository[] | null
@@ -32,3 +37,10 @@ export type ProjectComboboxProps = {
*/
focusSignal?: number
}
+
+/** The field's invalid ring: red for an error, amber for a fixable precondition (glossary). */
+export function projectFieldInvalidClass(tone: 'error' | 'warning'): string {
+ return tone === 'warning'
+ ? 'border-status-warning ring-status-warning/20 focus-within:border-status-warning focus-within:ring-status-warning/40 dark:ring-status-warning/40'
+ : 'border-destructive ring-destructive/20 focus-within:border-destructive focus-within:ring-destructive/40 dark:ring-destructive/40'
+}
diff --git a/src/renderer/src/components/new-workspace/run-on/OrcaCloudNeedsRepoLine.tsx b/src/renderer/src/components/new-workspace/run-on/OrcaCloudNeedsRepoLine.tsx
index ccf006cba43..bf3859a60b3 100644
--- a/src/renderer/src/components/new-workspace/run-on/OrcaCloudNeedsRepoLine.tsx
+++ b/src/renderer/src/components/new-workspace/run-on/OrcaCloudNeedsRepoLine.tsx
@@ -10,12 +10,44 @@ import { ORCA_CLOUD_NEEDS_REPO_ID } from './cloud-start-view'
*/
export function OrcaCloudNeedsRepoLine({
hasRepos,
- onAddRepository
+ onAddRepository,
+ unreachable = false,
+ onMoreAccess
}: {
hasRepos: boolean
onAddRepository: () => void
+ /** UNREACHABLE-REPO-NO-ROUTE: the project's own GitHub repository is out of Orca Cloud's reach. */
+ unreachable?: boolean
+ /** Opens GitHub to give Orca access to more repositories ("Give Orca access"). */
+ onMoreAccess?: () => void
}): React.JSX.Element {
const t = useTranslate()
+ if (unreachable) {
+ return (
+
+
+ {t(
+ 'components.newWorkspace.cloudOnly.remoteServerUnreachable',
+ 'Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access to it, or pick another repository.'
+ )}
+
+ {onMoreAccess ? (
+
+
+
+ {t('worktreeCreation.cloneAccess.giveAccess', 'Give Orca access')}
+
+
+
+ ) : null}
+
+ )
+ }
return (
-
+
{t(
'components.newWorkspace.cloudOnly.needsRepository',
'Orca Cloud needs a GitHub repository.'
diff --git a/src/renderer/src/components/new-workspace/run-on/OrcaCloudNeedsRepoSubmit.fixture.tsx b/src/renderer/src/components/new-workspace/run-on/OrcaCloudNeedsRepoSubmit.fixture.tsx
index 7a6370ac1cb..8a947d9b74d 100644
--- a/src/renderer/src/components/new-workspace/run-on/OrcaCloudNeedsRepoSubmit.fixture.tsx
+++ b/src/renderer/src/components/new-workspace/run-on/OrcaCloudNeedsRepoSubmit.fixture.tsx
@@ -37,17 +37,40 @@ const orcaCloud: EphemeralVmRecipeOption = {
create: 'orca-cloud create'
}
const NEEDS_REPO = { hasRepos: true }
+// UNREACHABLE-REPO-NO-ROUTE: Hello-World cloned only on a paired server, out of Orca Cloud's reach.
+const UNREACHABLE = { hasRepos: true, unreachable: true }
-function Composer(): React.JSX.Element {
+function Composer({ unreachable = false }: { unreachable?: boolean }): React.JSX.Element {
const [project, setProject] = useState('folder')
const [created, setCreated] = useState(false)
- const needsRepo = project === 'folder' ? NEEDS_REPO : null
+ const needsRepo = project === 'folder' ? (unreachable ? UNREACHABLE : NEEDS_REPO) : null
const guard = useNeedsRepoSubmitGuard(needsRepo, () => setCreated(true))
const cloudStart = {
onSelectOrcaCloud: () => {},
onUserPick: () => {},
folderProject: project === 'folder',
- needsGitHubRepo: guard.shown
+ needsGitHubRepo: guard.shown,
+ ...(unreachable
+ ? {
+ cloudOnly: {
+ repos: [],
+ showMore: null,
+ retryRepos: null,
+ retryingRepos: false,
+ picked: null,
+ folderProject: false,
+ needsGitHubPick: true,
+ remoteServerProject: true,
+ githubStep: null,
+ pick: () => {},
+ clear: () => {},
+ moreAccess: () => {},
+ githubHandoff: 'idle' as const,
+ checkGitHubAgain: () => {},
+ start: async () => false
+ }
+ }
+ : {})
}
return (
@@ -99,7 +122,8 @@ function Composer(): React.JSX.Element {
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
const clickCreate = (frame: HTMLElement): void =>
[...frame.querySelectorAll('button')]
- .find((button) => button.textContent?.includes('Create workspace'))
+ // The footer's primary reads "Create" (its shortcut chip follows).
+ .find((button) => /^Create\b/.test(button.textContent?.trim() ?? ''))
?.click()
const base = {
@@ -131,3 +155,12 @@ export const fixed = defineFixture({
await wait(200)
}
})
+/** A remote-server project whose GitHub repository Orca Cloud can't reach, after Create. */
+export const unreachableAfterSubmit = defineFixture({
+ ...base,
+ render: () => ,
+ interact: async (frame) => {
+ clickCreate(frame)
+ await wait(200)
+ }
+})
diff --git a/src/renderer/src/components/new-workspace/run-on/cloud-start-view.ts b/src/renderer/src/components/new-workspace/run-on/cloud-start-view.ts
index f61a1ca8391..00135de3a03 100644
--- a/src/renderer/src/components/new-workspace/run-on/cloud-start-view.ts
+++ b/src/renderer/src/components/new-workspace/run-on/cloud-start-view.ts
@@ -16,7 +16,7 @@ export type CloudStartView = {
* Project. `hasRepos`: any to choose from. `attempt` counts those stopped Creates (focus moves to
* Project on each); absent until the first.
*/
- needsGitHubRepo?: { hasRepos: boolean; attempt?: number } | null
+ needsGitHubRepo?: { hasRepos: boolean; unreachable?: boolean; attempt?: number } | null
/** Bumped to open the Project picker at its GitHub repositories (CLONE-ACCESS-ERROR's Choose another). */
openRepoPickerSignal?: number
}
@@ -32,10 +32,19 @@ export function orcaCloudNeedsGitHubRepo(args: {
pickedRepo: boolean
repoCount: number
githubProjectCount: number
-}): { hasRepos: boolean } | null {
- return args.cloudSelected && args.folderProject && !args.pickedRepo
- ? { hasRepos: args.repoCount > 0 || args.githubProjectCount > 0 }
- : null
+ /**
+ * UNREACHABLE-REPO-NO-ROUTE: a GitHub project cloned only on a remote server whose repository
+ * Orca Cloud can't reach. The line says so and offers More access, instead of the folder wording.
+ */
+ remoteServerProject?: boolean
+}): { hasRepos: boolean; unreachable?: true } | null {
+ if (!args.cloudSelected || !args.folderProject || args.pickedRepo) {
+ return null
+ }
+ return {
+ hasRepos: args.repoCount > 0 || args.githubProjectCount > 0,
+ ...(args.remoteServerProject ? { unreachable: true as const } : {})
+ }
}
/** A project that can start in Orca Cloud: git, with a GitHub remote (or one not read yet). */
diff --git a/src/renderer/src/components/new-workspace/run-on/orca-cloud-unreachable-repo.test.tsx b/src/renderer/src/components/new-workspace/run-on/orca-cloud-unreachable-repo.test.tsx
new file mode 100644
index 00000000000..cf86f479f06
--- /dev/null
+++ b/src/renderer/src/components/new-workspace/run-on/orca-cloud-unreachable-repo.test.tsx
@@ -0,0 +1,55 @@
+// @vitest-environment happy-dom
+// UNREACHABLE-REPO-NO-ROUTE: Orca Cloud on a GitHub project cloned only on a remote server, whose
+// repository the team's GitHub access doesn't cover. Create waits, and the line under Project says
+// why (not the folder wording) with More access; the Project picker lists repositories to pick.
+import { cleanup, fireEvent, render, screen } from '@testing-library/react'
+import { afterEach, describe, expect, it, vi } from 'vitest'
+import { orcaCloudNeedsGitHubRepo } from './cloud-start-view'
+import { OrcaCloudNeedsRepoLine } from './OrcaCloudNeedsRepoLine'
+
+afterEach(cleanup)
+
+const base = {
+ cloudSelected: true,
+ folderProject: true,
+ pickedRepo: false,
+ repoCount: 2,
+ githubProjectCount: 1
+}
+
+describe('unreachable repository on a remote-server project', () => {
+ it('marks the wait as unreachable only for a remote-server project', () => {
+ expect(orcaCloudNeedsGitHubRepo({ ...base, remoteServerProject: true })).toEqual({
+ hasRepos: true,
+ unreachable: true
+ })
+ expect(orcaCloudNeedsGitHubRepo(base)).toEqual({ hasRepos: true })
+ // Reachable (auto-picked): nothing waits.
+ expect(
+ orcaCloudNeedsGitHubRepo({ ...base, remoteServerProject: true, pickedRepo: true })
+ ).toBeNull()
+ })
+
+ it('says Orca Cloud cannot reach the repository and offers Give Orca access, in a muted tone', () => {
+ const onMoreAccess = vi.fn()
+ render(
+ {}}
+ unreachable
+ onMoreAccess={onMoreAccess}
+ />
+ )
+ expect(screen.getByRole('alert').textContent).toContain('can’t reach this project’s')
+ expect(screen.queryByText('Orca Cloud needs a GitHub repository.')).toBeNull()
+ // A fixable access precondition, not an error: never red.
+ expect(screen.getByRole('alert').querySelector('p')?.className).not.toContain('destructive')
+ fireEvent.click(screen.getByRole('button', { name: 'Give Orca access' }))
+ expect(onMoreAccess).toHaveBeenCalledTimes(1)
+ })
+
+ it('a folder keeps its own wording', () => {
+ render( {}} />)
+ expect(screen.getByRole('alert').textContent).toBe('Orca Cloud needs a GitHub repository.')
+ })
+})
diff --git a/src/renderer/src/components/new-workspace/run-on/use-cloud-only-repo.ts b/src/renderer/src/components/new-workspace/run-on/use-cloud-only-repo.ts
index 7eac4f9db25..2ec95248dc5 100644
--- a/src/renderer/src/components/new-workspace/run-on/use-cloud-only-repo.ts
+++ b/src/renderer/src/components/new-workspace/run-on/use-cloud-only-repo.ts
@@ -34,6 +34,8 @@ export type CloudOnlyRepo = {
* cloned only on a remote server (RUNON-NO-NEW-SANDBOX). Create waits until one is picked.
*/
needsGitHubPick: boolean
+ /** A GitHub project cloned only on a remote server (RUNON-NO-NEW-SANDBOX). */
+ remoteServerProject: boolean
/** The GitHub row is done for a repository listed from the team's installations. */
githubStep: CloudSetupStep | null
pick: (fullName: string) => void
@@ -140,6 +142,7 @@ export function useCloudOnlyRepo({
githubStep: githubStep ?? folderStep ?? (remoteServer ? remoteServerStep() : null),
folderProject,
needsGitHubPick: folderProject || remoteServer,
+ remoteServerProject: remoteServer,
pick,
clear,
moreAccess,
@@ -159,8 +162,8 @@ function remoteServerStep(): CloudSetupStep {
label: translate('orcaCloud.setup.github.repositoryLabel', 'GitHub repository'),
status: translate('orcaCloud.setup.github.neededStatus', 'Needed'),
detail: translate(
- 'components.newWorkspace.cloudOnly.remoteServerDetail',
- 'Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access with More access, or pick another repository.'
+ 'components.newWorkspace.cloudOnly.remoteServerUnreachable',
+ 'Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access to it, or pick another repository.'
)
}
}
diff --git a/src/renderer/src/components/new-workspace/run-on/use-cloud-start-composer.ts b/src/renderer/src/components/new-workspace/run-on/use-cloud-start-composer.ts
index fb5f4953159..88c77fd57be 100644
--- a/src/renderer/src/components/new-workspace/run-on/use-cloud-start-composer.ts
+++ b/src/renderer/src/components/new-workspace/run-on/use-cloud-start-composer.ts
@@ -222,6 +222,7 @@ export function useCloudStartComposer({
needsGitHubRepo: orcaCloudNeedsGitHubRepo({
cloudSelected,
folderProject: cloudOnly.needsGitHubPick,
+ remoteServerProject: cloudOnly.remoteServerProject,
pickedRepo: cloudOnly.picked !== null,
repoCount: cloudOnly.repos?.length ?? 0,
githubProjectCount
diff --git a/src/renderer/src/i18n/en-runtime-required.json b/src/renderer/src/i18n/en-runtime-required.json
index e3243ae9c76..605ac027522 100644
--- a/src/renderer/src/i18n/en-runtime-required.json
+++ b/src/renderer/src/i18n/en-runtime-required.json
@@ -2731,13 +2731,6 @@
"sendingNotConfigured": "sending diagnostics is not configured for this build"
}
},
- "enterprise": {
- "audit": {
- "action": {
- "wsOwnerNotified": "Notified the previous owner about an owner change"
- }
- }
- },
"groups": {
"orcaCloud": "Orca Cloud"
},
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json
index d954b701df3..ccfbd3ce3bd 100644
--- a/src/renderer/src/i18n/locales/en.json
+++ b/src/renderer/src/i18n/locales/en.json
@@ -24287,7 +24287,7 @@
"theAgent": "The agent",
"showMore": "Show more repositories…",
"folderDetail": "Orca Cloud starts from a GitHub repository. This project is a folder on this computer.",
- "remoteServerDetail": "Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access with More access, or pick another repository.",
+ "remoteServerUnreachable": "Orca Cloud starts from a GitHub repository and can’t reach this project’s. Give Orca access to it, or pick another repository.",
"headingShort": "GitHub",
"badge": "Cloud only. Orca Cloud clones it, so no copy on this computer is needed.",
"rowPrivate": "{{repo}}, private, cloud only",