docs: migrate from Mintlify to Docusaurus 3#1174
Conversation
Replaces the Mintlify configuration with a fully-working Docusaurus 3.10 site deployable to GitHub Pages. The existing docs/docs/ content is preserved with minimal changes: canonical frontmatter removed, sidebarTitle → sidebar_label, admonition syntax updated, numeric-prefix tutorial links stripped to match Docusaurus ID conventions, and broken old-path links fixed. Key additions: - docs/docusaurus.config.ts: single-plugin setup, 63 Mintlify redirects, IBM Plex Sans, local search, analytics script, fork/upstream base-URL logic - docs/sidebars.ts: manual sidebar matching existing content structure - docs/src/: custom CSS (monochrome palette), Mintlify shim components (Card, CardGroup, Note, Warning), MDXComponents registration - docs/static/CNAME: docs.mellea.ai - _category_.json files for all 11 doc sections - docs/docs/api/index.md: placeholder until autogen pipeline runs CI workflow (docs-publish.yml) updated to build with Docusaurus and deploy to gh-pages branch; DOCS_BASE_URL set to /mellea/ on fork, / on upstream. Autogen pipeline (build.py, generate-ast.py) guards against missing docs.json when running in Docusaurus mode. codespell skip list updated for package-lock.json. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
The API docs pipeline regenerates docs/docs/api/ on each CI run,
deleting any manually-committed placeholder. Switch apiSidebar from
a fixed {type:'doc', id:'api/index'} entry to {type:'autogenerated',
dirName:'api'} so it works with the placeholder locally and with the
generated MDX tree in CI.
Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
The inject_sidebar_fix step emits a Mintlify-specific import into every generated API MDX file. Since the docs site is now Docusaurus, this import resolves to nothing and breaks the webpack build. Disable the injection; the Mintlify SidebarFix component is not needed in Docusaurus. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
mdxify emits <Icon icon="github" style="..."/> in every API page heading as a source link. Register an Icon MDX shim that renders the GitHub SVG and handles the string-form style attribute that mdxify produces. Set onBrokenLinks: 'warn' (was 'throw') because the generated api-reference.mdx and reference/cli.md contain cross-reference links that break when resolved against the Docusaurus route tree. These are a known deferred item (full autogen pipeline retargeting). Static docs are clean; only generated pages emit warnings. Fix community/building-extensions link to point to the live API backend doc. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
- Add missing `id: build_site` to the Docusaurus build step so that
`steps.build_site.outcome` resolves correctly in the job summary
- Rewrite both summary steps to use block redirects
({ ... } >> "$GITHUB_STEP_SUMMARY") — fixes SC2129 (individual
redirects) and SC2086 (unquoted variable)
- Remove unused REPO shell variable in the deploy summary — fixes SC2034
Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Incorporate improvements identified by comparing against the sgd-docusaurus template-v0.1.0 reference: - Prism code block themes: github (light) / dracula (dark), replacing the Docusaurus default - Article max-width: 48rem for comfortable reading line length - Rich dark-mode backgrounds: #111827 base, #1a2234 surface, #0d1117 navbar/footer (vs bare default) - Navbar border-bottom separator (subtle but improves section clarity) - Sidebar category headers: uppercase + letter-spaced for hierarchy - Increased line height (1.7) and explicit navbar height (3.5rem) - Code block size: 0.85rem / 1.6 line-height IBM Plex fonts, monochrome primary palette, and single-plugin routing are kept — they match project branding better than the template's Inter + teal choices. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
The homepage had 27 cards across 5 grids (How Mellea works, Key patterns, Backends, How-to guides). These are teasers for content that already exists in the sidebar — they added visual noise without aiding navigation, especially with icons stripped in the Card shim. Keep: intro blurb, install snippet, 4-card primary nav Remove: How Mellea works, Key patterns, Backends, How-to guides grids Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Four root causes addressed:
1. Directory-index relative links (decorate_api_mdx.py):
Module overview files like backends/backends.mdx are served by
Docusaurus at the directory route (/api/mellea/backends), not at
/api/mellea/backends/backends. The effective URL base is one level
higher than the module path implies, causing relative cross-reference
links like "backend#class-foo" to resolve to /api/mellea/backend
instead of /api/mellea/backends/backend.
Fixed by detecting directory-index files (stem == parent dir name) and
adjusting the relative path calculation in add_cross_references().
2. Explicit heading IDs (decorate_api_mdx.py):
Docusaurus strips JSX span text when generating heading anchors, so
"### <span>CLASS</span> `Backend`" produces #backend, not #class-backend.
Cross-reference links target #class-backend. Fixed by injecting
{#class-backend} explicit heading IDs in label_heading().
3. TypeAlias cross-references (decorate_api_mdx.py):
build_symbol_cache() included TypeAliases (e.g. PluginResult) in the
cross-reference lookup table. TypeAliases don't get class headings in
generated MDX, so linking to them produces broken anchors. Fixed by
filtering build_symbol_cache() to only include griffe kind=class members.
4. Stale Mintlify paths in CLI docstrings:
cli/decompose/decompose.py: guide/m-decompose -> how-to/m-decompose
cli/eval/commands.py: evaluation-and-observability/... -> how-to/...
Also:
- generate-ast.py: fix api-reference.mdx hrefs for directory-index modules
(use /api/mellea/backends not /api/mellea/backends/backends)
- generate-ast.py: preserve static seed files (e.g. index.md) when the
pipeline wipes and replaces the api/ directory
Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
- docs/docusaurus.config.ts: promote onBrokenLinks and onBrokenAnchors from 'warn' to 'throw' so broken links/anchors fail CI rather than silently passing with warning output - test_cross_references.py: add three regression tests for the directory-index relative-path fix: same-dir sibling, regular-file same-dir, and cross-package from a directory-index file - test_cli_reference.py: add test_see_also_targets_exist which walks all CLI command docstrings, extracts See Also paths, and asserts each referenced doc page exists in docs/docs/ — prevents stale Mintlify paths from silently surviving future migrations Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
… baseUrl Docusaurus URL-traversal resolution for relative links from a route named 'examples' (index page) with baseUrl=/mellea/ drops the baseUrl prefix, causing `../how-to/safety-guardrails` to resolve to /how-to/safety-guardrails instead of /mellea/how-to/safety-guardrails. Using .md-extension links switches Docusaurus to filesystem-based resolution which is always correct. Fixes the warning that was silently suppressed under onBrokenLinks:'warn' and surfaced as a hard failure after promoting it to 'throw'. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Node 20 reached end-of-life April 2026; GHA warns on every run. Node 22 is the current LTS and satisfies the >=18 engine constraint in docs/package.json. All actions in this workflow are already SHA-pinned. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Landing page cards had three bugs: - Raw <a href> bypassed Docusaurus Link, so baseUrl (/mellea/) was never prepended on the fork — all internal hrefs were 404s - /tutorials/01-your-first-generative-program used the raw filename; Docusaurus strips numeric prefixes, so the route is /tutorials/your-first-generative-program - Code examples card linked to GitHub source tree; changed to /examples (the in-docs examples index) - API reference card linked deep into /api/mellea/backends/backend; changed to /api-reference (the landing page) Fix: Card.tsx now uses @docusaurus/Link (to= instead of href=) which applies baseUrl automatically and is tracked by the broken-link checker. Also add docs/src/pages/404.tsx: a custom not-found page with links back to the docs home and to GitHub issues for reporting broken links. Fix JSX.Element → React.ReactElement in all shim components (CardGroup, Note, Warning, Card) to satisfy the project tsconfig. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
- index.mdx: add displayed_sidebar: docsSidebar so the left nav appears on the landing page (without it Docusaurus shows no sidebar because the root page isn't listed as a sidebar item) - docusaurus.config.ts: remove 'Contribution Guide' and 'Support' from the navbar — both are reachable from the footer and from the GitHub link; removing them eliminates the logo/nav-item overlap at medium screen widths Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
…match src/pages/404.tsx only controls the static 404.html served by GitHub Pages. When the Docusaurus SPA boots on that page it renders @theme/NotFound (the default "Page Not Found" component), overwriting the custom content with the generic Docusaurus 404. Fix: create src/theme/NotFound.tsx which overrides @theme/NotFound for the SPA catch-all route. src/pages/404.tsx now re-exports the same component so both the static file and the SPA route are identical. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
DocRoot renders @theme/NotFound/Content directly when no doc route
matches an unknown URL — not @theme/NotFound (the SPA catch-all).
Because <Route path="/"> matches every path before the * catch-all in
the Switch, the generic theme-classic NotFoundContent ("We could not
find what you were looking for.") was always shown after SPA boot,
overwriting the correct static 404.html.
Fix: swizzle @theme/NotFound/Content with our branded 404 content
(no Layout wrapper, as DocsRoot already provides one). Restructure
@theme/NotFound into a directory so both index.tsx and Content/ can
coexist; index.tsx now delegates to NotFoundContent rather than
duplicating markup.
Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
Merge coordination — DNS/CNAME cutover requiredThis PR is ready from a code and CI perspective, but it cannot be merged in isolation. Merging without coordinating the DNS change will cause a gap where The cutover steps (from the PR description) need to happen as a coordinated sequence:
There's also a bit more planning needed on the exact sequence — e.g. whether to keep Mintlify serving in parallel for a short overlap period, who holds the DNS access, whether there are any Mintlify-side redirects in flight that need to be replicated first. Suggest we pick a short timeslot (30 min should be enough) where a few of us with DNS/GitHub Pages access are available to walk through this together. Happy to coordinate that once reviewers have signed off. |
TODO: docs staging/release lifecycleThe current setup deploys continuously from
This doesn't need to block this PR, but should be tracked as follow-on work once the Mintlify → Docusaurus cutover is complete. |
Update on the staging/release lifecycle TODOFollowing up on the gap analysis above — this PR now addresses two of the four points: ✅ Versioned docs (now done): Docusaurus versioning is wired into `publish-release.yml`. From the next final release onwards, each `publish-release` dispatch with `bump_type: final` or `patch-final` automatically snapshots the current docs as `versioned_docs/version-X.Y.Z/`, updates `lastVersion`, and pushes to `main` — which triggers `docs-publish.yml` via the `docs/**` path filter. Production defaults to the latest release; a navbar dropdown gives access to `main` (unreleased) and any prior snapshot. No manual release-manager step needed. ✅ Relationship to package releases (now defined): Docs versioning is fully coupled to the Python release pipeline. RC bumps skip snapshot creation; only `final` and `patch-final` create a new default version. The bootstrap step (creating the first snapshot from the current latest tag) is documented in the PR description and needs to happen once after merge. ❌ Staging environment: still not addressed — no staging gate on `generative-computing/mellea` itself. The fork preview (`planetf1.github.io/mellea/`) remains the working substitute for this PR. ❌ PR preview deploys for contributor PRs: still not addressed. Needs separate infrastructure (e.g. Vercel/Netlify, or a dedicated `gh-pages` preview workflow per PR). These two remaining items can be tracked as separate issues post-cutover. |
Add snapshot-docs job to publish-release.yml that fires on final and patch-final bumps. After the GitHub Release is created, the job checks out main, runs docusaurus docs:version X.Y.Z, rewrites lastVersion in docusaurus.config.ts via set-last-version.mjs, and pushes to main. The push triggers docs-publish.yml automatically via the docs/** path filter, deploying production with the new release as the default version. Before the first release through this pipeline, production defaults to main (current). Once the first snapshot lands, visitors see the released docs by default; the navbar version dropdown lets them switch to main (unreleased) or any prior snapshot. remove the explicit docs-publish.yml dispatch from release.sh for finals — it is now superseded by the automatic trigger from the snapshot commit. Signed-off-by: Nigel Jones <nigel@planetf1.com> Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
With lastVersion: 'current' and path: 'main' set on the current version, Docusaurus served all docs under /main/... — making every plugin-client-redirects target invalid (they point at /reference/glossary etc. with no prefix). Remove path: 'main' from the current version config so that while current is the default (pre-first-release), docs remain at the root path and redirects work correctly. set-last-version.mjs now also injects path: 'main' into the current version block on its first run (when the first snapshot becomes the default), ensuring main is served at /main/... from the first release onwards. Signed-off-by: Nigel Jones <nigel@planetf1.com> Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
|
Is the link |
…ssues
Pass ${{ expression }} values via env: variables in run: steps, not
inline — prevents workflow injection and satisfies the security hook.
Signed-off-by: Nigel Jones <nigel@planetf1.com>
Assisted-by: Claude Code
Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
… if:/with:) Signed-off-by: Nigel Jones <nigel@planetf1.com> Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
…X.Y.Z for finals normalize_version now returns "main" for any non-final version (dev, rc, alpha, beta) so that source links in the API reference point to the live branch rather than a non-existent tag. Final releases (X.Y.Z) continue to produce blob/vX.Y.Z/ links, which resolve to the tagged commit on GitHub. Previously, build.py's normalize_version only stripped hyphen suffixes (e.g. 0.5.0-rc1 -> 0.5.0) and ignored PEP 440 pre-release forms like 0.7.0.dev0 and 0.7.0rc0, producing blob/v0.7.0.dev0/ URLs that 404. fix_source_links in decorate_api_mdx.py updated to add the "v" prefix only for version-number refs, leaving branch names (main) unchanged. Signed-off-by: Nigel Jones <nigel@planetf1.com> Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
All is hosted on the same github pages branch. The UI defaults to showing the last released version. The user can click that to get a dropdown of other versions including 'main (unreleased)'. That's a way around the separate staging site we had before and seems to be a common pattern. Harder to properly test since we don't currently have a frozen release. Suggestion is we place close attention when we do the release... |
- Footer link hover was invisible (black on dark bg) — add explicit footer link colour overrides in custom.css - Footer links column had no title, making the logo appear isolated — add 'Community' column title - Homepage logo was a 300 px standalone image creating a tall sparse hero — replace with a flex row (logo + intro text side-by-side) Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>
|
@serjikibm — footer fixes just pushed (commit
|
Static images referenced as bare /images/... in JSX don't get the Docusaurus baseUrl prepended, causing a 404 on the fork preview where baseUrl is /mellea/. Import via @site/static so webpack resolves the correct path regardless of deployment base. Assisted-by: Claude Code Signed-off-by: Nigel Jones <jonesn@uk.ibm.com>



Fixes #1176
Fixes #557
👉 Live preview
Browse the site now: https://planetf1.github.io/mellea/
This is the full migration, deployed from
planetf1/melleaon every push tomain(on my fork). All 11 sidebar sections are live. Use it to review navigation, dark mode, search, and content before voting to merge.What this does
This PR replaces the Mintlify documentation setup with a Docusaurus 3.10 site
that builds cleanly and deploys to GitHub Pages via a single CI job.
The content of
docs/docs/is preserved almost entirely — changes are purelymechanical: canonical frontmatter removed,
sidebarTitlerenamed tosidebar_label, a handful of admonitions updated to Docusaurus syntax, andinternal links de-prefixed to match the way Docusaurus strips numeric filename
prefixes from doc IDs (
01-your-first-generative-program→your-first-generative-program).The homepage has been simplified: the four lower card grids (How Mellea works,
Key patterns, Backends, How-to guides) are removed. They were teasers for content
that already lives in the sidebar — keeping only the four primary nav cards (Get
started, Tutorial, Code examples, API reference) gives a cleaner first impression.
Key design decisions
Single docs plugin, two named sidebars. Rather than two separate
@docusaurus/plugin-content-docsinstances, the site uses one plugin withrouteBasePath: '/'and bothdocsSidebarandapiSidebardefined insidebars.ts. This avoids SSR failures at the secondary plugin's root routewhen only a placeholder API doc exists.
apiSidebaris autogenerated. Thedocs/docs/api/tree is wiped andregenerated by the autogen pipeline on every CI run. A static
{type:'doc'}entry in the sidebar would be deleted on each build;
{type:'autogenerated', dirName:'api'}picks up whatever the pipeline emits.SidebarFix injection disabled.
decorate_api_mdx.pywas emitting aMintlify-specific
import { SidebarFix } from "/snippets/SidebarFix.mdx"into every generated API MDX file. Step 3 in
decorate()is now a no-op.<Icon>shim added.mdxifygenerates<Icon icon="github" .../>as asource link in every API heading. An
IconMDX component shim is registeredglobally; it renders the GitHub SVG and handles the string-form
styleattribute that
mdxifyproduces.onBrokenLinks: 'throw',onBrokenAnchors: 'throw'. The Docusaurus buildfails on any broken internal link or anchor in static docs — this is stricter
than Mintlify, which ran cloud-side and didn't surface link failures in CI. The
generated API MDX files produce some cross-reference warnings that are pre-existing
and do not break the build today; fixing them requires autogen pipeline retargeting
(deferred, tracked in "What's deferred" below).
Fork/upstream base URL separation.
DOCS_BASE_URLis set to/mellea/when running on
planetf1/melleaand/ongenerative-computing/mellea.Visual polish from template comparison. Compared against the
sgd-docusaurustemplate prototype. Adopted: Prism github/dracula code themes,article max-width (48rem), rich dark-mode backgrounds, navbar border separator,
uppercase sidebar section headers, increased line height. Kept: IBM Plex fonts,
monochrome primary palette, single-plugin routing.
Docs versioning is fully integrated into the release pipeline.
Docusaurus's built-in versioning is wired into
publish-release.ymlso thedocs lifecycle tracks the Python package lifecycle automatically — no manual
steps added to the release checklist.
For users: after the first final release ships, visiting
docs.mellea.ailands on the latest released version by default. A version dropdown in the
navbar lets anyone switch to
main(unreleased, labelled "main (unreleased)"with a banner) or any prior release snapshot. Visiting today, before the first
snapshot, shows
main(which is the only version).For the release manager: nothing changes. Running
publish-release.ymlwithbump_type: final(orpatch-final) now also triggers a newsnapshot-docsjob that commits
versioned_docs/version-X.Y.Z/tomain, sets that versionas the new default, and pushes. That push triggers
docs-publish.ymlvia thedocs/**path filter, deploying production. No additional dispatch, no extra UIstep.
For GitHub/CI: the
snapshot-docsjob runs in theauto-releaseenvironment(same bypass rights as the
releasejob), checks outmain, runsdocusaurus docs:version X.Y.Z, rewriteslastVersionindocusaurus.config.tsvia a small helper script, and pushes. The push commitmessage (
docs: snapshot X.Y.Z) triggersdocs-publish.ymlnaturally. RCs anddev bumps skip this job entirely (
if: bump_type == 'final' || 'patch-final').API reference source links use the correct git ref.
The GitHub source links rendered next to every function/class heading in the API
reference (
View source/ the GitHub icon) now resolve correctly:X.Y.Z), links point toblob/vX.Y.Z/— the exacttagged commit on GitHub.
main/ dev / pre-release builds (.devN,rcN, etc.), links pointto
blob/main/— the live default branch, which is the right target when nostable tag exists for that version.
Previously, dev builds produced
blob/v0.7.0.dev0/which 404s on GitHub(PEP 440 pre-release suffixes have no corresponding git tag). The fix lives in
tooling/docs-autogen/build.py(normalize_version) andtooling/docs-autogen/decorate_api_mdx.py(fix_source_links).What's included
docs/docusaurus.config.ts— full config: single-plugin setup, 63 Mintlifyredirect rules, IBM Plex Sans typography, local search, analytics script;
Docusaurus versioning config (
lastVersion,versions.current,docsVersionDropdownin navbar)docs/sidebars.ts— 11-section manual sidebar plus autogenerated API sidebardocs/src/— custom CSS, Mintlify shim components (Card, CardGroup, Icon,Note, Warning), global MDX registration
docs/static/CNAME—docs.mellea.ai_category_.jsonfor all 11 doc sectionsnpm ci, Docusaurus build, deploy viapeaceiris/actions-gh-pages.github/workflows/publish-release.yml— newsnapshot-docsjob (finals only).github/scripts/set-last-version.mjs— helper to rewritelastVersionon each release.github/scripts/release.sh— removed explicitdocs-publish.ymldispatch for finals (now triggered automatically by the snapshot commit)RELEASE.md— updated to document automatic docs versioning behaviourtooling/docs-autogen/build.py—normalize_versionfix for PEP 440 pre-releasestooling/docs-autogen/decorate_api_mdx.py—fix_source_linksfix for correctv-prefix logicContent validation
The
build-and-validateCI job runs on every push and PR that touchesdocs/,mellea/, or the autogen tooling. Compared to Mintlify (cloud-hosted, no local build step — link failures were invisible in CI), Docusaurus adds transparent, local validation with explicit severity levels.Hard failures — block CI
onBrokenLinks: 'throw'in Docusaurus config[link](path)that doesn't resolve to a real pageonBrokenAnchors: 'throw'#headingreference that doesn't exist on the target pageonDuplicateRoutes: 'throw'audit_coverage.py --quality --fail-on-quality --threshold 100 --orphansraisestatement missing aRaises:docstring entry; orphaned API pagespytest tooling/docs-autogen/test_cli_reference.pynpm run buildSoft checks — recorded in job summary, non-blocking
markdownlint-cliondocs/docs/**/*.mdaudit_coverage.py --threshold 80Pre-existing warnings (not regressions)
/api/mellea/*tutorials/./01-foo) that Docusaurus stripsCannot infer update dateThese warnings existed under Mintlify too — they just weren't visible because Mintlify's build was opaque to CI.
Going live (after merge — not part of this PR)
generative-computing/mellea: Settings → Pages →gh-pagesbranchdocs.mellea.aiDNS CNAME from Mintlify togenerative-computing.github.iocd docs && npm run docusaurus -- docs:version X.Y.Z(using the current latest released tag) and commit the resultingversioned_docs/,versioned_sidebars/,versions.json, andlastVersionconfig edit tomain. This creates the first snapshot so the version dropdown is populated before the next release fires.What's deferred
cross-reference cleanup — root cause of the broken link warnings above)
markdownlint-cli→markdownlint-cli2bumpTesting
Spot checks:
/guide/glossary→/reference/glossary) worksmainpoint toblob/main/(not a 404 tag URL)