({
worktreePath,
sessionId: targetSessionId,
autoSubscribe = true,
}: UseTestLogsOptions)
| 114 | * ``` |
| 115 | */ |
| 116 | export function useTestLogs({ |
| 117 | worktreePath, |
| 118 | sessionId: targetSessionId, |
| 119 | autoSubscribe = true, |
| 120 | }: UseTestLogsOptions) { |
| 121 | const [state, setState] = useState<TestLogState>(initialState); |
| 122 | |
| 123 | // Keep track of whether we've fetched initial logs |
| 124 | const hasFetchedInitialLogs = useRef(false); |
| 125 | |
| 126 | // Track the current session ID for filtering events |
| 127 | const currentSessionId = useRef<string | null>(targetSessionId ?? null); |
| 128 | |
| 129 | // Guard against stale fetch results when switching worktrees/sessions |
| 130 | const fetchSeq = useRef(0); |
| 131 | |
| 132 | /** |
| 133 | * Derived state: whether tests are currently running |
| 134 | */ |
| 135 | const isRunning = state.status === 'running' || state.status === 'pending'; |
| 136 | |
| 137 | /** |
| 138 | * Fetch buffered logs from the server |
| 139 | */ |
| 140 | const fetchLogs = useCallback(async () => { |
| 141 | if (!worktreePath && !targetSessionId) return; |
| 142 | |
| 143 | // Increment sequence to guard against stale responses |
| 144 | const seq = ++fetchSeq.current; |
| 145 | |
| 146 | setState((prev) => ({ ...prev, isLoading: true, error: null })); |
| 147 | |
| 148 | try { |
| 149 | const api = getElectronAPI(); |
| 150 | if (!api?.worktree?.getTestLogs) { |
| 151 | // Check if this request is still current |
| 152 | if (seq !== fetchSeq.current) return; |
| 153 | setState((prev) => ({ |
| 154 | ...prev, |
| 155 | isLoading: false, |
| 156 | error: 'Test logs API not available', |
| 157 | })); |
| 158 | return; |
| 159 | } |
| 160 | |
| 161 | const result = await api.worktree.getTestLogs(worktreePath ?? undefined, targetSessionId); |
| 162 | |
| 163 | // Check if this request is still current (prevent stale updates) |
| 164 | if (seq !== fetchSeq.current) return; |
| 165 | |
| 166 | if (result.success && result.result) { |
| 167 | const { sessionId, command, status, testFile, logs, startedAt, finishedAt, exitCode } = |
| 168 | result.result; |
| 169 | |
| 170 | // Update current session ID for event filtering |
| 171 | currentSessionId.current = sessionId; |
| 172 | |
| 173 | setState((prev) => ({ |
no test coverage detected