@@ -94,6 +94,14 @@ export const docsFile = function docsFile(opts?: DocsFileOpts): Generator {
9494 const rc = ctx . renderContext ;
9595 const provider = projectProvider ( ctx . projectRoot ?? process . cwd ( ) ) ;
9696 const layout = ctx . config . outputLayout ?? "flat" ;
97+ // One {label, href} per api surface, every href computed via the shared
98+ // `apiSurfaceHref` from the FROM page's own output path (so it resolves
99+ // relative in BOTH layouts, or absolute when the surface declares a
100+ // baseUrl). The api page for a node lives at `<subDir>/<same placement>`,
101+ // so the from-path doubles as the page placement. ABSENT/empty surfaces ⇒
102+ // undefined → output byte-identical to historical model-only runs.
103+ const apiRefsFor = ( fromPath : string ) : Array < { label : string ; href : string } > | undefined =>
104+ opts ?. apiSurfaces ?. map ( ( s ) => ( { label : s . label , href : apiSurfaceHref ( fromPath , s , fromPath ) } ) ) ;
97105 // Track every (path, fqn) so we can hard-error on a collision (defense
98106 // against silent doc-page overwrite) AFTER all pages are placed.
99107 const placements : DocPagePlacement [ ] = [ ] ;
@@ -110,15 +118,8 @@ export const docsFile = function docsFile(opts?: DocsFileOpts): Generator {
110118 entityNodes . push ( node ) ;
111119 const path = docPageOutputPath ( layout , node ) ;
112120 placements . push ( { path, fqn : entity . resolutionKey ( ) } ) ;
113- // Cross-link to the sibling api surfaces, when emitted. The api page for
114- // this node lives at `<subDir>/<same placement>` per surface; each href
115- // is derived from the SAME docPageOutputPath placement via apiSurfaceHref
116- // so it resolves in both flat and package layout (or absolute when the
117- // surface declares a baseUrl). ABSENT otherwise.
118- const apiRefs = opts ?. apiSurfaces ?. map ( ( s ) => ( {
119- label : s . label ,
120- href : apiSurfaceHref ( path , s , path ) ,
121- } ) ) ;
121+ // Cross-link to the sibling api surfaces, when emitted (shared builder).
122+ const apiRefs = apiRefsFor ( path ) ;
122123 const payload = buildEntityDocData ( entity , {
123124 dialect : rc . dialect ,
124125 layout,
@@ -157,14 +158,9 @@ export const docsFile = function docsFile(opts?: DocsFileOpts): Generator {
157158 // produces nothing (no orphan landing page with an empty diagram).
158159 if ( files . length > 0 ) {
159160 // The api index lives at `<subDir>/README.md` per surface; the model index
160- // lives at the docs root, so each cross-link href is computed via the
161- // shared `apiSurfaceHref` from the root-level index path (relative-path
162- // rule shared, never hand-rolled; absolute when a baseUrl is given).
163- // ABSENT when no api surface is emitted → index output byte-identical.
164- const apiIndexRefs = opts ?. apiSurfaces ?. map ( ( s ) => ( {
165- label : s . label ,
166- href : apiSurfaceHref ( INDEX_FILENAME , s , INDEX_FILENAME ) ,
167- } ) ) ;
161+ // lives at the docs root, so the from-path is the root-level index path
162+ // (shared builder — same relative/absolute rule as the entity refs).
163+ const apiIndexRefs = apiRefsFor ( INDEX_FILENAME ) ;
168164 const indexContent = renderIndexPage (
169165 ctx . loadedRoot ,
170166 layout ,
0 commit comments