Raised from the review of #1841, which is approved and merging — this is follow-up work, not a blocker.
The call site that carries the fix is unverified
#1841 fixed guided-tour re-entry by calling guidedHandler.resetProgress() at interactive-guided.tsx:388, ahead of the loop in executeStep. That single line is the whole production fix, and nothing tests it.
Mutation experiments in the review deleted only that line:
- 103/103 tests green across the four directly related suites
- 52 suites, 941 tests green across
src/components/interactive-tutorial + src/interactive-engine
- 535 suites, 8567 tests green across the whole repository, with
eslint and tsc --noEmit also clean
As a control, emptying resetProgress's body does redden the new test — so the delta's tests guard the method, which was already correct. The missing call was the broken half, and that half is still unguarded. A future refactor can delete it and every gate stays green.
The observation point already exists: interactive-guided.test.tsx:137 mocks resetProgress.
Suggested fix: hoist that mock to a module-level fn and assert it fires on a run start, ideally ordered before the first executeGuidedStep, using the callOrder harness the file already applies to waitForReactUpdates vs executeGuidedStep.
A test that makes the natural hardening a breaking change
resetProgress() is the only writer that clears completedSteps, and executeGuidedStep never notices that a stepIndex === 0 arrived with credit already banked. So both in-app callers must each remember to clear independently — and the bug class this originally fixed is exactly "a caller forgot".
The new test carries stale credit into a second sequence when it is not called (guided-handler.test.ts:125-132) asserts completedSteps: [0, 1] on run B's first paint. That pins the absence of a handler-side guard as expected behaviour, so making the handler self-healing later becomes a test-breaking change rather than a green one.
Pick one, not both: reframe that test as characterization a future self-healing handler is free to break, or move the invariant into executeGuidedStep (clear on stepIndex === 0) and let the two caller-side calls become belt-and-braces.
Review: #1841 (review)
Raised from the review of #1841, which is approved and merging — this is follow-up work, not a blocker.
The call site that carries the fix is unverified
#1841 fixed guided-tour re-entry by calling
guidedHandler.resetProgress()atinteractive-guided.tsx:388, ahead of the loop inexecuteStep. That single line is the whole production fix, and nothing tests it.Mutation experiments in the review deleted only that line:
src/components/interactive-tutorial+src/interactive-engineeslintandtsc --noEmitalso cleanAs a control, emptying
resetProgress's body does redden the new test — so the delta's tests guard the method, which was already correct. The missing call was the broken half, and that half is still unguarded. A future refactor can delete it and every gate stays green.The observation point already exists:
interactive-guided.test.tsx:137mocksresetProgress.Suggested fix: hoist that mock to a module-level fn and assert it fires on a run start, ideally ordered before the first
executeGuidedStep, using thecallOrderharness the file already applies towaitForReactUpdatesvsexecuteGuidedStep.A test that makes the natural hardening a breaking change
resetProgress()is the only writer that clearscompletedSteps, andexecuteGuidedStepnever notices that astepIndex === 0arrived with credit already banked. So both in-app callers must each remember to clear independently — and the bug class this originally fixed is exactly "a caller forgot".The new test
carries stale credit into a second sequence when it is not called(guided-handler.test.ts:125-132) assertscompletedSteps: [0, 1]on run B's first paint. That pins the absence of a handler-side guard as expected behaviour, so making the handler self-healing later becomes a test-breaking change rather than a green one.Pick one, not both: reframe that test as characterization a future self-healing handler is free to break, or move the invariant into
executeGuidedStep(clear onstepIndex === 0) and let the two caller-side calls become belt-and-braces.Review: #1841 (review)