@@ -82,20 +82,49 @@ export default function DocPage() {
8282 } ;
8383 } , [ name , appName , adapter ] ) ;
8484
85- // SPA navigation for rewritten doc-to-doc links: anchors render as
86- // plain <a href="/docs/...">; intercept same-app clicks so following a
87- // cross-reference doesn't trigger a full page reload.
85+ // Scroll a heading into view ourselves rather than letting the browser
86+ // follow a bare `#id` href. When the console is served under a sub-path the
87+ // host injects `<base href="…/_console/">` so relative asset URLs resolve; a
88+ // side effect is that fragment-only links resolve against that base instead
89+ // of the current page, so a plain `#id` click navigates to the console root
90+ // ("home") rather than scrolling. Reflect the section in the URL via an
91+ // absolute path that ignores <base>.
92+ const scrollToHeading = useCallback ( ( id : string ) => {
93+ document . getElementById ( id ) ?. scrollIntoView ( { behavior : 'smooth' , block : 'start' } ) ;
94+ window . history . replaceState ( null , '' , `${ window . location . pathname } ${ window . location . search } #${ id } ` ) ;
95+ } , [ ] ) ;
96+
97+ // SPA navigation for rewritten doc-to-doc links: anchors render as plain
98+ // <a href="/docs/...">; intercept same-app clicks so following a
99+ // cross-reference doesn't trigger a full page reload. In-body fragment links
100+ // (`[x](#section)`) get the same JS-scroll treatment as the ToC so <base>
101+ // doesn't bounce them to home.
88102 const onContentClick = useCallback (
89103 ( e : React . MouseEvent < HTMLDivElement > ) => {
90104 if ( e . defaultPrevented || e . metaKey || e . ctrlKey || e . shiftKey || e . altKey || e . button !== 0 ) return ;
91105 const anchor = ( e . target as HTMLElement ) . closest ( 'a' ) ;
92106 const href = anchor ?. getAttribute ( 'href' ) ;
93- if ( href && href . startsWith ( '/docs/' ) ) {
107+ if ( ! href ) return ;
108+ if ( href . startsWith ( '/docs/' ) ) {
94109 e . preventDefault ( ) ;
95110 navigate ( href ) ;
111+ } else if ( href . startsWith ( '#' ) ) {
112+ e . preventDefault ( ) ;
113+ scrollToHeading ( decodeURIComponent ( href . slice ( 1 ) ) ) ;
96114 }
97115 } ,
98- [ navigate ] ,
116+ [ navigate , scrollToHeading ] ,
117+ ) ;
118+
119+ // ToC entries are bare `#id` anchors; scroll in JS for the same <base> reason.
120+ const onTocClick = useCallback (
121+ ( e : React . MouseEvent < HTMLAnchorElement > , id : string ) => {
122+ // Leave modified / non-primary clicks (new tab, etc.) to the browser.
123+ if ( e . defaultPrevented || e . metaKey || e . ctrlKey || e . shiftKey || e . altKey || e . button !== 0 ) return ;
124+ e . preventDefault ( ) ;
125+ scrollToHeading ( id ) ;
126+ } ,
127+ [ scrollToHeading ] ,
99128 ) ;
100129
101130 // Long-doc table of contents (h2–h3). Slugs match rehype-slug so a #id
@@ -161,6 +190,7 @@ export default function DocPage() {
161190 < li key = { item . id } style = { { paddingLeft : ( item . depth - 2 ) * 12 } } >
162191 < a
163192 href = { `#${ item . id } ` }
193+ onClick = { ( e ) => onTocClick ( e , item . id ) }
164194 className = "-ml-px block border-l border-transparent py-0.5 pl-3 text-muted-foreground hover:border-primary hover:text-foreground"
165195 >
166196 { item . text }
0 commit comments