Files
orca/src/renderer/src/components/worktree-creation/VmBuildStepRow.tsx
T
Neil a8d6bb4698 fix(cloud): build sheet round 4: glossary cards, GitHub not connected + no GitHub remote before any sandbox, measured start hint from /v1/config, Orca Cloud host label
- Account/billing cards use glossary titles (Out of sandbox hours, Spend cap reached, Payment failed,
  running-sandbox limits), role-aware next steps, and 'Nothing was created, and you weren't charged.'
- 409 github_not_connected: 'GitHub isn't connected for <owner/repo>' + Connect GitHub (POST
  /v1/github/connect, opens the GitHub install page) + Try again.
- A repo without a GitHub remote is refused in main before the control plane is called, with
  Start empty / Push to GitHub first.
- GET /v1/config serves createTiming (p50/p95 of recent create-to-ready); the sheet's 'usually ~Ns'
  reads it and shows no hint without a measurement.
- Orca Cloud servers display as 'Orca Cloud' in sidebar host labels and the usage segment; legacy
  '<repo> VM <id>' names are renamed at startup.
2026-11-09 14:48:06 -08:00

92 lines
3.4 KiB
TypeScript

import React from 'react'
import { CircleSlash } from 'lucide-react'
import { cn } from '@/lib/utils'
import type { Translate } from '@/i18n/use-translate'
import { Progress } from '@/components/ui/progress'
import { StepIcon } from './WorktreeSetupSteps'
import type { VmBuildStep } from './vm-build-steps'
import { typicalStepSeconds, type VmBuildStepTiming } from './vm-build-timing'
import { formatElapsedTime } from './worktree-creation-clock'
import { stepLabel, stepStateLabel } from './vm-build-sheet-copy'
/** One timed row of the build checklist; the failure card, not the row, carries the red. */
export function VmBuildStepRow({
step,
cloud,
detail,
timing,
t
}: {
step: VmBuildStep
cloud: boolean
/** The step's measured duration, when there is one. */
timing: VmBuildStepTiming | null
/** What the running step is doing right now, in plain words. */
detail: string | null
t: Translate
}): React.JSX.Element {
const labelId = React.useId()
// A live percent already says how far along the step is.
const hint = step.state === 'running' && step.percent === null ? timing : null
const slow = hint !== null && step.elapsedMs !== null && step.elapsedMs > hint.p95Ms
return (
<li
className="flex flex-col gap-1 py-1.5"
aria-current={step.state === 'running' ? 'step' : undefined}
>
<div className="flex items-center gap-2.5">
{step.skipped ? (
<CircleSlash aria-hidden className="size-3.5 shrink-0 text-muted-foreground" />
) : (
<StepIcon state={step.state} />
)}
<span
id={labelId}
className={cn(
'min-w-0 flex-1 truncate transition-colors duration-200 motion-reduce:transition-none',
(step.state === 'pending' || step.skipped) && 'text-muted-foreground'
)}
>
{stepLabel(step, cloud, t)}
</span>
<span className="sr-only">
{step.skipped
? t('auto.components.worktree.creation.VmBuildSheet.stateSkipped', 'Skipped')
: stepStateLabel(step.state, t)}
</span>
{step.skipped ? (
<span aria-hidden className="text-xs text-muted-foreground">
{t('auto.components.worktree.creation.VmBuildSheet.stateSkipped', 'Skipped')}
</span>
) : null}
{hint ? (
<span className="text-xs text-muted-foreground">
{slow
? t('auto.components.worktree.creation.VmBuildSheet.slow', 'Taking longer than usual')
: t(
'auto.components.worktree.creation.VmBuildSheet.usually',
'usually ~{{seconds}}s',
{ seconds: typicalStepSeconds(hint) }
)}
</span>
) : null}
{step.percent !== null ? (
<span className="w-9 text-right text-xs tabular-nums text-muted-foreground">
{step.percent}%
</span>
) : null}
{step.elapsedMs !== null && step.state !== 'pending' ? (
<span className="w-12 text-right text-xs tabular-nums text-muted-foreground">
{formatElapsedTime(step.elapsedMs)}
</span>
) : null}
</div>
{detail ? <p className="ml-6 text-xs text-muted-foreground">{detail}</p> : null}
{step.percent !== null ? (
<Progress value={step.percent} aria-labelledby={labelId} className="ml-6 h-1 w-auto" />
) : null}
</li>
)
}