# duckfn documentation site
Static site for <https://shijianjs.github.io/duckfn/>, built with
[Docusaurus](https://docusaurus.io/) and deployed by
[`.github/workflows/DeployDocs.yml`](../.github/workflows/DeployDocs.yml).
## Layout
| `docs/intro.md` | Introduction; its `slug` keeps `/docs/intro` stable. |
| `docs/getting-started/` | Creating a project, installation and quick start. |
| `docs/guide/` | The feature guide: attributes, each registration kind, type mapping, custom types, errors. |
| `docs/examples/duckfn.md` | The example extension shipped with the crate, feature by feature. |
| `docs/docs-kit/` | Documentation tooling: the `duckfn-docs-kit` features that build this site — an overview page plus one page per feature (runnable SQL blocks, preloaded extensions, TOC toggle, home components, version placeholder). Its overview `slug` keeps `/docs/docs-kit` stable. |
| `docs/internals/` | — (removed; the architecture page now lives in `docs/development/`). |
| `docs/development/` | The development guide — its own sidebar and navbar entry: `architecture.md` (how duckfn works inside), `build-and-release.md`, `contributing.md`. |
| `docs/faq.md`, `docs/known-issues.md`, `docs/community-extension-docs.md` | User-guide reference pages, at the top level of the user-guide sidebar. |
| `i18n/zh-Hans/` | Simplified Chinese translations of all of the above, plus the UI strings. |
| `src/pages/index.tsx` | Home page: hero, feature cards, the Rust/SQL showcase, and the "where to go next" cards. Every string is a `<Translate>` and has an entry in `i18n/zh-Hans/code.json` under `homepage.*`. The hero, feature grid and "next steps" grid are `<dfk-*>` custom elements from `duckfn-docs-kit`, fed through callback refs. |
| `src/css/custom.css` | Palette and theme overrides: the seven `--ifm-color-primary*` steps come from the logo blue. The `--duckfn-*` brand tokens themselves are defined once in `duckfn-docs-kit/src/theme/tokens.css` and pulled in by the `@import` at the top of this file. |
| `static/` | Files copied to the site root (images, `favicon.ico`, `.nojekyll`). |
| `sidebars.ts` | The three sidebars, one per navbar entry: `userGuide`, `docsKit` and `development`. The last two are autogenerated from their folder; the user guide lists its pages and wraps the `getting-started/`, `guide/` and `examples/` folders in categories (an `autogenerated` entry yields a folder's contents, not the folder). Order inside a folder still comes from `sidebar_position`. |
| `docusaurus.config.ts` | Site configuration, including the locale list and the footer links. |
This package is one workspace of the repository root: the shared building blocks
(`duckfn-docs-kit/` — the runnable SQL blocks, the extension preloading, the TOC
toggle, the `<dfk-*>` home-page elements, the brand tokens and the two remark
plugins) are a sibling npm workspace, consumed as `duckfn-docs-kit`. They are
documented under [`docs/docs-kit/`](docs/docs-kit/) (English and Chinese). The
lockfile and `node_modules` live at the root.
## Preloaded DuckDB extensions
The runnable SQL blocks preload the `duckfn` extension through the
`dfkExtensions` plugin (`duckfn-docs-kit/sql/extensions`), configured in
`docusaurus.config.ts`. Where that file comes from is decided by
`DOCS_EXTENSION_FROM_RELEASE`:
- **Set — only the GitHub Pages build sets it.** The build fetches the latest
release's `duckfn-wasm_eh.duckdb_extension.wasm` into
`static/duckdb-extensions/duckfn.duckdb_extension.wasm`. That is safe there
because Deploy Docs runs *after* the extension pipeline published the release
(see Deployment below).
- **Unset — every local run.** Nothing is fetched: the plugin serves whatever
already sits under `static/duckdb-extensions/`, i.e. the wasm
`just build_wasm_eh` put there. `just test_wasm` builds it and then runs the
SQL test, so local runs never touch a release; `just docs_build` / `docs_start`
on their own fail until that file exists.
Either way the file name must keep `duckfn` before the first dot, because that
base is the entry symbol DuckDB looks up. The plugin also injects the ordered
preload list into every page, and the kit's runtime loads it while DuckDB
initialises — which starts in the background as soon as a page with a runnable
block opens, so the first Run click does not wait for the download.
The plugins also carry the client wiring: `dfkExtensions` registers the `dfk-*`
elements and `dfkTocToggle` (`duckfn-docs-kit/toc-toggle/plugin`) adds the TOC
collapse control, so the site keeps no `src/clientModules/` files of its own.
Release downloads are cached under `.cache/duckfn-docs-kit/` and only re-fetched
when the release asset's sha256 changes. Both `.cache/` and
`static/duckdb-extensions/` are gitignored — a file placed there by hand needs
`git add -f`.
The extension is built by CI for DuckDB v1.5.6, and the site pins
`@duckdb/duckdb-wasm` to the exact dev build whose engine matches
(`1.33.1-dev65.0`, engine v1.5.6 — npm's `next` tag at the time of writing;
stable `1.32.0` bundles v1.4.3 and rejects the extension with a C-API layout
mismatch). When either side moves, re-check the Docs kit pages: the live blocks
there must run.
## Commands
```shell
npm install # once, from the repository root (npm workspaces)
npm start -w docs # dev server at http://localhost:3000
npm start -w docs -- --locale zh-Hans # dev server, Chinese
npm run build -w docs # static site into docs/build/
npm run serve -w docs # preview the build
npm run typecheck -w docs # tsc
npm test -w docs # run every runnable SQL block in DuckDB-Wasm
```
`just test_wasm` is the local end-to-end check and wraps the whole path: build
the wasm extension (`just build_wasm_eh`), copy it into
`static/duckdb-extensions/` and run `npm test -w docs`. Run that rather than
`npm run build` / `npm start` alone — those need the extension file to be there
already (see above).
`duckfn-docs-kit` is a source dependency, so rebuild it (`npm run build -w duckfn-docs-kit`)
after editing anything under `duckfn-docs-kit/src/`. The components' styles are inlined into
the JS bundle at build time, so rebuild after touching `home.css`; the global CSS that ships
as source (`tokens.css`, `toc-toggle.css`) needs no build step.
`npm run build` is the check that matters: `onBrokenLinks` is set to `throw`, so a link to a page
that does not exist fails the build for both locales.
## Markdown conventions
**Admonitions.** The opening directive goes on a line of its own and takes an optional title in
square brackets — a bare `:::note Title` does not render. The content always starts on the next line:
```md
:::note[Limitations]
- the first point
:::
```
Nesting works by using more colons for each level: `:::::info[Parent]` → `::::danger[Child]` →
`:::tip[Deep Child]`.
Two more things worth knowing: `onBrokenLinks` is `throw`, so every internal link and anchor has to
resolve (in both locales), and code fences should use one of the languages enabled for Prism in
`docusaurus.config.ts` — `bash`, `rust`, `sql` or `toml`.
**Mermaid.** A ```` ```mermaid ```` fence renders a diagram through the kit's `<dfk-mermaid>` element,
wired up by `remarkMermaid` in the docs preset's `remarkPlugins`. `@docusaurus/theme-mermaid` is
deliberately **not** installed and `markdown.mermaid` is not set — the element replaces both, and
two renderers on one page would fight (see [`duckfn-docs-kit/CONVENTIONS.md`](../duckfn-docs-kit/CONVENTIONS.md),
*Mermaid*). The look and the palette come from the kit's default — `options.look: 'neo'`, `theme:
{light: 'redux-color', dark: 'redux-dark-color'}` — and this site does not override them; a site that
wants its own passes `remarkMermaid({config: {theme:…, options:…}})`. That `theme` has to be the
`{light, dark}` object (the element re-renders on a mode switch) while `look` has no per-mode variant
and goes through `options`. Both settings are easy to get wrong silently: mermaid ignores an
unrecognised value and falls back, so after changing them check a diagram in a browser rather than
trusting `npm run build` (`node` elements carry `data-look="neo"`, which is the quick way to confirm
the look). Diagrams render on the client, so a syntax error shows up in the page rather than failing
the build. Keep labels quoted (`A["text"]`) and use `<br/>` for line breaks; avoid `#` and
unescaped `&` in labels. Each diagram carries reset-zoom, fullscreen, source-editing and
download-SVG buttons in its top-right corner on hover.
**Prefer `.md`.** In Docusaurus 3 both formats go through the same MDX pipeline,
and the kit's runnable blocks need no MDX feature — so the tree sticks to `.md`
and there is no `.mdx` file today. If a page ever genuinely needs JSX, `.mdx`
would behave identically.
## Translations
The site ships in English (`en`, default) and Simplified Chinese (`zh-Hans`). Routes are prefixed per
locale: `/docs/...` and `/zh-Hans/docs/...`.
A translated page is a full copy of its English source, placed under
`i18n/zh-Hans/docusaurus-plugin-content-docs/current/` with the same relative path:
- Translate the body and the reader-facing front matter (`title`, `description`).
- Keep `id`, `slug` and `sidebar_position` identical so both languages share routes and order.
- Link to other pages with **relative file paths** (`./types.md`, `../guide/types.md`). A hard-coded
`/docs/...` link would send a Chinese page to the English one.
UI strings live in `i18n/zh-Hans/*.json`. After changing text in `docusaurus.config.ts`, in
`src/`, or a `_category_.json`, regenerate the stubs and fill in the new entries:
```shell
npx docusaurus write-translations --locale zh-Hans
```
`write-translations` keeps existing messages, so it only adds what is missing. Two things to check
afterwards: the new entries it appends are in English, and the manually translated `homepage.*`
entries in `code.json` are still present — it warns about `homepage.tagline` because that one cannot
be extracted statically, which is expected.
Add another language by listing it in `i18n.locales` in `docusaurus.config.ts` and repeating the
steps above.
## Deployment
`.github/workflows/DeployDocs.yml` builds and publishes the site to GitHub Pages. It is triggered by
the `Main Extension Distribution Pipeline` **finishing**, not by the version tag itself:
```yaml
on:
workflow_run:
workflows: ['Main Extension Distribution Pipeline']
types: [completed]
```
The order is the point. The deployed site preloads the released wasm
(`DOCS_EXTENSION_FROM_RELEASE=1`), and that release is created by the pipeline's last job — deploying
on the tag push raced the build and fetched the *previous* release. The build job's guard admits only
a run that succeeded, was started by pushing a `v*.*.*` tag, and the tag is then checked out by SHA
(a `workflow_run` runs on the default branch). Pull-request runs of the pipeline, and manual runs of
it, stop at that guard. The workflow can still be started by hand from the Actions tab.
`url` and `baseUrl` are not hard-coded — the workflow reads them from `actions/configure-pages` and
passes them to the build as `DOCS_URL` and `DOCS_BASE_URL`, which `docusaurus.config.ts` picks up.
Outside CI they fall back to `http://localhost:3000` and `/`.
One-time setup: in the repository settings, set **Pages → Build and deployment → Source** to
**GitHub Actions**.