From 0acc9081817aa61b62b1cef5cbe77b05716e4081 Mon Sep 17 00:00:00 2001 From: Vivek Date: Mon, 27 Jul 2026 16:17:27 +0530 Subject: [PATCH 01/27] refactor(website): extract the docs shell into lib/docs-shell.ts The sidebar, mobile drawer, and .prose-docs typography move out of the docs sub-layout into a shared shell so the component library at /ui (#1099) can render the exact same chrome instead of a drifting copy. No DOM change for /docs; the source-reading chrome guards now point at the shell file. --- website/app/docs/layout.ts | 273 ++--------------------- website/lib/docs-shell.ts | 318 +++++++++++++++++++++++++++ website/test/ssr/docs-chrome.test.ts | 28 ++- 3 files changed, 352 insertions(+), 267 deletions(-) create mode 100644 website/lib/docs-shell.ts diff --git a/website/app/docs/layout.ts b/website/app/docs/layout.ts index 99ab294d9..74f78f7fe 100644 --- a/website/app/docs/layout.ts +++ b/website/app/docs/layout.ts @@ -1,4 +1,5 @@ import { html } from '@webjsdev/core'; +import { docsShell } from '#lib/docs-shell.ts'; import '#components/doc-search.ts'; /** @@ -7,12 +8,17 @@ import '#components/doc-search.ts'; * This is a NON-ROOT layout (invariant 8), so it writes no document shell. * The header, footer, theme toggle, fonts, and design tokens all come from * `app/layout.ts`, exactly as they do on /what-is-webjs and /blog. The - * sidebar below is the ONLY docs-specific chrome, which is the whole point - * of serving the docs from webjs.dev instead of a separate subdomain: one + * sidebar is the ONLY docs-specific chrome, which is the whole point of + * serving the docs from webjs.dev instead of a separate subdomain: one * design system, one set of tokens, no parallel shell to drift. * + * The shell itself (sidebar, mobile drawer, .prose-docs typography) lives in + * lib/docs-shell.ts, shared with the component library at /ui, so the two + * sections cannot drift apart. This file contributes only the docs nav tree + * and the docs-scoped metadata. + * * Doc page bodies are plain HTML with no component wrapper, so their - * typography is styled through the `.prose-docs` rules below rather than + * typography is styled through the shell's `.prose-docs` rules rather than * per-element utility classes across 45 pages. */ const NAV_SECTIONS = [ @@ -140,257 +146,12 @@ export function generateMetadata(ctx: { url: string }) { } export default function DocsLayout({ children }: { children: unknown }) { - return html` - - -
- - -
- -
- -
${children}
-
-
- `; + return docsShell({ + nav: NAV_SECTIONS, + label: 'Documentation', + menuLabel: 'Documentation menu', + asideTop: html``, + contentClass: 'prose-docs', + children, + }); } diff --git a/website/lib/docs-shell.ts b/website/lib/docs-shell.ts new file mode 100644 index 000000000..db1df6d0f --- /dev/null +++ b/website/lib/docs-shell.ts @@ -0,0 +1,318 @@ +import { html } from '@webjsdev/core'; + +/** + * The shared documentation shell: the page-tree sidebar column plus the + * content column, extracted from app/docs/layout.ts so the component + * library at /ui renders the exact same chrome as /docs instead of + * growing a parallel copy that drifts. + * + * Both consumers are NON-ROOT layouts (invariant 8), so this writes no + * document shell. The header, footer, theme toggle, fonts, and design + * tokens all come from app/layout.ts; the sidebar below is the only + * section-specific chrome. + * + * The mobile drawer rides the same body attribute for every consumer + * (data-docs-nav-open): the ROOT layout owns the listener that clears it + * on navigation (it survives client-router swaps precisely because it is + * outside every swap range), so a second attribute would need a second + * root-level listener. Only one shell is ever on a page at a time, so + * sharing the attribute and the #docs-sidebar id is safe. + */ + +export type ShellNavItem = { href: string; label: string }; +export type ShellNavSection = { + title: string; + /** + * Optional item count rendered right-aligned on the section header, the + * way the component library labels its tiers. Absent on the docs. + */ + count?: number; + items: ShellNavItem[]; +}; + +export type DocsShellOptions = { + /** Sidebar sections, rendered in order. */ + nav: ShellNavSection[]; + /** aria-label for the