From eb512650012b099d9beb3e5d5b892a6eb91905bb Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 4 May 2026 12:23:49 +0530 Subject: [PATCH 1/4] fix: enable search in paper theme via Cmd+K shortcut Search component was missing from paper theme. Add it with hidden trigger button so only keyboard shortcut (Cmd/Ctrl+K) opens the dialog. Refactor Search props from className to classNames object for flexibility. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/components/ui/search.tsx | 6 +++--- packages/chronicle/src/pages/ApiLayout.tsx | 2 +- packages/chronicle/src/themes/paper/Layout.module.css | 4 ++++ packages/chronicle/src/themes/paper/Layout.tsx | 2 ++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/chronicle/src/components/ui/search.tsx b/packages/chronicle/src/components/ui/search.tsx index c2a3e7d6..1a313ac4 100644 --- a/packages/chronicle/src/components/ui/search.tsx +++ b/packages/chronicle/src/components/ui/search.tsx @@ -13,10 +13,10 @@ import { usePageContext } from '@/lib/page-context'; import styles from './search.module.css'; interface SearchProps { - className?: string; + classNames?: { trigger?: string }; } -export function Search({ className }: SearchProps) { +export function Search({ classNames }: SearchProps) { const [open, setOpen] = useState(false); const navigate = useNavigate(); const { version } = usePageContext(); @@ -60,7 +60,7 @@ export function Search({ className }: SearchProps) { aria-label='Search' title='Search (Ctrl/⌘K)' onClick={() => setOpen(true)} - className={className} + className={classNames?.trigger} > diff --git a/packages/chronicle/src/pages/ApiLayout.tsx b/packages/chronicle/src/pages/ApiLayout.tsx index bfd6631a..8e201f62 100644 --- a/packages/chronicle/src/pages/ApiLayout.tsx +++ b/packages/chronicle/src/pages/ApiLayout.tsx @@ -26,7 +26,7 @@ export function ApiLayout({ children }: ApiLayoutProps) { content: styles.content }} > - + {children} ); diff --git a/packages/chronicle/src/themes/paper/Layout.module.css b/packages/chronicle/src/themes/paper/Layout.module.css index 4fb5bf08..27d98c40 100644 --- a/packages/chronicle/src/themes/paper/Layout.module.css +++ b/packages/chronicle/src/themes/paper/Layout.module.css @@ -79,3 +79,7 @@ flex: 1; background: var(--rs-color-background-neutral-primary); } + +.hiddenTrigger { + display: none; +} diff --git a/packages/chronicle/src/themes/paper/Layout.tsx b/packages/chronicle/src/themes/paper/Layout.tsx index d52c80ab..974fdaa7 100644 --- a/packages/chronicle/src/themes/paper/Layout.tsx +++ b/packages/chronicle/src/themes/paper/Layout.tsx @@ -6,6 +6,7 @@ import { useLocation, useNavigate } from 'react-router'; import { getLandingEntries } from '@/lib/config'; import { getActiveContentDir } from '@/lib/navigation'; import { usePageContext } from '@/lib/page-context'; +import { Search } from '@/components/ui/search'; import type { ThemeLayoutProps } from '@/types'; import { ChapterNav } from './ChapterNav'; import styles from './Layout.module.css'; @@ -82,6 +83,7 @@ function LayoutInner({ ) : null}
+ {config.search?.enabled && } {children}
From 17bbd4f20bd802c494114dff3498b7ed24837f83 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 4 May 2026 15:37:50 +0530 Subject: [PATCH 2/4] fix: resolve relative links in MDX content to absolute routes Replace remark-strip-md-extensions with remark-resolve-links that resolves relative links (./page.mdx, ../dir/page.md) to absolute routes based on the source file's location. Also strips index/readme suffixes to match route conventions. Co-Authored-By: Claude Opus 4.6 (1M context) --- examples/versioned/content/dev/api.mdx | 5 +++ examples/versioned/content/dev/index.mdx | 4 +++ examples/versioned/content/docs/guide.mdx | 4 +++ examples/versioned/content/docs/index.mdx | 4 +++ .../chronicle/src/lib/remark-resolve-links.ts | 32 +++++++++++++++++++ .../src/lib/remark-strip-md-extensions.ts | 14 -------- packages/chronicle/src/server/vite-config.ts | 4 +-- 7 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 packages/chronicle/src/lib/remark-resolve-links.ts delete mode 100644 packages/chronicle/src/lib/remark-strip-md-extensions.ts diff --git a/examples/versioned/content/dev/api.mdx b/examples/versioned/content/dev/api.mdx index f1afad0b..82668e13 100644 --- a/examples/versioned/content/dev/api.mdx +++ b/examples/versioned/content/dev/api.mdx @@ -6,3 +6,8 @@ order: 2 # Dev API notes — latest Latest `/dev/api`. + +- [Dev Home](./index.mdx) +- [Docs Guide](../docs/guide.mdx) +- [Docs Home](../docs/index.mdx) +- [Guide with hash](../docs/guide.mdx#some-section) diff --git a/examples/versioned/content/dev/index.mdx b/examples/versioned/content/dev/index.mdx index bff05478..1ff62d55 100644 --- a/examples/versioned/content/dev/index.mdx +++ b/examples/versioned/content/dev/index.mdx @@ -6,3 +6,7 @@ order: 1 # Dev — latest Latest `/dev`. + +- [API Notes](./api.mdx) +- [Docs Home](../docs/index.mdx) +- [Docs Guide](../docs/guide.mdx) diff --git a/examples/versioned/content/docs/guide.mdx b/examples/versioned/content/docs/guide.mdx index 96a4f64a..cf1058c7 100644 --- a/examples/versioned/content/docs/guide.mdx +++ b/examples/versioned/content/docs/guide.mdx @@ -6,3 +6,7 @@ order: 2 # Guide — latest docs Latest `/docs/guide`. + +- [Docs Home](./index.mdx) +- [Dev API](../dev/api.mdx) +- [Dev Home](../dev/index.mdx) diff --git a/examples/versioned/content/docs/index.mdx b/examples/versioned/content/docs/index.mdx index 99b03d74..289077b8 100644 --- a/examples/versioned/content/docs/index.mdx +++ b/examples/versioned/content/docs/index.mdx @@ -8,6 +8,10 @@ order: 1 This is `/docs` on latest (3.0). +## Links + +- [Guide](./guide.mdx) + ## Getting Started Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. diff --git a/packages/chronicle/src/lib/remark-resolve-links.ts b/packages/chronicle/src/lib/remark-resolve-links.ts new file mode 100644 index 00000000..9026b16b --- /dev/null +++ b/packages/chronicle/src/lib/remark-resolve-links.ts @@ -0,0 +1,32 @@ +import path from 'node:path' +import { visit } from 'unist-util-visit' +import type { Plugin } from 'unified' +import type { Link } from 'mdast' + +const remarkResolveLinks: Plugin = () => { + return (tree, file) => { + const filePath = file.path + if (!filePath) return + + const contentIdx = filePath.indexOf('/content/') + if (contentIdx === -1) return + + const relative = filePath.slice(contentIdx + '/content/'.length) + const dir = path.posix.dirname(relative) + + visit(tree, 'link', (node: Link) => { + if (!node.url) return + if (node.url.startsWith('http://') || node.url.startsWith('https://')) return + if (node.url.startsWith('#')) return + if (node.url.startsWith('/')) return + + const [rawPath, hash] = node.url.split('#') + const stripped = rawPath.replace(/\.mdx?$/, '') + let resolved = path.posix.normalize(path.posix.join(dir, stripped)) + resolved = resolved.replace(/\/(index|readme)$/i, '') || '.' + node.url = `/${resolved}${hash ? `#${hash}` : ''}` + }) + } +} + +export default remarkResolveLinks diff --git a/packages/chronicle/src/lib/remark-strip-md-extensions.ts b/packages/chronicle/src/lib/remark-strip-md-extensions.ts deleted file mode 100644 index 91858eff..00000000 --- a/packages/chronicle/src/lib/remark-strip-md-extensions.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { visit } from 'unist-util-visit' -import type { Plugin } from 'unified' - -const remarkStripMdExtensions: Plugin = () => { - return (tree) => { - visit(tree, 'link', (node: any) => { - if (!node.url) return - if (node.url.startsWith('http://') || node.url.startsWith('https://')) return - node.url = node.url.replace(/\.mdx?(#|$)/, '$1') - }) - } -} - -export default remarkStripMdExtensions diff --git a/packages/chronicle/src/server/vite-config.ts b/packages/chronicle/src/server/vite-config.ts index c0084331..b285fdfa 100644 --- a/packages/chronicle/src/server/vite-config.ts +++ b/packages/chronicle/src/server/vite-config.ts @@ -7,7 +7,7 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import remarkDirective from 'remark-directive'; import { type InlineConfig } from 'vite'; -import remarkStripMdExtensions from '../lib/remark-strip-md-extensions'; +import remarkResolveLinks from '../lib/remark-resolve-links'; import remarkReadingTime from 'remark-reading-time'; import remarkUnusedDirectives from '../lib/remark-unused-directives'; @@ -77,7 +77,7 @@ export async function createViteConfig( }, }], remarkUnusedDirectives, - remarkStripMdExtensions, + remarkResolveLinks, remarkMdxMermaid, remarkReadingTime, ], From a192944a32744960fb9312f101fa53e0073377d2 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 4 May 2026 15:47:57 +0530 Subject: [PATCH 3/4] fix: include reading time in page API for client navigation Load MDX module in /api/page endpoint to include _readingTime in frontmatter response, matching SSR behavior in entry-server.tsx. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/server/api/page.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/chronicle/src/server/api/page.ts b/packages/chronicle/src/server/api/page.ts index 568d6aa4..6d4ac035 100644 --- a/packages/chronicle/src/server/api/page.ts +++ b/packages/chronicle/src/server/api/page.ts @@ -1,5 +1,5 @@ import { defineHandler, HTTPError } from 'nitro'; -import { getPage, getPageNav, extractFrontmatter, getRelativePath, getOriginalPath } from '@/lib/source'; +import { getPage, getPageNav, extractFrontmatter, getRelativePath, getOriginalPath, loadPageModule } from '@/lib/source'; export default defineHandler(async event => { const slugParam = event.url.searchParams.get('slug') ?? ''; @@ -11,11 +11,17 @@ export default defineHandler(async event => { } const nav = await getPageNav(slug); + const originalPath = getOriginalPath(page); + const relativePath = getRelativePath(page); + const mdxModule = (originalPath || relativePath) ? await loadPageModule(originalPath || relativePath) : null; return { - frontmatter: extractFrontmatter(page, slug[slug.length - 1]), - relativePath: getRelativePath(page), - originalPath: getOriginalPath(page), + frontmatter: { + ...extractFrontmatter(page, slug[slug.length - 1]), + _readingTime: mdxModule?._readingTime, + }, + relativePath, + originalPath, prev: nav.prev, next: nav.next, }; From 62a7a7ce97d95abdd334b2b54c1207c1ae4fddbd Mon Sep 17 00:00:00 2001 From: Rishabh Date: Mon, 4 May 2026 15:57:49 +0530 Subject: [PATCH 4/4] fix: address CodeRabbit review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use lastIndexOf for /content/ to avoid matching outer path directories - Guard all URI schemes (mailto:, tel:, ftp:, etc.) not just http(s) - Remove duplicate Search mount from ApiLayout — theme Layouts own search Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/chronicle/src/lib/remark-resolve-links.ts | 4 ++-- packages/chronicle/src/pages/ApiLayout.tsx | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/chronicle/src/lib/remark-resolve-links.ts b/packages/chronicle/src/lib/remark-resolve-links.ts index 9026b16b..68efe2d5 100644 --- a/packages/chronicle/src/lib/remark-resolve-links.ts +++ b/packages/chronicle/src/lib/remark-resolve-links.ts @@ -8,7 +8,7 @@ const remarkResolveLinks: Plugin = () => { const filePath = file.path if (!filePath) return - const contentIdx = filePath.indexOf('/content/') + const contentIdx = filePath.lastIndexOf('/content/') if (contentIdx === -1) return const relative = filePath.slice(contentIdx + '/content/'.length) @@ -16,7 +16,7 @@ const remarkResolveLinks: Plugin = () => { visit(tree, 'link', (node: Link) => { if (!node.url) return - if (node.url.startsWith('http://') || node.url.startsWith('https://')) return + if (/^[a-z][a-z0-9+\-.]*:/i.test(node.url)) return if (node.url.startsWith('#')) return if (node.url.startsWith('/')) return diff --git a/packages/chronicle/src/pages/ApiLayout.tsx b/packages/chronicle/src/pages/ApiLayout.tsx index 8e201f62..66593c92 100644 --- a/packages/chronicle/src/pages/ApiLayout.tsx +++ b/packages/chronicle/src/pages/ApiLayout.tsx @@ -1,6 +1,5 @@ import { cx } from 'class-variance-authority'; import type { ReactNode } from 'react'; -import { Search } from '@/components/ui/search'; import { buildApiPageTree } from '@/lib/api-routes'; import { usePageContext } from '@/lib/page-context'; import { getTheme } from '@/themes/registry'; @@ -26,7 +25,6 @@ export function ApiLayout({ children }: ApiLayoutProps) { content: styles.content }} > - {children} );