77 */
88
99import { useMemo } from 'react' ;
10- import { Link , useParams } from 'react-router-dom' ;
11- import { FileText , FileQuestion , Loader2 } from 'lucide-react' ;
10+ import { Navigate , useParams } from 'react-router-dom' ;
11+ import { BookOpen , FileQuestion , Loader2 } from 'lucide-react' ;
1212import { DocShell } from './DocShell' ;
13- import { BookSidebar } from './BookSidebar' ;
1413import { useBookData } from './use-book-data' ;
15- import { resolveBookTree , scopeDocsToBook , bookSlug } from './book-nav' ;
14+ import { resolveBookTree , scopeDocsToBook , bookSlug , firstDoc } from './book-nav' ;
1615
1716/**
18- * `/docs/:slug` — a book landing page (ADR-0046 §6). Resolves the book's spine
19- * against the current docs and shows the grouped table of contents: the
20- * persistent nav sidebar plus an overview that lists every section's docs. Each
21- * doc links to the in-book reader (`/docs/:slug/:name`), which keeps this
22- * sidebar in view. `:slug` is the book's `slug` (an implicit per-package book
23- * uses its packageId).
17+ * `/docs/:slug` — a book landing (ADR-0046 §6). Opening a book means starting
18+ * to read it, so this resolves the book's spine and redirects to its overview
19+ * doc (`*_index`, else the first doc in order); the reader then shows that doc
20+ * with the book sidebar. This avoids duplicating the table of contents (the
21+ * sidebar already is it). A book with no readable docs shows an empty state.
2422 */
2523export default function BookPage ( ) {
2624 const { slug, appName } = useParams < { slug : string ; appName ?: string } > ( ) ;
27- const { books, docs, state, error } = useBookData ( ) ;
25+ const { books, docs, state } = useBookData ( ) ;
2826
2927 const found = useMemo ( ( ) => books . find ( ( b ) => bookSlug ( b ) === slug ) , [ books , slug ] ) ;
30- const resolved = useMemo (
31- ( ) => ( found ? resolveBookTree ( found , scopeDocsToBook ( found , docs ) ) : null ) ,
28+ const opensTo = useMemo (
29+ ( ) => ( found ? firstDoc ( resolveBookTree ( found , scopeDocsToBook ( found , docs ) ) ) : undefined ) ,
3230 [ found , docs ] ,
3331 ) ;
3432
35- const base = appName ? `/apps/${ appName } /docs` : '/docs' ;
36- const docHref = ( name : string ) => `${ base } /${ slug } /${ name } ` ;
37-
3833 if ( state === 'loading' ) {
3934 return (
4035 < div className = "flex h-full items-center justify-center p-10 text-muted-foreground" >
@@ -43,87 +38,30 @@ export default function BookPage() {
4338 ) ;
4439 }
4540
46- if ( state === 'error' || ! found || ! resolved ) {
47- return (
48- < DocShell breadcrumb = { slug } >
49- < div className = "mx-auto flex max-w-3xl flex-col items-center gap-3 p-10 text-center" >
50- < FileQuestion className = "h-10 w-10 text-muted-foreground" />
51- < h1 className = "text-lg font-semibold" >
52- { state === 'error' ? 'Failed to load documentation' : 'Book not found' }
53- </ h1 >
54- < p className = "text-sm text-muted-foreground" >
55- { error ?? (
56- < >
57- No book named < code className = "rounded bg-muted px-1 py-0.5" > { slug } </ code > is installed.
58- </ >
59- ) }
60- </ p >
61- </ div >
62- </ DocShell >
63- ) ;
41+ if ( found && opensTo ) {
42+ const base = appName ? `/apps/${ appName } /docs` : '/docs' ;
43+ return < Navigate to = { `${ base } /${ slug } /${ opensTo } ` } replace /> ;
6444 }
6545
46+ // Unknown book, or a book with no readable docs yet.
6647 return (
67- < DocShell breadcrumb = { resolved . label ?? slug } >
68- < div className = "mx-auto flex max-w-5xl gap-8 p-4 sm:p-6" >
69- < aside className = "hidden w-60 shrink-0 lg:block" >
70- < div className = "sticky top-20 max-h-[calc(100vh-6rem)] overflow-auto pr-1" >
71- < BookSidebar book = { resolved } docHref = { docHref } />
72- </ div >
73- </ aside >
74-
75- < div className = "min-w-0 max-w-3xl flex-1" >
76- < h1 className = "text-2xl font-bold" > { resolved . label ?? slug } </ h1 >
77- { resolved . description ? (
78- < p className = "mt-2 text-sm text-muted-foreground" > { resolved . description } </ p >
79- ) : null }
80-
81- < div className = "mt-6 space-y-8" >
82- { resolved . groups . map ( ( group ) => (
83- < section key = { group . key } >
84- < h2 className = "mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground" >
85- { group . label }
86- </ h2 >
87- < ul className = "divide-y divide-border rounded-md border border-border" >
88- { group . entries
89- . filter ( ( e ) => ! e . separator )
90- . map ( ( entry , i ) =>
91- entry . href ? (
92- < li key = { `ext-${ i } ` } >
93- < a
94- href = { entry . href }
95- target = "_blank"
96- rel = "noreferrer"
97- className = "flex items-start gap-3 px-3 py-3 hover:bg-muted/50"
98- >
99- < FileText className = "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
100- < span className = "text-sm font-medium" > { entry . label ?? entry . href } </ span >
101- </ a >
102- </ li >
103- ) : entry . doc ? (
104- < li key = { entry . doc } >
105- < Link
106- to = { docHref ( entry . doc ) }
107- className = "flex items-start gap-3 px-3 py-3 hover:bg-muted/50"
108- >
109- < FileText className = "mt-0.5 h-4 w-4 shrink-0 text-muted-foreground" />
110- < div className = "min-w-0" >
111- < div className = "text-sm font-medium" > { entry . label ?? entry . doc } </ div >
112- { entry . description ? (
113- < div className = "mt-0.5 line-clamp-2 text-xs text-muted-foreground" >
114- { entry . description }
115- </ div >
116- ) : null }
117- </ div >
118- </ Link >
119- </ li >
120- ) : null ,
121- ) }
122- </ ul >
123- </ section >
124- ) ) }
125- </ div >
126- </ div >
48+ < DocShell breadcrumb = { found ? ( found . label ?? slug ) : slug } >
49+ < div className = "mx-auto flex max-w-3xl flex-col items-center gap-3 p-10 text-center" >
50+ { found ? (
51+ < >
52+ < BookOpen className = "h-10 w-10 text-muted-foreground" />
53+ < h1 className = "text-lg font-semibold" > { found . label ?? slug } </ h1 >
54+ < p className = "text-sm text-muted-foreground" > This book has no documents yet.</ p >
55+ </ >
56+ ) : (
57+ < >
58+ < FileQuestion className = "h-10 w-10 text-muted-foreground" />
59+ < h1 className = "text-lg font-semibold" > Book not found</ h1 >
60+ < p className = "text-sm text-muted-foreground" >
61+ No book named < code className = "rounded bg-muted px-1 py-0.5" > { slug } </ code > is installed.
62+ </ p >
63+ </ >
64+ ) }
12765 </ div >
12866 </ DocShell >
12967 ) ;
0 commit comments