Skip to content

Commit ddf8e53

Browse files
fix(docs): prevent API reference title from inheriting child tag description overview page (#14870)
* fix(docs): prevent API reference title from inheriting child tag description overview page When tag-description-pages is enabled, child apiPackage nodes get overviewPageIds from their tag descriptions. Previously, the parent API reference node would inherit the first child's overviewPageId, making the top-level API reference title clickable (navigating to a folder's tag description page). This was undesirable — the folder overview pages belong to the individual tag sections, not the parent. Now the inheritance is skipped when tagDescriptionPages is enabled, keeping the top-level API reference title as a non-clickable header. Co-Authored-By: tristan.declarin <tristan.declarin@buildwithfern.com> * test(docs): add test for overviewPageId inheritance guard with tag-description-pages Co-Authored-By: tristan.declarin <tristan.declarin@buildwithfern.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: tristan.declarin <tristan.declarin@buildwithfern.com>
1 parent a308fbf commit ddf8e53

2 files changed

Lines changed: 107 additions & 7 deletions

File tree

packages/cli/docs-resolver/src/ApiReferenceNodeConverter.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,12 @@ export class ApiReferenceNodeConverter {
126126
this.#idgen
127127
).orUndefined();
128128

129-
// When no explicit overview is set, inherit the first apiPackage child's overview page (e.g., tag description).
130-
// This works for both flattened and non-flattened cases:
131-
// - Flattened: The section will inherit this overview page via DocsDefinitionResolver.toSectionNode()
132-
// - Non-flattened: The API Reference itself will show the tag description when clicked
133-
// We search through all children to find an apiPackage with an overviewPageId, rather than assuming
134-
// the first child is an apiPackage (which may not be true when endpoints are explicitly listed in the layout).
129+
// When no explicit overview is set and tag-description-pages is NOT enabled,
130+
// inherit the first apiPackage child's overview page. When tag-description-pages
131+
// IS enabled, the child overview pages belong to the individual tag sections and
132+
// should not bubble up to make the top-level API reference title clickable.
135133
let overviewPageId = this.#overviewPageId;
136-
if (overviewPageId == null) {
134+
if (overviewPageId == null && !this.apiSection.tagDescriptionPages) {
137135
const apiPackageWithOverview = this.#children.find(
138136
(child): child is FernNavigation.V1.ApiPackageNode =>
139137
child.type === "apiPackage" && child.overviewPageId != null

packages/cli/docs-resolver/src/__test__/tag-description-pages.test.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,108 @@ describe("tag-description-pages", () => {
101101
expect(apiSection.tagDescriptionPages).toMatchSnapshot("tag-description-pages-disabled");
102102
});
103103

104+
describe("overviewPageId inheritance", () => {
105+
it("does not inherit child tag overviewPageId when tag-description-pages is enabled", async () => {
106+
const docsWorkspace = await loadDocsWorkspace({
107+
fernDirectory: FIXTURE_DIR,
108+
context
109+
});
110+
111+
if (docsWorkspace == null) {
112+
throw new Error("Workspace is null");
113+
}
114+
115+
const parsedDocsConfig = await parseDocsConfiguration({
116+
rawDocsConfiguration: docsWorkspace.config,
117+
context,
118+
absolutePathToFernFolder: docsWorkspace.absoluteFilePath,
119+
absoluteFilepathToDocsConfig: docsWorkspace.absoluteFilepathToDocsConfig
120+
});
121+
122+
if (parsedDocsConfig.navigation.type !== "untabbed") {
123+
throw new Error("Expected untabbed navigation");
124+
}
125+
126+
if (parsedDocsConfig.navigation.items[0]?.type !== "apiSection") {
127+
throw new Error("Expected apiSection");
128+
}
129+
130+
const apiSection = parsedDocsConfig.navigation.items[0];
131+
expect(apiSection.tagDescriptionPages).toBe(true);
132+
133+
const result = await loadAPIWorkspace({
134+
absolutePathToWorkspace: FIXTURE_DIR,
135+
context,
136+
cliVersion: "0.0.0",
137+
workspaceName: undefined
138+
});
139+
140+
if (!result.didSucceed) {
141+
throw new Error("API workspace failed to load");
142+
}
143+
144+
const apiWorkspace = await result.workspace.toFernWorkspace({ context });
145+
const slug = FernNavigation.V1.SlugGenerator.init("/docs");
146+
147+
const ir = generateIntermediateRepresentation({
148+
workspace: apiWorkspace,
149+
audiences: { type: "all" },
150+
generationLanguage: undefined,
151+
keywords: undefined,
152+
smartCasing: false,
153+
exampleGeneration: { disabled: false },
154+
readme: undefined,
155+
version: undefined,
156+
packageName: undefined,
157+
context,
158+
sourceResolver: new SourceResolverImpl(context, apiWorkspace)
159+
});
160+
161+
const apiDefinition = convertIrToApiDefinition({
162+
ir,
163+
apiDefinitionId: "test-api-id",
164+
context
165+
});
166+
167+
const openApiTags: Record<string, { id: string; description: string | undefined }> = {
168+
pet: { id: "pet", description: "Everything about your Pets" },
169+
store: { id: "store", description: "Access to Petstore orders" },
170+
user: { id: "user", description: "Operations about user" }
171+
};
172+
173+
const converter = new ApiReferenceNodeConverter(
174+
apiSection,
175+
apiDefinition,
176+
slug,
177+
docsWorkspace,
178+
context,
179+
new Map(),
180+
new Map(),
181+
new Map(),
182+
NodeIdGenerator.init(),
183+
new Map(),
184+
apiWorkspace,
185+
undefined,
186+
undefined,
187+
openApiTags
188+
);
189+
190+
const node = converter.get();
191+
192+
// The top-level API reference should NOT inherit a child tag's overviewPageId
193+
// when tag-description-pages is enabled. Each tag keeps its own overview page;
194+
// the parent title should remain non-clickable.
195+
expect(node.overviewPageId).toBeUndefined();
196+
expect(node.type).toBe("apiReference");
197+
198+
// Verify that child apiPackage nodes DO have overviewPageIds (tag description pages)
199+
const childrenWithOverview = node.children.filter(
200+
(child) => child.type === "apiPackage" && child.overviewPageId != null
201+
);
202+
expect(childrenWithOverview.length).toBeGreaterThan(0);
203+
});
204+
});
205+
104206
describe("tag description content preservation", () => {
105207
it("does not escape curly braces or angle brackets in tag descriptions", async () => {
106208
const docsWorkspace = await loadDocsWorkspace({

0 commit comments

Comments
 (0)