import figures from 'figures'; import * as React from 'react'; import { useState } from 'react'; import type { Root } from '@anthropic/ink'; import { Box, Text, useAnimationFrame } from '@anthropic/ink'; import { AppStateProvider } from '../state/AppState.js'; import { checkOutTeleportedSessionBranch, processMessagesForTeleportResume, type TeleportProgressStep, type TeleportResult, teleportResumeCodeSession, } from '../utils/teleport.js'; type Props = { currentStep: TeleportProgressStep; sessionId?: string; }; const SPINNER_FRAMES = ['◐', '◓', '◑', '◒']; const STEPS: { key: TeleportProgressStep; label: string }[] = [ { key: 'validating', label: 'Validating session' }, { key: 'fetching_logs', label: 'Fetching session logs' }, { key: 'fetching_branch', label: 'Getting branch info' }, { key: 'checking_out', label: 'Checking out branch' }, ]; export function TeleportProgress({ currentStep, sessionId }: Props): React.ReactNode { const [ref, time] = useAnimationFrame(100); const frame = Math.floor(time / 100) % SPINNER_FRAMES.length; const currentStepIndex = STEPS.findIndex(s => s.key === currentStep); return ( {SPINNER_FRAMES[frame]} Teleporting session… {sessionId && ( {sessionId} )} {STEPS.map((step, index) => { const isComplete = index < currentStepIndex; const isCurrent = index === currentStepIndex; const isPending = index > currentStepIndex; let icon: string; let color: string | undefined; if (isComplete) { icon = figures.tick; color = 'green'; } else if (isCurrent) { icon = SPINNER_FRAMES[frame]!; color = 'claude'; } else { icon = figures.circle; color = undefined; } return ( {icon} {step.label} ); })} ); } /** * Teleports to a remote session with progress UI rendered into the existing root. * Fetches the session, checks out the branch, and returns the result. */ export async function teleportWithProgress(root: Root, sessionId: string): Promise { // Capture the setState function from the rendered component let setStep: (step: TeleportProgressStep) => void = () => {}; function TeleportProgressWrapper(): React.ReactNode { const [step, _setStep] = useState('validating'); setStep = _setStep; return ; } root.render( , ); const result = await teleportResumeCodeSession(sessionId, setStep); setStep('checking_out'); const { branchName, branchError } = await checkOutTeleportedSessionBranch(result.branch); return { messages: processMessagesForTeleportResume(result.log, branchError), branchName, }; }