mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-24 20:09:02 +00:00
A page ships the page tree without its fallback, and an operation page breadcrumbs as its product
The fallback listed the 2,400 operation pages no meta lists, and every page carried it five times over: 16 GB of the 19 GB export. The browser used it only for an operation page's breadcrumb, which now comes from the product page above it. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -9,7 +9,7 @@ import { Provider } from './provider';
|
||||
import type { ReactNode } from 'react';
|
||||
import { Zen, ZenMono } from '@hanzo/font';
|
||||
import { TreeContextProvider } from '@hanzo/docs-base-ui/contexts/tree';
|
||||
import { source } from '@/lib/source';
|
||||
import { clientTree } from '@/lib/source';
|
||||
import { NextProvider } from '@hanzo/docs/core/framework/next';
|
||||
import { Analytics } from '@hanzo/docs-analytics';
|
||||
import { AppearanceDock } from '@/components/appearance-dock';
|
||||
@@ -36,7 +36,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
<html lang="en" className={`${Zen.variable} ${ZenMono.variable}`} suppressHydrationWarning>
|
||||
<Body>
|
||||
<NextProvider>
|
||||
<TreeContextProvider tree={source.getPageTree()}>
|
||||
<TreeContextProvider tree={clientTree()}>
|
||||
<Provider>{children}</Provider>
|
||||
</TreeContextProvider>
|
||||
</NextProvider>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { DocsNavbar } from '@/components/docs-navbar';
|
||||
import { Brand } from '@/components/brand';
|
||||
import { HanzoDocsIcon } from '@/app/layout.client';
|
||||
import { baseOptions } from '@/components/layouts/shared';
|
||||
import { source } from '@/lib/source';
|
||||
import { clientTree, source } from '@/lib/source';
|
||||
import { getSection } from '@/lib/source/navigation';
|
||||
import { SidebarSearch } from '@/components/sidebar-search';
|
||||
import { SidebarAccount } from '@/components/sidebar-account';
|
||||
@@ -43,7 +43,7 @@ export function Docs({
|
||||
// The theme control lives in the bottom-right dock (components/appearance-dock),
|
||||
// the same corner hanzo.ai keeps it in — so the rail carries none.
|
||||
slots={{ header: DocsNavbar, themeSwitch: false }}
|
||||
tree={source.getPageTree()}
|
||||
tree={clientTree()}
|
||||
nav={{
|
||||
...base.nav,
|
||||
// The mark and the wordmark are alternatives, not a pair — see Brand.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { baseOptions, linkItems, logo } from '@/components/layouts/shared';
|
||||
import { source } from '@/lib/source';
|
||||
import { clientTree, source } from '@/lib/source';
|
||||
import { getSection } from '@/lib/source/navigation';
|
||||
import { getSidebarTabs } from '@hanzo/docs-base-ui/components/sidebar/tabs/index';
|
||||
import type { ReactNode } from 'react';
|
||||
@@ -12,7 +12,7 @@ export function FluxLayout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<LayoutClient
|
||||
{...base}
|
||||
tree={source.getPageTree()}
|
||||
tree={clientTree()}
|
||||
// just icon items
|
||||
links={linkItems.filter((item) => item.type === 'icon')}
|
||||
nav={{
|
||||
|
||||
@@ -141,6 +141,17 @@ for (const theme of ['dark', 'light'] as const) {
|
||||
});
|
||||
}
|
||||
|
||||
// Every page carried the page tree's fallback — the 2,400 operation pages no
|
||||
// meta lists — so an operation page weighed 1.7 MB of HTML, most of it a list of
|
||||
// other pages. It ships without it and still says where it is.
|
||||
test('an operation page carries no fallback tree and names its product', async ({ page }) => {
|
||||
const res = await page.goto('/docs/openapi/bot/get-bot-runs/', { waitUntil: 'load' });
|
||||
const html = await res!.text();
|
||||
expect(html).not.toContain('fallback:openapi');
|
||||
expect(html.length, 'HTML bytes').toBeLessThan(1_000_000);
|
||||
await expect(page.locator('article a[href="/docs/openapi/bot/"]', { hasText: /^Bot$/ })).toBeVisible();
|
||||
});
|
||||
|
||||
test('prose is spaced: a paragraph has a margin', async ({ page }) => {
|
||||
await open(page, '/docs/quickstart/', 'dark');
|
||||
const margin = await page
|
||||
|
||||
@@ -63,6 +63,26 @@ function pageTreeCodeTitles(): LoaderPlugin {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The page tree the chrome sends to the browser: the tree without its fallback.
|
||||
*
|
||||
* The fallback holds every page no meta.json lists — the 2,400 operation pages
|
||||
* under /docs/openapi — and the layout serialises the tree into every page it
|
||||
* renders, so each of 6,000 pages carried 0.8 MB of it, five times over (the
|
||||
* HTML and four RSC files): 16 GB of the 19 GB export. The browser used it for
|
||||
* one thing, the breadcrumb of an operation page, and that is found from the
|
||||
* operation's product page instead (base-ui contexts/tree `nearest`). The server
|
||||
* keeps the whole tree for neighbours, the search index and the sitemap.
|
||||
*/
|
||||
let shipped: ReturnType<typeof source.getPageTree> | undefined;
|
||||
export function clientTree(): ReturnType<typeof source.getPageTree> {
|
||||
if (!shipped) {
|
||||
const { fallback: _served, ...tree } = source.getPageTree();
|
||||
shipped = tree;
|
||||
}
|
||||
return shipped;
|
||||
}
|
||||
|
||||
export const blog = loader(createSource(blogPosts, []), {
|
||||
baseUrl: '/blog',
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type * as PageTree from '@hanzo/docs-core/page-tree';
|
||||
import { usePathname } from '@hanzo/docs-core/framework';
|
||||
import { type ReactNode, useMemo, useRef, createContext, use } from 'react';
|
||||
import { searchPath } from '@hanzo/docs-core/breadcrumb';
|
||||
import { searchNearestPath, searchPath } from '@hanzo/docs-core/breadcrumb';
|
||||
|
||||
type MakeRequired<O, K extends keyof O> = Omit<O, K> & Pick<Required<O>, K>;
|
||||
|
||||
@@ -32,6 +32,8 @@ export function TreeContextProvider({
|
||||
return (
|
||||
searchPath(tree.children, pathname) ??
|
||||
(tree.fallback ? searchPath(tree.fallback.children, pathname) : null) ??
|
||||
// An operation page no meta lists breadcrumbs as its product's page.
|
||||
searchNearestPath(tree.children, pathname) ??
|
||||
[]
|
||||
);
|
||||
}, [tree, pathname]);
|
||||
|
||||
@@ -117,3 +117,23 @@ export function searchPath(nodes: PageTree.Node[], url: string): PageTree.Node[]
|
||||
|
||||
return findPath(nodes, (node) => node.type === 'page' && node.url === normalizedUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* The path to the closest page ABOVE `url`, for a page the tree does not list.
|
||||
*
|
||||
* A generated reference lists its product pages and not every operation under
|
||||
* them: `/docs/openapi/bot/get-bot-runs` has no node, and `/docs/openapi/bot`
|
||||
* does. The page that answered is an ancestor, not the page being read, so it
|
||||
* leaves the path and its folder ends it — a breadcrumb then names the product,
|
||||
* and a sidebar opens where the product page opens it.
|
||||
*
|
||||
* - When no page above `url` exists either, return null
|
||||
*/
|
||||
export function searchNearestPath(nodes: PageTree.Node[], url: string): PageTree.Node[] | null {
|
||||
for (let at = normalizeUrl(url); at.lastIndexOf('/') > 0; ) {
|
||||
at = at.slice(0, at.lastIndexOf('/'));
|
||||
const found = searchPath(nodes, at);
|
||||
if (found) return found.at(-1)?.type === 'page' ? found.slice(0, -1) : found;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { joinPath, splitPath } from '@/source/path';
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import type { Root } from '@/page-tree/definitions';
|
||||
import type { Folder, Item, Root } from '@/page-tree/definitions';
|
||||
import { findNeighbour, findSiblings } from '@/page-tree/utils';
|
||||
import { getBreadcrumbItems } from '@/breadcrumb';
|
||||
import { getBreadcrumbItems, searchNearestPath } from '@/breadcrumb';
|
||||
import { DefaultFormatter } from '@/i18n/middleware';
|
||||
import { NextURL } from 'next/dist/server/web/next-url';
|
||||
import { updateHref } from '@/dynamic-link';
|
||||
@@ -266,3 +266,18 @@ test('Dynamic Link: update href', () => {
|
||||
// relative -> relative
|
||||
expect(updateHref('[lang]/test', {})).toBe('test');
|
||||
});
|
||||
|
||||
// A generated reference lists a product's page and not each operation under it,
|
||||
// so an operation page is placed under its product rather than nowhere.
|
||||
test('Search Nearest Path', () => {
|
||||
const index: Item = { type: 'page', name: 'Bot', url: '/docs/openapi/bot' };
|
||||
const bot: Folder = { type: 'folder', name: 'Bot', index, children: [] };
|
||||
const api: Folder = { type: 'folder', name: 'By domain', children: [bot] };
|
||||
const top: Item = { type: 'page', name: 'Home', url: '/docs' };
|
||||
const nodes = [top, api];
|
||||
|
||||
expect(searchNearestPath(nodes, '/docs/openapi/bot/get-bot-runs')).toEqual([api, bot]);
|
||||
expect(searchNearestPath(nodes, '/docs/openapi/bot/get-bot-runs/')).toEqual([api, bot]);
|
||||
expect(searchNearestPath(nodes, '/docs/games')).toEqual([]);
|
||||
expect(searchNearestPath(nodes, '/elsewhere')).toBeNull();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user