Skip to content

fix: upgrade fumadocs-core and fumadocs-mdx for compatibility#39

Merged
rsbh merged 1 commit intomainfrom
fix/upgrade-fumadocs-core
Apr 21, 2026
Merged

fix: upgrade fumadocs-core and fumadocs-mdx for compatibility#39
rsbh merged 1 commit intomainfrom
fix/upgrade-fumadocs-core

Conversation

@ravisuhag
Copy link
Copy Markdown
Member

@ravisuhag ravisuhag commented Apr 20, 2026

Summary

  • Upgrades fumadocs-core from 16.6.15 to 16.8.1
  • Upgrades fumadocs-mdx from 14.2.6 to 14.3.1

Problem

fumadocs-mdx@14.3.x imports fumadocs-core/content/md/frontmatter which was only added in fumadocs-core@16.8.0. When the published chronicle package is installed via npx, npm resolves fumadocs-mdx to 14.3.1 (due to the caret range in the published package), but fumadocs-core stays pinned at 16.6.15 which doesn't have that export. This causes:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'fumadocs-core/dist/content/md/frontmatter.js'
imported from fumadocs-mdx/dist/remark-include-VDoXZSBN.js

Impact

This breaks all Vercel (and local) builds using @raystack/chronicle@0.5.2,

Test plan

  • Verify npx @raystack/chronicle build completes without ERR_MODULE_NOT_FOUND
  • Verify Vercel deployment succeeds for a downstream project

fumadocs-mdx@14.3.x imports `fumadocs-core/content/md/frontmatter`
which was only added in fumadocs-core@16.8.0. The previous pin of
fumadocs-core@16.6.15 causes ERR_MODULE_NOT_FOUND when npm resolves
fumadocs-mdx to 14.3.x at install time (e.g. via npx).

Upgrade both to compatible versions:
- fumadocs-core: 16.6.15 → 16.8.1
- fumadocs-mdx: 14.2.6 → 14.3.1
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chronicle Ready Ready Preview, Comment Apr 20, 2026 10:29pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

Updated runtime dependencies in the chronicle package: fumadocs-core bumped from 16.6.15 to 16.8.1, and fumadocs-mdx bumped from 14.2.6 to 14.3.1. No other configuration changes, scripts, or interfaces were modified.

Changes

Cohort / File(s) Summary
Dependency Updates
packages/chronicle/package.json
Bumped fumadocs-core to 16.8.1 and fumadocs-mdx to 14.3.1.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Possibly related PRs

  • fix: pin fumadocs-mdx to 14.2.6 #38: Directly related as it modifies the same dependencies in packages/chronicle/package.json, pinning fumadocs-mdx to 14.2.6 while this PR updates it to 14.3.1.

Suggested reviewers

  • rohanchkrabrty
  • whoAbhishekSah
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: upgrading fumadocs-core and fumadocs-mdx for compatibility.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description clearly explains the problem, solution, and impact with specific version numbers and error messages.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/upgrade-fumadocs-core

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ravisuhag ravisuhag requested a review from rsbh April 20, 2026 22:43
@rsbh rsbh merged commit 3d15711 into main Apr 21, 2026
3 checks passed
rsbh added a commit that referenced this pull request Apr 21, 2026
Main picked up fumadocs-core/mdx bumps (#39) while this branch was
in flight; regenerate bun.lock against the current package.json so
bun install --frozen-lockfile passes on CI's merge-ref checkout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rsbh added a commit that referenced this pull request Apr 22, 2026
* feat: add versioned multi-content config schema

Rewrite chronicleConfigSchema for multi-content + versioning:
- site.title replaces top-level title
- content is now {dir,label}[] (single string form removed)
- latest + versions[] with per-version content, api, and badge
- badge variant maps to Apsara Badge color prop
- strict root + dir-uniqueness refines

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: rewrite config loader for multi-content + versions

- Validate via chronicleConfigSchema.parse instead of ad-hoc spread
- Default config uses new {site, content[]} shape
- Add helpers: getLatestContentRoots, getVersionContentRoots, getAllVersions
- ContentRoot resolves fs path (content/<dir> or versions/<v>/<dir>) and URL prefix

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: add test script using bun's built-in runner

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: cover config schema + loader helpers

- Validates parse rules: required site, non-empty content[], strict root
- Rejects legacy title, content:string, versions-without-latest, duplicate dirs
- Covers getLatestContentRoots, getVersionContentRoots, getAllVersions order
- Covers loadConfig fallback + yaml parsing via __CHRONICLE_CONFIG_RAW__

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: use lodash/uniqBy for dir uniqueness check

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: thread projectRoot through CLI instead of contentDir

- loadCLIConfig returns projectRoot (configPath's dirname) and drops
  resolveContentDir now that content is an array of {dir,label}
- Commands drop --content flag; content path is config-driven
- Vite define __CHRONICLE_CONTENT_DIR__ points at packageRoot/.content
  mirror so downstream routes remain stable as the mirror grows to
  include versioned subtrees
- linkContent still called with a bridge path (projectRoot/content);
  scaffold gets its real multi-root rewrite next commit

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: build multi-content + versioned .content mirror

linkContent now takes (projectRoot, config) and rebuilds
packageRoot/.content to mirror the configured layout:

  .content/<contentDir>           → <projectRoot>/content/<contentDir>
  .content/<versionDir>/<contentDir> → <projectRoot>/versions/<v>/<contentDir>

The mirror is wiped on each run (handles legacy single-symlink and
stale entries), then rebuilt from getLatestContentRoots and
getVersionContentRoots. CLI commands pass config through and rename
vite's return to viteConfig to avoid shadowing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add version-aware selectors to source

Pure tree/page filters live in src/lib/version-source.ts, keyed off
the config's versions[]. source.ts exposes thin wrappers:

- getPageTreeForVersion(ctx) returns a subtree scoped to the version
- getPagesForVersion(ctx)   returns pages filtered to the version
- getVersionContextForUrl   resolves URL -> VersionContext

Latest (ctx.dir === null) excludes anything under /<versionDir>/*,
while a version returns only its subtree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: cover content mirror scaffold

Extract buildContentMirror(mirrorRoot, projectRoot, config) as the pure
unit; linkContent stays a thin wrapper binding PACKAGE_ROOT/.content.

Tests use tmpdir to exercise:
- single and multi content latest layouts
- nested versioned layout
- idempotency on re-run
- stale entries wiped when config shrinks
- legacy single-symlink mirror replaced by directory + child symlinks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: cover version-aware source filters

- resolveVersionFromUrl: matches versions exactly, falls back to latest
  (no false positive on substring overlap e.g. /v1 vs /v1beta)
- filterPagesByVersion: latest excludes versioned pages, version scopes
  to its prefix
- filterPageTreeByVersion: latest strips version folders, version unwraps
  its folder, absent version returns empty

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: add pure route resolver for multi-content + versions

resolveRoute(pathname, config) classifies a URL into:

- redirect      single-content root -> /<dir> or /<v>/<dir>
- docs-index    multi-content root (latest or versioned) for landing
- docs-page     slug is the full URL slug incl. version prefix
                (fumadocs page URLs already include /<v>/ so the
                mirror + loader handle lookup without stripping)
- api-index / api-page  /apis or /<v>/apis routes

Resolver stays classifier-only; invalid content dirs fall through
to docs-page and let page lookup return 404 downstream.

Tests cover single/multi/versioned configs, trailing slash, and
version-shaped-but-unknown prefixes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: expose RouteType enum for route classifier

Replace string literal types with a `RouteType` const-object so
callers can reference RouteType.DocsPage etc. instead of repeating
hyphenated strings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: apply version-aware routing in SSR + client

- entry-server.tsx uses resolveRoute:
  - RouteType.Redirect returns 302 with Location (single-content
    root -> /<dir>; version root -> /<v>/<dir>)
  - RouteType.DocsPage loads page + version-scoped tree via
    getPageTreeForVersion; missing page -> 404
  - RouteType.DocsIndex returns 404 today (multi-content landing
    lands in phase 3B)
  - API routes load specs only when the URL is actually an API
    route instead of on every request
- PageProvider now takes initialVersion and exposes it via context;
  client nav re-runs resolveRoute on pathname changes so the version
  ctx stays in sync
- App.tsx delegates to the shared resolver and updates Head/JSON-LD
  references to read config.site.title (schema change from phase 1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: wire initialVersion into hydration + guard redirect narrowing

- entry-client now forwards embedded.version into PageProvider and
  uses the shared resolveRoute to decide when to fetch /api/specs
  (previously hard-coded to pathname.startsWith('/apis'), which
  missed /<v>/apis)
- PageProvider only pushes version state when the route actually
  carries one (Redirect has no version payload)
- Default config fallback matches the new {site, content[]} shape

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: multi-content landing page for docs-index route

- getLandingEntries(config, versionDir) returns {label, href,
  contentDir} per content root (null = latest) so both UI and
  tests share the same source of truth
- LandingPage renders a grid of cards from getLandingEntries; it
  reads version via usePageContext so a versioned /<v> root
  (multi-content) shows that version's dirs + labels
- App.tsx routes RouteType.DocsIndex to LandingPage inside
  DocsLayout; entry-server returns 200 and PageProvider stops
  forcing a 404 for docs-index
- Tests: getLandingEntries covers latest, versioned, and unknown
  version cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: per-version API specs

- getApiConfigsForVersion(config, dir|null) picks config.api for
  latest or versions[].api for a version; tests cover both
- /api/specs accepts ?version=<dir>; entry-server and entry-client
  pass the active version so /apis and /<v>/apis resolve to their
  own spec set
- page-context always refetches on api-nav so switching versions
  from the api page updates the spec list

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: version-aware server routes (llms, sitemap, og)

- buildLlmsTxt(config, pages, version) centralises the llms.txt
  rendering so /llms.txt and /<v>/llms.txt share one codepath
- /llms.txt now scopes to LATEST_CONTEXT via getPagesForVersion
- New /<version>/llms.txt handler looks up the version from config,
  404s for unknown, and emits pages from getPagesForVersion(ctx)
- sitemap.xml iterates getAllVersions and emits /<v>/apis/... for
  each version's api specs (latest stays unprefixed)
- og.tsx reads config.site.title (schema change from phase 1)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: remaining config.title references + init template

- head.tsx, default/paper Layout.tsx, ApiPage.tsx read config.site.title
- init.ts defaultConfig matches the new {site, content[]} schema and
  scaffolds content/<dir>/index.mdx so chronicle dev finds the mirror
  entry; drops --content flag that no longer maps to the config shape
- [version]/llms.txt.ts reads the version param via h3's
  getRouterParam (nitro doesn't re-export it)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat!: move description under site

description is now a site-level field in chronicleConfigSchema.
Callers (llms.ts, LandingPage, App Head/JSON-LD, init template,
page-context fallback) read config.site.description.

BREAKING CHANGE: top-level `description` is no longer accepted; move
it under `site:` in chronicle.yaml.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: migrate docs/ to new content layout

- Content moved under docs/content/docs/; chronicle.yaml uses
  {site, content[]} shape with description nested under site
- docs URL shape unchanged (/docs/<slug>) since content dir is `docs`
  and existing cross-links already use /docs/ prefix

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: inject synthetic meta.json per content root

Runtime-only — no filesystem writes. buildFiles() emits a meta entry
for each (version, contentDir) pair using the config label and
root:true so fumadocs treats the folder as a root and renders its
children at the top of the sidebar instead of wrapping them under
the folder index's frontmatter title.

User-provided meta.json files at the same path still win (synthetic
is skipped when a user entry exists).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: nav helpers for version + content-dir selectors

- getActiveContentDir(url, config) extracts the active content dir
  from the URL after stripping any version prefix
- getVersionHomeHref(config, versionDir) returns the href a version
  switcher should point to — single content collapses to /<dir>,
  multi content returns the version root so the landing renders
- splitContentButtons(items, max) splits a list into visible +
  overflow for the default theme's 3-button-plus-more layout

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: version switcher + content-dir buttons in default theme

- VersionSwitcher: Apsara Menu triggered from a navbar Button; shows
  active version's label + optional Badge, hides when no versions
- ContentDirButtons: first 3 content dirs render as Buttons (active =
  solid, rest = outline); overflow collapses into a More Menu; hides
  when the active version has <= 1 content dir
- Both hook into usePageContext for the active version and navigate
  with react-router; neither component adds state

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: version switcher + content-dir dropdown in paper theme

Paper theme puts both selectors in the sidebar (not the navbar):

- VersionSwitcher: full-width Apsara Menu; active version label + badge
- ContentDirDropdown: full-width Menu listing config.content[] entries
- Both hide when there are no versions / single content dir
- Layout stacks them above ChapterNav with a new .nav group

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: opt-in landing page via latest.landing / versions[].landing

- Add optional boolean `landing` to latestSchema and versionSchema
- Route resolver checks the flag first: landing=true -> DocsIndex,
  otherwise redirects to the first content dir (default behaviour
  when the field is absent)
- Resolver tests updated: multi-content-no-landing now redirects,
  new case covers the default-redirect path, versioned fixture sets
  landing:true on v1 to keep the DocsIndex assertion

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: chromeless landing page (no sidebar)

Add hideSidebar flag to ThemeLayoutProps; both theme Layouts skip
their sidebar when set. App.tsx passes hideSidebar=true for the
DocsIndex (landing) route so /<...> landing pages render without
the sidebar chrome while keeping navbar/footer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: versioned example opts into landing pages

latest + v1 set landing:true so / and /v1 show the chromeless
landing; v2 leaves it default so /v2 redirects to /v2/docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: drop node:path from config.ts for browser compat

config.ts runs both server- and client-side (imported by
LandingPage/ContentDirButtons via getLandingEntries).
Vite externalises node:path on the client, so path.join(...) threw
at runtime. Replace with forward-slash template literals; scaffold
still normalises via path.resolve when it consumes fsPath.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: switchers use DropdownMenu (apsara 0.55 export)

Apsara 0.55 exposes DropdownMenu, not Menu. Replace Menu + render
prop with DropdownMenu + asChild pattern in both themes' switchers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: filterPageTreeByContentDir for per-content sidebar scope

Adds a pure helper that trims the page tree down to a single
content folder's children given the active (version, contentDir)
pair. DocsLayout already calls this so the sidebar at /docs shows
only docs pages, /dev only dev pages, etc.

Tests cover latest, missing content dir, and versioned disambiguation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: add dev/build scripts for examples

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: per-version search index

- /api/search reads a `tag` param (used by fumadocs' fetchClient) to
  resolve a VersionContext; unknown/missing tag falls back to latest
- Pages come from getPagesForVersion, api docs from
  getApiConfigsForVersion + the version's urlPrefix so /apis entries
  for v1 link to /v1/apis/...
- Indexes and raw docs cached per version key so switching versions
  doesn't rebuild latest and vice-versa
- Search component pulls active version from usePageContext and
  forwards it as `tag` to useDocsSearch

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: canonical URL in Head

Head now reads pathname via useLocation and emits
<link rel='canonical'> plus og:url when config.url is set. Since
pathname already carries the version prefix, versioned pages get
distinct canonical URLs (e.g. https://site.example.com/v1/docs/intro).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test: cover chronicle init scaffolding

Refactor init into a pure runInit(projectDir) that returns a list of
events (created/skipped/updated); the Command only wires stdout. This
makes init unit-testable.

Tests cover:
- empty projects scaffolding content/docs, chronicle.yaml (with a
  sample index.mdx), and .gitignore
- emitted chronicle.yaml round-tripping through chronicleConfigSchema
- existing chronicle.yaml is left untouched
- existing content/docs with files skips the sample index.mdx
- .gitignore with partial entries gets the missing ones appended

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: migrate examples/basic to new content layout

- chronicle.yaml uses {site, content[]} shape; title -> site.title,
  description -> site.description, content string removed in favour of
  the `docs` content root
- All mdx moved into content/docs/ (api/, guides/, etc. preserved
  under the same dir); petstore.json and frontier.yaml stay at the
  project root so config.api spec paths keep resolving

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: rewrite configuration reference for new schema

- Adds project-layout section showing content/ + versions/ roots
- Drops removed top-level title/description/content-string, adds new
  site, content[], latest, versions sections with landing + badge
- Notes search scoping per version and llms.txt per-version output

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: add migration guide

Step-by-step walk-through for upgrading a legacy Chronicle project:
before/after config and filesystem diffs, content move, optional
landing + version setup, breaking-change table.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* revert: drop migration guide from published docs

Migration notes stay in the local VERSIONING_MIGRATION.md instead of
shipping as a docs page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: add lint + test workflow for PRs

Runs bun lint and bun test on every pull request and push to main
from packages/chronicle. Uses the same Bun runtime as the release
workflow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: mirror content with per-file symlinks so prod build finds pages

Vite's build-time import.meta.glob doesn't descend into symlinked
directories, so the previous dir-level symlinks made the production
bundle ship an empty page tree (/docs etc. 404). Replace
buildContentMirror's dir symlinks with a recursive walk that mkdirs
each subfolder in the mirror and symlinks files individually; dev
live-reload is preserved, and vite build now walks real dirs.

Tests updated to assert on real dirs + per-file symlinks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: add --config flag to chronicle start

The start command lost --config in the phase-2 CLI refactor; pass
the user's chronicle.yaml path through loadCLIConfig so
npm-script workflows like `chronicle start --config docs/...`
resolve the right config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: reject '.', '..', and path-shaped dir names

Content + version dir names now fail schema validation unless they
are simple folder names. Rejects '.', '..', and anything containing
'/' or '\\' so neither fsPath nor urlPrefix can resolve to a path
traversal or produce a broken URL like '/./.'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: update vercel output path

* feat: validate content/version dir overlap + reserved route segments

- versions[].dir can no longer collide with a top-level content[].dir;
  the URL segment would otherwise shadow the content root
- 'apis' (and future RESERVED_ROUTE_SEGMENTS entries) are rejected as
  content or version dir names to avoid colliding with built-in routes
- Tests cover both refines

Addresses coderabbit review on types/config.ts:166.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: harden buildLlmsTxt against missing title + empty description

- LlmsPage.title is now optional; buildLlmsTxt falls back to the
  page URL when title is missing or whitespace-only (extractFrontmatter
  defaults to 'Untitled' today but the helper should defend itself)
- Empty description no longer produces a stray blank line between
  the heading and the index
- Tests cover both cases

Addresses coderabbit review on routes/llms.txt.ts:22.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: address coderabbit review findings

- scaffold.ts: mirrorTree/removeMirror only swallow ENOENT now;
  other fs errors (permission, read failures) propagate
- navigation.ts + route-resolver.ts: resolve content dirs through
  getLatestContentRoots / getVersionContentRoots so the mirror,
  source, nav, and resolver all agree on how content roots are
  derived
- page-context.tsx: clear stale page + errorStatus when entering
  an API route so a prior 404 doesn't linger on the API screen
- LandingPage.tsx: switch content-dir cards from <a> to RouterLink
  to keep navigation SPA-internal and preserve hydrated state

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: pin bun to 1.3.9 to keep lockfile reproducible

Bun 1.3.13 rolled out mid-PR and resolves the lockfile slightly
differently from 1.3.9, causing --frozen-lockfile to fail even
without any dep change. Pin the setup-bun action to 1.3.9 so CI
matches the lockfile that was committed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: sync lockfile after rebase on main

Main picked up fumadocs-core/mdx bumps (#39) while this branch was
in flight; regenerate bun.lock against the current package.json so
bun install --frozen-lockfile passes on CI's merge-ref checkout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: address minor coderabbit review findings

- init.test.ts: the "preserve existing index.mdx" case now actually
  writes an index.mdx and asserts its contents are untouched
- head.tsx: og:image + twitter:image are absolute URLs when config.url
  is set (crawlers require absolute); relative fallback otherwise
- api/search.ts: unknown ?tag=<x> now returns HTTP 400 instead of
  silently folding into LATEST_CONTEXT — easier to spot client bugs
- entry-server.tsx: the chronicle:ssr-rendered hook now fires on
  302 redirects too so analytics/metrics don't under-count them
- types/config.ts: dirNameSchema accepts only /^[a-zA-Z0-9][\w.-]*$/
  so hidden (".git"), whitespace, and control-char names are rejected

Tests cover the new accept/reject sets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor: address copilot review findings

- config.ts: RESERVED_ROUTE_SEGMENTS now includes every top-level
  server-owned route (api, apis, og, llms.txt, robots.txt,
  sitemap.xml), and the reserved-segment check uses superRefine so
  zod points at the offending path (content[i].dir,
  versions[vi].dir, or versions[vi].content[ci].dir) instead of a
  generic 'content' path
- api/specs.ts: unknown ?version=<x> returns HTTP 400 to match the
  search endpoint's behaviour
- entry-client.tsx: api spec resolution + specsUrl now read
  route.version.dir so versioned URLs fetch their own specs even
  when embedded data is missing; embedded.version still wins for
  initialVersion when present
- App.tsx: RouteType.Redirect renders react-router's <Navigate>
  so client nav (e.g. clicking the title to /) follows the same
  302 target the server would emit

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: fail fast on missing content roots + guard empty-folder match

- scaffold.mirrorTree now rethrows ENOENT as 'Content directory not
  found: <path>' so a config that points at a non-existent dir errors
  out instead of silently building an empty mirror
- filterPageTreeByContentDir requires the candidate folder to carry
  at least one URL before checking the prefix, otherwise an empty
  top-level folder's vacuous every() match would shadow the actual
  content folder

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: preserve defaults + always include latest in getAllVersions

- loadConfig shallow-merges user config over defaultConfig so an
  omitted theme or search still falls back to the defaults that
  defaultConfig defines (previously dropped on any user config load)
- getAllVersions always emits the latest entry (even when config.latest
  is absent and versions[] is empty), so consumers like sitemap.xml
  don't miss /apis when there are no explicit versions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix: match .gitignore entries by line in chronicle init

Substring match treats 'dist' as already present when the existing
.gitignore only has 'distribution'. Split on newlines and compare
trimmed lines exactly; added regression test covering that case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat: SSR-full page tree + client-side version+content filter

Switching versions client-side (e.g. via VersionSwitcher) previously
kept the SSR's version-scoped tree in PageContext, leaving the
sidebar showing the wrong version's pages. Now:

- entry-server.tsx emits the full unfiltered pageTree
- DocsLayout runs filterPageTreeByVersion followed by
  filterPageTreeByContentDir per render, so nav across versions
  re-derives the sidebar from pathname + active context
- entry-client.tsx resolves routeVersion via resolveVersionFromUrl
  (not LATEST_CONTEXT) so a direct client hit to a redirect target
  like /v1 hydrates with the correct version ctx

Also: entry-server no longer catches loadApiSpecs failures — broken
API specs now surface as server errors instead of rendering an empty
API page with a 200.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs: clarify versioned content path in configuration reference

Make it explicit that versions[].content[].dir is rooted at
versions/<version-dir>/<dir>/, not directly under versions/<v>/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants