diff --git a/src/renderer/src/components/feature-tips/CliFeatureTipVisual.tsx b/src/renderer/src/components/feature-tips/CliFeatureTipVisual.tsx new file mode 100644 index 000000000..55e1e3531 --- /dev/null +++ b/src/renderer/src/components/feature-tips/CliFeatureTipVisual.tsx @@ -0,0 +1,103 @@ +import { useEffect, useState, type JSX } from 'react' +import { AgentsOrchestrationVisual } from '@/components/feature-wall/AgentsOrchestrationVisual' +import { + ORCHESTRATION_CLI_COMMAND_LOOP_MS, + ORCHESTRATION_CLI_COMMAND_TIMINGS_MS +} from '@/components/feature-wall/agents-orchestration/orchestration-types' +import { usePrefersReducedMotion } from '@/components/feature-wall/feature-wall-modal-helpers' + +const CLI_AGENT_COMMANDS = [ + 'orca worktree create --name auth-pr-1', + 'orca worktree create --name auth-pr-2', + 'orca orchestration dispatch --task pr1 --to w1', + 'orca orchestration dispatch --task pr2 --to w2' +] + +export function CliFeatureTipVisual(): JSX.Element { + const reducedMotion = usePrefersReducedMotion() + const [animatedVisibleCommandCount, setAnimatedVisibleCommandCount] = useState(0) + // Why: reduced-motion users should see the static completed state without a + // post-render state repair; only the animated path needs timer-backed state. + const visibleCommandCount = reducedMotion + ? CLI_AGENT_COMMANDS.length + : animatedVisibleCommandCount + + useEffect(() => { + if (reducedMotion) { + return + } + + let cancelled = false + const timeouts: number[] = [] + const later = (fn: () => void, ms: number): void => { + timeouts.push(window.setTimeout(() => !cancelled && fn(), ms)) + } + + // Why: terminal lines mirror the orchestration tour beat timings so the + // shell shows each command as the parent agent runs it. + const runOnce = (): void => { + setAnimatedVisibleCommandCount(0) + ORCHESTRATION_CLI_COMMAND_TIMINGS_MS.forEach((ms, index) => { + later(() => setAnimatedVisibleCommandCount(index + 1), ms) + }) + later(runOnce, ORCHESTRATION_CLI_COMMAND_LOOP_MS) + } + + runOnce() + return () => { + cancelled = true + timeouts.forEach((id) => window.clearTimeout(id)) + } + }, [reducedMotion]) + + return ( +