@@ -104,10 +104,10 @@ export function SpecDetailClient({ initialSpec, initialSubSpec }: SpecDetailClie
104104
105105 // Fetch complete dependency graph when dialog opens
106106 const { data : dependencyGraphData } = useSWR < {
107- current : any ;
108- dependsOn : any [ ] ;
109- requiredBy : any [ ] ;
110- related : any [ ] ;
107+ current : { specName : string ; specNumber ?: number } ;
108+ dependsOn : { specName : string ; specNumber ?: number } [ ] ;
109+ requiredBy : { specName : string ; specNumber ?: number } [ ] ;
110+ related : { specName : string ; specNumber ?: number } [ ] ;
111111 } > (
112112 dependenciesDialogOpen ? `/api/specs/${ initialSpec . specNumber || initialSpec . id } /dependency-graph` : null ,
113113 fetcher ,
@@ -118,7 +118,7 @@ export function SpecDetailClient({ initialSpec, initialSubSpec }: SpecDetailClie
118118 ) ;
119119
120120 const spec = specData ?. spec || initialSpec ;
121- const tags = spec . tags || [ ] ;
121+ const tags = React . useMemo ( ( ) => spec . tags || [ ] , [ spec . tags ] ) ;
122122 const updatedRelative = spec . updatedAt ? formatRelativeTime ( spec . updatedAt ) : 'N/A' ;
123123 const relationships = spec . relationships ;
124124
@@ -175,10 +175,45 @@ export function SpecDetailClient({ initialSpec, initialSubSpec }: SpecDetailClie
175175 router . push ( newUrl , { scroll : false } ) ;
176176 } ;
177177
178+ const headerRef = React . useRef < HTMLElement > ( null ) ;
179+
180+ // Handle scroll padding for sticky header
181+ React . useEffect ( ( ) => {
182+ const updateScrollPadding = ( ) => {
183+ const navbarHeight = 56 ; // 3.5rem / top-14
184+ let offset = navbarHeight ;
185+
186+ // On large screens, the spec header is also sticky
187+ if ( window . innerWidth >= 1024 && headerRef . current ) {
188+ offset += headerRef . current . offsetHeight - navbarHeight ;
189+ }
190+
191+ document . documentElement . style . scrollPaddingTop = `${ offset } px` ;
192+ } ;
193+
194+ // Initial update
195+ updateScrollPadding ( ) ;
196+
197+ // Update on resize
198+ window . addEventListener ( 'resize' , updateScrollPadding ) ;
199+
200+ // Update when content changes (might affect header height if tags wrap)
201+ const observer = new ResizeObserver ( updateScrollPadding ) ;
202+ if ( headerRef . current ) {
203+ observer . observe ( headerRef . current ) ;
204+ }
205+
206+ return ( ) => {
207+ window . removeEventListener ( 'resize' , updateScrollPadding ) ;
208+ observer . disconnect ( ) ;
209+ document . documentElement . style . scrollPaddingTop = '' ;
210+ } ;
211+ } , [ spec , tags ] ) ; // Re-run if spec metadata changes
212+
178213 return (
179214 < >
180215 { /* Compact Header - sticky on desktop, static on mobile */ }
181- < header className = "lg:sticky lg:top-14 lg:z-20 border-b bg-card" >
216+ < header ref = { headerRef } className = "lg:sticky lg:top-14 lg:z-20 border-b bg-card" >
182217 < div className = "px-3 sm:px-6 py-3 sm:py-4" >
183218 { /* Line 1: Spec number + H1 Title */ }
184219 < h1 className = "text-xl sm:text-2xl font-bold tracking-tight mb-2 sm:mb-3" >
@@ -367,7 +402,7 @@ export function SpecDetailClient({ initialSpec, initialSubSpec }: SpecDetailClie
367402 </ main >
368403
369404 { /* Right Sidebar for TOC (Desktop only) */ }
370- < aside className = "hidden xl:block w-72 shrink-0 px-6 py-8 sticky top-40 h-[calc(100vh-10rem)] overflow-y-auto" >
405+ < aside className = "hidden xl:block w-72 shrink-0 px-6 py-8 sticky top-40 h-[calc(100vh-10rem)] overflow-y-auto scrollbar-auto-hide " >
371406 < TableOfContentsSidebar content = { displayContent } />
372407 </ aside >
373408 </ div >
0 commit comments