From b9955101b7e5308369e2c74984b2b31b681b5745 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Fri, 17 Apr 2026 11:34:12 +0530 Subject: [PATCH 1/9] feat: add prev/next nav fields to Page type Prepare Page type for backend-provided prev/next links used by the docs sub-navbar. Adds PageNavLink/PageNav types, extends Page via extends PageNav, unifies PageData with Page in page context, and threads null placeholders through SSR + hydration until backend computes real values. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/chronicle/src/lib/page-context.tsx | 21 +++++++------------ packages/chronicle/src/pages/DocsPage.tsx | 7 +------ .../chronicle/src/server/entry-client.tsx | 6 +++++- .../chronicle/src/server/entry-server.tsx | 4 ++++ packages/chronicle/src/types/content.ts | 12 ++++++++++- 5 files changed, 28 insertions(+), 22 deletions(-) diff --git a/packages/chronicle/src/lib/page-context.tsx b/packages/chronicle/src/lib/page-context.tsx index a421459..b88de29 100644 --- a/packages/chronicle/src/lib/page-context.tsx +++ b/packages/chronicle/src/lib/page-context.tsx @@ -7,21 +7,14 @@ import { } from 'react'; import { useLocation } from 'react-router'; import type { ApiSpec } from '@/lib/openapi'; -import type { ChronicleConfig, Frontmatter, Root, TableOfContents } from '@/types'; +import type { ChronicleConfig, Frontmatter, Page, Root, TableOfContents } from '@/types'; export type MdxLoader = (relativePath: string) => Promise<{ content: ReactNode; toc: TableOfContents }>; -interface PageData { - slug: string[]; - frontmatter: Frontmatter; - content: ReactNode; - toc: TableOfContents; -} - interface PageContextValue { config: ChronicleConfig; tree: Root; - page: PageData | null; + page: Page | null; errorStatus: number | null; apiSpecs: ApiSpec[]; } @@ -46,7 +39,7 @@ export function usePageContext(): PageContextValue { interface PageProviderProps { initialConfig: ChronicleConfig; initialTree: Root; - initialPage: PageData | null; + initialPage: Page | null; initialApiSpecs: ApiSpec[]; loadMdx: MdxLoader; children: ReactNode; @@ -56,7 +49,7 @@ function isApisRoute(pathname: string): boolean { return pathname === '/apis' || pathname.startsWith('/apis/'); } -function getInitialErrorStatus(page: PageData | null, pathname: string): number | null { +function getInitialErrorStatus(page: Page | null, pathname: string): number | null { if (page) return null; if (pathname === '/' || isApisRoute(pathname)) return null; return 404; @@ -72,7 +65,7 @@ export function PageProvider({ }: PageProviderProps) { const { pathname } = useLocation(); const [tree] = useState(initialTree); - const [page, setPage] = useState(initialPage); + const [page, setPage] = useState(initialPage); const [errorStatus, setErrorStatus] = useState(getInitialErrorStatus(initialPage, pathname)); const [apiSpecs, setApiSpecs] = useState(initialApiSpecs); const [currentPath, setCurrentPath] = useState(pathname); @@ -112,12 +105,12 @@ export function PageProvider({ } return res.json(); }) - .then(async (data: { frontmatter: Frontmatter; relativePath: string; originalPath?: string } | undefined) => { + .then(async (data: { frontmatter: Frontmatter; relativePath: string; originalPath?: string; prev: Page['prev']; next: Page['next'] } | undefined) => { if (cancelled.current || !data) return; const { content, toc } = await loadMdx(data.originalPath || data.relativePath); if (cancelled.current) return; setErrorStatus(null); - setPage({ slug, frontmatter: data.frontmatter, content, toc }); + setPage({ slug, frontmatter: data.frontmatter, content, toc, prev: data.prev, next: data.next }); }) .catch(() => { if (!cancelled.current) { diff --git a/packages/chronicle/src/pages/DocsPage.tsx b/packages/chronicle/src/pages/DocsPage.tsx index e1710b4..35ce71f 100644 --- a/packages/chronicle/src/pages/DocsPage.tsx +++ b/packages/chronicle/src/pages/DocsPage.tsx @@ -32,12 +32,7 @@ export function DocsPage({ slug }: DocsPageProps) { }} /> diff --git a/packages/chronicle/src/server/entry-client.tsx b/packages/chronicle/src/server/entry-client.tsx index f972f85..f5a9032 100644 --- a/packages/chronicle/src/server/entry-client.tsx +++ b/packages/chronicle/src/server/entry-client.tsx @@ -5,7 +5,7 @@ import { BrowserRouter } from 'react-router'; import { ReactRouterProvider } from 'fumadocs-core/framework/react-router'; import { mdxComponents } from '@/components/mdx'; import { PageProvider } from '@/lib/page-context'; -import type { ChronicleConfig, Frontmatter, Root, TableOfContents } from '@/types'; +import type { ChronicleConfig, Frontmatter, PageNavLink, Root, TableOfContents } from '@/types'; import type { ApiSpec } from '@/lib/openapi'; import type { ReactNode } from 'react'; import { App } from './App'; @@ -17,6 +17,8 @@ interface EmbeddedData { frontmatter: Frontmatter; relativePath: string; originalPath?: string; + prev: PageNavLink | null; + next: PageNavLink | null; } const contentModules = import.meta.glob<{ default?: React.ComponentType; toc?: TableOfContents }>( @@ -60,6 +62,8 @@ async function hydrate() { ? { slug: embedded!.slug, frontmatter: embedded!.frontmatter, + prev: embedded!.prev, + next: embedded!.next, ...(await loadMdxModule(mdxPath)), } : null; diff --git a/packages/chronicle/src/server/entry-server.tsx b/packages/chronicle/src/server/entry-server.tsx index fe23e1a..6a5e3e7 100644 --- a/packages/chronicle/src/server/entry-server.tsx +++ b/packages/chronicle/src/server/entry-server.tsx @@ -43,6 +43,8 @@ export default { ? React.createElement(mdxModule.default, { components: mdxComponents }) : null, toc: mdxModule?.toc ?? [], + prev: null, + next: null, } : null; @@ -53,6 +55,8 @@ export default { frontmatter: pageData?.frontmatter ?? null, relativePath, originalPath, + prev: pageData?.prev ?? null, + next: pageData?.next ?? null, }; const safeJson = JSON.stringify(embeddedData).replace(/ Date: Fri, 17 Apr 2026 11:45:46 +0530 Subject: [PATCH 2/9] fix: add std-env as root dep to unblock nitro beta nitro@3.0.260311-beta lists std-env in devDependencies but its runtime imports it. Node ESM resolution from nitro's isolated cache path fails without std-env at top-level node_modules. Co-Authored-By: Claude Opus 4.7 (1M context) --- bun.lock | 5 +++++ package.json | 3 +++ 2 files changed, 8 insertions(+) diff --git a/bun.lock b/bun.lock index d0cf6f1..8380cd4 100644 --- a/bun.lock +++ b/bun.lock @@ -4,6 +4,9 @@ "workspaces": { "": { "name": "chronicle", + "dependencies": { + "std-env": "^4.0.0", + }, }, "packages/chronicle": { "name": "@raystack/chronicle", @@ -1234,6 +1237,8 @@ "srvx": ["srvx@0.11.12", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-AQfrGqntqVPXgP03pvBDN1KyevHC+KmYVqb8vVf4N+aomQqdhaZxjvoVp+AOm4u6x+GgNQY3MVzAUIn+TqwkOA=="], + "std-env": ["std-env@4.1.0", "", {}, "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ=="], + "string.prototype.codepointat": ["string.prototype.codepointat@0.2.1", "", {}, "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="], "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], diff --git a/package.json b/package.json index 6421a8f..0effbab 100644 --- a/package.json +++ b/package.json @@ -13,5 +13,8 @@ "dev:docs": "./packages/chronicle/bin/chronicle.js dev --config docs/chronicle.yaml", "start:docs": "./packages/chronicle/bin/chronicle.js start --config docs/chronicle.yaml", "build:docs": "./packages/chronicle/bin/chronicle.js build --config docs/chronicle.yaml" + }, + "dependencies": { + "std-env": "^4.0.0" } } From 70e9404ec6d121b283c47c5e489ae5f03ae3a7d4 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Fri, 17 Apr 2026 11:45:51 +0530 Subject: [PATCH 3/9] feat: restructure default theme layout with tab bar Drop the top Navbar; move navigation into a per-section tab bar alongside the sidebar. Resize sidebar to 262px, swap button-based nav links for pill-style tabs using mini typography tokens. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/themes/default/Layout.module.css | 80 +++++++++---------- .../chronicle/src/themes/default/Layout.tsx | 72 +++++++---------- 2 files changed, 68 insertions(+), 84 deletions(-) diff --git a/packages/chronicle/src/themes/default/Layout.module.css b/packages/chronicle/src/themes/default/Layout.module.css index dadbe5d..cccb5f0 100644 --- a/packages/chronicle/src/themes/default/Layout.module.css +++ b/packages/chronicle/src/themes/default/Layout.module.css @@ -2,76 +2,70 @@ min-height: 100vh; } -.header { - border-bottom: 1px solid var(--rs-color-border-base-primary); -} - -.search { - margin-left: var(--rs-space-5); -} - .body { flex: 1; } .sidebar { - width: 260px; + width: 262px; position: sticky; top: 0; height: 100vh; } -.content { +.mainArea { flex: 1; - padding: var(--rs-space-9); + min-width: 0; } -.sidebarList { - list-style: none; - padding: 0; - margin: 0; -} - -.separator { - height: 1px; - background: var(--rs-color-border-base-primary); - margin: var(--rs-space-3) 0; -} - -.folder { - margin-bottom: var(--rs-space-3); -} - -.folderLabel { - font-weight: 500; - font-size: 0.875rem; - color: var(--rs-color-text-base-secondary); - text-transform: uppercase; - letter-spacing: 0.05em; +.tabBar { + display: flex; + align-items: center; + height: 48px; + padding: 0 var(--rs-space-2); + background: var(--rs-color-background-base-secondary); + border-bottom: 1px solid var(--rs-color-border-base-primary); } -.folder > .sidebarList { - margin-top: var(--rs-space-2); - padding-left: var(--rs-space-4); +.tabs { + display: flex; + align-items: center; + gap: var(--rs-space-3); } -.navButton { +.tab { display: flex; align-items: center; - height: 32px; - padding: 0 var(--rs-space-4); - border: 1px solid var(--rs-color-border-base-primary); + gap: var(--rs-space-2); + padding: var(--rs-space-2) var(--rs-space-3); + border: 0.5px solid var(--rs-color-border-base-primary); border-radius: var(--rs-radius-2); - font-size: var(--rs-font-size-small); + font-size: var(--rs-font-size-mini); font-weight: var(--rs-font-weight-medium); - color: var(--rs-color-foreground-base-primary); + letter-spacing: var(--rs-letter-spacing-mini); + line-height: var(--rs-line-height-mini); + color: var(--rs-color-foreground-base-secondary); text-decoration: none; + white-space: nowrap; + cursor: pointer; } -.navButton:hover { +.tab:hover { + color: var(--rs-color-foreground-base-primary); background: var(--rs-color-background-base-primary-hover); } +.tabActive { + background: var(--rs-color-background-neutral-primary); + border-color: var(--rs-color-border-base-secondary); + color: var(--rs-color-foreground-base-primary); +} + +.content { + flex: 1; + padding: var(--rs-space-9); +} + .groupItems { padding-left: var(--rs-space-4); } diff --git a/packages/chronicle/src/themes/default/Layout.tsx b/packages/chronicle/src/themes/default/Layout.tsx index 625784d..37438a1 100644 --- a/packages/chronicle/src/themes/default/Layout.tsx +++ b/packages/chronicle/src/themes/default/Layout.tsx @@ -1,19 +1,17 @@ import { RectangleStackIcon } from '@heroicons/react/24/outline'; import { - Button, + DocumentTextIcon, + CodeBracketSquareIcon +} from '@heroicons/react/24/outline'; +import { Flex, - Headline, - Link, - Navbar, Sidebar } from '@raystack/apsara'; import { cx } from 'class-variance-authority'; import { useEffect, useRef } from 'react'; import { Link as RouterLink, useLocation } from 'react-router'; import { MethodBadge } from '@/components/api/method-badge'; -import { ClientThemeSwitcher } from '@/components/ui/client-theme-switcher'; import { Footer } from '@/components/ui/footer'; -import { Search } from '@/components/ui/search'; import type { Node } from 'fumadocs-core/page-tree'; import type { ThemeLayoutProps } from '@/types'; import styles from './Layout.module.css'; @@ -37,6 +35,7 @@ export function Layout({ }: ThemeLayoutProps) { const { pathname } = useLocation(); const scrollRef = useRef(null); + const isApiRoute = pathname.startsWith('/apis'); useEffect(() => { const el = scrollRef.current; @@ -58,38 +57,6 @@ export function Layout({ return ( - - - - - {config.title} - - - - - - {config.api?.map(api => ( - - {api.name} API - - ))} - {config.navigation?.links?.map(link => ( - - {link.label} - - ))} - {config.search?.enabled && } - - - - -
- {children} -
+ + +
+ {children} +
+