Subject of the issue/enhancement/features
On a trickle-gated block, if a popup opens and closes while the step is unlocked and the button has not been clicked, the Continue button stays disabled permanently and the page can never be completed.
Any popup does it. A question's feedback popup is the most common way to hit it, but the drawer does it too, on a page with no questions at all.
This is a regression introduced in 7.9.1 by #264. Version 7.8.0 is not affected.
Your environment
- Adapt Framework 5.56.2, adapt-contrib-core 6.78.3, adapt-contrib-trickle 7.9.1
- Also reproduced on adapt-contrib-core 6.77.1, the version Framework 5.56.2 ships with
- Chrome 151, macOS
Steps to reproduce
The shortest version needs no course JSON and no questions. On any page with a trickled block whose step is unlocked and whose button is enabled, run this in the console:
require('core/js/drawer').open();
require('core/js/drawer').close();
The Continue button is now disabled and never recovers. Opening the drawer with the navigation bar button and closing it again does exactly the same thing.
The way learners hit it in a real course:
- Author a block with
_trickle._isEnabled: true and _trickle._button._isEnabled: true, leaving _stepLocking at its default so completion is required. Add at least one block after it.
- Put a question in that block with
_canShowFeedback: true.
- Answer the question and submit it, so the feedback popup opens.
- Dismiss the feedback popup.
The Continue button is disabled and stays that way.
Two other routes to the same dead end, both confirmed:
- Open the drawer to look at resources on a trickled page, then close it.
- With
_pageIncompletePrompt enabled, try to leave an incomplete trickled page and answer "No" to stay. Staying is what breaks the page, which leaves the learner with no way forward at all.
Expected behaviour
Once the last popup closes, the Continue button becomes clickable again and the learner can carry on.
Actual behaviour
The button stays disabled forever, so the page cannot be completed. It is left with aria-disabled="true" and the is-disabled is-locked classes while no popup is open. Note it is not natively disabled, so anything checking the button's disabled property will wrongly think it is fine.
The cause is event ordering. Core fires popup:closing before the closing popup is removed from the a11y stack, so any handler on that event still sees a11y.isPopupOpen === true for the very popup that is going away. #264 changed trickle to ask exactly that question at exactly that moment, in two places:
- the guard at the top of
onPopupClosed, which therefore always returns early
calculateButtonState, which therefore always recomputes the button as disabled
Before #264 trickle kept its own count of open popups and decremented it at the top of the handler, which cancelled the ordering out. Both call sites need to account for the closing popup, not just the guard: fixing only the guard still leaves the recalculation computing "disabled".
Worth noting for anyone considering a straight revert: #264 exists to fix #263, so reverting would reintroduce unlocking behind popups on returning visits. A fix needs to keep a11y as the source of truth and account for the closing phase instead.
Suggested Fix (if known)
Use a11y.popupStack.length rather than a11y.isPopupOpen at both sites, and discount the closing popup while popup:closing is being handled. A pull request follows.
Posted via collaboration with Claude Code
Subject of the issue/enhancement/features
On a trickle-gated block, if a popup opens and closes while the step is unlocked and the button has not been clicked, the Continue button stays disabled permanently and the page can never be completed.
Any popup does it. A question's feedback popup is the most common way to hit it, but the drawer does it too, on a page with no questions at all.
This is a regression introduced in 7.9.1 by #264. Version 7.8.0 is not affected.
Your environment
Steps to reproduce
The shortest version needs no course JSON and no questions. On any page with a trickled block whose step is unlocked and whose button is enabled, run this in the console:
The Continue button is now disabled and never recovers. Opening the drawer with the navigation bar button and closing it again does exactly the same thing.
The way learners hit it in a real course:
_trickle._isEnabled: trueand_trickle._button._isEnabled: true, leaving_stepLockingat its default so completion is required. Add at least one block after it._canShowFeedback: true.The Continue button is disabled and stays that way.
Two other routes to the same dead end, both confirmed:
_pageIncompletePromptenabled, try to leave an incomplete trickled page and answer "No" to stay. Staying is what breaks the page, which leaves the learner with no way forward at all.Expected behaviour
Once the last popup closes, the Continue button becomes clickable again and the learner can carry on.
Actual behaviour
The button stays disabled forever, so the page cannot be completed. It is left with
aria-disabled="true"and theis-disabled is-lockedclasses while no popup is open. Note it is not nativelydisabled, so anything checking the button'sdisabledproperty will wrongly think it is fine.The cause is event ordering. Core fires
popup:closingbefore the closing popup is removed from the a11y stack, so any handler on that event still seesa11y.isPopupOpen === truefor the very popup that is going away. #264 changed trickle to ask exactly that question at exactly that moment, in two places:onPopupClosed, which therefore always returns earlycalculateButtonState, which therefore always recomputes the button as disabledBefore #264 trickle kept its own count of open popups and decremented it at the top of the handler, which cancelled the ordering out. Both call sites need to account for the closing popup, not just the guard: fixing only the guard still leaves the recalculation computing "disabled".
Worth noting for anyone considering a straight revert: #264 exists to fix #263, so reverting would reintroduce unlocking behind popups on returning visits. A fix needs to keep
a11yas the source of truth and account for the closing phase instead.Suggested Fix (if known)
Use
a11y.popupStack.lengthrather thana11y.isPopupOpenat both sites, and discount the closing popup whilepopup:closingis being handled. A pull request follows.Posted via collaboration with Claude Code