@@ -27,6 +27,23 @@ const initialState: DebuggerState = {
2727 skipFailedConditions : false ,
2828} ;
2929
30+ /**
31+ * Get filtered step indices based on skipFailedConditions setting.
32+ * Returns indices of steps that should be shown during debugging.
33+ */
34+ function getFilteredStepIndices ( trace : ExecutionTrace | null , skipFailedConditions : boolean ) : number [ ] {
35+ if ( ! trace || trace . steps . length === 0 ) {
36+ return [ ] ;
37+ }
38+ if ( ! skipFailedConditions ) {
39+ return trace . steps . map ( ( _ , i ) => i ) ;
40+ }
41+ return trace . steps
42+ . map ( ( step , i ) => ( { step, index : i } ) )
43+ . filter ( ( { step } ) => step . result !== 'skipped' )
44+ . map ( ( { index } ) => index ) ;
45+ }
46+
3047/**
3148 * Debugger reducer
3249 */
@@ -110,25 +127,30 @@ function debuggerReducer(state: DebuggerState, action: DebuggerAction): Debugger
110127 return state ;
111128 }
112129
113- let nextIndex = state . currentStepIndex + 1 ;
114-
115- // If skip failed conditions is enabled, find the next executed step
116- if ( state . skipFailedConditions ) {
117- while (
118- nextIndex < state . trace . steps . length &&
119- state . trace . steps [ nextIndex ] . result === 'skipped'
120- ) {
121- nextIndex ++ ;
122- }
130+ const filteredIndices = getFilteredStepIndices ( state . trace , state . skipFailedConditions ) ;
131+ if ( filteredIndices . length === 0 ) {
132+ return state ;
123133 }
124134
125- // If at or past end, just pause
126- if ( nextIndex >= state . trace . steps . length ) {
135+ // Find current position in filtered list
136+ const currentFilteredPos = filteredIndices . findIndex ( i => i === state . currentStepIndex ) ;
137+
138+ let nextIndex : number ;
139+ if ( state . currentStepIndex === - 1 ) {
140+ // At ready state, go to first filtered step
141+ nextIndex = filteredIndices [ 0 ] ;
142+ } else if ( currentFilteredPos === - 1 ) {
143+ // Current step is not in filtered list (shouldn't happen), go to first
144+ nextIndex = filteredIndices [ 0 ] ;
145+ } else if ( currentFilteredPos >= filteredIndices . length - 1 ) {
146+ // At end of filtered steps, pause
127147 return {
128148 ...state ,
129- currentStepIndex : state . trace . steps . length - 1 , // Go to last step
130- playbackState : 'paused' , // Auto-pause at end
149+ playbackState : 'paused' ,
131150 } ;
151+ } else {
152+ // Move to next filtered step
153+ nextIndex = filteredIndices [ currentFilteredPos + 1 ] ;
132154 }
133155
134156 return {
@@ -137,14 +159,38 @@ function debuggerReducer(state: DebuggerState, action: DebuggerAction): Debugger
137159 } ;
138160 }
139161
140- case 'STEP_BACKWARD' :
141- // Allow going back to -1 (ready state)
142- if ( state . currentStepIndex <= - 1 ) return state ;
162+ case 'STEP_BACKWARD' : {
163+ if ( ! state . trace || state . currentStepIndex <= - 1 ) {
164+ return state ;
165+ }
166+
167+ const filteredIndices = getFilteredStepIndices ( state . trace , state . skipFailedConditions ) ;
168+ if ( filteredIndices . length === 0 ) {
169+ return {
170+ ...state ,
171+ currentStepIndex : - 1 ,
172+ playbackState : 'paused' ,
173+ } ;
174+ }
175+
176+ // Find current position in filtered list
177+ const currentFilteredPos = filteredIndices . findIndex ( i => i === state . currentStepIndex ) ;
178+
179+ let prevIndex : number ;
180+ if ( currentFilteredPos <= 0 ) {
181+ // At or before first filtered step, go to ready state
182+ prevIndex = - 1 ;
183+ } else {
184+ // Move to previous filtered step
185+ prevIndex = filteredIndices [ currentFilteredPos - 1 ] ;
186+ }
187+
143188 return {
144189 ...state ,
145- currentStepIndex : state . currentStepIndex - 1 ,
146- playbackState : 'paused' , // Pause on manual step
190+ currentStepIndex : prevIndex ,
191+ playbackState : 'paused' ,
147192 } ;
193+ }
148194
149195 case 'GO_TO_STEP' :
150196 if ( ! state . trace || action . index < 0 || action . index >= state . trace . steps . length ) return state ;
@@ -160,11 +206,28 @@ function debuggerReducer(state: DebuggerState, action: DebuggerAction): Debugger
160206 playbackSpeed : Math . max ( 100 , Math . min ( 2000 , action . speed ) ) ,
161207 } ;
162208
163- case 'SET_SKIP_FAILED_CONDITIONS' :
209+ case 'SET_SKIP_FAILED_CONDITIONS' : {
210+ // If enabling filter and current step would be filtered out, move to nearest valid step
211+ if ( action . skip && state . trace && state . currentStepIndex >= 0 ) {
212+ const currentStep = state . trace . steps [ state . currentStepIndex ] ;
213+ if ( currentStep && currentStep . result === 'skipped' ) {
214+ // Find the next non-skipped step, or go to ready state
215+ const filteredIndices = getFilteredStepIndices ( state . trace , true ) ;
216+ const nextValidIndex = filteredIndices . find ( i => i > state . currentStepIndex ) ;
217+ const prevValidIndex = [ ...filteredIndices ] . reverse ( ) . find ( i => i < state . currentStepIndex ) ;
218+
219+ return {
220+ ...state ,
221+ skipFailedConditions : action . skip ,
222+ currentStepIndex : nextValidIndex ?? prevValidIndex ?? - 1 ,
223+ } ;
224+ }
225+ }
164226 return {
165227 ...state ,
166228 skipFailedConditions : action . skip ,
167229 } ;
230+ }
168231
169232 default :
170233 return state ;
@@ -204,6 +267,10 @@ interface DebuggerContextValue {
204267 hasTrace : boolean ;
205268 progress : number ;
206269 totalSteps : number ;
270+ /** Current position within filtered steps (0-indexed), -1 if at ready state */
271+ currentFilteredPosition : number ;
272+ /** Array of actual step indices that are shown (for navigation) */
273+ filteredStepIndices : number [ ] ;
207274 isEngineReady : boolean ;
208275 skipFailedConditions : boolean ;
209276}
@@ -349,12 +416,20 @@ export function DebuggerProvider({
349416 ? getChangesAtStep ( state . trace , state . currentStepIndex )
350417 : [ ] ;
351418
352- const totalSteps = state . trace ? state . trace . steps . length : 0 ;
419+ // Compute filtered indices for accurate step counting
420+ const filteredStepIndices = getFilteredStepIndices ( state . trace , state . skipFailedConditions ) ;
421+ const totalSteps = filteredStepIndices . length ;
422+
423+ // Find current position within filtered steps
424+ const currentFilteredPos = state . currentStepIndex >= 0
425+ ? filteredStepIndices . findIndex ( i => i === state . currentStepIndex )
426+ : - 1 ;
427+
353428 const isAtStart = state . currentStepIndex <= - 1 ; // -1 is "ready" state (before step 0)
354- const isAtEnd = state . currentStepIndex >= totalSteps - 1 && state . currentStepIndex >= 0 ;
429+ const isAtEnd = currentFilteredPos >= totalSteps - 1 && currentFilteredPos >= 0 ;
355430 const hasTrace = state . trace !== null && totalSteps > 0 ;
356- const progress = totalSteps > 0 && state . currentStepIndex >= 0
357- ? ( state . currentStepIndex + 1 ) / totalSteps
431+ const progress = totalSteps > 0 && currentFilteredPos >= 0
432+ ? ( currentFilteredPos + 1 ) / totalSteps
358433 : 0 ;
359434
360435 const value : DebuggerContextValue = {
@@ -384,6 +459,8 @@ export function DebuggerProvider({
384459 hasTrace,
385460 progress,
386461 totalSteps,
462+ currentFilteredPosition : currentFilteredPos ,
463+ filteredStepIndices,
387464 isEngineReady,
388465 skipFailedConditions : state . skipFailedConditions ,
389466 } ;
0 commit comments