@fern-api/fdr-sdk 1.2.42-0c024f5b0d → 1.2.42-1fb0ba4870

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.
@@ -0,0 +1,256 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import * as FernNavigation from "../index.js";
3
+ import { buildFullRootFromSegments } from "../ledger-root-builder.js";
4
+ /**
5
+ * Tests for the case where a section with skipUrlSlug (section="") sits inside
6
+ * a sidebarGroup alongside sibling sections. The `childIndex` metadata field
7
+ * (set by `walkNavSubTrees` on the write side) distinguishes these from true
8
+ * root-section wrappers (direct sidebarRoot children with no childIndex).
9
+ *
10
+ * Without the fix, the builder treats ANY section="" segment as a root wrapper,
11
+ * incorrectly nesting all depth-0 siblings under it and producing spurious
12
+ * breadcrumb entries (e.g. "Introduction > Echo" instead of just "Echo").
13
+ *
14
+ * Real-world repro: mangopay-ledger-test, where the "Introduction" section
15
+ * has skipUrlSlug (slug == tab slug) and shares a sidebarGroup with ~10 peer
16
+ * sections (Users, Echo, SCA, Payouts, …).
17
+ */
18
+ function detail(opts) {
19
+ return {
20
+ id: opts.id,
21
+ displayName: opts.name,
22
+ icon: null,
23
+ sortOrder: 0,
24
+ hidden: false,
25
+ viewers: [],
26
+ orphaned: false,
27
+ featureFlags: [],
28
+ metadata: { slug: opts.slug, default: false, pointsTo: opts.pointsTo }
29
+ };
30
+ }
31
+ function seg(opts) {
32
+ return {
33
+ segmentId: opts.segmentHash,
34
+ segmentHash: opts.segmentHash,
35
+ section: opts.section,
36
+ locale: "en",
37
+ sortOrder: opts.sortOrder ?? 0,
38
+ hidden: false,
39
+ metadata: {
40
+ type: opts.type,
41
+ ...(opts.title != null ? { title: opts.title } : {}),
42
+ ...(opts.childIndex != null ? { childIndex: opts.childIndex } : {})
43
+ },
44
+ product: null,
45
+ version: null,
46
+ variant: null,
47
+ tab: opts.tab ?? null
48
+ };
49
+ }
50
+ function markdownRoute(fullPath, title, pageId) {
51
+ return { type: "markdown", fullPath, hidden: false, metadata: { title, pageId } };
52
+ }
53
+ function buildRoot(opts) {
54
+ return buildFullRootFromSegments({
55
+ basePath: opts.basePath,
56
+ title: "Test",
57
+ segments: opts.segments,
58
+ navBySegment: opts.navBySegment
59
+ });
60
+ }
61
+ function findSectionNodes(root) {
62
+ const sections = [];
63
+ FernNavigation.traverseDF(root, (node) => {
64
+ if (node.type === "section") {
65
+ sections.push(node);
66
+ }
67
+ });
68
+ return sections;
69
+ }
70
+ function findSidebarRootChildren(root) {
71
+ let sidebarChildren = [];
72
+ FernNavigation.traverseDF(root, (node) => {
73
+ if (node.type === "sidebarRoot" && sidebarChildren.length === 0) {
74
+ sidebarChildren = [...node.children];
75
+ }
76
+ });
77
+ return sidebarChildren;
78
+ }
79
+ describe("buildFullRootFromSegments — skipUrlSlug sibling sections are NOT adopted as root-section children", () => {
80
+ // Reproduces the mangopay-ledger-test bug: "Introduction" (section="",
81
+ // childIndex=0) shares a sidebarGroup with "Echo" and "Users".
82
+ // Without the fix, "Echo" and "Users" are incorrectly nested inside
83
+ // "Introduction", adding a spurious "Introduction" breadcrumb to every
84
+ // page under Echo and Users.
85
+ const tab = detail({
86
+ id: "t-guides",
87
+ name: "Guides",
88
+ slug: "guides",
89
+ pointsTo: "guides/e-wallet-system"
90
+ });
91
+ const tabRoot = seg({
92
+ segmentHash: "s-tabroot",
93
+ section: "",
94
+ type: "tabRoot",
95
+ tab
96
+ });
97
+ // "Introduction" section with skipUrlSlug — its slug matches the tab slug,
98
+ // so the write side assigns section="" with childIndex=0 (from walkNavSubTrees).
99
+ const introSection = seg({
100
+ segmentHash: "s-intro",
101
+ section: "",
102
+ type: "section",
103
+ title: "Introduction",
104
+ sortOrder: 1,
105
+ childIndex: 0,
106
+ tab
107
+ });
108
+ // Sibling sections — these should NOT be nested inside Introduction.
109
+ const echoSection = seg({
110
+ segmentHash: "s-echo",
111
+ section: "echo",
112
+ type: "section",
113
+ title: "Echo",
114
+ sortOrder: 2,
115
+ childIndex: 1,
116
+ tab
117
+ });
118
+ const usersSection = seg({
119
+ segmentHash: "s-users",
120
+ section: "users",
121
+ type: "section",
122
+ title: "Users",
123
+ sortOrder: 3,
124
+ childIndex: 2,
125
+ tab
126
+ });
127
+ const segments = [tabRoot, introSection, echoSection, usersSection];
128
+ const navBySegment = new Map([
129
+ ["s-tabroot", []],
130
+ ["s-intro", [markdownRoute("guides/e-wallet-system", "E-wallet system", "pg-ewallet")]],
131
+ [
132
+ "s-echo",
133
+ [
134
+ markdownRoute("guides/echo", "Mangopay Echo", "pg-echo"),
135
+ markdownRoute("guides/echo/reconciliation", "Reconciliation", "pg-recon")
136
+ ]
137
+ ],
138
+ ["s-users", [markdownRoute("guides/users/types", "Types", "pg-user-types")]]
139
+ ]);
140
+ it("sections with childIndex remain siblings, not nested under section='' segment", () => {
141
+ const root = buildRoot({ basePath: "", segments, navBySegment });
142
+ const sections = findSectionNodes(root);
143
+ const introSec = sections.find((s) => s.title === "Introduction");
144
+ const echoSec = sections.find((s) => s.title === "Echo");
145
+ const usersSec = sections.find((s) => s.title === "Users");
146
+ expect(introSec).toBeDefined();
147
+ expect(echoSec).toBeDefined();
148
+ expect(usersSec).toBeDefined();
149
+ // Echo and Users must NOT be children of Introduction.
150
+ const introChildren = (introSec?.children ?? [])
151
+ .filter((c) => c.type === "section")
152
+ .map((c) => c.title);
153
+ expect(introChildren).not.toContain("Echo");
154
+ expect(introChildren).not.toContain("Users");
155
+ // All three should be at the sidebar root level (possibly inside sidebarGroups).
156
+ const sidebarChildren = findSidebarRootChildren(root);
157
+ const topLevelTitles = [];
158
+ for (const child of sidebarChildren) {
159
+ if (child.type === "section") {
160
+ topLevelTitles.push(child.title);
161
+ }
162
+ else if (child.type === "sidebarGroup") {
163
+ for (const gc of child.children) {
164
+ if (gc.type === "section") {
165
+ topLevelTitles.push(gc.title);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ expect(topLevelTitles).toContain("Introduction");
171
+ expect(topLevelTitles).toContain("Echo");
172
+ expect(topLevelTitles).toContain("Users");
173
+ });
174
+ it("breadcrumb for a page inside Echo does NOT include Introduction", () => {
175
+ const root = buildRoot({ basePath: "", segments, navBySegment });
176
+ FernNavigation.utils.mutableUpdatePointsTo(root);
177
+ const found = FernNavigation.utils.findNode(root, FernNavigation.Slug("guides/echo"));
178
+ expect(found.type).toBe("found");
179
+ if (found.type !== "found") {
180
+ return;
181
+ }
182
+ const breadcrumbTitles = found.breadcrumb.map((b) => b.title);
183
+ expect(breadcrumbTitles).toContain("Echo");
184
+ expect(breadcrumbTitles).not.toContain("Introduction");
185
+ });
186
+ it("breadcrumb for a page inside Users does NOT include Introduction", () => {
187
+ const root = buildRoot({ basePath: "", segments, navBySegment });
188
+ FernNavigation.utils.mutableUpdatePointsTo(root);
189
+ const found = FernNavigation.utils.findNode(root, FernNavigation.Slug("guides/users/types"));
190
+ expect(found.type).toBe("found");
191
+ if (found.type !== "found") {
192
+ return;
193
+ }
194
+ const breadcrumbTitles = found.breadcrumb.map((b) => b.title);
195
+ expect(breadcrumbTitles).toContain("Users");
196
+ expect(breadcrumbTitles).not.toContain("Introduction");
197
+ });
198
+ });
199
+ describe("buildFullRootFromSegments — true root-section wrappers (no childIndex) still work", () => {
200
+ // Regression guard: when a section has section="" WITHOUT childIndex,
201
+ // it IS a true root wrapper and should adopt depth-0 children.
202
+ const tab = detail({
203
+ id: "t-docs",
204
+ name: "Docs",
205
+ slug: "docs",
206
+ pointsTo: "docs/overview/intro"
207
+ });
208
+ it("adopts depth-0 children when childIndex is absent", () => {
209
+ const root = buildRoot({
210
+ basePath: "",
211
+ segments: [
212
+ // True root section wrapper (no childIndex)
213
+ seg({
214
+ segmentHash: "s-root",
215
+ section: "",
216
+ type: "section",
217
+ title: "Overview",
218
+ tab
219
+ }),
220
+ seg({
221
+ segmentHash: "s-gs",
222
+ section: "get-started",
223
+ type: "section",
224
+ title: "Get Started",
225
+ sortOrder: 1,
226
+ tab
227
+ }),
228
+ seg({
229
+ segmentHash: "s-adv",
230
+ section: "advanced",
231
+ type: "section",
232
+ title: "Advanced",
233
+ sortOrder: 2,
234
+ tab
235
+ })
236
+ ],
237
+ navBySegment: new Map([
238
+ ["s-root", [markdownRoute("docs/overview/intro", "Intro", "pg-intro")]],
239
+ ["s-gs", [markdownRoute("docs/overview/get-started/setup", "Setup", "pg-setup")]],
240
+ ["s-adv", [markdownRoute("docs/overview/advanced/tips", "Tips", "pg-tips")]]
241
+ ])
242
+ });
243
+ const sections = findSectionNodes(root);
244
+ const overviewSec = sections.find((s) => s.title === "Overview");
245
+ expect(overviewSec).toBeDefined();
246
+ // Child sections should be nested under Overview (root wrapper behavior)
247
+ const childTitles = (overviewSec?.children ?? [])
248
+ .filter((c) => c.type === "section")
249
+ .map((c) => c.title);
250
+ expect(childTitles).toContain("Get Started");
251
+ expect(childTitles).toContain("Advanced");
252
+ // 3 total sections (Overview + Get Started + Advanced), not duplicated
253
+ expect(sections.length).toBe(3);
254
+ });
255
+ });
256
+ //# sourceMappingURL=ledger-root-builder.skipSlugSiblingSection.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledger-root-builder.skipSlugSiblingSection.test.js","sourceRoot":"","sources":["../../../src/navigation/__test__/ledger-root-builder.skipSlugSiblingSection.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ledger-root-builder.d.ts","sourceRoot":"","sources":["../../src/navigation/ledger-root-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACnG,OAAO,KAAK,cAAc,MAAM,YAAY,CAAC;AAG7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,KAAK,OAAO,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AA6B9C,MAAM,WAAW,gBAAgB;IAC7B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,6DAA6D;IAC7D,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtC;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACpC;AAID;;;;;;GAMG;AACH,UAAU,YAAY;IAClB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,cAAc,CAAC,MAAM,CAAC;IAC/C,kFAAkF;IAClF,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,cAAc,CAAC,IAAI,CAAC;CAC9D;AAED,2CAA2C;AAC3C,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAuBjE;AA+OD;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACjB,cAAc,CAAC,YAAY,GAC3B,cAAc,CAAC,WAAW,GAC1B,cAAc,CAAC,aAAa,GAC5B,cAAc,CAAC,QAAQ,GACvB,cAAc,CAAC,WAAW,CAAC;AAOjC;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACnC,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,MAAM,EAChB,sBAAsB,EAAE,MAAM,GAC/B,WAAW,GAAG,SAAS,CAGzB;AA+KD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACtC,MAAM,EAAE,QAAQ,EAAE,EAClB,QAAQ,EAAE,MAAM,EAChB,sBAAsB,EAAE,MAAM,GAC/B,WAAW,EAAE,CAKf;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,SAAS,CASxF;AAiWD;;;;;;GAMG;AACH,2CAA2C;AAC3C,wBAAgB,kBAAkB,CAC9B,GAAG,EAAE,YAAY,EACjB,SAAS,EAAE,WAAW,EAAE,EACxB,MAAM,EAAE,QAAQ,EAAE,GACnB,cAAc,CAAC,eAAe,EAAE,CAsElC;AAkmBD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,GAAG,cAAc,CAAC,QAAQ,CAmF5F"}
1
+ {"version":3,"file":"ledger-root-builder.d.ts","sourceRoot":"","sources":["../../src/navigation/ledger-root-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,uBAAuB,EAAE,MAAM,wCAAwC,CAAC;AACnG,OAAO,KAAK,cAAc,MAAM,YAAY,CAAC;AAG7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,KAAK,OAAO,GAAG,uBAAuB,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAE3D,KAAK,QAAQ,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AA6B9C,MAAM,WAAW,gBAAgB;IAC7B,6EAA6E;IAC7E,QAAQ,EAAE,MAAM,CAAC;IACjB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,6DAA6D;IAC7D,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;IACtC;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CACpC;AAID;;;;;;GAMG;AACH,UAAU,YAAY;IAClB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,cAAc,CAAC,MAAM,CAAC;IAC/C,kFAAkF;IAClF,WAAW,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,cAAc,CAAC,IAAI,CAAC;CAC9D;AAED,2CAA2C;AAC3C,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,CAuBjE;AAoRD;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GACjB,cAAc,CAAC,YAAY,GAC3B,cAAc,CAAC,WAAW,GAC1B,cAAc,CAAC,aAAa,GAC5B,cAAc,CAAC,QAAQ,GACvB,cAAc,CAAC,WAAW,CAAC;AAOjC;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACnC,KAAK,EAAE,QAAQ,EACf,QAAQ,EAAE,MAAM,EAChB,sBAAsB,EAAE,MAAM,GAC/B,WAAW,GAAG,SAAS,CAGzB;AA+KD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACtC,MAAM,EAAE,QAAQ,EAAE,EAClB,QAAQ,EAAE,MAAM,EAChB,sBAAsB,EAAE,MAAM,GAC/B,WAAW,EAAE,CAKf;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,SAAS,CASxF;AAiWD;;;;;;GAMG;AACH,2CAA2C;AAC3C,wBAAgB,kBAAkB,CAC9B,GAAG,EAAE,YAAY,EACjB,SAAS,EAAE,WAAW,EAAE,EACxB,MAAM,EAAE,QAAQ,EAAE,GACnB,cAAc,CAAC,eAAe,EAAE,CAsElC;AA8oBD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,gBAAgB,GAAG,cAAc,CAAC,QAAQ,CAmF5F"}
@@ -60,6 +60,41 @@ 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
+ }
63
98
  function metaBool(metadata, key) {
64
99
  if (metadata != null && typeof metadata === "object" && key in metadata) {
65
100
  const v = metadata[key];
@@ -198,7 +233,7 @@ function artifactToNode(ctx, route) {
198
233
  }),
199
234
  pageId: FernNavigation.PageId(pageId),
200
235
  noindex: metaBool(md, "noindex"),
201
- availability: metaStr(md, "availability")
236
+ availability: metaAvailability(md)
202
237
  };
203
238
  return node;
204
239
  }
@@ -269,7 +304,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
269
304
  endpointId: FernNavigation.EndpointId(endpointId),
270
305
  apiDefinitionId,
271
306
  isResponseStream: metaBool(md, "isResponseStream"),
272
- availability: metaStr(md, "availability"),
307
+ availability: metaAvailability(md),
273
308
  playground: undefined
274
309
  };
275
310
  }
@@ -285,7 +320,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
285
320
  method: method,
286
321
  webhookId: FernNavigation.WebhookId(webhookId),
287
322
  apiDefinitionId,
288
- availability: metaStr(md, "availability")
323
+ availability: metaAvailability(md)
289
324
  };
290
325
  }
291
326
  if (route.type === "asyncapi") {
@@ -298,7 +333,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
298
333
  ...meta,
299
334
  webSocketId: FernNavigation.WebSocketId(webSocketId),
300
335
  apiDefinitionId,
301
- availability: metaStr(md, "availability"),
336
+ availability: metaAvailability(md),
302
337
  playground: undefined
303
338
  };
304
339
  }
@@ -314,7 +349,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
314
349
  method: method,
315
350
  grpcId: FernNavigation.GrpcId(grpcId),
316
351
  apiDefinitionId,
317
- availability: metaStr(md, "availability")
352
+ availability: metaAvailability(md)
318
353
  };
319
354
  }
320
355
  if (route.type === "graphql") {
@@ -330,7 +365,7 @@ function apiLeafNodeFromNavRouteInternal(ctx, route, segmentApiDefinitionId) {
330
365
  graphqlOperationId: FernNavigation.GraphQlOperationId(graphqlOperationId),
331
366
  graphqlOperationIds: undefined,
332
367
  apiDefinitionId,
333
- availability: metaStr(md, "availability"),
368
+ availability: metaAvailability(md),
334
369
  playground: undefined
335
370
  };
336
371
  }
@@ -597,7 +632,7 @@ function sectionNode(ctx, scoped, sectionChildren, scopePrefix) {
597
632
  noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
598
633
  collapsible: metaBool(seg.metadata, "collapsible") ?? (metaCollapsed(seg.metadata) === true ? true : undefined),
599
634
  collapsedByDefault: metaBool(seg.metadata, "collapsedByDefault") ?? (metaCollapsed(seg.metadata) === true ? true : undefined),
600
- availability: metaStr(seg.metadata, "availability"),
635
+ availability: metaAvailability(seg.metadata),
601
636
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
602
637
  children: mergeChildrenByPosition(ctx, nav, sectionChildren)
603
638
  };
@@ -686,7 +721,7 @@ function apiPackageChildNode(ctx, scoped, scopeSegs, parentApiDefinitionId, scop
686
721
  featureFlags: metaFeatureFlags(seg.metadata)
687
722
  }),
688
723
  apiDefinitionId: FernNavigation.ApiDefinitionId(apiDefinitionId),
689
- availability: metaStr(seg.metadata, "availability"),
724
+ availability: metaAvailability(seg.metadata),
690
725
  overviewPageId: overviewPageId != null ? FernNavigation.PageId(overviewPageId) : undefined,
691
726
  noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
692
727
  pointsTo: metaPointsTo != null ? pointsToSlug(metaPointsTo) : undefined,
@@ -796,7 +831,7 @@ function apiReferenceNode(ctx, scoped, scopeSegs, scopePrefix) {
796
831
  featureFlags: metaFeatureFlags(seg.metadata)
797
832
  }),
798
833
  apiDefinitionId: FernNavigation.ApiDefinitionId(apiDefinitionId),
799
- availability: metaStr(seg.metadata, "availability"),
834
+ availability: metaAvailability(seg.metadata),
800
835
  overviewPageId: overviewPageId != null ? FernNavigation.PageId(overviewPageId) : undefined,
801
836
  noindex: metaBool(overview?.metadata, "noindex") ?? metaBool(seg.metadata, "noindex"),
802
837
  paginated: true,
@@ -1055,12 +1090,19 @@ function buildSectionTree(ctx, scopeSegs, parentPath, scopePrefix, changelogScop
1055
1090
  function computeSiblingSortBoundaries(ordered) {
1056
1091
  const result = new Map();
1057
1092
  // Collect root-level sections grouped by their section path.
1093
+ // Sections with sec==="" (skip-slug sections) are included: when multiple
1094
+ // skip-slug sections exist at the root, they share path "" and need
1095
+ // boundaries so each only adopts its own children.
1058
1096
  const sectionPaths = new Set(ordered.map((s) => s.seg.section));
1059
1097
  const rootSections = [];
1060
1098
  for (const s of ordered) {
1061
1099
  const type = metaStr(s.seg.metadata, "type");
1062
1100
  const sec = s.seg.section;
1063
- if (type !== "section" || sec === "") {
1101
+ if (type !== "section") {
1102
+ continue;
1103
+ }
1104
+ if (sec === "") {
1105
+ rootSections.push(s);
1064
1106
  continue;
1065
1107
  }
1066
1108
  const parent = parentSectionPath(sec);
@@ -1109,6 +1151,17 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1109
1151
  // segments. Both index into the SAME child list at write time, so they
1110
1152
  // interleave directly (e.g. [page 0, page 1, section 2…15, page 16, 17]).
1111
1153
  const annotated = [];
1154
+ // ── Sibling section ownership ────────────────────────────────────────
1155
+ //
1156
+ // Multiple root-level sections can share the same section path (e.g.
1157
+ // three sections all with path equal to the version slug). When this
1158
+ // happens, buildSectionTree would incorrectly give ALL child subsections
1159
+ // to each sibling. We resolve ownership via sortOrder: a section owns
1160
+ // segments whose sortOrder falls between its own and the next sibling's.
1161
+ //
1162
+ // Computed first because hasRootSection (below) uses it to decide
1163
+ // whether bounded skip-slug siblings should activate adoption.
1164
+ const siblingBoundaries = computeSiblingSortBoundaries(ordered);
1112
1165
  // ── Root-section adoption flow ──────────────────────────────────────
1113
1166
  //
1114
1167
  // A scope may contain a "root section" — a segment with type "section"
@@ -1127,15 +1180,20 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1127
1180
  // → SKIP — already adopted by (B) via buildSectionTree
1128
1181
  //
1129
1182
  // 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 ────────────────────────────────────────
1132
1183
  //
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);
1184
+ // A section with sec=="" AND `childIndex` in its metadata is a sibling
1185
+ // inside a V1 sidebarGroup that has skipUrlSlug not a true structural
1186
+ // wrapper. `walkNavSubTrees` always passes `childIndex`; direct
1187
+ // sidebarRoot children (true wrappers) never receive one.
1188
+ //
1189
+ // Such siblings only activate adoption when they have sortOrder
1190
+ // boundaries (multiple sec="" sections sharing the same path), which
1191
+ // correctly scope each section's children. A *single* unbounded
1192
+ // skip-slug sibling (e.g. mangopay's "Introduction") must NOT adopt
1193
+ // its depth-0 peers — doing so produces spurious breadcrumb entries.
1194
+ const hasRootSection = ordered.some((s) => metaStr(s.seg.metadata, "type") === "section" &&
1195
+ s.seg.section === "" &&
1196
+ (metaNum(s.seg.metadata, "childIndex") == null || siblingBoundaries.has(s)));
1139
1197
  for (const s of ordered) {
1140
1198
  const type = metaStr(s.seg.metadata, "type");
1141
1199
  const sec = s.seg.section;
@@ -1158,8 +1216,16 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1158
1216
  continue;
1159
1217
  }
1160
1218
  // (B) Root section wrapper — adopts depth-0 children via buildSectionTree.
1161
- if (type === "section" && sec === "") {
1162
- const sectionChildren = buildSectionTree(ctx, scopeSegs, "", scopePrefix);
1219
+ // Only applies to true root wrappers (no childIndex); sibling
1220
+ // sections with skipUrlSlug (have childIndex) fall through to (C).
1221
+ // When multiple sections share sec==="" (skip-slug), scope children
1222
+ // to each section's sortOrder range so subsections aren't duplicated.
1223
+ if (type === "section" && sec === "" && hasRootSection) {
1224
+ const bounds = siblingBoundaries.get(s);
1225
+ const ownedSegs = bounds != null
1226
+ ? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
1227
+ : scopeSegs;
1228
+ const sectionChildren = buildSectionTree(ctx, ownedSegs, "", scopePrefix, changelogScopePrefix);
1163
1229
  const node = sectionNode(ctx, s, sectionChildren, scopePrefix);
1164
1230
  annotated.push({ node, pos: metaNum(s.seg.metadata, "childIndex"), isLeaf: false });
1165
1231
  continue;
@@ -1190,7 +1256,20 @@ function buildSidebarRootChildren(ctx, scopeSegs, scopePrefix, changelogScopePre
1190
1256
  const ownedSegs = bounds != null
1191
1257
  ? scopeSegs.filter((seg) => seg.seg.sortOrder >= bounds.lo && seg.seg.sortOrder < bounds.hi)
1192
1258
  : scopeSegs;
1193
- node = sectionNode(ctx, s, buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix), scopePrefix);
1259
+ // A section with sec="" that reaches here (not adopted as a
1260
+ // root wrapper in case B) is a skipUrlSlug sibling.
1261
+ // When sortOrder boundaries exist (multiple sec="" sections),
1262
+ // the scoped ownedSegs correctly limits which subsections
1263
+ // each section can adopt. When there are NO boundaries
1264
+ // (single sec="" section, e.g. mangopay's "Introduction"),
1265
+ // buildSectionTree("") would re-adopt all depth-0 peers;
1266
+ // pass an empty child list instead — its leaf artifacts are
1267
+ // already in its own segment nav.
1268
+ const isUnboundedSkipSlugSibling = sec === "" && metaNum(s.seg.metadata, "childIndex") != null && bounds == null;
1269
+ const sectionChildren = isUnboundedSkipSlugSibling
1270
+ ? []
1271
+ : buildSectionTree(ctx, ownedSegs, sec, scopePrefix, changelogScopePrefix);
1272
+ node = sectionNode(ctx, s, sectionChildren, scopePrefix);
1194
1273
  }
1195
1274
  // Shared/merged sections (paths not rooted in scopePrefix) must not
1196
1275
  // carry childIndex — their source-version positions would sort them