@@ -272,34 +272,33 @@ function Collapsible({ summary, children }: { summary: React.ReactNode; children
272272
273273// --- Main Reader ---
274274
275+ interface LatticeExport {
276+ project : string
277+ description : string
278+ generated_at ?: string
279+ nodes : LatticeNode [ ]
280+ }
281+
275282export function ReaderPage ( ) {
276- const [ nodes , setNodes ] = useState < LatticeNode [ ] | null > ( null )
283+ const [ data , setData ] = useState < LatticeExport | null > ( null )
277284 const [ error , setError ] = useState < string | null > ( null )
278285 const [ priorityFilter , setPriorityFilter ] = useState ( 'all' )
279286 const [ statusFilter , setStatusFilter ] = useState ( 'all' )
280287
281288 const url = new URLSearchParams ( window . location . search ) . get ( 'url' )
282289
283- // Derive project name from the JSON URL path
284- // e.g. https://forkzero.github.io/forkzero.ai/lattice-data.json → "forkzero.ai"
285- // e.g. https://example.com/my-project/lattice-data.json → "my-project"
286- const projectName = ( ( ) => {
287- if ( ! url ) return null
288- try {
289- const parts = new URL ( url ) . pathname . split ( '/' ) . filter ( Boolean )
290- // Find the last path segment before the JSON filename
291- const jsonIndex = parts . findIndex ( p => p . endsWith ( '.json' ) )
292- if ( jsonIndex > 0 ) return parts [ jsonIndex - 1 ]
293- if ( parts . length > 1 ) return parts [ parts . length - 2 ]
294- return null
295- } catch { return null }
296- } ) ( )
297-
298290 useEffect ( ( ) => {
299291 if ( ! url ) { setError ( 'No URL provided. Use ?url=<json-url>' ) ; return }
300292 fetch ( url )
301293 . then ( r => { if ( ! r . ok ) throw new Error ( `HTTP ${ r . status } ` ) ; return r . json ( ) } )
302- . then ( data => setNodes ( data ) )
294+ . then ( raw => {
295+ // Handle both new format (object with nodes) and old format (flat array)
296+ if ( Array . isArray ( raw ) ) {
297+ setData ( { project : '' , description : '' , nodes : raw } )
298+ } else {
299+ setData ( { project : raw . project ?? '' , description : raw . description ?? '' , nodes : raw . nodes ?? [ ] } )
300+ }
301+ } )
303302 . catch ( e => setError ( `Failed to load: ${ e . message } ` ) )
304303 } , [ url ] )
305304
@@ -315,7 +314,7 @@ export function ReaderPage() {
315314 )
316315 }
317316
318- if ( ! nodes ) {
317+ if ( ! data ) {
319318 return (
320319 < div style = { s . page } >
321320 < PoweredByHeader />
@@ -324,6 +323,7 @@ export function ReaderPage() {
324323 )
325324 }
326325
326+ const nodes = data . nodes
327327 const stats = computeStats ( nodes )
328328 const traceability = buildTraceability ( nodes )
329329 const sources = nodes . filter ( n => n . type === 'source' )
@@ -343,8 +343,11 @@ export function ReaderPage() {
343343 < div style = { s . page } >
344344 < PoweredByHeader />
345345 < div style = { s . container } >
346- < h1 style = { s . title } > { projectName ?? 'Lattice' } </ h1 >
347- < p style = { s . subtitle } > Lattice Dashboard</ p >
346+ < h1 style = { s . title } > { data . project || 'Lattice Dashboard' } </ h1 >
347+ { data . description && < p style = { { ...s . subtitle , marginBottom : '0.5rem' } } > { data . description } </ p > }
348+ < p style = { { ...s . subtitle , fontSize : '0.8rem' , opacity : 0.6 , marginBottom : '2rem' } } >
349+ Lattice Dashboard{ data . generated_at && < > · Generated { new Date ( data . generated_at ) . toLocaleDateString ( ) } </ > }
350+ </ p >
348351
349352 { /* Stats grid */ }
350353 < div style = { s . statsGrid } >
0 commit comments