Skip to content

Commit 88286c7

Browse files
rsbhclaude
andauthored
feat: multi-content + versioned docs (#40)
* 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>
1 parent 3d15711 commit 88286c7

75 files changed

Lines changed: 3111 additions & 452 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: ci
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [main]
7+
8+
jobs:
9+
test:
10+
name: Test
11+
runs-on: ubuntu-latest
12+
timeout-minutes: 10
13+
defaults:
14+
run:
15+
working-directory: ./packages/chronicle
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
20+
- name: Setup Bun
21+
uses: oven-sh/setup-bun@v2
22+
with:
23+
bun-version: 1.3.9
24+
25+
- name: Install dependencies
26+
run: bun install --frozen-lockfile
27+
working-directory: .
28+
29+
- name: Lint
30+
run: bun run lint
31+
32+
- name: Test
33+
run: bun test

bun.lock

Lines changed: 92 additions & 132 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/chronicle.yaml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
title: Chronicle
2-
description: Config-driven documentation framework
3-
content: .
1+
site:
2+
title: Chronicle
3+
description: Config-driven documentation framework
4+
5+
content:
6+
- dir: docs
7+
label: Docs
48

59
theme:
610
name: paper
@@ -20,6 +24,8 @@ llms:
2024
telemetry:
2125
enabled: true
2226

27+
preset: "vercel"
28+
2329
footer:
2430
copyright: "© 2026 Raystack. All rights reserved."
2531
links:
File renamed without changes.
File renamed without changes.
Lines changed: 143 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,34 @@ order: 3
66

77
# Configuration
88

9-
All site configuration lives in a single `chronicle.yaml` file in your project root. The config is validated using Zod — invalid fields will produce clear error messages at startup.
9+
All site configuration lives in a single `chronicle.yaml` file in your project root. The config is validated using Zod — invalid fields produce clear errors at startup.
1010

11-
## Full Example
11+
## Project layout
12+
13+
```
14+
my-docs-site/
15+
├── chronicle.yaml
16+
├── content/ ← latest
17+
│ ├── docs/
18+
│ └── dev/
19+
└── versions/ ← only if versions: is declared
20+
├── v2/
21+
│ └── docs/
22+
└── v1/
23+
├── docs/
24+
└── dev/
25+
```
26+
27+
Content dirs declared in top-level `content:` are resolved under `content/<dir>/` for the latest version; each `versions[].content[].dir` is resolved under `versions/<version-dir>/<dir>/`.
28+
29+
## Full example
1230

1331
```yaml
14-
title: My Project Docs
15-
description: Documentation for My Project
32+
site:
33+
title: My Project Docs
34+
description: Documentation for My Project
35+
1636
url: https://docs.example.com
17-
content: docs
1837
preset: vercel
1938

2039
logo:
@@ -24,12 +43,45 @@ logo:
2443
theme:
2544
name: default
2645

46+
content:
47+
- dir: docs
48+
label: Docs
49+
- dir: dev
50+
label: Dev Docs
51+
52+
latest:
53+
label: "3.0"
54+
landing: true
55+
56+
versions:
57+
- dir: v2
58+
label: "2.0"
59+
content:
60+
- dir: docs
61+
label: Docs
62+
63+
- dir: v1
64+
label: "1.0"
65+
landing: true
66+
badge:
67+
label: deprecated
68+
variant: warning
69+
content:
70+
- dir: dev
71+
label: Developer Guide
72+
- dir: docs
73+
label: Docs
74+
api:
75+
- name: REST API (v1)
76+
spec: ./v1-openapi.yaml
77+
basePath: /apis
78+
server:
79+
url: https://api.example.com/v1
80+
2781
navigation:
2882
links:
2983
- label: GitHub
3084
href: https://github.com/myorg/myproject
31-
- label: Blog
32-
href: https://blog.example.com
3385

3486
search:
3587
enabled: true
@@ -40,8 +92,6 @@ footer:
4092
links:
4193
- label: GitHub
4294
href: https://github.com/myorg/myproject
43-
- label: License
44-
href: /license
4595

4696
api:
4797
- name: REST API
@@ -71,49 +121,108 @@ telemetry:
71121
72122
## Reference
73123
74-
### title
124+
### site
75125
76-
**Required.** The site title displayed in the navbar and browser tab.
126+
**Required.** Site-level metadata.
77127
78128
```yaml
79-
title: My Documentation
129+
site:
130+
title: My Documentation
131+
description: Documentation powered by Chronicle
80132
```
81133
134+
| Field | Type | Description |
135+
|-------|------|-------------|
136+
| `title` | `string` | Site title (navbar, browser tab, canonical metadata). Required. |
137+
| `description` | `string` | Meta description for SEO and OG. |
138+
82139
### url
83140

84-
Optional site URL. Used for SEO metadata, sitemap, and canonical URLs.
141+
Optional site URL used for the sitemap and canonical URLs.
85142

86143
```yaml
87144
url: https://docs.example.com
88145
```
89146

90147
### content
91148

92-
Optional content directory path. Can be overridden by the `--content` CLI flag.
149+
**Required.** Content dirs for the latest version. Each entry maps to `content/<dir>/` on disk and to `/<dir>/...` in URLs.
93150

94151
```yaml
95-
content: docs
152+
content:
153+
- dir: docs
154+
label: Docs
155+
- dir: dev
156+
label: Dev Docs
96157
```
97158

98-
### preset
159+
| Field | Type | Description |
160+
|-------|------|-------------|
161+
| `dir` | `string` | Folder name under `content/`. Must be unique. |
162+
| `label` | `string` | Display label in navigation and landing pages. |
163+
164+
### latest
99165

100-
Optional deploy preset. Can be overridden by the `--preset` CLI flag.
166+
Optional metadata for the latest version. Required when `versions:` is declared.
101167

102168
```yaml
103-
preset: vercel # vercel, cloudflare, or node-server
169+
latest:
170+
label: "3.0"
171+
landing: true
104172
```
105173

106-
### description
174+
| Field | Type | Description | Default |
175+
|-------|------|-------------|---------|
176+
| `label` | `string` | Version label (e.g. `3.0`). Shown in the version switcher. | — |
177+
| `landing` | `boolean` | `true` → `/` renders a landing page listing content dirs. `false` (default) → `/` 302s to the first content dir. | `false` |
107178

108-
Optional meta description for SEO.
179+
### versions
180+
181+
Optional list of older versions. Each entry lives under `versions/<dir>/` on disk and is reachable at `/<dir>/...` in URLs.
109182

110183
```yaml
111-
description: Documentation powered by Chronicle
184+
versions:
185+
- dir: v1
186+
label: "1.0"
187+
landing: true
188+
badge:
189+
label: deprecated
190+
variant: warning
191+
content:
192+
- dir: dev
193+
label: Developer Guide
194+
- dir: docs
195+
label: Docs
196+
api:
197+
- name: REST API (v1)
198+
spec: ./v1-openapi.yaml
199+
basePath: /apis
200+
server:
201+
url: https://api.example.com/v1
202+
```
203+
204+
| Field | Type | Description |
205+
|-------|------|-------------|
206+
| `dir` | `string` | Folder name under `versions/`. Doubles as URL prefix. Must be unique. |
207+
| `label` | `string` | Version label. Shown in the switcher. |
208+
| `landing` | `boolean` | `true` → `/<dir>` renders a landing page; otherwise 302s to the version's first content dir. Default `false`. |
209+
| `badge` | `object` | Optional Apsara badge next to the version label. |
210+
| `badge.label` | `string` | Badge text. |
211+
| `badge.variant` | `"accent" \| "warning" \| "danger" \| "success" \| "neutral" \| "gradient"` | Badge colour. Default `accent`. |
212+
| `content` | `{dir, label}[]` | Content dirs for this version. Entries may rename, reorder, or omit top-level content dirs. |
213+
| `api` | `ApiConfig[]` | Version-scoped API specs, rendered at `/<dir>/apis/...`. Same shape as top-level `api:`. |
214+
215+
### preset
216+
217+
Optional deploy preset. Can be overridden by `--preset`.
218+
219+
```yaml
220+
preset: vercel # vercel, cloudflare, or node-server
112221
```
113222

114223
### logo
115224

116-
Logo configuration with theme-aware variants.
225+
Logo with theme-aware variants.
117226

118227
```yaml
119228
logo:
@@ -151,13 +260,9 @@ navigation:
151260
links:
152261
- label: GitHub
153262
href: https://github.com/myorg/myproject
154-
- label: API
155-
href: /apis
156263
social:
157264
- type: github
158265
href: https://github.com/myorg/myproject
159-
- type: discord
160-
href: https://discord.gg/example
161266
```
162267

163268
**navigation.links**
@@ -176,7 +281,7 @@ navigation:
176281

177282
### search
178283

179-
Search functionality powered by Fumadocs.
284+
Search functionality powered by Fumadocs. Automatically scoped to the active version.
180285

181286
```yaml
182287
search:
@@ -189,7 +294,7 @@ search:
189294
| `enabled` | `boolean` | Enable/disable search | `true` |
190295
| `placeholder` | `string` | Search input placeholder | `Search...` |
191296

192-
When enabled, search is accessible via the navbar button or keyboard shortcut `Cmd+K` / `Ctrl+K`.
297+
When enabled, search is accessible via the navbar button or keyboard shortcut `Cmd+K` / `Ctrl+K`. Active version comes from the URL; switching versions scopes the index.
193298

194299
### footer
195300

@@ -212,7 +317,7 @@ footer:
212317

213318
### api
214319

215-
OpenAPI specification configuration for interactive API documentation.
320+
OpenAPI specification configuration at the top level applies to the latest version (served at `/apis/...`). Version-scoped specs live under each `versions[].api`.
216321

217322
```yaml
218323
api:
@@ -228,8 +333,6 @@ api:
228333
placeholder: Enter your API key
229334
```
230335

231-
Each entry in the `api` array creates a section of API documentation.
232-
233336
| Field | Type | Description |
234337
|-------|------|-------------|
235338
| `name` | `string` | API display name |
@@ -241,11 +344,9 @@ Each entry in the `api` array creates a section of API documentation.
241344
| `auth.header` | `string` | Header name for auth token |
242345
| `auth.placeholder` | `string` | Placeholder text in auth input |
243346

244-
API pages include a "Try it out" panel that uses the configured server URL and auth settings.
245-
246347
### llms
247348

248-
Configuration for LLM-friendly content generation. When enabled, Chronicle generates `/llms.txt` and `/llms-full.txt` endpoints.
349+
Per-version `llms.txt` generation.
249350

250351
```yaml
251352
llms:
@@ -254,7 +355,7 @@ llms:
254355

255356
| Field | Type | Description | Default |
256357
|-------|------|-------------|---------|
257-
| `enabled` | `boolean` | Enable/disable LLM content endpoints | `false` |
358+
| `enabled` | `boolean` | Emit `/llms.txt` and `/<version-dir>/llms.txt` | `false` |
258359

259360
### analytics
260361

@@ -274,7 +375,7 @@ analytics:
274375

275376
### telemetry
276377

277-
Prometheus metrics export via OpenTelemetry. When enabled, metrics are served on a separate port.
378+
Prometheus metrics export via OpenTelemetry. Served on a separate port.
278379

279380
```yaml
280381
telemetry:
@@ -293,10 +394,14 @@ Metrics are available at `http://localhost:<port>/metrics` in Prometheus exposit
293394

294395
## Defaults
295396

296-
If `chronicle.yaml` is missing or fields are omitted, these defaults apply:
397+
When `chronicle.yaml` is missing or fields are omitted, these defaults apply:
297398

298399
```yaml
299-
title: Documentation
400+
site:
401+
title: Documentation
402+
content:
403+
- dir: docs
404+
label: Docs
300405
theme:
301406
name: default
302407
search:
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)