Problem
When state.progress is not loaded, getPromoStatus returns 'forbidden' (not 'finished'), so finishPromo falls through to stateActions.addPromoToFinished, which calls assertProgressLoaded and throws 'Promo manager progress not loaded'. Unlike requestStart, finishPromo never awaits fetchProgressState.
Real path: onboarding integration subscribes finishPreset -> finishPromo in the constructor. If the user finishes a preset while getProgressState() is still in flight (slow network), the handler throws, the finish is not recorded, and the promo shows again in the next session. Combined with #221 (a failed fetch is cached forever), the broken window lasts the whole page session, not just a few seconds.
Expected
finishPromo should either await progress loading (like requestStart does) or queue the finish until progress arrives — not throw and drop it.
Where
src/promo-manager/core/controller.ts — finishPromo -> stateActions.addPromoToFinished -> assertProgressLoaded
Problem
When
state.progressis not loaded,getPromoStatusreturns'forbidden'(not'finished'), sofinishPromofalls through tostateActions.addPromoToFinished, which callsassertProgressLoadedand throws'Promo manager progress not loaded'. UnlikerequestStart,finishPromonever awaitsfetchProgressState.Real path: onboarding integration subscribes
finishPreset -> finishPromoin the constructor. If the user finishes a preset whilegetProgressState()is still in flight (slow network), the handler throws, the finish is not recorded, and the promo shows again in the next session. Combined with #221 (a failed fetch is cached forever), the broken window lasts the whole page session, not just a few seconds.Expected
finishPromoshould either await progress loading (likerequestStartdoes) or queue the finish until progress arrives — not throw and drop it.Where
src/promo-manager/core/controller.ts—finishPromo->stateActions.addPromoToFinished->assertProgressLoaded