Polish setup guide integration layout (#5856)

This commit is contained in:
Brennan Benson
2026-06-22 15:15:03 -07:00
committed by GitHub
parent b98fd2ff8b
commit 20a51978e5
14 changed files with 194 additions and 149 deletions
@@ -1007,9 +1007,6 @@
"auto.components.feature.wall.ConnectIntegrationsList.33b650af52": {
"ko": "팀이 작업을 추적하는 서비스를 연결하세요. Orca는 이슈 제목, 링크, 컨텍스트가 이미 첨부된 워크스페이스를 시작합니다."
},
"auto.components.feature.wall.ConnectIntegrationsList.3a1fcdddad": {
"ko": "두 단계만 거치면 됩니다. 먼저 코드 리뷰 서비스를 연결한 다음, 팀이 작업을 계획하는 서비스를 연결하세요."
},
"auto.components.feature.wall.ConnectIntegrationsList.3dddb2d565": {
"ko": "작업용으로 연결됨"
},
@@ -10,8 +10,12 @@ import {
JiraIntegrationCard,
LinearIntegrationCard
} from '@/components/settings/task-tracker-integration-cards'
import {
IntegrationCardGroup,
IntegrationCardPresentationProvider
} from '@/components/settings/integration-card-presentation'
import { useIntegrationProviderStatusRefresh } from '@/components/settings/use-integration-provider-status-refresh'
import { IntegrationProgress, IntegrationStep } from './connect-integration-step'
import { IntegrationStep } from './connect-integration-step'
import {
deriveIntegrationFlowState,
useIntegrationConnectionStatus
@@ -91,100 +95,98 @@ export function ConnectIntegrationsList(): React.JSX.Element {
const taskExpanded = taskToggleCurrent ? taskToggle.expanded : reviewDone && !trackerDone
return (
<div className="space-y-2.5">
<div className="flex items-center justify-between gap-3">
<p className="text-[13px] leading-snug text-muted-foreground">
{translate(
'auto.components.feature.wall.ConnectIntegrationsList.3a1fcdddad',
'Two quick steps: connect where your code is reviewed, then where your team plans work.'
<IntegrationCardPresentationProvider value="setup-guide">
<div className="space-y-2.5">
<IntegrationStep
index={0}
state={flow.review}
expanded={reviewExpanded}
title={translate(
'auto.components.feature.wall.ConnectIntegrationsList.review_step_title',
'See PR status while agents work'
)}
</p>
<IntegrationProgress states={[flow.review, flow.task]} />
</div>
description={translate(
'auto.components.feature.wall.ConnectIntegrationsList.review_step_description',
'Connect a review provider so Orca can show PR or MR status, checks, and reviews.'
)}
summary={
<>
<span className="font-semibold text-foreground">{status.reviewProviderName}</span>{' '}
{translate(
'auto.components.feature.wall.ConnectIntegrationsList.5b3577a492',
'connected for review status'
)}
</>
}
onToggle={() => setReviewReopened((value) => !value)}
canToggle={reviewCanToggle}
>
<IntegrationCardGroup>
<GitHubIntegrationCard />
<GitLabIntegrationCard />
<BitbucketIntegrationCard />
<AzureDevOpsIntegrationCard />
<GiteaIntegrationCard />
</IntegrationCardGroup>
</IntegrationStep>
<IntegrationStep
index={0}
state={flow.review}
expanded={reviewExpanded}
title={translate(
'auto.components.feature.wall.ConnectIntegrationsList.review_step_title',
'See PR status while agents work'
)}
description={translate(
'auto.components.feature.wall.ConnectIntegrationsList.review_step_description',
'Connect a review provider so Orca can show PR or MR status, checks, and reviews.'
)}
summary={
<>
<span className="font-semibold text-foreground">{status.reviewProviderName}</span>{' '}
<IntegrationStep
index={1}
state={flow.task}
expanded={taskExpanded}
title={translate(
'auto.components.feature.wall.ConnectIntegrationsList.task_step_title',
'Start agents on your tasks without leaving Orca'
)}
description={translate(
'auto.components.feature.wall.ConnectIntegrationsList.33b650af52',
'Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.'
)}
summary={
status.trackerProviderName ? (
<>
<TaskSourceNameList names={status.taskSourceNames} />{' '}
{translate(
'auto.components.feature.wall.ConnectIntegrationsList.3dddb2d565',
'connected for tasks'
)}
</>
) : (
<>
<span className="font-semibold text-foreground">
{status.codeHostTaskProviderName}
</span>{' '}
{translate(
'auto.components.feature.wall.ConnectIntegrationsList.code_host_tasks_summary',
'issues available as tasks · add Linear or Jira if your team plans work there'
)}
</>
)
}
onToggle={() =>
setTaskToggle({
expanded: !taskExpanded,
whenTrackerDone: trackerDone,
whenReviewDone: reviewDone
})
}
>
<IntegrationCardGroup>
<LinearIntegrationCard />
<JiraIntegrationCard />
</IntegrationCardGroup>
<p className="px-1 pt-0.5 text-[12px] leading-snug text-muted-foreground">
{translate(
'auto.components.feature.wall.ConnectIntegrationsList.5b3577a492',
'connected for review status'
'auto.components.feature.wall.ConnectIntegrationsList.code_host_tasks_caption',
"Your code host's issues also work as tasks."
)}
</>
}
onToggle={() => setReviewReopened((value) => !value)}
canToggle={reviewCanToggle}
>
<GitHubIntegrationCard />
<GitLabIntegrationCard />
<BitbucketIntegrationCard />
<AzureDevOpsIntegrationCard />
<GiteaIntegrationCard />
</IntegrationStep>
<IntegrationStep
index={1}
state={flow.task}
expanded={taskExpanded}
title={translate(
'auto.components.feature.wall.ConnectIntegrationsList.task_step_title',
'Start agents on your tasks without leaving Orca'
)}
description={translate(
'auto.components.feature.wall.ConnectIntegrationsList.33b650af52',
'Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.'
)}
summary={
status.trackerProviderName ? (
<>
<TaskSourceNameList names={status.taskSourceNames} />{' '}
{translate(
'auto.components.feature.wall.ConnectIntegrationsList.3dddb2d565',
'connected for tasks'
)}
</>
) : (
<>
<span className="font-semibold text-foreground">
{status.codeHostTaskProviderName}
</span>{' '}
{translate(
'auto.components.feature.wall.ConnectIntegrationsList.code_host_tasks_summary',
'issues available as tasks · add Linear or Jira if your team plans work there'
)}
</>
)
}
onToggle={() =>
setTaskToggle({
expanded: !taskExpanded,
whenTrackerDone: trackerDone,
whenReviewDone: reviewDone
})
}
>
<LinearIntegrationCard />
<JiraIntegrationCard />
<p className="px-1 pt-1 text-[12px] leading-snug text-muted-foreground">
{translate(
'auto.components.feature.wall.ConnectIntegrationsList.code_host_tasks_caption',
"Your code host's issues also work as tasks."
)}
</p>
<GitHubIntegrationCard />
<GitLabIntegrationCard />
</IntegrationStep>
</div>
</p>
<IntegrationCardGroup>
<GitHubIntegrationCard />
<GitLabIntegrationCard />
</IntegrationCardGroup>
</IntegrationStep>
</div>
</IntegrationCardPresentationProvider>
)
}
@@ -279,19 +279,21 @@ export function FeatureWallSetupChecklist(
? 'gap-8 xl:grid-cols-[minmax(0,1fr)_auto] xl:gap-12'
: 'gap-8 sm:grid-cols-[minmax(0,48ch)_auto] sm:gap-10'
// Why: in the modal, a stacked checklist can leave medium-width windows with
// only a tiny action pane; switch to the rail/content layout sooner.
return (
<div
className={cn(
'grid h-full min-h-0',
isEmbedded
? 'grid-rows-[auto_minmax(0,1fr)] gap-10 lg:grid-cols-[minmax(15rem,17rem)_minmax(0,1fr)] lg:grid-rows-[minmax(0,1fr)] lg:gap-16'
: 'grid-rows-[auto_minmax(0,1fr)] gap-5 lg:grid-cols-[minmax(200px,300px)_minmax(0,1fr)] lg:grid-rows-[minmax(0,1fr)]'
: 'grid-rows-[auto_minmax(0,1fr)] gap-5 md:grid-cols-[minmax(190px,260px)_minmax(0,1fr)] md:grid-rows-[minmax(0,1fr)]'
)}
>
<div
className={cn(
'scrollbar-sleek min-h-0 space-y-5 overflow-y-auto',
isEmbedded ? 'pr-4' : 'pr-1'
isEmbedded ? 'pr-4' : 'max-h-[min(18rem,40vh)] pr-1 md:max-h-none'
)}
>
<SetupSection
@@ -325,7 +327,7 @@ export function FeatureWallSetupChecklist(
'scrollbar-sleek min-h-0 overflow-y-auto',
isEmbedded
? 'pt-10 lg:border-l lg:border-border/50 lg:pl-14 lg:pt-0'
: 'border-t border-border pt-5 lg:border-l lg:border-t-0 lg:pl-7 lg:pt-0'
: 'border-t border-border pt-5 md:border-l md:border-t-0 md:pl-7 md:pt-0'
)}
>
{activeStep ? (
@@ -92,28 +92,8 @@ export function IntegrationStep(props: {
) : null}
</button>
{expanded ? (
<div className="space-y-2 border-t border-border bg-muted/30 p-3">{props.children}</div>
<div className="space-y-2 border-t border-border bg-card p-3">{props.children}</div>
) : null}
</div>
)
}
// Two progress dots tracking step state; the active one stretches into a bar.
export function IntegrationProgress(props: {
states: readonly IntegrationStepState[]
}): React.JSX.Element {
return (
<div className="flex items-center gap-1.5 pt-2" aria-hidden>
{props.states.map((state, i) => (
<span
key={i}
className={cn(
'h-[7px] rounded-full transition-all',
state === 'active' ? 'w-[22px] bg-foreground' : 'w-[7px]',
state === 'done' ? 'bg-status-success' : state !== 'active' && 'bg-border'
)}
/>
))}
</div>
)
}
@@ -2,15 +2,15 @@ import { ExternalLink, Github, Gitlab, Terminal } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { useAppStore } from '@/store'
import { IntegrationCardDetails, IntegrationCardShell } from './integration-card-shell'
import {
useIntegrationCommandRowClass,
useIntegrationSubordinateRowClass
} from './integration-card-presentation'
import { getProviderAccountScope } from './provider-account-scope'
import { ProviderHostScopeControl } from './ProviderHostScopeControl'
import { usePreflightCardStatuses } from './source-control-preflight-card-status'
import { translate } from '@/i18n/i18n'
const INTEGRATION_COMMAND_ROW_CLASS =
'flex items-center gap-2 rounded-md border border-border/50 bg-muted/50 px-3 py-2 font-mono text-xs'
const INTEGRATION_SUBORDINATE_ROW_CLASS = 'rounded-md border border-border/50 bg-muted/50 px-3 py-2'
function ProviderAccountScopeDetails({
children
}: {
@@ -18,6 +18,7 @@ function ProviderAccountScopeDetails({
}): React.JSX.Element {
const settings = useAppStore((s) => s.settings)
const accountScope = getProviderAccountScope(settings)
const subordinateRowClass = useIntegrationSubordinateRowClass('text-xs')
return (
<IntegrationCardDetails>
@@ -27,7 +28,7 @@ function ProviderAccountScopeDetails({
'Account scope'
)}
scope={accountScope}
className={`text-xs ${INTEGRATION_SUBORDINATE_ROW_CLASS}`}
className={subordinateRowClass}
/>
{children}
</IntegrationCardDetails>
@@ -38,6 +39,7 @@ export function GitHubIntegrationCard(): React.JSX.Element {
const { statuses, unavailable, refresh } = usePreflightCardStatuses('gh')
const status = unavailable ? 'unavailable' : statuses.ghStatus
const connected = status === 'connected'
const commandRowClass = useIntegrationCommandRowClass()
return (
<IntegrationCardShell
@@ -126,7 +128,7 @@ export function GitHubIntegrationCard(): React.JSX.Element {
'The GitHub CLI is installed but not authenticated. Run this command in a terminal:'
)}
</p>
<div className={INTEGRATION_COMMAND_ROW_CLASS}>
<div className={commandRowClass}>
<Terminal className="size-3.5 shrink-0 text-muted-foreground" />
{translate(
'auto.components.settings.cli.source.control.integration.cards.8d90249d22',
@@ -166,6 +168,7 @@ export function GitLabIntegrationCard(): React.JSX.Element {
const { statuses, unavailable, refresh } = usePreflightCardStatuses('glab')
const status = unavailable ? 'unavailable' : statuses.glabStatus
const connected = status === 'connected'
const commandRowClass = useIntegrationCommandRowClass()
return (
<IntegrationCardShell
@@ -256,7 +259,7 @@ export function GitLabIntegrationCard(): React.JSX.Element {
'The GitLab CLI is installed but not authenticated. Run this command in a terminal:'
)}
</p>
<div className={INTEGRATION_COMMAND_ROW_CLASS}>
<div className={commandRowClass}>
<Terminal className="size-3.5 shrink-0 text-muted-foreground" />
{translate(
'auto.components.settings.cli.source.control.integration.cards.707180d09c',
@@ -0,0 +1,70 @@
import { createContext, useContext } from 'react'
import { cn } from '@/lib/utils'
type IntegrationCardPresentation = 'default' | 'setup-guide'
const IntegrationCardPresentationContext = createContext<IntegrationCardPresentation>('default')
export function IntegrationCardPresentationProvider(props: {
value: IntegrationCardPresentation
children: React.ReactNode
}): React.JSX.Element {
return (
<IntegrationCardPresentationContext.Provider value={props.value}>
{props.children}
</IntegrationCardPresentationContext.Provider>
)
}
export function useIntegrationCardPresentation(): IntegrationCardPresentation {
return useContext(IntegrationCardPresentationContext)
}
export function useIntegrationCardShellClass(className?: string): string {
const presentation = useIntegrationCardPresentation()
return cn(
presentation === 'setup-guide'
? 'bg-transparent px-4 py-3'
: 'rounded-xl border border-border bg-card px-4 py-3.5 shadow-xs',
className
)
}
export function IntegrationCardGroup(props: {
children: React.ReactNode
className?: string
}): React.JSX.Element {
const presentation = useIntegrationCardPresentation()
return (
<div
className={cn(
presentation === 'setup-guide'
? 'overflow-hidden rounded-lg border border-border/50 bg-card/30 divide-y divide-border/40'
: 'space-y-3',
props.className
)}
>
{props.children}
</div>
)
}
export function useIntegrationSubordinateRowClass(className?: string): string {
const presentation = useIntegrationCardPresentation()
return cn(
presentation === 'setup-guide'
? 'border-t border-border/40 px-0 py-2 first:border-t-0'
: 'rounded-md border border-border/50 bg-muted/50 px-3 py-2',
className
)
}
export function useIntegrationCommandRowClass(): string {
const presentation = useIntegrationCardPresentation()
return cn(
'flex items-center gap-2 font-mono text-xs',
presentation === 'setup-guide'
? 'border-t border-border/40 px-0 py-2'
: 'rounded-md border border-border/50 bg-muted/50 px-3 py-2'
)
}
@@ -1,5 +1,6 @@
import { LoaderCircle } from 'lucide-react'
import { cn } from '@/lib/utils'
import { useIntegrationCardShellClass } from './integration-card-presentation'
export type IntegrationCardStatusTone = 'connected' | 'attention' | 'neutral'
@@ -20,6 +21,7 @@ export function IntegrationCardShell(props: {
actions?: React.ReactNode
children?: React.ReactNode
}): React.JSX.Element {
const shellClass = useIntegrationCardShellClass(props.className)
const status = props.checking ? (
<LoaderCircle className="size-4 shrink-0 animate-spin text-muted-foreground" />
) : (
@@ -34,12 +36,7 @@ export function IntegrationCardShell(props: {
)
return (
<div
className={cn(
'rounded-xl border border-border bg-card px-4 py-3.5 shadow-xs',
props.className
)}
>
<div className={shellClass}>
<div className="flex flex-wrap items-start gap-3">
<span className="shrink-0 text-muted-foreground">{props.icon}</span>
<div className="min-w-0 flex-1 basis-[16rem] space-y-0.5">
@@ -10,12 +10,12 @@ import {
} from '@/lib/provider-runtime-context'
import { useAppStore } from '@/store'
import { IntegrationCardDetails, IntegrationCardShell } from './integration-card-shell'
import { useIntegrationSubordinateRowClass } from './integration-card-presentation'
import { getProviderAccountScope } from './provider-account-scope'
import { ProviderHostScopeControl } from './ProviderHostScopeControl'
import { translate } from '@/i18n/i18n'
type VerificationResult = { state: 'ok' | 'error'; error?: string }
const INTEGRATION_SUBORDINATE_ROW_CLASS = 'rounded-md border border-border/50 bg-muted/50 px-3 py-2'
export function JiraIntegrationCard(): React.JSX.Element {
const jiraStatus = useAppStore((s) => s.jiraStatus)
@@ -40,6 +40,8 @@ export function JiraIntegrationCard(): React.JSX.Element {
const credentialCopy = hasRemoteProviderRuntime(settings)
? 'Connect a Jira Cloud site with your Atlassian email and an API token. Credentials are sent to the selected remote runtime and stored there with runtime-supported encryption.'
: 'Connect a Jira Cloud site with your Atlassian email and an API token. Credentials are stored locally and encrypted when local runtime storage supports it.'
const subordinateRowClass = useIntegrationSubordinateRowClass('flex items-center gap-3')
const accountScopeRowClass = useIntegrationSubordinateRowClass('text-xs')
const handleDisconnect = async (siteId?: string): Promise<void> => {
await disconnectJira(siteId)
@@ -117,7 +119,7 @@ export function JiraIntegrationCard(): React.JSX.Element {
'Account scope'
)}
scope={accountScope}
className={`text-xs ${INTEGRATION_SUBORDINATE_ROW_CLASS}`}
className={accountScopeRowClass}
/>
{connected && sites.length > 0 ? (
<div className="space-y-2">
@@ -125,10 +127,7 @@ export function JiraIntegrationCard(): React.JSX.Element {
const testResult = testResultBySite[site.id]
const testing = testingSiteId === site.id
return (
<div
key={site.id}
className={`flex items-center gap-3 ${INTEGRATION_SUBORDINATE_ROW_CLASS}`}
>
<div key={site.id} className={subordinateRowClass}>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-foreground">
{site.displayName}
@@ -7,12 +7,12 @@ import { useMountedRef } from '@/hooks/useMountedRef'
import { getProviderRuntimeContextKey } from '@/lib/provider-runtime-context'
import { useAppStore } from '@/store'
import { IntegrationCardDetails, IntegrationCardShell } from './integration-card-shell'
import { useIntegrationSubordinateRowClass } from './integration-card-presentation'
import { getProviderAccountScope } from './provider-account-scope'
import { ProviderHostScopeControl } from './ProviderHostScopeControl'
import { translate } from '@/i18n/i18n'
type VerificationResult = { state: 'ok' | 'error'; error?: string }
const INTEGRATION_SUBORDINATE_ROW_CLASS = 'rounded-md border border-border/50 bg-muted/50 px-3 py-2'
export function LinearIntegrationCard(): React.JSX.Element {
const linearStatus = useAppStore((s) => s.linearStatus)
@@ -36,6 +36,7 @@ export function LinearIntegrationCard(): React.JSX.Element {
const connected = contextMatches && linearStatus.connected
const workspaces = linearStatus.workspaces ?? []
const accountScope = getProviderAccountScope(settings)
const subordinateRowClass = useIntegrationSubordinateRowClass('flex items-center gap-3')
const handleDisconnect = async (workspaceId?: string): Promise<void> => {
await (workspaceId ? disconnectLinearWorkspace(workspaceId) : disconnectLinear())
@@ -116,10 +117,7 @@ export function LinearIntegrationCard(): React.JSX.Element {
const testResult = testResultByWorkspace[workspace.id]
const testing = testingWorkspaceId === workspace.id
return (
<div
key={workspace.id}
className={`flex items-center gap-3 ${INTEGRATION_SUBORDINATE_ROW_CLASS}`}
>
<div key={workspace.id} className={subordinateRowClass}>
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-foreground">
{workspace.organizationName}
@@ -217,6 +215,8 @@ export function LinearIntegrationCard(): React.JSX.Element {
}
function ProviderAccountScopeRow({ scope }: { scope: ReturnType<typeof getProviderAccountScope> }) {
const subordinateRowClass = useIntegrationSubordinateRowClass('text-xs')
return (
<ProviderHostScopeControl
labelPrefix={translate(
@@ -224,7 +224,7 @@ function ProviderAccountScopeRow({ scope }: { scope: ReturnType<typeof getProvid
'Account scope'
)}
scope={scope}
className={`text-xs ${INTEGRATION_SUBORDINATE_ROW_CLASS}`}
className={subordinateRowClass}
/>
)
}
-1
View File
@@ -10146,7 +10146,6 @@
"3dddb2d565": "connected for tasks",
"33b650af52": "Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.",
"5b3577a492": "connected for review status",
"3a1fcdddad": "Two quick steps: connect where your code is reviewed, then where your team plans work.",
"list_end": ", and ",
"list_pair": " and ",
"list_mid": ", ",
-1
View File
@@ -10146,7 +10146,6 @@
"3dddb2d565": "connected for tasks",
"33b650af52": "Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.",
"5b3577a492": "connected for review status",
"3a1fcdddad": "Two quick steps: connect where your code is reviewed, then where your team plans work.",
"list_end": ", and ",
"list_pair": " and ",
"list_mid": ", ",
-1
View File
@@ -10146,7 +10146,6 @@
"3dddb2d565": "connected for tasks",
"33b650af52": "Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.",
"5b3577a492": "connected for review status",
"3a1fcdddad": "Two quick steps: connect where your code is reviewed, then where your team plans work.",
"list_end": ", and ",
"list_pair": " and ",
"list_mid": ", ",
-1
View File
@@ -10146,7 +10146,6 @@
"3dddb2d565": "작업용으로 연결됨",
"33b650af52": "팀이 작업을 추적하는 서비스를 연결하세요. Orca는 이슈 제목, 링크, 컨텍스트가 이미 첨부된 워크스페이스를 시작합니다.",
"5b3577a492": "리뷰 상태용으로 연결됨",
"3a1fcdddad": "두 단계만 거치면 됩니다. 먼저 코드 리뷰 서비스를 연결한 다음, 팀이 작업을 계획하는 서비스를 연결하세요.",
"list_end": ", 그리고 ",
"list_pair": " and ",
"list_mid": ", ",
-1
View File
@@ -10146,7 +10146,6 @@
"3dddb2d565": "connected for tasks",
"33b650af52": "Connect where your team tracks work. Orca starts workspaces with the issue title, link, and context already attached.",
"5b3577a492": "connected for review status",
"3a1fcdddad": "Two quick steps: connect where your code is reviewed, then where your team plans work.",
"list_end": ", and ",
"list_pair": " and ",
"list_mid": ", ",