A static Getting Started site for the MariaDB AI Plugins, built by GitHub Pages' own Jekyll. There is no build step to run and no theme gem: everything — layouts, includes, CSS, the small progressive-enhancement JS — lives in this folder.
Settings → Pages → Build and deployment → Deploy from a branch → main / /docs.
That is the whole setup. GitHub builds the site with its pinned github-pages
bundle (Jekyll 3.x) on every push to main.
The site is served from the custom domain in CNAME,
ai-plugins.mariadb.org. Only two settings in _config.yml are
environment-specific, and they must agree with it:
url: "https://ai-plugins.mariadb.org"
baseurl: ""Every internal link goes through relative_url or is relative to the page, so
moving the site is CNAME plus those two lines (serving from
<org>.github.io/ai-plugins/ instead needs baseurl: "/ai-plugins") and
nothing else.
serve.sh is the one command, and the repo-root package.json
exposes it as npm scripts so it also shows up in editors' script runners:
npm run docs # serve with live reload
npm run docs:open # …and open a browser
npm run docs:build # build into docs/_site and exit
npm run docs:skills # regenerate _data/skills.yml (see below)
./docs/serve.sh --port 4010 # or call it directly
./docs/serve.sh -- --incremental # anything after -- goes straight to jekyll
./docs/serve.sh --helpMind the baseurl. The site is served under the GitHub Pages project path, so
it lives at http://127.0.0.1:4000/ai-plugins/ and a bare / answers 404. The
script prints the full URL for that reason.
Either of these; serve.sh detects which and adapts:
gem install jekyll jekyll-seo-tag jekyll-sitemap # quick local preview
cd docs && bundle install # matches GitHub Pages exactlyThe second uses the Gemfile, which pins the github-pages bundle —
the same Jekyll 3.x build GitHub runs. serve.sh prefers it whenever a
Gemfile.lock is present, and otherwise runs a plain jekyll with
JEKYLL_NO_BUNDLER_REQUIRE set so it does not read the Gemfile and demand a
github-pages that was never installed. GitHub itself ignores the file.
Both plugins are required, not decoration. _includes/head.html
calls {% seo %}, which is an unknown Liquid tag without
jekyll-seo-tag, and Jekyll aborts on any plugin in _config.yml it cannot
require. serve.sh preflights them and fails with the install command rather
than producing a half-built site.
docs/
├── _config.yml # site config; `url` + `baseurl` are the only env-specific bits
├── _data/
│ ├── nav.yml # the header's top-row links
│ ├── subnav.yml # the header's second-row links (external, absolute urls)
│ ├── sponsors.yml # mariadb.org's footer sponsor banner (images hot-linked, never copied)
│ ├── paths.yml # learning paths (each `steps` entry is a tutorial `slug`)
│ └── skills.yml # GENERATED — see below
├── _includes/ # navbar, footer, hero wave, the two card partials
├── _layouts/ # default → home | page | tutorial
├── _tutorials/ # the tutorial collection, one file per tutorial
├── assets/{css,js,img}/ # one stylesheet, one script, the header artwork + favicon
├── serve.sh # local preview (npm run docs)
├── index.html # home
├── get-started.md # install + MCP setup + the working example
├── tutorials.html # filterable catalog
├── learning-paths.html
├── skills.html # renders _data/skills.yml
├── how-it-works.md # architecture + security model
└── mcp-tools.md # tool reference
Drop a file in _tutorials/. The filename becomes the URL
(_tutorials/my-thing.md → /tutorials/my-thing/), and the front matter drives
the card, the filters, the metadata header and the prev/next pager:
---
order: 10 # sort order; also the pager order
slug: my-thing # must match the filename — _data/paths.yml refers to it
title: "What the reader will do"
description: >-
One or two sentences. Used on the card and under the page title.
level: beginner # beginner | intermediate | advanced — drives the filter
duration: "20 min"
area: sql # must match a filter button in tutorials.html
tools: ["db.connect"] # MCP tools exercised; rendered as chips
skills: ["mariadb-select"] # skills exercised; rendered as chips
path_label: "First Steps, Step 2" # optional footer line on the card
prerequisites: # optional; rendered as the "Before you start" box
- "Markdown is allowed here."
---Conventions the layout expects:
- Each
##heading is a numbered step — thestep-contentcounter puts a numbered bubble on it. Write them as actions. - The closing summary is
<h2 class="no-step" id="what-you-built">, written as raw HTML so it keeps its heading but loses the number. - A tutorial with three or more headings gets a sidebar table of contents automatically.
Then add the slug to a path in _data/paths.yml if it
belongs to one, and add its area to the filter buttons in
tutorials.html if it introduces a new one.
_data/skills.yml and the skill_count in
_config.yml are both generated from the plugins' own
vendored manifests, so neither the catalog nor the counts quoted in prose can
drift from what ships.
scripts/sync-skills.sh runs the generator itself as its last step, so a normal
sync needs nothing extra. Run it by hand after any other change to the vendored
skills — or if the sync warned that python3 was missing:
npm run docs:skills # or: python3 docs/regenerate-skills-data.pyIt reads claude/dev-plugin/skills/.skills-manifest.json for the full list and
claude/sql-plugin/… for the subset marker. A new upstream layer renders with
its raw id as the title until prose for it is added to LAYER_TEXT in the
script — it shows up rather than vanishing.
tool_count, shell_floor and mariadb_baseline are still hand-maintained
_config.yml variables — nothing derives them — but they are each in one place
and read from Liquid, so a change is a single edit.
The stylesheet is a small token system in
assets/css/style.css — --ch-* custom properties for
colour, spacing, radius and shadow, redefined once under
html[data-theme="dark"]. Nothing else in the file hardcodes a colour, so a
palette change is confined to the two token blocks at the top.
Two things worth knowing before editing:
- The hero wave.
--ch-wave-his shared by the wave SVG's height and the hero background's bottom overhang. They must stay equal or a band of gradient shows below the curve. min-width: 0on grid children. Grid and flex items default tomin-width: auto, so a long identifier likesandbox.list_available_versionswill widen the page instead of wrapping. Every grid in the file sets it, and.prose codemay break mid-token — except inside a table cell, where the table scrolls instead.
The site was checked for horizontal overflow at 320, 360, 390, 768, 1024 and 1440 px across every page; keep it that way.
assets/js/site.js is progressive enhancement only — theme
toggle, mobile nav, table of contents, catalog filters. Every page is fully
readable with JavaScript disabled.