()
| 906 | let liveTickTimestamp: number | null = null; |
| 907 | |
| 908 | export const initializeTimelineStore = () => { |
| 909 | if (unsubscribeExecutionStore) { |
| 910 | unsubscribeExecutionStore(); |
| 911 | unsubscribeExecutionStore = null; |
| 912 | } |
| 913 | |
| 914 | void import('./executionStore') |
| 915 | .then(({ useExecutionStore }) => { |
| 916 | // Track previous runStatus to detect completion |
| 917 | let prevRunStatus: ExecutionStatusResponse | null = null; |
| 918 | |
| 919 | unsubscribeExecutionStore = useExecutionStore.subscribe((state) => { |
| 920 | const { events: liveEvents, runId, status, runStatus } = state; |
| 921 | const timelineStore = useExecutionTimelineStore.getState(); |
| 922 | |
| 923 | // Check if workflow has completed or failed |
| 924 | const isTerminalStatus = runStatus && TERMINAL_STATUSES.includes(runStatus.status as any); |
| 925 | const isTerminalLifecycle = status === 'completed' || status === 'failed'; |
| 926 | |
| 927 | // Check if status changed from non-terminal to terminal (workflow just completed) |
| 928 | const statusJustChanged = |
| 929 | prevRunStatus && |
| 930 | runStatus && |
| 931 | !TERMINAL_STATUSES.includes(prevRunStatus.status as any) && |
| 932 | TERMINAL_STATUSES.includes(runStatus.status as any); |
| 933 | |
| 934 | // Update prevRunStatus for next comparison |
| 935 | prevRunStatus = runStatus; |
| 936 | |
| 937 | // If workflow is done and we're in live mode, switch to replay mode |
| 938 | if (timelineStore.playbackMode === 'live' && timelineStore.selectedRunId === runId) { |
| 939 | if (isTerminalStatus || isTerminalLifecycle || statusJustChanged) { |
| 940 | // Workflow completed/failed - switch to replay mode |
| 941 | // Reload timeline to ensure all final events are loaded, then position at end |
| 942 | if (!runId) return; |
| 943 | |
| 944 | console.log( |
| 945 | '[TimelineStore] Workflow completed/failed detected, switching from live to replay mode', |
| 946 | { |
| 947 | isTerminalStatus, |
| 948 | isTerminalLifecycle, |
| 949 | statusJustChanged, |
| 950 | runStatus: runStatus?.status, |
| 951 | status, |
| 952 | }, |
| 953 | ); |
| 954 | |
| 955 | // Update run in run store to mark it as completed (removes from live runs) |
| 956 | if (runStatus) { |
| 957 | void import('./runStore').then(({ useRunStore }) => { |
| 958 | const runStore = useRunStore.getState(); |
| 959 | const existingRun = runStore.getRunById(runId); |
| 960 | if (existingRun) { |
| 961 | // Update run with final status and endTime |
| 962 | const endTime = |
| 963 | runStatus.completedAt || runStatus.updatedAt || new Date().toISOString(); |
| 964 | runStore.upsertRun({ |
| 965 | ...existingRun, |
no test coverage detected