Skip to content

Fix navigator losing merged-archive root on direct page load (#1024) - #1032

Open
VictorPuga wants to merge 4 commits into
swiftlang:mainfrom
victorpuga-forks:navigator-fix
Open

VictorPuga wants to merge 4 commits into
swiftlang:mainfrom
victorpuga-forks:navigator-fix

Conversation

@VictorPuga

Copy link
Copy Markdown
Contributor

Bug/issue #, if applicable: #1024

Summary

On a hard/direct load of a page belonging to a member module of a merged archive (e.g. /documentation/alphakit/), the navigator dropped the synthesized package root and all sibling modules, showing only the module that matched the current URL. This happened because extractRootModule flattened the root module together with its nested module children into a single pool before matching candidates against the URL path, so a member module's own path matched and outranked its ancestor root. This PR makes extractRootModule return the sole top-level module immediately when only one is provided, and only fall back to searching nested descendants once no top-level candidate matches the URL, so a descendant can never outrank its own ancestor root. Users navigating directly to a member module's documentation page will now see the full merged-archive navigator, including the package root and sibling modules, matching the behavior seen when navigating from the root page.

Dependencies

None.

Testing

Steps:

  1. Build or load a merged-archive documentation set where a package root module has multiple child modules (use se sample steps in Navigator loses the merged-archive root (package tree) on direct page load since Swift 6.3 renderers #1024 (comment) or see the fixture used in the new test spec: a Spec SDK root with AlphaKit and BetaKit children, each with their own article).
  2. Navigate directly to a page nested under one of the member modules (e.g. /documentation/alphakit/alphaarticle) as a hard/first load, not a client-side navigation from the root.
  3. Confirm the navigator still lists the package root and both AlphaKit and BetaKit as siblings, instead of only showing AlphaKit.

Checklist

Make sure you check off the following items. If they cannot be completed, provide a reason.

  • Added tests
  • Ran npm test, and it succeeded
  • Updated documentation if necessary

…ng#1024)

extractRootModule flattened a root module together with its nested
module children into one pool, then matched candidates against the
current URL path. On a hard load of a member module's page (e.g.
/documentation/alphakit/), that module's own path matched the URL
and outranked its ancestor, so the synthesized package root and
sibling modules were dropped from the navigator.

Return the sole top-level module immediately when only one is
provided, and only search nested descendants once no top-level
candidate matches the URL, so a descendant can never outrank its
own ancestor root.

@marinaaisa marinaaisa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @VictorPuga ! thank you for your PR, I've been testing it and it enables DocC’s combined documentation within DocC Render.
Thank you for fixing the regression that reverted the intended functionality!

I added some comments to simplify the code. Thanks!

Comment thread src/utils/navigatorData.js Outdated
// most of the time, it is expected that `data` always has a single item
// that represents the top-level root node of the navigation tree
//
const matchesRootPath = module => module.path.toLowerCase().endsWith(rootPath.toLowerCase());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const matchesRootPath = module => module.path.toLowerCase().endsWith(rootPath.toLowerCase());
const matchesRootPath = ({ path }) => path.toLowerCase().endsWith(rootPath.toLowerCase());

Comment thread src/utils/navigatorData.js Outdated
Comment on lines +209 to +218
const topLevelMatch = modules.find(matchesRootPath);
if (topLevelMatch) return topLevelMatch;

// otherwise, a matching root may be nested within one of the top-level
// modules—only fall back to searching nested modules once none of the
// top-level candidates themselves match, so a nested module can never
// outrank its own ancestor root
//
// otherwise, the first provided node will be used
return flattenedModules.length === 1 ? flattenedModules[0] : (flattenedModules.find(module => (
module.path.toLowerCase().endsWith(rootPath.toLowerCase())
)) ?? flattenedModules[0]);
// if nothing matches at all, the first provided module will be used
return flattenModules(modules).find(matchesRootPath) ?? modules[0];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const topLevelMatch = modules.find(matchesRootPath);
if (topLevelMatch) return topLevelMatch;
// otherwise, a matching root may be nested within one of the top-level
// modules—only fall back to searching nested modules once none of the
// top-level candidates themselves match, so a nested module can never
// outrank its own ancestor root
//
// otherwise, the first provided node will be used
return flattenedModules.length === 1 ? flattenedModules[0] : (flattenedModules.find(module => (
module.path.toLowerCase().endsWith(rootPath.toLowerCase())
)) ?? flattenedModules[0]);
// if nothing matches at all, the first provided module will be used
return flattenModules(modules).find(matchesRootPath) ?? modules[0];
// in rare cases multiple top-level roots are provided
// prefer the one whose path matches the current URL, then a matching
// nested module, and finally the first module
return modules.find(matchesRootPath)
?? flattenModules(modules).find(matchesRootPath)
?? modules[0];

Comment thread src/utils/navigatorData.js Outdated
Comment on lines +206 to +208
// there may be rare, unexpected scenarios where multiple top-level root
// nodes are provide for some reason—if that happens, we would prefer the one
// with a path that most closely resembles the current URL path
// nodes are provided for some reason—if that happens, we would prefer the
// one with a path that most closely resembles the current URL path

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove these comments.

VictorPuga and others added 2 commits September 30, 2026 11:45
Destructure path in matchesRootPath, collapse the fallback chain into a
single return, and trim the redundant comments per review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@VictorPuga

Copy link
Copy Markdown
Contributor Author

Comments were addressed

@heckj

heckj commented Oct 1, 2026

Copy link
Copy Markdown
Member

Wow - thank you both!

@VictorPuga
VictorPuga requested a review from marinaaisa October 1, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants