This issue has been created
There are 2 updates.
 
 
XWiki Platform / cid:jira-generated-image-avatar-13705be1-acc3-4e04-a556-c65762d022a4 XWIKI-24857 Open

Navigation Panel administration: dragging pages in or out of the tree sometimes has no effect on the tree

 
View issue   ·   Add comment
 

Issue created

 
cid:jira-generated-image-avatar-95b5a51a-e22d-49c4-83e3-b6ecc0e32580 Michael Hamann created this issue on 10/Sep/26 13:31
 
Summary: Navigation Panel administration: dragging pages in or out of the tree sometimes has no effect on the tree
Issue Type: cid:jira-generated-image-avatar-13705be1-acc3-4e04-a556-c65762d022a4 Bug
Affects Versions: 14.1
Assignee: Unassigned
Components: Panels
Created: 10/Sep/26 13:31
Priority: cid:jira-generated-image-static-major-e23a4cfd-1b78-42be-9850-fbc1c314742e Major
Reporter: Michael Hamann
Description:

Sometimes, when you open Administration > Look & Feel > Navigation Panel, the page tree on the left stops reacting to the configuration changes you make. Nothing indicates that something is wrong – the "Excluded Pages" panel on the right behaves normally, so the breakage only shows up in the tree, and only once you try to change something. Reloading the page usually makes it work again.

What you can observe, all in the same broken state:

  • You drag the last remaining page from the tree into "Excluded Pages". The page is correctly added to the "Excluded Pages" list, but the tree goes completely blank instead of displaying "No pages found".
  • You drag a page from "Excluded Pages" back onto the tree. The page disappears from the "Excluded Pages" list – so the form now says it will be included – but it is not added back to the tree. The tree keeps showing its previous content, or "No pages found". Re-including a page looks impossible until you reload.
  • You untick "Top Level Extension Pages". The pages that filter was hiding are not put back into the tree.
  • The pin / unpin buttons are missing from the tree nodes, and pages that are already pinned are not displayed as pinned, so you cannot tell or change the pinned state.

The practical impact is that the tree stops being a preview of the configuration: what you see while editing no longer matches what gets saved, and an administrator can save a navigation configuration they did not intend.

Steps to reproduce

This is a page-load race, so it does not reproduce on demand – it depends on whether the tree
finishes loading before or after the section's own JavaScript. It is much more frequent in Firefox.
Two ways to force it:

  1. Throttle the browser network (or slow the server) so the section's translations request,
    GET /rest/wikis/<wiki>/localization/translations, resolves after the tree's children request.
    Then go to the Navigation Panel section, drag a top level page into "Excluded Pages" and drag it
    back – it will not return to the tree.
  2. Deterministically, in the JSX of PanelsCode.NavigationConfigurationSheet: wrap everything from const $navigationTree = ... to the end of the require callback in a function and invoke it from a one-shot ready.jstree listener. Every run then reproduces the bug.
const forceRaceTreeSetup = function() { /* ...original tail of the module... */ };
const $forceRaceTree = $('.navigationPanelConfiguration .xtree');
if ($forceRaceTree.jstree?.(true)?._data?.core?.ready) {
  forceRaceTreeSetup();
} else {
  $forceRaceTree.one('ready.jstree', function() {
    setTimeout(forceRaceTreeSetup, 0);
  });
}

Technical cause

PanelsCode.NavigationConfigurationSheet registers its jsTree listeners inside require(['jquery', 'xwiki-l10n!...', 'xwiki-meta', 'jquery-ui', 'xwiki-events-bridge'], ...), that is, after a REST round trip for the translations. The tree is initialized and loaded independently (xwiki-platform-tree-war/.../widgets/tree.js on DOM ready, then AJAX). Nothing synchronizes them, so the tree can fire ready.jstree / model.jstree / redraw.jstree before the sheet's listeners exist.

The decisive one is ready.jstree, which is where the sheet declares the node types the tree root accepts. Without it, getChildren in tree.js leaves the root's validChildren at the value it inferred from the server response, and check_callback / canAcceptChild then silently rejects every create_node of the missing type:

State when the page was loaded Inferred validChildren What gets silently rejected
Tree contains pages ['document'] (or ['document', 'pagination']) the empty placeholder, so the tree renders blank
All pages excluded ['empty'] every document node, so pages cannot be put back

The missed model.jstree and redraw.jstree listeners account for the pinned marks and the
missing pin buttons. The sheet already worked around a symptom of this race for the node template, but that mitigation is incomplete and its redraw(true) runs before the redraw.jstree listener is registered.

Proposed fix

  • Set the root node data on the tree element itself ($navigationTree.data('root', ...)) as soon as the module runs, so the tree picks up the valid child node types when it loads the root and never infers them. This also prevents the types from being re-inferred when the root is refreshed.
  • Extract the bodies of the ready.jstree / model.jstree / redraw.jstree listeners into functions, and add a catch-up at the end of the module that applies them to an already loaded tree and then does a full redraw – after all listeners are registered.

Versions

The defect has been present since 10.5 (XWIKI-15260), which introduced the drag and drop
configuration and registered the ready.jstree listener without handling the case where the tree is already ready. It only became realistically triggerable in 14.1, when XWIKI-14928 added the xwiki-l10n! dependency to the section's JavaScript: before that the module only waited for local scripts and practically always won the race against the tree's AJAX load, whereas now both sides make a server round trip. The pin related symptoms additionally require the manual ordering feature added in 16.x (XWIKI-14422), with the current pin action from 16.4.7 / 16.10.4 / 17.1 (XWIKI-23950).

Related

XWIKI-23126NavigationPanelAdministrationIT#navigationPanelAdministration has been failing on CI with exactly this cause: about 11% of 1167 runs over 60 days, Firefox only, in two signatures matching the table above:

org.opentest4j.AssertionFailedError: expected: <[No pages found]> but was: <[]>
org.opentest4j.AssertionFailedError: expected: <[Alice, Bob, D e%n+i/s, XWiki]> but was: <[No pages found]>

The failure rate jumped from 3.4% to 21.7% on 2026-08-13 across all branches at once, which points at a browser image update shifting the race rather than at a code change. The test side wait added under XWIKI-23126 did not help – it only converted some assertion failures into timeouts. This CI flicker is therefore the product bug, not a test problem.

 
 

2 updates

 
cid:jira-generated-image-avatar-95b5a51a-e22d-49c4-83e3-b6ecc0e32580 Changes by Michael Hamann on 10/Sep/26 13:31
 
Description: Sometimes, when you open {*}Administration > Look & Feel > Navigation Panel{*}, the page tree on the left stops reacting to the configuration changes you make. Nothing indicates that something is wrong – the "Excluded Pages" panel on the right behaves normally, so the breakage only shows up in the tree, and only once you try to change something. Reloading the page usually makes it work again.

What you can observe, all in the same broken state:
* You drag the last remaining page from the tree into "Excluded Pages". The page is correctly added to the "Excluded Pages" list, but the tree goes *completely blank* instead of displaying "No pages found".
* You drag a page from "Excluded Pages" back onto the tree. The page disappears from the "Excluded Pages" list – so the form now says it will be included – but it is {*}not added back to the tree{*}. The tree keeps showing its previous content, or "No pages found". Re-including a page looks impossible until you reload.
* You untick "Top Level Extension Pages". The pages that filter was hiding are *not put back* into the tree.
* The pin / unpin buttons are *missing* from the tree nodes, and pages that are already pinned are {*}not displayed as pinned{*}, so you cannot tell or change the pinned state.

The practical impact is that the tree stops being a preview of the configuration: what you see while editing no longer matches what gets saved, and an administrator can save a navigation configuration they did not intend.
h3. Steps to reproduce

This is a page-load race, so it does not reproduce on demand – it depends on whether the tree

finishes loading before or after the section's own JavaScript. It is much more frequent in Firefox.
Two ways to force it:
# Throttle the browser network (or slow the server) so the section's translations request,

{{{}GET /rest/wikis/<wiki>/localization/translations{}}}, resolves _after_ the tree's children request.
Then go to the Navigation Panel section, drag a top level page into "Excluded Pages" and drag it

back – it will not return to the tree.
# Deterministically, in the JSX of {{{}PanelsCode.NavigationConfigurationSheet{}}}: wrap everything from {{const $navigationTree = ...}} to the end of the {{require}} callback in a function and invoke it from a one-shot {{ready.jstree}} listener. Every run then reproduces the bug.

{code:javascript}
const forceRaceTreeSetup = function() { /* ...original tail of the module... */ };
const $forceRaceTree = $('.navigationPanelConfiguration .xtree');
if ($forceRaceTree.jstree?.(true)?._data?.core?.ready) {
  forceRaceTreeSetup();
} else {
  $forceRaceTree.one('ready.jstree', function() {
    setTimeout(forceRaceTreeSetup, 0);
  });
}
{code}
h3. Technical cause

{{PanelsCode.NavigationConfigurationSheet}} registers its jsTree listeners inside {{{}require(['jquery', 'xwiki-l10n!...', 'xwiki-meta', 'jquery-ui', 'xwiki-events-bridge'], ...){}}}, that is, after a REST round trip for the translations. The tree is initialized and loaded independently ({{{}xwiki-platform-tree-war/.../widgets/tree.js{}}} on DOM ready, then AJAX). Nothing synchronizes them, so the tree can fire {{ready.jstree}} / {{model.jstree}} / {{redraw.jstree}} before the sheet's listeners exist.

The decisive one is {{{}ready.jstree{}}}, which is where the sheet declares the node types the tree root accepts. Without it, {{getChildren}} in {{tree.js}} leaves the root's {{validChildren}} at the value it {_}inferred from the server response{_}, and {{check_callback}} / {{canAcceptChild}} then silently rejects every {{create_node}} of the missing type:
||State when the page was loaded||Inferred {{validChildren}}||What gets silently rejected||
|Tree contains pages|{{['document']}} (or {{{}['document', 'pagination']{}}})|the {{empty}} placeholder, so the tree renders blank|
|All pages excluded|{{['empty']}}|every document node, so pages cannot be put back|

The missed {{model.jstree}} and {{redraw.jstree}} listeners account for the pinned marks and the

missing pin buttons. The sheet already worked around a symptom of this race for the node template, but that mitigation is incomplete and its {{redraw(true)}} runs before the {{redraw.jstree}} listener is registered.
h3. Proposed fix
* Set the root node data on the tree element itself ({{{}$navigationTree.data('root', ...){}}}) as soon as the module runs, so the tree picks up the valid child node types when it loads the root and never infers them. This also prevents the types from being re-inferred when the root is refreshed.
* Extract the bodies of the {{ready.jstree}} / {{model.jstree}} / {{redraw.jstree}} listeners into functions, and add a catch-up at the end of the module that applies them to an already loaded tree and then does a full redraw – after all listeners are registered.

h3. Versions

The defect has been present since *10.5* (XWIKI-15260), which introduced the drag and drop

configuration and registered the {{ready.jstree}} listener without handling the case where the tree is already ready. It only became realistically triggerable in {*}14.1{*}, when XWIKI-14928 added the {{xwiki-l10n!}} dependency to the section's JavaScript: before that the module only waited for local scripts and practically always won the race against the tree's AJAX load, whereas now both sides make a server round trip. The pin related symptoms additionally require the manual ordering feature added in 16.x (XWIKI-14422), with the current pin action from 16.4.7 / 16.10.4 / 17.1 (XWIKI-23950).
h3. Related

XWIKI-23126 – {{NavigationPanelAdministrationIT#navigationPanelAdministration}} has been failing on CI with exactly this cause: about 11% of 1167 runs over 60 days, {*}Firefox only{*}, in two signatures matching the table above:
{noformat}
org.opentest4j.AssertionFailedError: expected: <[No pages found]> but was: <[]>
org.opentest4j.AssertionFailedError: expected: <[Alice, Bob, D e%n+i/s, XWiki]> but was: <[No pages found]>
{noformat}
The failure rate jumped from 3.4% to 21.7% on 2026-08-13 across all branches at once, which points at a browser image update shifting the race rather than at a code change. The test side wait added under XWIKI-23126 did not help – it only converted some assertion failures into timeouts. This CI flicker is therefore the product bug, not a test problem.


Bug reproduced with and issue description written by Claude Opus 5 (1M context).
Assignee: Michael Hamann