Skip to content

Fix articles locked in error when a trickled block sits in an unavailable article #269

Open

Description

@joe-replin

Subject of the issue/enhancement/features

A custom extension controls which articles on a page are available, which is the usual pattern for gated or branching content. When the available articles sit either side of an unavailable article that contains a trickle-configured block, every available article on the page is locked, including the first one. Nothing renders, and the page force-readies at core's 10 second timeout with every model on it listed as not ready.

#164 reports this same scenario and is closed as released. Its thread accumulated two different causes, and the pull request that closed it (#224) fixed the second one. The behaviour in the original report is still present in 7.9.1.

Your environment

  • Adapt Framework 5.56.2, adapt-contrib-core 6.78.3, adapt-contrib-trickle 7.8.0
  • Also present in 7.9.1: _getAncestorNextSiblings() and the site filter are byte-identical to 7.8.0, so upgrading does not clear it
  • Firefox , Windows 11

Steps to reproduce

The trigger is an unavailable article whose blocks are still available. Blocks default to _isAvailable: true, so setting only the article to false leaves them eligible.

  1. Build a page with four articles, a-1 to a-4, each with one block.
  2. On the block inside a-3, set _trickle._isEnabled: true, leaving _stepLocking at its default so completion is required. Leave that block incomplete.
  3. Set all four articles to _isAvailable: false in the course JSON.
  4. From an extension, before the page is visited, set a-1 and a-4 back to _isAvailable: true. Leave a-2 and a-3 unavailable, and leave every block's own _isAvailable untouched.
  5. Navigate to the page.

Expected behaviour

A trickle site inside an unavailable article should lock nothing, because that content cannot be reached. a-1 should render.

Actual behaviour

a-1 and a-4 both carry _isTrickled: true and _isLocked: true, taken from the incomplete block in a-3. Trickle's view:addChild handler stops a-1, the first child, so addChildren() adds no views, the page model's _isReady is never set, and after ten seconds core force-readies everything:

ERROR: View a-1 (article), b-1 (block), ... failed to become ready, forcing ready status.

Two things combine.

Site selection in applyLocks() (models.js line 208) filters candidates on each model's own _isAvailable and does not walk ancestors, so the block in a-3 is still a site while a-3 is unavailable.

_getAncestorNextSiblings() (models.js line 287) then looks that block's article up among the page's available children:

const subsequentAncestorSiblings = allGrandParentChildren.slice(allGrandParentChildren.findIndex(child => child === parent) + 1);

a-3 is not in getAvailableChildModels(), so findIndex returns -1 and slice(0) returns the entire array rather than nothing. Every available article on the page is then treated as subsequent to the site, including the ones before it.

The 2022 review of #165 noted that "the _isAvailable: false models are filtered from the changes before this code block is executed". That is true of site selection and does not cover this case. The filter excludes a model whose own flag is false. It does not exclude an available model with an unavailable ancestor, which is exactly what the block inside a-3 is.

On the relationship to #164: the original report on that thread is this defect, including "rendering therefore stops at the article and the first block is not rendered as it should be". A later comment described a different fault, where a role selector changed availability and a site model's descendants kept a stale _isLocked in page-level progress. #224 fixed that second one by adding setDescendantLocks, declared "Fixes #164", and the release bot closed the thread. #165 had earlier proposed resetting _isLocked before recalculating, which would probably have covered the original report, and was closed unmerged.

Dynamic availability comes up repeatedly rather than being a one-off. The reporter of #164 said the reset workaround was in use across several of their projects, and the later comment on that thread came from a role selector hitting the same area.

Suggested Fix (if known)

Guard the lookup. A findIndex of -1 means the ancestor is unavailable, so it has no subsequent available siblings to lock:

const parentIndex = allGrandParentChildren.findIndex(child => child === parent);
if (parentIndex === -1) return;
const subsequentAncestorSiblings = allGrandParentChildren.slice(parentIndex + 1);

This is offered as the "more specific solution" the close of #165 asked for. It resets nothing, considers no model that was not already being considered, and adds no change:_isLocked triggers, which was the stated objection to the mass reset in that pull request.

Worth considering alongside it: filtering site candidates on hierarchy availability rather than each model's own flag, the way adapt-contrib-scoring does with isModelAvailableInHierarchy. That would stop blocks inside unavailable articles being sites at all. The guard above is the smaller change and clears the reported behaviour on its own.

For anyone hitting this before a fix lands, our workaround is to have the extension that toggles _isAvailable cascade it to the article's descendants in both directions, which keeps those blocks out of site selection. Unlike the setOnChildren('_isLocked', false) workaround suggested on #165, it prevents the wrong lock being computed rather than clearing it afterwards, and it leaves models managed by another locking mechanism alone.

Screenshots (if you can)

Not applicable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions