@fern-api/fdr-sdk 1.2.42-080b4f1d94 → 1.2.42-0c024f5b0d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/js/client/FdrClient.js +0 -1
  2. package/dist/js/client/FdrClient.js.map +1 -1
  3. package/dist/js/client/FdrClient.mjs +0 -1
  4. package/dist/js/client/FdrClient.mjs.map +1 -1
  5. package/dist/js/index.js +0 -1
  6. package/dist/js/index.js.map +1 -1
  7. package/dist/js/index.mjs +0 -1
  8. package/dist/js/index.mjs.map +1 -1
  9. package/dist/js/navigation/ledger-root-builder.js +33 -164
  10. package/dist/js/navigation/ledger-root-builder.js.map +1 -1
  11. package/dist/js/navigation/ledger-root-builder.mjs +33 -164
  12. package/dist/js/navigation/ledger-root-builder.mjs.map +1 -1
  13. package/dist/js/orpc-client.js +0 -1
  14. package/dist/js/orpc-client.js.map +1 -1
  15. package/dist/js/orpc-client.mjs +0 -1
  16. package/dist/js/orpc-client.mjs.map +1 -1
  17. package/dist/navigation/__test__/ledger-root-builder.metadataPreservation.test.js +4 -109
  18. package/dist/navigation/__test__/ledger-root-builder.metadataPreservation.test.js.map +1 -1
  19. package/dist/navigation/__test__/ledger-root-builder.rootChildOrder.test.js +0 -102
  20. package/dist/navigation/__test__/ledger-root-builder.rootChildOrder.test.js.map +1 -1
  21. package/dist/navigation/ledger-root-builder.d.ts.map +1 -1
  22. package/dist/navigation/ledger-root-builder.js +44 -253
  23. package/dist/navigation/ledger-root-builder.js.map +1 -1
  24. package/dist/orpc-client/docs-ledger/contract.d.ts +0 -9
  25. package/dist/orpc-client/docs-ledger/contract.d.ts.map +1 -1
  26. package/dist/orpc-client/docs-ledger/contract.js +0 -1
  27. package/dist/orpc-client/docs-ledger/contract.js.map +1 -1
  28. package/dist/tsconfig.tsbuildinfo +1 -1
  29. package/dist/types/navigation/ledger-root-builder.d.ts.map +1 -1
  30. package/dist/types/orpc-client/docs-ledger/contract.d.ts +0 -9
  31. package/dist/types/orpc-client/docs-ledger/contract.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts +0 -2
  34. package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts.map +0 -1
  35. package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.js +0 -267
  36. package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.js.map +0 -1
  37. package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts +0 -2
  38. package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts.map +0 -1
  39. package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.js +0 -306
  40. package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.js.map +0 -1
  41. package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts +0 -2
  42. package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts.map +0 -1
  43. package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.js +0 -273
  44. package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.js.map +0 -1
  45. package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts +0 -2
  46. package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts.map +0 -1
  47. package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.js +0 -234
  48. package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.js.map +0 -1
  49. package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts +0 -2
  50. package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts.map +0 -1
  51. package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js +0 -360
  52. package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js.map +0 -1
  53. package/dist/types/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts +0 -2
  54. package/dist/types/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts.map +0 -1
  55. package/dist/types/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts +0 -2
  56. package/dist/types/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts.map +0 -1
  57. package/dist/types/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts +0 -2
  58. package/dist/types/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts.map +0 -1
  59. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts +0 -2
  60. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts.map +0 -1
  61. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts +0 -2
  62. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts.map +0 -1
@@ -60,41 +60,6 @@ function metaStr(metadata, key) {
60
60
  }
61
61
  return undefined;
62
62
  }
63
- /**
64
- * V1 availability values stored in ledger metadata are lowercase (e.g. "deprecated"),
65
- * but the latest Availability type uses PascalCase (e.g. "Deprecated"). This map
66
- * normalises both formats so the badge component can look up display names correctly.
67
- */
68
- const AVAILABILITY_LOOKUP = {
69
- // V1 lowercase values (stored by docsPublishTransform from V1 navigation tree)
70
- alpha: FernNavigation.Availability.Alpha,
71
- stable: FernNavigation.Availability.Stable,
72
- "generally-available": FernNavigation.Availability.GenerallyAvailable,
73
- "in-development": FernNavigation.Availability.InDevelopment,
74
- "pre-release": FernNavigation.Availability.PreRelease,
75
- deprecated: FernNavigation.Availability.Deprecated,
76
- beta: FernNavigation.Availability.Beta,
77
- preview: FernNavigation.Availability.Preview,
78
- legacy: FernNavigation.Availability.Legacy,
79
- // Latest PascalCase values (pass-through)
80
- Alpha: FernNavigation.Availability.Alpha,
81
- Stable: FernNavigation.Availability.Stable,
82
- GenerallyAvailable: FernNavigation.Availability.GenerallyAvailable,
83
- InDevelopment: FernNavigation.Availability.InDevelopment,
84
- PreRelease: FernNavigation.Availability.PreRelease,
85
- Deprecated: FernNavigation.Availability.Deprecated,
86
- Beta: FernNavigation.Availability.Beta,
87
- Preview: FernNavigation.Availability.Preview,
88
- Legacy: FernNavigation.Availability.Legacy
89
- };
90
- /** Read an availability field from metadata, converting V1 lowercase to latest PascalCase. */
91
- function metaAvailability(metadata) {
92
- const raw = metaStr(metadata, "availability");
93
- if (raw == null) {
94
- return undefined;
95
- }
96
- return AVAILABILITY_LOOKUP[raw];
97
- }
98
63
  function metaBool(metadata, key) {
99
64
  if (metadata != null && typeof metadata === "object" && key in metadata) {
100
65
  const v = metadata[key];
@@ -233,7 +198,7 @@ function artifactToNode(ctx, route) {
233
198
  }),
234
199
  pageId: FernNavigation.PageId(pageId),
235
200
  noindex: metaBool(md, "noindex"),
236
- availability: metaAvailability(md)
201
+ availability: metaStr(md, "availability")
237
202
  };
238
203
  return node;
239
204
  }
@@ -304,7 +269,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
304
269
  endpointId: FernNavigation.EndpointId(endpointId),
305
270
  apiDefinitionId,
306
271
  isResponseStream: metaBool(md, "isResponseStream"),
307
- availability: metaAvailability(md),
272
+ availability: metaStr(md, "availability"),
308
273
  playground: undefined
309
274
  };
310
275
  }
@@ -320,7 +285,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
320
285
  method: method,
321
286
  webhookId: FernNavigation.WebhookId(webhookId),
322
287
  apiDefinitionId,
323
- availability: metaAvailability(md)
288
+ availability: metaStr(md, "availability")
324
289
  };
325
290
  }
326
291
  if (route.type === "asyncapi") {
@@ -333,7 +298,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
333
298
  ...meta,
334
299
  webSocketId: FernNavigation.WebSocketId(webSocketId),
335
300
  apiDefinitionId,
336
- availability: metaAvailability(md),
301
+ availability: metaStr(md, "availability"),
337
302
  playground: undefined
338
303
  };
339
304
  }
@@ -349,7 +314,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
349
314
  method: method,
350
315
  grpcId: FernNavigation.GrpcId(grpcId),
351
316
  apiDefinitionId,
352
- availability: metaAvailability(md)
317
+ availability: metaStr(md, "availability")
353
318
  };
354
319
  }
355
320
  if (route.type === "graphql") {
@@ -365,7 +330,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
365
330
  graphqlOperationId: FernNavigation.GraphQlOperationId(graphqlOperationId),
366
331
  graphqlOperationIds: undefined,
367
332
  apiDefinitionId,
368
- availability: metaAvailability(md),
333
+ availability: metaStr(md, "availability"),
369
334
  playground: undefined
370
335
  };
371
336
  }
@@ -569,30 +534,6 @@ function stripLeadingScopePrefix(section, scopePrefix) {
569
534
  }
570
535
  return section;
571
536
  }
572
- function computeDuplicateSiblingSortBoundaries(siblings) {
573
- const result = new Map();
574
- const byPath = new Map();
575
- for (const sibling of siblings) {
576
- if (metaStr(sibling.seg.metadata, "type") !== "section") {
577
- continue;
578
- }
579
- const group = byPath.get(sibling.seg.section) ?? [];
580
- group.push(sibling);
581
- byPath.set(sibling.seg.section, group);
582
- }
583
- for (const [, group] of byPath) {
584
- if (group.length <= 1) {
585
- continue;
586
- }
587
- for (let i = 0; i < group.length; i++) {
588
- result.set(group[i], {
589
- lo: group[i].seg.sortOrder,
590
- hi: i + 1 < group.length ? group[i + 1].seg.sortOrder : Number.MAX_SAFE_INTEGER
591
- });
592
- }
593
- }
594
- return result;
595
- }
596
537
  /**
597
538
  * Merge leaf children and section children by their original V1 tree position.
598
539
  * Leaves carry position via `displaySortOrder` (stored in nav routes), sections
@@ -656,7 +597,7 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
656
597
  noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
657
598
  collapsible: metaBool(seg.metadata, "collapsible") ?? (metaCollapsed(seg.metadata) === true ? true : undefined),
658
599
  collapsedByDefault: metaBool(seg.metadata, "collapsedByDefault") ?? (metaCollapsed(seg.metadata) === true ? true : undefined),
659
- availability: metaAvailability(seg.metadata),
600
+ availability: metaStr(seg.metadata, "availability"),
660
601
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
661
602
  children: mergeChildrenByPosition(ctx, nav, sectionChildren)
662
603
  };
@@ -666,16 +607,13 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
666
607
  * `parentSectionPath(seg.section) === parentSection`, or is an orphaned segment
667
608
  * whose nearest existing ancestor is `parentSection`.
668
609
  */
669
- function directChildApiPackages(parentSection, scopeSegs, apiDefinitionId) {
610
+ function directChildApiPackages(parentSection, scopeSegs) {
670
611
  const sectionPaths = new Set(scopeSegs.map((s) => s.seg.section));
671
612
  return scopeSegs
672
613
  .filter((s) => {
673
614
  if (metaStr(s.seg.metadata, "type") !== "apiPackage") {
674
615
  return false;
675
616
  }
676
- if (metaStr(s.seg.metadata, "apiDefinitionId") !== apiDefinitionId) {
677
- return false;
678
- }
679
617
  const parent = parentSectionPath(s.seg.section);
680
618
  if (parent === parentSection) {
681
619
  return true;
@@ -700,37 +638,11 @@ function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix)
700
638
  // API leaf types.
701
639
  const leafNodes = positionedApiLeafChildren(ctx, nav, apiDefinitionId);
702
640
  // Build nested ApiPackageNodes from direct child apiPackage segments.
703
- let childPackages = directChildApiPackages(seg.section, scopeSegs, apiDefinitionId);
704
- // When multiple API-type segments share the same section path (skipUrlSlug
705
- // group packages that collapse to their parent's section), children from
706
- // different groups get mixed together. Scope children to this segment's DFS
707
- // subtree using sortOrder boundaries to restore correct ownership.
708
- const samePathApiSiblings = scopeSegs.filter((s) => s.seg.section === seg.section &&
709
- s.seg.sortOrder !== seg.sortOrder &&
710
- API_NODE_SEGMENT_TYPES.has(metaStr(s.seg.metadata, "type") ?? ""));
711
- const hasCollapsedGroups = samePathApiSiblings.length > 0;
712
- if (hasCollapsedGroups && metaStr(seg.metadata, "type") !== "apiReference") {
713
- // This is a group package sharing its section with siblings.
714
- // Only claim children whose sortOrder falls within our DFS subtree.
715
- const nextSibling = samePathApiSiblings
716
- .filter((s) => s.seg.sortOrder > seg.sortOrder)
717
- .sort((a, b) => a.seg.sortOrder - b.seg.sortOrder)[0];
718
- const lo = seg.sortOrder;
719
- const hi = nextSibling?.seg.sortOrder ?? Number.MAX_SAFE_INTEGER;
720
- childPackages = childPackages.filter((c) => c.seg.sortOrder > lo && c.seg.sortOrder < hi);
721
- }
641
+ const childPackages = directChildApiPackages(seg.section, scopeSegs);
722
642
  const packageNodes = childPackages.map((child) => ({
723
643
  node: apiPackageChildNode(ctx, child, scopeSegs, apiDefinitionId, scopePrefix),
724
644
  childIndex: metaNum(child.seg.metadata, "childIndex")
725
645
  }));
726
- // When collapsed groups are detected at apiReference level, children's
727
- // childIndex values are relative to different parent groups and cannot be
728
- // used for interleaving. Fall back to sortOrder (DFS creation order).
729
- if (hasCollapsedGroups && metaStr(seg.metadata, "type") === "apiReference") {
730
- return [...leafNodes, ...packageNodes.map((p, i) => ({ node: p.node, pos: leafNodes.length + i }))]
731
- .sort((a, b) => a.pos - b.pos)
732
- .map((p) => p.node);
733
- }
734
646
  // Interleave by position when childIndex metadata is available
735
647
  const hasChildIndex = packageNodes.some((p) => p.childIndex != null);
736
648
  if (!hasChildIndex) {
@@ -774,7 +686,7 @@ function apiPackageChildNode(ctx, scoped, scopeSegs, parentApiDefinitionId, scop
774
686
  featureFlags: metaFeatureFlags(seg.metadata)
775
687
  }),
776
688
  apiDefinitionId: FernNavigation.ApiDefinitionId(apiDefinitionId),
777
- availability: metaAvailability(seg.metadata),
689
+ availability: metaStr(seg.metadata, "availability"),
778
690
  overviewPageId: overviewPageId != null ? FernNavigation.PageId(overviewPageId) : undefined,
779
691
  noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
780
692
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
@@ -884,10 +796,10 @@ function apiReferenceNode(ctx, scoped, scopeSegs, scopePrefix) {
884
796
  featureFlags: metaFeatureFlags(seg.metadata)
885
797
  }),
886
798
  apiDefinitionId: FernNavigation.ApiDefinitionId(apiDefinitionId),
887
- availability: metaAvailability(seg.metadata),
799
+ availability: metaStr(seg.metadata, "availability"),
888
800
  overviewPageId: overviewPageId != null ? FernNavigation.PageId(overviewPageId) : undefined,
889
801
  noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
890
- paginated: metaBool(seg.metadata, "paginated"),
802
+ paginated: true,
891
803
  showErrors: metaBool(seg.metadata, "showErrors"),
892
804
  hideTitle: metaBool(seg.metadata, "hideTitle"),
893
805
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
@@ -905,12 +817,7 @@ function attachedApiChangelogNode(ctx, scoped, scopeSegs, apiDefinitionId, scope
905
817
  }
906
818
  return metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId;
907
819
  });
908
- const apiReferenceCount = scopeSegs.filter((candidate) => {
909
- return (metaStr(candidate.seg.metadata, "type") === "apiReference" &&
910
- metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId);
911
- }).length;
912
- const changelogSeg = candidates.find((candidate) => parentSectionPath(candidate.seg.section) === seg.section) ??
913
- (apiReferenceCount === 1 ? candidates[0] : undefined);
820
+ const changelogSeg = candidates.find((candidate) => parentSectionPath(candidate.seg.section) === seg.section) ?? candidates[0];
914
821
  return changelogSeg != null ? changelogNode(ctx, changelogSeg, scopePrefix) : undefined;
915
822
  }
916
823
  function hasOwningApiReference(scoped, scopeSegs) {
@@ -918,14 +825,12 @@ function hasOwningApiReference(scoped, scopeSegs) {
918
825
  if (apiDefinitionId == null || metaStr(scoped.seg.metadata, "type") !== "changelog") {
919
826
  return false;
920
827
  }
921
- const candidates = scopeSegs.filter((candidate) => {
828
+ return scopeSegs.some((candidate) => {
922
829
  if (metaStr(candidate.seg.metadata, "type") !== "apiReference") {
923
830
  return false;
924
831
  }
925
832
  return metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId;
926
833
  });
927
- return (candidates.some((candidate) => parentSectionPath(scoped.seg.section) === candidate.seg.section) ||
928
- candidates.length === 1);
929
834
  }
930
835
  /**
931
836
  * True when this segment is an `apiPackage` that will be nested under an in-scope
@@ -1106,12 +1011,6 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1106
1011
  if (hasOwningApiReference(s, scopeSegs) || isNestedApiPackage(s, scopeSegs)) {
1107
1012
  return false;
1108
1013
  }
1109
- // Segments marked as sidebar depth 0 are V1 root-level peers and
1110
- // must never be adopted as children of another section.
1111
- const depth = metaNum(s.seg.metadata, "sidebarDepth");
1112
- if (depth === 0) {
1113
- return false;
1114
- }
1115
1014
  const parent = parentSectionPath(sec);
1116
1015
  if (parent === parentPath) {
1117
1016
  return true;
@@ -1124,7 +1023,6 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1124
1023
  return false;
1125
1024
  })
1126
1025
  .sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
1127
- const siblingBoundaries = computeDuplicateSiblingSortBoundaries(direct);
1128
1026
  return direct.map((s) => {
1129
1027
  const type = metaStr(s.seg.metadata, "type");
1130
1028
  const childIndex = metaNum(s.seg.metadata, "childIndex");
@@ -1137,11 +1035,7 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1137
1035
  }
1138
1036
  else {
1139
1037
  // section (or unrecognised) → recurse for nested sections.
1140
- const boundary = siblingBoundaries.get(s);
1141
- const childScopeSegs = boundary != null
1142
- ? scopeSegs.filter((candidate) => candidate.seg.sortOrder >= boundary.lo && candidate.seg.sortOrder < boundary.hi)
1143
- : scopeSegs;
1144
- node = sectionNode(ctx, s, buildSectionTree(ctx, childScopeSegs, s.seg.section, scopePrefix, changelogScopePrefix), scopePrefix);
1038
+ node = sectionNode(ctx, s, buildSectionTree(ctx, scopeSegs, s.seg.section, scopePrefix, changelogScopePrefix), scopePrefix);
1145
1039
  }
1146
1040
  return { node, childIndex };
1147
1041
  });
@@ -1161,26 +1055,16 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1161
1055
  function computeSiblingSortBoundaries(ordered) {
1162
1056
  const result = new Map();
1163
1057
  // Collect root-level sections grouped by their section path.
1164
- // Sections with sec==="" (skip-slug sections) are included: when multiple
1165
- // skip-slug sections exist at the root, they share path "" and need
1166
- // boundaries so each only adopts its own children.
1167
1058
  const sectionPaths = new Set(ordered.map((s) => s.seg.section));
1168
1059
  const rootSections = [];
1169
1060
  for (const s of ordered) {
1170
1061
  const type = metaStr(s.seg.metadata, "type");
1171
1062
  const sec = s.seg.section;
1172
- if (type !== "section") {
1063
+ if (type !== "section" || sec === "") {
1173
1064
  continue;
1174
1065
  }
1175
- if (sec === "") {
1176
- rootSections.push(s);
1177
- continue;
1178
- }
1179
- const depth = metaNum(s.seg.metadata, "sidebarDepth");
1180
1066
  const parent = parentSectionPath(sec);
1181
- const isRootLevel = depth === 0 ||
1182
- parent === "" ||
1183
- (!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
1067
+ const isRootLevel = parent === "" || (!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
1184
1068
  if (isRootLevel) {
1185
1069
  rootSections.push(s);
1186
1070
  }
@@ -1225,17 +1109,6 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1225
1109
  // segments. Both index into the SAME child list at write time, so they
1226
1110
  // interleave directly (e.g. [page 0, page 1, section 2…15, page 16, 17]).
1227
1111
  const annotated = [];
1228
- // ── Sibling section ownership ────────────────────────────────────────
1229
- //
1230
- // Multiple root-level sections can share the same section path (e.g.
1231
- // three sections all with path equal to the version slug). When this
1232
- // happens, buildSectionTree would incorrectly give ALL child subsections
1233
- // to each sibling. We resolve ownership via sortOrder: a section owns
1234
- // segments whose sortOrder falls between its own and the next sibling's.
1235
- //
1236
- // Computed first because hasRootSection (below) uses it to decide
1237
- // whether bounded skip-slug siblings should activate adoption.
1238
- const siblingBoundaries = computeSiblingSortBoundaries(ordered);
1239
1112
  // ── Root-section adoption flow ──────────────────────────────────────
1240
1113
  //
1241
1114
  // A scope may contain a "root section" — a segment with type "section"
@@ -1247,42 +1120,28 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1247
1120
  // │ → emit pages/links inline at their displaySortOrder
1248
1121
  // │
1249
1122
  // ├─ (B) the root section itself (sec=="", type=="section")
1250
- // │ → buildSectionTree adopts nested children (depth > 0)
1251
- // │ → emit one sectionNode wrapping the subtree
1123
+ // │ → buildSectionTree adopts ALL depth-0 children as its own
1124
+ // │ → emit one sectionNode wrapping the whole subtree
1252
1125
  // │
1253
- // └─ (C) depth-0 sections (sidebarDepth==0 or parentSectionPath=="")
1254
- // → emit directly as root children (not adopted by B)
1126
+ // └─ (C) depth-0 sections (parentSectionPath=="")
1127
+ // → SKIP already adopted by (B) via buildSectionTree
1255
1128
  //
1256
1129
  // Without a root section, depth-0 sections are emitted directly.
1130
+ const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" && s.seg.section === "");
1131
+ // ── Sibling section ownership ────────────────────────────────────────
1257
1132
  //
1258
- // A section with sec=="" AND `childIndex` in its metadata is a sibling
1259
- // inside a V1 sidebarGroup that has skipUrlSlug not a true structural
1260
- // wrapper. `walkNavSubTrees` always passes `childIndex`; direct
1261
- // sidebarRoot children (true wrappers) never receive one.
1262
- //
1263
- // Such siblings only activate adoption when they have sortOrder
1264
- // boundaries (multiple sec="" sections sharing the same path), which
1265
- // correctly scope each section's children. A *single* unbounded
1266
- // skip-slug sibling (e.g. mangopay's "Introduction") must NOT adopt
1267
- // its depth-0 peers — doing so produces spurious breadcrumb entries.
1268
- const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" &&
1269
- s.seg.section === "" &&
1270
- (metaNum(s.seg.metadata, "childIndex") == null || siblingBoundaries.has(s)));
1133
+ // Multiple root-level sections can share the same section path (e.g.
1134
+ // three sections all with path equal to the version slug). When this
1135
+ // happens, buildSectionTree would incorrectly give ALL child subsections
1136
+ // to each sibling. We resolve ownership via sortOrder: a section owns
1137
+ // segments whose sortOrder falls between its own and the next sibling's.
1138
+ const siblingBoundaries = computeSiblingSortBoundaries(ordered);
1271
1139
  for (const s of ordered) {
1272
1140
  const type = metaStr(s.seg.metadata, "type");
1273
1141
  const sec = s.seg.section;
1274
1142
  if (hasOwningApiReference(s, scopeSegs) || isNestedApiPackage(s, scopeSegs)) {
1275
1143
  continue;
1276
1144
  }
1277
- // Skip apiPackage segments that share their section path with an
1278
- // apiReference sibling (skipUrlSlug structural wrappers). Their
1279
- // children are already claimed by the apiReference via DFS ordering.
1280
- if (type === "apiPackage") {
1281
- const hasApiRefSibling = ordered.some((sib) => sib.seg.section === sec && metaStr(sib.seg.metadata, "type") === "apiReference");
1282
- if (hasApiRefSibling) {
1283
- continue;
1284
- }
1285
- }
1286
1145
  if (type === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true) {
1287
1146
  continue;
1288
1147
  }
@@ -1299,35 +1158,21 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1299
1158
  continue;
1300
1159
  }
1301
1160
  // (B) Root section wrapper — adopts depth-0 children via buildSectionTree.
1302
- // Only applies to true root wrappers (no childIndex); sibling
1303
- // sections with skipUrlSlug (have childIndex) fall through to (C).
1304
- // When multiple sections share sec==="" (skip-slug), scope children
1305
- // to each section's sortOrder range so subsections aren't duplicated.
1306
- if (type === "section" && sec === "" && hasRootSection) {
1307
- const bounds = siblingBoundaries.get(s);
1308
- const ownedSegs = bounds != null
1309
- ? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
1310
- : scopeSegs;
1311
- const sectionChildren = buildSectionTree(ctx, ownedSegs, "", scopePrefix, changelogScopePrefix);
1161
+ if (type === "section" && sec === "") {
1162
+ const sectionChildren = buildSectionTree(ctx, scopeSegs, "", scopePrefix);
1312
1163
  const node = sectionNode(ctx, s, sectionChildren, scopePrefix);
1313
1164
  annotated.push({ node, pos: metaNum(s.seg.metadata, "childIndex"), isLeaf: false });
1314
1165
  continue;
1315
1166
  }
1316
1167
  // (C) Depth-0 or orphaned sections — emit at root unless already
1317
1168
  // adopted by a root section (B).
1318
- const depth = metaNum(s.seg.metadata, "sidebarDepth");
1319
1169
  const parent = parentSectionPath(sec);
1320
- const isRootLevel = depth === 0 ||
1321
- parent === "" ||
1322
- (!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
1323
- // Skip sections already adopted by the root section wrapper (B).
1324
- // Sections with sidebarDepth===0 are true V1 root peers that
1325
- // buildSectionTree now excludes they must NOT be skipped.
1326
- // Sections with sidebarDepth>0 are nested children that SHOULD
1327
- // be adopted, so they must be skipped to avoid duplication.
1328
- // Sections without sidebarDepth (old data) are also skipped —
1329
- // they rely on path-based hierarchy in buildSectionTree.
1330
- if (isRootLevel && hasRootSection && sec !== "" && depth !== 0) {
1170
+ const isRootLevel = parent === "" || (!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
1171
+ // Skip depth-0 sections already adopted by the root section wrapper (B),
1172
+ // but NOT structural types with sec="" (skip-slug). buildSectionTree
1173
+ // filters out sec="" segments (line 1249), so they wouldn't be adopted
1174
+ // and must be emitted here directly.
1175
+ if (isRootLevel && hasRootSection && sec !== "") {
1331
1176
  continue;
1332
1177
  }
1333
1178
  if (isRootLevel) {
@@ -1345,20 +1190,7 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1345
1190
  const ownedSegs = bounds != null
1346
1191
  ? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
1347
1192
  : scopeSegs;
1348
- // A section with sec="" that reaches here (not adopted as a
1349
- // root wrapper in case B) is a skipUrlSlug sibling.
1350
- // When sortOrder boundaries exist (multiple sec="" sections),
1351
- // the scoped ownedSegs correctly limits which subsections
1352
- // each section can adopt. When there are NO boundaries
1353
- // (single sec="" section, e.g. mangopay's "Introduction"),
1354
- // buildSectionTree("") would re-adopt all depth-0 peers;
1355
- // pass an empty child list instead — its leaf artifacts are
1356
- // already in its own segment nav.
1357
- const isUnboundedSkipSlugSibling = sec === "" && metaNum(s.seg.metadata, "childIndex") != null && bounds == null;
1358
- const sectionChildren = isUnboundedSkipSlugSibling
1359
- ? []
1360
- : buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix);
1361
- node = sectionNode(ctx, s, sectionChildren, scopePrefix);
1193
+ node = sectionNode(ctx, s, buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix), scopePrefix);
1362
1194
  }
1363
1195
  // Shared/merged sections (paths not rooted in scopePrefix) must not
1364
1196
  // carry childIndex — their source-version positions would sort them
@@ -1428,6 +1260,11 @@ function groupSidebarRootChildren(ctx, children) {
1428
1260
  flushRun();
1429
1261
  out.push(child);
1430
1262
  }
1263
+ else if (child.type === "changelog") {
1264
+ flushRun();
1265
+ run.push(child);
1266
+ flushRun();
1267
+ }
1431
1268
  else {
1432
1269
  run.push(child);
1433
1270
  }
@@ -1594,7 +1431,7 @@ function buildVariantedNode(ctx, variants, scopeSegs, scopePrefix) {
1594
1431
  ? asSlug(FernNavigation.slugjoin(scopePrefix, variantIndividualSlug))
1595
1432
  : scopePrefix;
1596
1433
  const metaPointsTo = metaStr(variant.metadata, "pointsTo");
1597
- const children = buildSidebarRootChildren(ctx, variantSegs, variantSlug);
1434
+ const children = buildSidebarRootChildren(ctx, variantSegs, scopePrefix);
1598
1435
  return {
1599
1436
  type: "variant",
1600
1437
  ...withMetadataDefaults({
@@ -2130,14 +1967,6 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
2130
1967
  };
2131
1968
  return node;
2132
1969
  });
2133
- // Cross-product canonical slug deduplication (V2 parity).
2134
- // V2's migrator deduplicates pages across products via a global pageId map:
2135
- // the first product to register a pageId wins, and subsequent products'
2136
- // pages with the same pageId receive a canonicalSlug pointing to the
2137
- // canonical (default-product) page. Without this, shared content that is
2138
- // materialized under every product scope (e.g. merge-api-basics pages)
2139
- // appears as separate sitemap entries per product.
2140
- markCrossProductCanonicalSlugs(children);
2141
1970
  return {
2142
1971
  type: "productgroup",
2143
1972
  id: ctx.nodeId("productgroup"),
@@ -2146,43 +1975,5 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
2146
1975
  children
2147
1976
  };
2148
1977
  }
2149
- /**
2150
- * Deduplicate pages across products using pageId as a content-identity key.
2151
- * Pages with the same pageId in different products are the same content
2152
- * materialized per-scope; the default product's slug becomes canonical and
2153
- * non-default copies receive `canonicalSlug` so the sitemap collapses them.
2154
- *
2155
- * API leaf nodes (endpoints, websockets, etc.) are unaffected — `getPageId`
2156
- * returns undefined for those types, so product-specific API surface is
2157
- * never suppressed.
2158
- */
2159
- function markCrossProductCanonicalSlugs(productNodes) {
2160
- const pageIdToCanonical = new Map();
2161
- // Process default product first so its slugs become the canonical targets.
2162
- const sorted = [...productNodes].sort((a, b) => {
2163
- if (a.default !== b.default) {
2164
- return a.default ? -1 : 1;
2165
- }
2166
- return 0;
2167
- });
2168
- for (const product of sorted) {
2169
- FernNavigation.traverseDF(product, (child) => {
2170
- if (FernNavigation.hasMetadata(child) && FernNavigation.isPage(child)) {
2171
- const pid = FernNavigation.getPageId(child);
2172
- if (pid == null) {
2173
- return;
2174
- }
2175
- const key = `pid:${pid}`;
2176
- const canonical = pageIdToCanonical.get(key);
2177
- if (canonical != null && !product.default) {
2178
- child.canonicalSlug = canonical;
2179
- }
2180
- else if (canonical == null) {
2181
- pageIdToCanonical.set(key, child.canonicalSlug ?? child.slug);
2182
- }
2183
- }
2184
- });
2185
- }
2186
- }
2187
1978
  // (root keeps the bare "root" id; it is the single tree root.)
2188
1979
  //# sourceMappingURL=ledger-root-builder.js.map