@fern-api/fdr-sdk 1.2.42-54880bfeac → 1.2.42-6d01f91096

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 (90) 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/converters/index.js +0 -9
  6. package/dist/js/converters/index.js.map +1 -1
  7. package/dist/js/converters/index.mjs +0 -9
  8. package/dist/js/converters/index.mjs.map +1 -1
  9. package/dist/js/index.js +0 -10
  10. package/dist/js/index.js.map +1 -1
  11. package/dist/js/index.mjs +0 -10
  12. package/dist/js/index.mjs.map +1 -1
  13. package/dist/js/navigation/index.js +0 -9
  14. package/dist/js/navigation/index.js.map +1 -1
  15. package/dist/js/navigation/index.mjs +0 -9
  16. package/dist/js/navigation/index.mjs.map +1 -1
  17. package/dist/js/navigation/ledger-root-builder.js +36 -212
  18. package/dist/js/navigation/ledger-root-builder.js.map +1 -1
  19. package/dist/js/navigation/ledger-root-builder.mjs +36 -212
  20. package/dist/js/navigation/ledger-root-builder.mjs.map +1 -1
  21. package/dist/js/orpc-client.js +0 -1
  22. package/dist/js/orpc-client.js.map +1 -1
  23. package/dist/js/orpc-client.mjs +0 -1
  24. package/dist/js/orpc-client.mjs.map +1 -1
  25. package/dist/navigation/__test__/ledger-root-builder.metadataPreservation.test.js +4 -109
  26. package/dist/navigation/__test__/ledger-root-builder.metadataPreservation.test.js.map +1 -1
  27. package/dist/navigation/__test__/ledger-root-builder.rootChildOrder.test.js +0 -102
  28. package/dist/navigation/__test__/ledger-root-builder.rootChildOrder.test.js.map +1 -1
  29. package/dist/navigation/__test__/ledger-root-builder.skipSlugApiReference.test.js +0 -276
  30. package/dist/navigation/__test__/ledger-root-builder.skipSlugApiReference.test.js.map +1 -1
  31. package/dist/navigation/ledger-root-builder.d.ts.map +1 -1
  32. package/dist/navigation/ledger-root-builder.js +47 -312
  33. package/dist/navigation/ledger-root-builder.js.map +1 -1
  34. package/dist/navigation/utils/index.d.ts +0 -1
  35. package/dist/navigation/utils/index.d.ts.map +1 -1
  36. package/dist/navigation/utils/index.js +0 -1
  37. package/dist/navigation/utils/index.js.map +1 -1
  38. package/dist/orpc-client/docs-ledger/contract.d.ts +0 -9
  39. package/dist/orpc-client/docs-ledger/contract.d.ts.map +1 -1
  40. package/dist/orpc-client/docs-ledger/contract.js +0 -1
  41. package/dist/orpc-client/docs-ledger/contract.js.map +1 -1
  42. package/dist/tsconfig.tsbuildinfo +1 -1
  43. package/dist/types/navigation/ledger-root-builder.d.ts.map +1 -1
  44. package/dist/types/navigation/utils/index.d.ts +0 -1
  45. package/dist/types/navigation/utils/index.d.ts.map +1 -1
  46. package/dist/types/orpc-client/docs-ledger/contract.d.ts +0 -9
  47. package/dist/types/orpc-client/docs-ledger/contract.d.ts.map +1 -1
  48. package/package.json +1 -1
  49. package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts +0 -2
  50. package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts.map +0 -1
  51. package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.js +0 -267
  52. package/dist/navigation/__test__/ledger-root-builder.crossProductDedup.test.js.map +0 -1
  53. package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts +0 -2
  54. package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts.map +0 -1
  55. package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.js +0 -306
  56. package/dist/navigation/__test__/ledger-root-builder.ledgerParity.test.js.map +0 -1
  57. package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts +0 -2
  58. package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts.map +0 -1
  59. package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.js +0 -273
  60. package/dist/navigation/__test__/ledger-root-builder.sidebarDepth.test.js.map +0 -1
  61. package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts +0 -2
  62. package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts.map +0 -1
  63. package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.js +0 -234
  64. package/dist/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.js.map +0 -1
  65. package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts +0 -2
  66. package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts.map +0 -1
  67. package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js +0 -360
  68. package/dist/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.js.map +0 -1
  69. package/dist/navigation/utils/__test__/syntheticSectionPath.test.d.ts +0 -2
  70. package/dist/navigation/utils/__test__/syntheticSectionPath.test.d.ts.map +0 -1
  71. package/dist/navigation/utils/__test__/syntheticSectionPath.test.js +0 -29
  72. package/dist/navigation/utils/__test__/syntheticSectionPath.test.js.map +0 -1
  73. package/dist/navigation/utils/syntheticSectionPath.d.ts +0 -13
  74. package/dist/navigation/utils/syntheticSectionPath.d.ts.map +0 -1
  75. package/dist/navigation/utils/syntheticSectionPath.js +0 -20
  76. package/dist/navigation/utils/syntheticSectionPath.js.map +0 -1
  77. package/dist/types/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts +0 -2
  78. package/dist/types/navigation/__test__/ledger-root-builder.crossProductDedup.test.d.ts.map +0 -1
  79. package/dist/types/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts +0 -2
  80. package/dist/types/navigation/__test__/ledger-root-builder.ledgerParity.test.d.ts.map +0 -1
  81. package/dist/types/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts +0 -2
  82. package/dist/types/navigation/__test__/ledger-root-builder.sidebarDepth.test.d.ts.map +0 -1
  83. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts +0 -2
  84. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugGroupOrdering.test.d.ts.map +0 -1
  85. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts +0 -2
  86. package/dist/types/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.d.ts.map +0 -1
  87. package/dist/types/navigation/utils/__test__/syntheticSectionPath.test.d.ts +0 -2
  88. package/dist/types/navigation/utils/__test__/syntheticSectionPath.test.d.ts.map +0 -1
  89. package/dist/types/navigation/utils/syntheticSectionPath.d.ts +0 -13
  90. package/dist/types/navigation/utils/syntheticSectionPath.d.ts.map +0 -1
@@ -1,5 +1,4 @@
1
1
  import * as FernNavigation from "./index.js";
2
- import { stripSyntheticSegments } from "./utils/syntheticSectionPath.js";
3
2
  const STRUCTURAL_SEGMENT_TYPES = new Set([
4
3
  "section",
5
4
  "apiReference",
@@ -60,41 +59,6 @@ function metaStr(metadata, key) {
60
59
  }
61
60
  return undefined;
62
61
  }
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
62
  function metaBool(metadata, key) {
99
63
  if (metadata != null && typeof metadata === "object" && key in metadata) {
100
64
  const v = metadata[key];
@@ -233,7 +197,7 @@ function artifactToNode(ctx, route) {
233
197
  }),
234
198
  pageId: FernNavigation.PageId(pageId),
235
199
  noindex: metaBool(md, "noindex"),
236
- availability: metaAvailability(md)
200
+ availability: metaStr(md, "availability")
237
201
  };
238
202
  return node;
239
203
  }
@@ -304,7 +268,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
304
268
  endpointId: FernNavigation.EndpointId(endpointId),
305
269
  apiDefinitionId,
306
270
  isResponseStream: metaBool(md, "isResponseStream"),
307
- availability: metaAvailability(md),
271
+ availability: metaStr(md, "availability"),
308
272
  playground: undefined
309
273
  };
310
274
  }
@@ -320,7 +284,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
320
284
  method: method,
321
285
  webhookId: FernNavigation.WebhookId(webhookId),
322
286
  apiDefinitionId,
323
- availability: metaAvailability(md)
287
+ availability: metaStr(md, "availability")
324
288
  };
325
289
  }
326
290
  if (route.type === "asyncapi") {
@@ -333,7 +297,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
333
297
  ...meta,
334
298
  webSocketId: FernNavigation.WebSocketId(webSocketId),
335
299
  apiDefinitionId,
336
- availability: metaAvailability(md),
300
+ availability: metaStr(md, "availability"),
337
301
  playground: undefined
338
302
  };
339
303
  }
@@ -349,7 +313,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
349
313
  method: method,
350
314
  grpcId: FernNavigation.GrpcId(grpcId),
351
315
  apiDefinitionId,
352
- availability: metaAvailability(md)
316
+ availability: metaStr(md, "availability")
353
317
  };
354
318
  }
355
319
  if (route.type === "graphql") {
@@ -365,7 +329,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
365
329
  graphqlOperationId: FernNavigation.GraphQlOperationId(graphqlOperationId),
366
330
  graphqlOperationIds: undefined,
367
331
  apiDefinitionId,
368
- availability: metaAvailability(md),
332
+ availability: metaStr(md, "availability"),
369
333
  playground: undefined
370
334
  };
371
335
  }
@@ -553,8 +517,7 @@ function structuralNodeSlug(ctx, overviewFullPath, section, scopePrefix) {
553
517
  // basepath-absolute (it is the version/product/tab node's slug), so join it
554
518
  // directly rather than going through `contentSlug` (which would re-prefix
555
519
  // the basepath).
556
- const cleanSection = stripSyntheticSegments(section);
557
- return cleanSection !== "" ? FernNavigation.slugjoin(scopePrefix, cleanSection) : scopePrefix;
520
+ return section !== "" ? FernNavigation.slugjoin(scopePrefix, section) : scopePrefix;
558
521
  }
559
522
  function stripLeadingScopePrefix(section, scopePrefix) {
560
523
  const prefix = scopePrefix;
@@ -569,30 +532,6 @@ function stripLeadingScopePrefix(section, scopePrefix) {
569
532
  }
570
533
  return section;
571
534
  }
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
535
  /**
597
536
  * Merge leaf children and section children by their original V1 tree position.
598
537
  * Leaves carry position via `displaySortOrder` (stored in nav routes), sections
@@ -656,7 +595,7 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
656
595
  noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
657
596
  collapsible: metaBool(seg.metadata, "collapsible") ?? (metaCollapsed(seg.metadata) === true ? true : undefined),
658
597
  collapsedByDefault: metaBool(seg.metadata, "collapsedByDefault") ?? (metaCollapsed(seg.metadata) === true ? true : undefined),
659
- availability: metaAvailability(seg.metadata),
598
+ availability: metaStr(seg.metadata, "availability"),
660
599
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
661
600
  children: mergeChildrenByPosition(ctx, nav, sectionChildren)
662
601
  };
@@ -666,16 +605,13 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
666
605
  * `parentSectionPath(seg.section) === parentSection`, or is an orphaned segment
667
606
  * whose nearest existing ancestor is `parentSection`.
668
607
  */
669
- function directChildApiPackages(parentSection, scopeSegs, apiDefinitionId) {
608
+ function directChildApiPackages(parentSection, scopeSegs) {
670
609
  const sectionPaths = new Set(scopeSegs.map((s) => s.seg.section));
671
610
  return scopeSegs
672
611
  .filter((s) => {
673
612
  if (metaStr(s.seg.metadata, "type") !== "apiPackage") {
674
613
  return false;
675
614
  }
676
- if (metaStr(s.seg.metadata, "apiDefinitionId") !== apiDefinitionId) {
677
- return false;
678
- }
679
615
  const parent = parentSectionPath(s.seg.section);
680
616
  if (parent === parentSection) {
681
617
  return true;
@@ -700,37 +636,11 @@ function buildApiChildren(ctx, scoped, scopeSegs, apiDefinitionId, scopePrefix)
700
636
  // API leaf types.
701
637
  const leafNodes = positionedApiLeafChildren(ctx, nav, apiDefinitionId);
702
638
  // 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
- }
639
+ const childPackages = directChildApiPackages(seg.section, scopeSegs);
722
640
  const packageNodes = childPackages.map((child) => ({
723
641
  node: apiPackageChildNode(ctx, child, scopeSegs, apiDefinitionId, scopePrefix),
724
642
  childIndex: metaNum(child.seg.metadata, "childIndex")
725
643
  }));
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
644
  // Interleave by position when childIndex metadata is available
735
645
  const hasChildIndex = packageNodes.some((p) => p.childIndex != null);
736
646
  if (!hasChildIndex) {
@@ -774,7 +684,7 @@ function apiPackageChildNode(ctx, scoped, scopeSegs, parentApiDefinitionId, scop
774
684
  featureFlags: metaFeatureFlags(seg.metadata)
775
685
  }),
776
686
  apiDefinitionId: FernNavigation.ApiDefinitionId(apiDefinitionId),
777
- availability: metaAvailability(seg.metadata),
687
+ availability: metaStr(seg.metadata, "availability"),
778
688
  overviewPageId: overviewPageId != null ? FernNavigation.PageId(overviewPageId) : undefined,
779
689
  noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
780
690
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
@@ -884,10 +794,10 @@ function apiReferenceNode(ctx, scoped, scopeSegs, scopePrefix) {
884
794
  featureFlags: metaFeatureFlags(seg.metadata)
885
795
  }),
886
796
  apiDefinitionId: FernNavigation.ApiDefinitionId(apiDefinitionId),
887
- availability: metaAvailability(seg.metadata),
797
+ availability: metaStr(seg.metadata, "availability"),
888
798
  overviewPageId: overviewPageId != null ? FernNavigation.PageId(overviewPageId) : undefined,
889
799
  noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
890
- paginated: metaBool(seg.metadata, "paginated"),
800
+ paginated: true,
891
801
  showErrors: metaBool(seg.metadata, "showErrors"),
892
802
  hideTitle: metaBool(seg.metadata, "hideTitle"),
893
803
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
@@ -905,12 +815,7 @@ function attachedApiChangelogNode(ctx, scoped, scopeSegs, apiDefinitionId, scope
905
815
  }
906
816
  return metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId;
907
817
  });
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);
818
+ const changelogSeg = candidates.find((candidate) => parentSectionPath(candidate.seg.section) === seg.section) ?? candidates[0];
914
819
  return changelogSeg != null ? changelogNode(ctx, changelogSeg, scopePrefix) : undefined;
915
820
  }
916
821
  function hasOwningApiReference(scoped, scopeSegs) {
@@ -918,68 +823,12 @@ function hasOwningApiReference(scoped, scopeSegs) {
918
823
  if (apiDefinitionId == null || metaStr(scoped.seg.metadata, "type") !== "changelog") {
919
824
  return false;
920
825
  }
921
- const candidates = scopeSegs.filter((candidate) => {
826
+ return scopeSegs.some((candidate) => {
922
827
  if (metaStr(candidate.seg.metadata, "type") !== "apiReference") {
923
828
  return false;
924
829
  }
925
830
  return metaStr(candidate.seg.metadata, "apiDefinitionId") === apiDefinitionId;
926
831
  });
927
- return (candidates.some((candidate) => parentSectionPath(scoped.seg.section) === candidate.seg.section) ||
928
- candidates.length === 1);
929
- }
930
- /**
931
- * True when this segment is an `apiPackage` that will be nested under an in-scope
932
- * `apiReference` for the same API by {@link buildApiChildren}, and therefore must
933
- * NOT also be emitted as a standalone root/section node.
934
- *
935
- * Without this guard, a skip-slug apiReference (section "") leaves its resource
936
- * subpackages with `parentSectionPath === ""`, so they look root-level and get
937
- * emitted a second time as always-expanded top-level apiReference nodes —
938
- * duplicating every resource and endpoint in the sidebar (the "67 apiReference
939
- * nodes vs 1" API-reference regression).
940
- *
941
- * Reachability mirrors {@link directChildApiPackages}: walk up the section-path
942
- * chain (hopping to nearestExistingAncestor when a parent section is absent)
943
- * until we reach an apiReference's section. If no apiReference is an ancestor the
944
- * package is left where it is, so it can never be dropped.
945
- */
946
- function isNestedApiPackage(scoped, scopeSegs) {
947
- if (metaStr(scoped.seg.metadata, "type") !== "apiPackage") {
948
- return false;
949
- }
950
- const apiDefinitionId = metaStr(scoped.seg.metadata, "apiDefinitionId");
951
- if (apiDefinitionId == null) {
952
- return false;
953
- }
954
- const apiReferenceSections = new Set(scopeSegs
955
- .filter((s) => metaStr(s.seg.metadata, "type") === "apiReference" &&
956
- metaStr(s.seg.metadata, "apiDefinitionId") === apiDefinitionId)
957
- .map((s) => s.seg.section));
958
- if (apiReferenceSections.size === 0) {
959
- return false;
960
- }
961
- const sectionPaths = new Set(scopeSegs.map((s) => s.seg.section));
962
- const resolveParent = (section) => {
963
- const parent = parentSectionPath(section);
964
- if (parent === "" || sectionPaths.has(parent)) {
965
- return parent;
966
- }
967
- return nearestExistingAncestor(section, sectionPaths);
968
- };
969
- let current = scoped.seg.section;
970
- const seen = new Set([current]);
971
- // Walk up until we hit an owning apiReference (nested → true) or run out.
972
- for (;;) {
973
- const parent = resolveParent(current);
974
- if (apiReferenceSections.has(parent)) {
975
- return true;
976
- }
977
- if (parent === "" || seen.has(parent)) {
978
- return false;
979
- }
980
- seen.add(parent);
981
- current = parent;
982
- }
983
832
  }
984
833
  function changelogNode(ctx, scoped, scopePrefix) {
985
834
  const { seg, nav } = scoped;
@@ -1103,13 +952,7 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1103
952
  if (sec === "" || type === "tabRoot") {
1104
953
  return false;
1105
954
  }
1106
- if (hasOwningApiReference(s, scopeSegs) || isNestedApiPackage(s, scopeSegs)) {
1107
- return false;
1108
- }
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) {
955
+ if (hasOwningApiReference(s, scopeSegs)) {
1113
956
  return false;
1114
957
  }
1115
958
  const parent = parentSectionPath(sec);
@@ -1124,7 +967,6 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1124
967
  return false;
1125
968
  })
1126
969
  .sort((a, b) => a.seg.sortOrder - b.seg.sortOrder);
1127
- const siblingBoundaries = computeDuplicateSiblingSortBoundaries(direct);
1128
970
  return direct.map((s) => {
1129
971
  const type = metaStr(s.seg.metadata, "type");
1130
972
  const childIndex = metaNum(s.seg.metadata, "childIndex");
@@ -1137,11 +979,7 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1137
979
  }
1138
980
  else {
1139
981
  // 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);
982
+ node = sectionNode(ctx, s, buildSectionTree(ctx, scopeSegs, s.seg.section, scopePrefix, changelogScopePrefix), scopePrefix);
1145
983
  }
1146
984
  return { node, childIndex };
1147
985
  });
@@ -1161,26 +999,16 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1161
999
  function computeSiblingSortBoundaries(ordered) {
1162
1000
  const result = new Map();
1163
1001
  // 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
1002
  const sectionPaths = new Set(ordered.map((s) => s.seg.section));
1168
1003
  const rootSections = [];
1169
1004
  for (const s of ordered) {
1170
1005
  const type = metaStr(s.seg.metadata, "type");
1171
1006
  const sec = s.seg.section;
1172
- if (type !== "section") {
1173
- continue;
1174
- }
1175
- if (sec === "") {
1176
- rootSections.push(s);
1007
+ if (type !== "section" || sec === "") {
1177
1008
  continue;
1178
1009
  }
1179
- const depth = metaNum(s.seg.metadata, "sidebarDepth");
1180
1010
  const parent = parentSectionPath(sec);
1181
- const isRootLevel = depth === 0 ||
1182
- parent === "" ||
1183
- (!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
1011
+ const isRootLevel = parent === "" || (!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
1184
1012
  if (isRootLevel) {
1185
1013
  rootSections.push(s);
1186
1014
  }
@@ -1225,17 +1053,6 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1225
1053
  // segments. Both index into the SAME child list at write time, so they
1226
1054
  // interleave directly (e.g. [page 0, page 1, section 2…15, page 16, 17]).
1227
1055
  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
1056
  // ── Root-section adoption flow ──────────────────────────────────────
1240
1057
  //
1241
1058
  // A scope may contain a "root section" — a segment with type "section"
@@ -1247,42 +1064,28 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1247
1064
  // │ → emit pages/links inline at their displaySortOrder
1248
1065
  // │
1249
1066
  // ├─ (B) the root section itself (sec=="", type=="section")
1250
- // │ → buildSectionTree adopts nested children (depth > 0)
1251
- // │ → emit one sectionNode wrapping the subtree
1067
+ // │ → buildSectionTree adopts ALL depth-0 children as its own
1068
+ // │ → emit one sectionNode wrapping the whole subtree
1252
1069
  // │
1253
- // └─ (C) depth-0 sections (sidebarDepth==0 or parentSectionPath=="")
1254
- // → emit directly as root children (not adopted by B)
1070
+ // └─ (C) depth-0 sections (parentSectionPath=="")
1071
+ // → SKIP already adopted by (B) via buildSectionTree
1255
1072
  //
1256
1073
  // Without a root section, depth-0 sections are emitted directly.
1074
+ const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" && s.seg.section === "");
1075
+ // ── Sibling section ownership ────────────────────────────────────────
1257
1076
  //
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)));
1077
+ // Multiple root-level sections can share the same section path (e.g.
1078
+ // three sections all with path equal to the version slug). When this
1079
+ // happens, buildSectionTree would incorrectly give ALL child subsections
1080
+ // to each sibling. We resolve ownership via sortOrder: a section owns
1081
+ // segments whose sortOrder falls between its own and the next sibling's.
1082
+ const siblingBoundaries = computeSiblingSortBoundaries(ordered);
1271
1083
  for (const s of ordered) {
1272
1084
  const type = metaStr(s.seg.metadata, "type");
1273
1085
  const sec = s.seg.section;
1274
- if (hasOwningApiReference(s, scopeSegs) || isNestedApiPackage(s, scopeSegs)) {
1086
+ if (hasOwningApiReference(s, scopeSegs)) {
1275
1087
  continue;
1276
1088
  }
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
1089
  if (type === "tabRoot" && metaBool(s.seg.metadata, "landingPage") === true) {
1287
1090
  continue;
1288
1091
  }
@@ -1299,35 +1102,21 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1299
1102
  continue;
1300
1103
  }
1301
1104
  // (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);
1105
+ if (type === "section" && sec === "") {
1106
+ const sectionChildren = buildSectionTree(ctx, scopeSegs, "", scopePrefix);
1312
1107
  const node = sectionNode(ctx, s, sectionChildren, scopePrefix);
1313
1108
  annotated.push({ node, pos: metaNum(s.seg.metadata, "childIndex"), isLeaf: false });
1314
1109
  continue;
1315
1110
  }
1316
1111
  // (C) Depth-0 or orphaned sections — emit at root unless already
1317
1112
  // adopted by a root section (B).
1318
- const depth = metaNum(s.seg.metadata, "sidebarDepth");
1319
1113
  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) {
1114
+ const isRootLevel = parent === "" || (!sectionPaths.has(parent) && nearestExistingAncestor(sec, sectionPaths) === "");
1115
+ // Skip depth-0 sections already adopted by the root section wrapper (B),
1116
+ // but NOT structural types with sec="" (skip-slug). buildSectionTree
1117
+ // filters out sec="" segments (line 1249), so they wouldn't be adopted
1118
+ // and must be emitted here directly.
1119
+ if (isRootLevel && hasRootSection && sec !== "") {
1331
1120
  continue;
1332
1121
  }
1333
1122
  if (isRootLevel) {
@@ -1345,20 +1134,7 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1345
1134
  const ownedSegs = bounds != null
1346
1135
  ? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
1347
1136
  : 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);
1137
+ node = sectionNode(ctx, s, buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix), scopePrefix);
1362
1138
  }
1363
1139
  // Shared/merged sections (paths not rooted in scopePrefix) must not
1364
1140
  // carry childIndex — their source-version positions would sort them
@@ -1428,6 +1204,11 @@ function groupSidebarRootChildren(ctx, children) {
1428
1204
  flushRun();
1429
1205
  out.push(child);
1430
1206
  }
1207
+ else if (child.type === "changelog") {
1208
+ flushRun();
1209
+ run.push(child);
1210
+ flushRun();
1211
+ }
1431
1212
  else {
1432
1213
  run.push(child);
1433
1214
  }
@@ -1594,7 +1375,7 @@ function buildVariantedNode(ctx, variants, scopeSegs, scopePrefix) {
1594
1375
  ? asSlug(FernNavigation.slugjoin(scopePrefix, variantIndividualSlug))
1595
1376
  : scopePrefix;
1596
1377
  const metaPointsTo = metaStr(variant.metadata, "pointsTo");
1597
- const children = buildSidebarRootChildren(ctx, variantSegs, variantSlug);
1378
+ const children = buildSidebarRootChildren(ctx, variantSegs, scopePrefix);
1598
1379
  return {
1599
1380
  type: "variant",
1600
1381
  ...withMetadataDefaults({
@@ -2130,14 +1911,6 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
2130
1911
  };
2131
1912
  return node;
2132
1913
  });
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
1914
  return {
2142
1915
  type: "productgroup",
2143
1916
  id: ctx.nodeId("productgroup"),
@@ -2146,43 +1919,5 @@ function buildProductGroup(ctx, products, scoped, rootSlug, skipSharedSectionMer
2146
1919
  children
2147
1920
  };
2148
1921
  }
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
1922
  // (root keeps the bare "root" id; it is the single tree root.)
2188
1923
  //# sourceMappingURL=ledger-root-builder.js.map