dotCMS / dotCMS/core

UVE: navigation active state lost after in-editor navigation — isActive() fails on folder-style Page API URLs with no page segment

Open
#37,105 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

dotCMS : UVE OKR : Customer Support Team : Maintenance Type : Defect
Dominant language
Java
Stars
970
Forks
486
Avg merge
3d 33m
Merged PRs (30d)
170

Description

Problem Statement

NavResultHydrated.isActive() determines the "current parent path" by chopping the request URI at the last slash:

// dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/navigation/NavResultHydrated.java:44-69
String reqURI = req.getRequestURI().replace("/api/v1/page/render", "");
String parentPath = reqURI.substring(0, reqURI.lastIndexOf("/"));
if (!parentPath.endsWith("/")) {
    parentPath = parentPath + "/";
}
if (isFolder() && !navResult.getHref().endsWith("/")) {
    String tempHref = navResult.getHref() + "/";
    return parentPath.startsWith(tempHref);
} else {
    return !isCodeLink() && navResult.getHref().equalsIgnoreCase(reqURI);
}

This assumes the request URI always ends in a page name. When the Page API renders a folder-style URL with no page segment, lastIndexOf("/") is 0, parentPath becomes the empty string and is then normalized to /. Nothing can match, so every nav item returns active == false. The page branch fails identically: a nav item whose href is /TravelHub/index never equals the URI /TravelHub.

Request URI parentPath tempHref for folder /TravelHub isActive()
/api/v1/page/render/TravelHub/index /TravelHub/ /TravelHub/ true
/api/v1/page/render/TravelHub / /TravelHub/ false
/api/v1/page/render/Partners / /Partners/ false

Why this only breaks in the editor. On the front end, CMSFilter 301-redirects a folder URL without a trailing slash and then appends CMS_INDEX_PAGE, so Velocity always sees /folder/index (CMSFilter.java:127-138). The Page API (/api/v1/page/render/<url>) is a JAX-RS resource and applies no such normalization. UVE navigates in-editor using the hrefs it gets from $navtool, which are folder identifier URIs with no /index — so the first load (opened from the Site Browser, which targets the page asset) is correct, and every navigation after that silently loses all active state.

Impact. Any custom navigation that keys off $nav.active — a very common Velocity pattern for marking the current section and expanding its children — renders collapsed and, depending on the theme CSS, completely empty in UVE. There is no error in the logs, in the console, or in the editor. The nav data itself is correct ($navtool.getNav(0) returns its children normally), so the failure looks nothing like a navigation problem and is extremely hard to diagnose. Once triggered it persists for the rest of the editing session, because every subsequent in-editor navigation also uses folder-style hrefs.

Prior art. Issue #17896 (closed 2020, commit a6ee51fe71) fixed the adjacent half of this — it added the .replace("/api/v1/page/render", "") prefix strip so that menu selection stayed active under the Page API. It never handled the case where the resulting URI has no page segment. This is the same defect class resurfacing under UVE.

Browser & OS: not browser-specific — the defect is server-side and reproduces wherever the Page API renders a folder-style URL.

Steps to Reproduce

  1. On any site, create a folder (e.g. /TravelHub) containing an index page, with child pages set to show on menu.
  2. Add a Velocity navigation snippet to the template that keys off the active flag, e.g.:
    #set($list = $navtool.getNav(0))
    #foreach($n in $list)
      <li class="#if($n.active)active visible#end">$n.title</li>
    #end
    
  3. Open /TravelHub/index in the Universal Visual Editor from the Site Browser. The correct item is marked active — the request URI is /api/v1/page/render/TravelHub/index.
  4. Click any internal navigation link in the rendered page so UVE navigates in-editor.
  5. Observe that no item is marked active — the request URI is now /api/v1/page/render/TravelHub (no /index).
  6. Navigate back to the original page. The active state does not return.
  7. View the same page on the published site — the active state is correct, because CMSFilter normalizes the URL to /TravelHub/index.

Expected: a folder-style URL resolves the same active nav item as the explicit index-page URL, in the editor exactly as it does on the front end.

Actual: every nav item reports active == false for the remainder of the editing session.

Confirmed by the reporting customer with in-template debug output — site, host identifier and $navtool.getNav(0) children were byte-identical between the working and broken renders, with requestURI the only variable that changed.

Acceptance Criteria

  • NavResultHydrated.isActive() returns the same result for /api/v1/page/render/<folder> as it does for /api/v1/page/render/<folder>/index
  • parentPath is never computed as an empty string for a valid page URL
  • The folder branch and the page branch both resolve correctly when the URI has no page segment (a nav item whose href is /folder/index is active when the URI is /folder)
  • Active state resolved through the Page API matches the active state the front end resolves for the equivalent URL
  • Navigating between pages in UVE preserves the active/expanded state of navigation menus built on $nav.active, and returning to the original page restores its original state
  • Site-root URIs (/api/v1/page/render/ and /api/v1/page/render/index) neither throw nor mark unrelated items active
  • The /api/v1/page/render prefix strip added in #17896 continues to work — no regression to that fix
  • Unit tests cover: /folder/index, /folder, /folder/, nested /a/b/c, site root, and a URL-mapped page

dotCMS Version

Reported on Current Release (dotEvergreen), dotCMS Cloud. The same symptom was reported by the same customer a year earlier on 24.12.27 LTS. The isActive() logic is unchanged on main as of 2026-08-18 — verified against the upstream file, so this is still open on latest.

Severity

High - Major functionality broken

Links

  • Freshdesk ticket 38763 — current report (Evergreen, staging)
  • Freshdesk ticket 32533 — same customer, same symptom, 24.12.27 LTS. Closed by rewriting the customer's template and recommending Professional Services; the underlying core defect was never identified.
  • #17896 — earlier fix to the same method that handled the API prefix but not the missing page segment
  • #36141 — separate UVE defect from the same investigation area (duplicate document.write re-executing inline scripts)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in dotCMS/src/main/java/com/dotcms/rendering/velocity/viewtools/navigation/NavResultHydrated.java, focusing on NavResultHydrated.isActive() and its request-URI handling. Review or run the existing navigation tests, then add coverage for folder, nested, root, and URL-mapped Page API paths. Done means folder-style and explicit index URLs produce equivalent active states without regressions to the existing prefix handling.

Written by the indexing model from the issue text.

Assessment

Tech stack
java
Domain
backend-api-design
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.