duckfn-docs-kit 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/AGENTS.md +326 -234
  2. package/README.md +10 -6
  3. package/dist/IconButton.d.ts +22 -0
  4. package/dist/codemirror.d.ts +29 -0
  5. package/dist/download.d.ts +51 -0
  6. package/dist/index.d.ts +28 -7
  7. package/dist/index.js +2 -2
  8. package/dist/mermaid/DfkMermaid.d.ts +25 -0
  9. package/dist/mermaid/config.d.ts +48 -0
  10. package/dist/mermaid/remark.d.ts +40 -0
  11. package/dist/mermaid/remark.js +32 -0
  12. package/dist/mermaid/render.d.ts +92 -0
  13. package/dist/mermaid/styles.d.ts +6 -0
  14. package/dist/panzoom-view.d.ts +17 -0
  15. package/dist/{register-CALCwFBv.js → register-wdwf0LC4.js} +1365 -738
  16. package/dist/remark.d.ts +1 -1
  17. package/dist/source-dialog.d.ts +35 -0
  18. package/dist/sql/PreviewTabs.d.ts +39 -23
  19. package/dist/sql/SvgViewer.d.ts +53 -0
  20. package/dist/sql/client.js +1 -1
  21. package/dist/sql/harness.js +1 -1
  22. package/dist/sql/remark.d.ts +6 -1
  23. package/dist/sql/renderers.d.ts +9 -0
  24. package/package.json +6 -2
  25. package/src/IconButton.ts +50 -0
  26. package/src/codemirror.ts +97 -0
  27. package/src/download.ts +169 -0
  28. package/src/index.ts +31 -7
  29. package/src/mermaid/DfkMermaid.css +326 -0
  30. package/src/mermaid/DfkMermaid.ts +411 -0
  31. package/src/mermaid/config.ts +74 -0
  32. package/src/mermaid/remark.ts +98 -0
  33. package/src/mermaid/render.ts +172 -0
  34. package/src/mermaid/styles.ts +24 -0
  35. package/src/panzoom-view.ts +235 -0
  36. package/src/register.ts +3 -0
  37. package/src/remark.ts +1 -1
  38. package/src/source-dialog.ts +126 -0
  39. package/src/sql/DfkSql.css +13 -10
  40. package/src/sql/DfkSql.ts +60 -51
  41. package/src/sql/PreviewTabs.ts +98 -52
  42. package/src/sql/SvgViewer.ts +155 -0
  43. package/src/sql/remark.ts +6 -1
  44. package/src/sql/renderers.ts +433 -159
  45. package/src/sql/sql.css +189 -19
  46. package/dist/sql/editor.d.ts +0 -16
  47. package/src/sql/editor.ts +0 -75
package/AGENTS.md CHANGED
@@ -1,234 +1,326 @@
1
- # duckfn-docs-kit — usage guide for agents and site authors
2
-
3
- This is the **consumer-facing** companion to [`README.md`](./README.md): the README says what to
4
- import and how to wire it into `docusaurus.config.ts`, this file states the **contracts and the
5
- traps** — the things that are easy to get subtly wrong when you write a docs page or review one.
6
-
7
- Read it before writing runnable SQL blocks or configuring preloads.
8
-
9
- > Developing the kit itself? Its internal conventions (rendering contract, shadow-DOM rules,
10
- > release flow) are in `CONVENTIONS.md` in the repository. That file is **not** published; this one
11
- > is, so it is what a dependency reader sees.
12
-
13
- ## The three things that most often go wrong
14
-
15
- 1. **A runnable block shows only the result of its *last* statement.** Stacking several independent
16
- examples in one block means the reader sees one result and the others silently vanish. One
17
- example per block; several statements only for a preamble (`SET`, `CREATE`, …) that the last
18
- query needs.
19
- 2. **`show: "html"` / `"iframe"` / `"svg"` need to be told which column holds the markup.** With more
20
- than one result column you **must** name it: `"field": "<column>"`. Without it the renderer has
21
- nothing to preview. `"tab_name"` names the column that labels each preview tab, and without it
22
- tabs read `Row 1`, `Row 2`, …
23
- 3. **Only a block whose info string is JSON with `"type":"duckfn"` becomes runnable.** A bare
24
- ```` ```sql ```` block (or any other metastring) stays a plain, un-runnable code block — no Run
25
- button, nothing executed.
26
-
27
- ## Install and wire it up
28
-
29
- See [`README.md`](./README.md) — install, the three `docusaurus.config.ts` plugins, and the one CSS
30
- import. Everything below assumes that is already done.
31
-
32
- ## Runnable SQL blocks
33
-
34
- ### The shape
35
-
36
- A fenced block whose info string is a JSON config. The block itself becomes a CodeMirror editor
37
- with a Run button; nothing executes until the reader clicks it.
38
-
39
- ````md
40
- ```sql {"type":"duckfn"}
41
- SELECT 40 + 2 AS answer;
42
- ```
43
- ````
44
-
45
- Works in `.md` and `.mdx` alike — the metastring is rewritten during the build, before either format
46
- is compiled.
47
-
48
- ### Config reference
49
-
50
- | Field | Meaning |
51
- | --- | --- |
52
- | `type` | `"duckfn"`. Required — this is what makes the block runnable. |
53
- | `show` | `table` (default), `text`, `html`, `iframe`, `svg`. See *Result renderers*. |
54
- | `field` | The column holding the markup, for `html` / `iframe` / `svg`. Required when the result has more than one column. |
55
- | `tab_name` | The column whose value labels each preview tab. Defaults to `Row N`. |
56
- | `option.width` · `option.height` | CSS lengths for the preview box (`"100%"`, `"640px"`). |
57
- | `option.sandbox` | Sandbox tokens for the iframe, replacing the default `allow-scripts`. Widen deliberately. |
58
- | `extensions` | Extra extension names to `LOAD` before this block runs, on top of the site's preloads. |
59
- | `repository` | Where those extensions come from: `community`, `core`, or a repository URL. |
60
- | `allowUnsignedExtensions` | Accept an unverifiable signature. Site-wide via the preload config, or per block; the first block to initialise the engine settles it. |
61
- | `expect` | `ok` (default) or `error`. `error` declares "this block must fail" — see *Testing*. |
62
-
63
- ### Behaviour you have to design around
64
-
65
- - **The result shown is the last statement's.** A block with `SET …; CREATE …; SELECT …` shows the
66
- `SELECT`. A block with three independent `SELECT`s shows the third one only.
67
- - **Default `show`:** a single column with a single row renders as `text`; anything else renders as
68
- a `table`. Set `show` explicitly when the shape matters.
69
- - **One DuckDB-Wasm instance per page, one connection per page.** Blocks on the same page share
70
- state — a table or macro created in one block is visible to the next — and pages are isolated
71
- from each other. Do not write a block that depends on another *page*.
72
- - **The site's preloaded extensions are already loaded.** Call into them directly; do not add
73
- `extensions` for the extension the site documents.
74
- - **Errors are a result, not a broken block.** A failing statement renders its message in the result
75
- area and keeps whatever the reader typed.
76
- - **Every result has a tab strip** (even a plain table), and the fullscreen toggle lives at its right
77
- end. Table results bring sorting, resizable rows/columns, a right-click menu and header drag.
78
-
79
- ### Result renderers
80
-
81
- | `show` | What it renders | Needs |
82
- | --- | --- | --- |
83
- | `table` | The result grid. | — |
84
- | `text` | A bare scalar, as one line. | A single column/row result. |
85
- | `html` / `iframe` | One tab per row; the markup goes into a sandboxed `iframe` (`srcdoc`), with a trailing `Table` tab that is always last. | `field` (unless the result has exactly one column). `tab_name` to label tabs. |
86
- | `svg` | The markup is spliced **into the page** (one tab per row, trailing `Table` tab). | Same as above. |
87
-
88
- Two facts worth knowing before you pick one:
89
-
90
- - `html` and `iframe` are the *same* renderer. The frame is sandboxed with `allow-scripts` and
91
- **without** `allow-same-origin`, so a report's JavaScript runs while the frame keeps an opaque
92
- origin — that is what makes charts work, and it is also why the parent page cannot read
93
- `iframe.contentDocument` (it is `null` by design).
94
- - `svg` shares the page, so anything that could execute or navigate — `script`, `foreignObject`,
95
- `on*` handlers, `javascript:` links — is stripped before insertion.
96
-
97
- ### Examples
98
-
99
- A table result, and a scalar that degrades to text:
100
-
101
- ````md
102
- ```sql {"type":"duckfn","show":"table"}
103
- SELECT * FROM range(10) WHERE range > 5;
104
- ```
105
-
106
- ```sql {"type":"duckfn"}
107
- SELECT 1;
108
- ```
109
- ````
110
-
111
- An HTML (or iframe) report — note `field` and `tab_name`:
112
-
113
- ````md
114
- ```sql {"type":"duckfn","show":"iframe","field":"html","tab_name":"label","option":{"height":"170px"}}
115
- SELECT * FROM (VALUES
116
- ('Bars', '<!doctype html><body><h4>Quarterly revenue</h4><svg viewBox="0 0 240 80">…</svg></body>')
117
- ) AS t(label, html);
118
- ```
119
- ````
120
-
121
- An inline SVG, and an extension that is not preloaded:
122
-
123
- ````md
124
- ```sql {"type":"duckfn","show":"svg","option":{"height":"140px"}}
125
- SELECT '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40"/></svg>';
126
- ```
127
-
128
- ```sql {"type":"duckfn","show":"table","extensions":["inet"]}
129
- SELECT '127.0.0.1'::INET::VARCHAR AS ip;
130
- ```
131
- ````
132
-
133
- ### Mistakes to check for when reviewing a page
134
-
135
- - Several independent examples stacked in one block — only the last result is visible. **Split
136
- them into one block per example.**
137
- - `show: "html"` / `"iframe"` / `"svg"` with a multi-column result and no `field`.
138
- - No `tab_name`, so the preview tabs read `Row 1`, `Row 2`, … instead of something meaningful.
139
- - A bare ```` ```sql ```` block where a runnable one was intended (it renders as a plain listing).
140
- - Declaring `extensions` for the extension the site already preloads.
141
- - A block that depends on a table created on a *different* page.
142
-
143
- ## Preloading extensions (the `dfkExtensions` plugin)
144
-
145
- The site declares an ordered preload list; the kit fetches the release assets at dev/build startup,
146
- injects the list into every page and loads them — in order — while DuckDB initialises. Blocks can
147
- then call into those extensions without declaring anything.
148
-
149
- Three source kinds:
150
-
151
- | Entry | Meaning |
152
- | --- | --- |
153
- | `'json'` | A name: `LOAD json` from the official repository. |
154
- | `{name: 'h3', repository: 'community'}` | `community`, `core` or a repository URL. On wasm `INSTALL` only records *where* a later `LOAD` fetches from. |
155
- | `{url: 'duckdb-extensions/x.duckdb_extension.wasm', release: {repository, asset}}` | The site serves the file itself; with `release`, the build fetches that asset from the repository's latest release (cached by sha256 in `<siteDir>/.cache/duckfn-docs-kit/`). |
156
-
157
- Constraints that bite:
158
-
159
- - **The file name is a contract**: the text before the first dot is the entry symbol DuckDB looks
160
- up, so a release asset named `duckfn-wasm_eh.duckdb_extension.wasm` must be served as
161
- `duckfn.duckdb_extension.wasm` (the `url` decides the file name).
162
- - **Platforms must match**: a `wasm_eh` extension needs a runtime bundle on the `eh` platform. Pin
163
- `@duckdb/duckdb-wasm` to the exact version whose bundled DuckDB is ABI-compatible with the
164
- extension build.
165
- - **Unsigned third-party extensions need `allowUnsignedExtensions: true`** (the WebAssembly
166
- equivalent of `duckdb -unsigned`). Community extensions are signed and load without it.
167
-
168
- ## Testing the blocks (`duckfn-sql-verify`)
169
-
170
- The kit runs every block in a **real browser**, on the same runtime the page uses, so what CI
171
- checks is what a reader gets:
172
-
173
- ```bash
174
- duckfn-sql-verify --site . # or: npx duckfn-sql-verify --site .
175
- ```
176
-
177
- It collects every runnable block (`docs/` plus each `i18n/<locale>/…/current/` by default), runs it
178
- in DuckDB-Wasm **in a headless browser** (driven with `playwright-core`, launching your system
179
- Chrome/Edge via `executablePath` — so no browser download) with the site's extension loaded, and
180
- fails the process when a block does not behave as it declares. It is fully offline: the engine and
181
- the extension are served from local files. Wire it into `package.json` as
182
- `"test": "duckfn-sql-verify --site ."`.
183
-
184
- - **A block that demonstrates a failure must say so**: `{"type":"duckfn","expect":"error"}`. The
185
- check is two-way — a block that declares `error` and starts succeeding is reported too — and a
186
- `-- error:` comment in the SQL is *not* read; only the metadata counts.
187
- - Useful options: `--extension <path|url>`, `--platform eh|mvp`, `--content <dir>` (repeatable),
188
- `--browser <path>` (the Chrome/Edge executable; otherwise a detected one or `DFK_BROWSER`),
189
- `--timeout <ms>`, `--report <file>`, `--quiet`. It needs `playwright-core`, which the kit lists
190
- as a dependency; unlike `playwright` it never downloads a browser.
191
- - The default extension is the single file under `static/duckdb-extensions/`.
192
- - **File-system examples stay plain code blocks.** Under DuckDB-Wasm the raw file system is not
193
- POSIX-faithful: `dfn_file_exists` reads a missing file as "opened" (always `true`), writes/append
194
- byte order is wrong, and there is no existence primitive in DuckDB's C API to correct it. So keep
195
- any `COPY … TO` / `output_dir` / file-write demo as a non-runnable block and note the reason.
196
-
197
- ## TOC collapse control (`dfkTocToggle()`)
198
-
199
- ```ts
200
- plugins: [dfkTocToggle()],
201
- ```
202
-
203
- Adds a collapse button to the desktop table of contents and remembers the choice in
204
- `localStorage` (`duckfn:toc-collapsed`). Custom labels come from the plugin's `labels` option,
205
- keyed by a lower-cased `html-lang` prefix (`{en: {hide, show}, 'zh-hans': {…}}`).
206
-
207
- ## Version placeholder (`remarkVersionPlaceholder`)
208
-
209
- ```ts
210
- remarkPlugins: [[remarkVersionPlaceholder, {version: DUCKFN_VERSION}]],
211
- ```
212
-
213
- Replaces `{{DUCKFN_VERSION}}` inside `text`, `inlineCode` and `code` nodes, so a release updates one
214
- file instead of every page. It only touches that exact placeholder — anything else is left alone.
215
-
216
- ## Home-page components
217
-
218
- `<dfk-hero>`, `<dfk-features>`, `<dfk-next-steps>` (and `<dfk-sql>`) are registered by the barrel
219
- import (`duckfn-docs-kit`). The home components are **setter-driven and do not reflect attributes**:
220
- give them named setters (`setTitle`, `setTagline`, `setFeatures`, …), not markup content. They
221
- render into shadow roots, inherit `--duckfn-*` / `--ifm-*` CSS variables from the page, and are
222
- safe to call before the element is connected.
223
-
224
- ## Troubleshooting
225
-
226
- | Symptom | Likely cause |
227
- | --- | --- |
228
- | A code block has no Run button | Its info string is not JSON with `"type":"duckfn"`. |
229
- | The block runs but nothing appears in the preview | `show: "html"` / `"iframe"` / `"svg"` without `field` on a multi-column result. |
230
- | The preview tabs are labelled `Row 1`, `Row 2`, … | No `tab_name`. |
231
- | Clicking Run shows an error like `Table with name … does not exist` | The block depends on something created on another page, or on a statement that is no longer the last one in its block. |
232
- | `LOAD` fails with a signature error | `allowUnsignedExtensions: true` missing for a third-party asset. |
233
- | A preloaded extension fails to load | The served file name's pre-dot part does not match the extension's entry symbol, or the platform does not match the runtime bundle. |
234
- | Only the last of several examples shows a result | That is the contract — split the block. |
1
+ # duckfn-docs-kit — usage guide for agents and site authors
2
+
3
+ This is the **consumer-facing** companion to [`README.md`](./README.md): the README says what to
4
+ import and how to wire it into `docusaurus.config.ts`, this file states the **contracts and the
5
+ traps** — the things that are easy to get subtly wrong when you write a docs page or review one.
6
+
7
+ Read it before writing runnable SQL blocks or configuring preloads.
8
+
9
+ > Developing the kit itself? Its internal conventions (rendering contract, shadow-DOM rules,
10
+ > release flow) are in `CONVENTIONS.md` in the repository. That file is **not** published; this one
11
+ > is, so it is what a dependency reader sees.
12
+
13
+ ## The three things that most often go wrong
14
+
15
+ 1. **A runnable block shows only the result of its *last* statement.** Stacking several independent
16
+ examples in one block means the reader sees one result and the others silently vanish. One
17
+ example per block; several statements only for a preamble (`SET`, `CREATE`, …) that the last
18
+ query needs.
19
+ 2. **`show: "html"` / `"iframe"` / `"svg"` / `"mermaid"` need to be told which column holds the
20
+ markup.** With more than one result column you **must** name it: `"field": "<column>"`. Without
21
+ it the renderer has nothing to preview. `"tab_name"` names the column that labels each preview
22
+ tab, and without it tabs read `Row 1`, `Row 2`, …
23
+ 3. **Only a block whose info string is JSON with `"type":"duckfn"` becomes runnable.** A bare
24
+ ```` ```sql ```` block (or any other metastring) stays a plain, un-runnable code block — no Run
25
+ button, nothing executed.
26
+
27
+ ## Install and wire it up
28
+
29
+ See [`README.md`](./README.md) — install, the three `docusaurus.config.ts` plugins, and the one CSS
30
+ import. Everything below assumes that is already done.
31
+
32
+ ## Runnable SQL blocks
33
+
34
+ ### The shape
35
+
36
+ A fenced block whose info string is a JSON config. The block itself becomes a CodeMirror editor
37
+ with a Run button; nothing executes until the reader clicks it.
38
+
39
+ ````md
40
+ ```sql {"type":"duckfn"}
41
+ SELECT 40 + 2 AS answer;
42
+ ```
43
+ ````
44
+
45
+ Works in `.md` and `.mdx` alike — the metastring is rewritten during the build, before either format
46
+ is compiled.
47
+
48
+ ### Config reference
49
+
50
+ | Field | Meaning |
51
+ | --- | --- |
52
+ | `type` | `"duckfn"`. Required — this is what makes the block runnable. |
53
+ | `show` | `table` (default), `text`, `html`, `iframe`, `svg`, `mermaid`. See *Result renderers*. |
54
+ | `field` | The column holding the markup, for `html` / `iframe` / `svg` / `mermaid`. Required when the result has more than one column. |
55
+ | `tab_name` | The column whose value labels each preview tab. Defaults to `Row N`. |
56
+ | `option.width` · `option.height` | CSS lengths for the preview box (`"100%"`, `"640px"`). |
57
+ | `option.sandbox` | Sandbox tokens for the iframe, replacing the default `allow-scripts`. Widen deliberately. |
58
+ | `extensions` | Extra extension names to `LOAD` before this block runs, on top of the site's preloads. |
59
+ | `repository` | Where those extensions come from: `community`, `core`, or a repository URL. |
60
+ | `allowUnsignedExtensions` | Accept an unverifiable signature. Site-wide via the preload config, or per block; the first block to initialise the engine settles it. |
61
+ | `expect` | `ok` (default) or `error`. `error` declares "this block must fail" — see *Testing*. |
62
+
63
+ ### Behaviour you have to design around
64
+
65
+ - **The result shown is the last statement's.** A block with `SET …; CREATE …; SELECT …` shows the
66
+ `SELECT`. A block with three independent `SELECT`s shows the third one only.
67
+ - **Default `show`:** a single column with a single row renders as `text`; anything else renders as
68
+ a `table`. Set `show` explicitly when the shape matters.
69
+ - **One DuckDB-Wasm instance per page, one connection per page.** Blocks on the same page share
70
+ state — a table or macro created in one block is visible to the next — and pages are isolated
71
+ from each other. Do not write a block that depends on another *page*.
72
+ - **The site's preloaded extensions are already loaded.** Call into them directly; do not add
73
+ `extensions` for the extension the site documents.
74
+ - **Errors are a result, not a broken block.** A failing statement renders its message in the result
75
+ area and keeps whatever the reader typed.
76
+ - **Every result has a tab strip** (even a plain table), and all of its result-wide chrome sits at the
77
+ strip's right end — see *The strip at the right of the tabs*. Table results bring sorting, resizable
78
+ rows/columns, a right-click menu and header drag.
79
+
80
+ ### Result renderers
81
+
82
+ | `show` | What it renders | Needs |
83
+ | --- | --- | --- |
84
+ | `table` | The result grid. | — |
85
+ | `text` | A bare scalar, as one line. | A single column/row result. |
86
+ | `html` / `iframe` | One tab per row; the markup goes into a sandboxed `iframe` (`srcdoc`), with a trailing `Table` tab that is always last. | `field` (unless the result has exactly one column). `tab_name` to label tabs. |
87
+ | `svg` | The markup is spliced **into the page** (one tab per row, trailing `Table` tab). Zoom and pan open in fullscreen, where the tab strip also offers **reset zoom** / **edit source**. | Same as above. |
88
+ | `mermaid` | The cell is handed to `<dfk-mermaid>`, which renders in its **embedded** mode: no frame of its own and no floating cluster — **reset zoom** and **edit source** join the result's tab strip instead. One tab per row, trailing `Table` tab. | Same as above. |
89
+
90
+ Two facts worth knowing before you pick one:
91
+
92
+ - `html` and `iframe` are the *same* renderer. The frame is sandboxed with `allow-scripts` and
93
+ **without** `allow-same-origin`, so a report's JavaScript runs while the frame keeps an opaque
94
+ origin — that is what makes charts work, and it is also why the parent page cannot read
95
+ `iframe.contentDocument` (it is `null` by design).
96
+ - `svg` shares the page, so anything that could execute or navigate — `script`, `foreignObject`,
97
+ `on*` handlers, `javascript:` links — is stripped before insertion.
98
+
99
+ ### The strip at the right of the tabs
100
+
101
+ Every result — a plain table included — carries the same tab strip, and the right end of it is where
102
+ the result-wide controls live, in this order: **the active tab's own buttons**, **download**, then
103
+ the **fullscreen** toggle.
104
+
105
+ | Active tab | Controls |
106
+ | --- | --- |
107
+ | `Table` | Search, copy table, column-width mode, reset view, unfreeze columns |
108
+ | `svg` / `mermaid` | Reset zoom, edit source |
109
+ | `html` / `iframe` / `text` | — |
110
+
111
+ - Those table buttons are the "whole table" half of the grid's right-click menu, placed where they
112
+ cannot cover a cell; the menu itself keeps the per-cell items (copy this cell, wrap this
113
+ row/column, freeze up to this column). Freezing, column widths and the current view therefore
114
+ survive a switch to another tab and back. **Unfreeze columns** is hidden until a column has
115
+ actually been frozen, so it does not sit there as a dead control.
116
+ - **Search** opens as an input in the strip rather than floating over the cells it searches, because
117
+ the table spans the full width. It highlights every hit, shows `3/12`, and steps with the arrows;
118
+ it is per result, not shared between blocks.
119
+ - **Download** saves whatever the active tab shows, in that tab's format: `.csv` for a table, `.svg`
120
+ for `svg` / `mermaid`, `.html` for `html` / `iframe`, `.txt` for `text`. It is hidden only while
121
+ there is nothing to save yet (a figure that has not rendered).
122
+ - **Fullscreen** makes the result fill the viewport; the same button (now "Exit fullscreen") stays
123
+ put, and <kbd>Esc</kbd> works too. It is also the only place a figure zooms or pans.
124
+
125
+ ### Examples
126
+
127
+ A table result, and a scalar that degrades to text:
128
+
129
+ ````md
130
+ ```sql {"type":"duckfn","show":"table"}
131
+ SELECT * FROM range(10) WHERE range > 5;
132
+ ```
133
+
134
+ ```sql {"type":"duckfn"}
135
+ SELECT 1;
136
+ ```
137
+ ````
138
+
139
+ An HTML (or iframe) report — note `field` and `tab_name`:
140
+
141
+ ````md
142
+ ```sql {"type":"duckfn","show":"iframe","field":"html","tab_name":"label","option":{"height":"170px"}}
143
+ SELECT * FROM (VALUES
144
+ ('Bars', '<!doctype html><body><h4>Quarterly revenue</h4><svg viewBox="0 0 240 80">…</svg></body>')
145
+ ) AS t(label, html);
146
+ ```
147
+ ````
148
+
149
+ An inline SVG, and an extension that is not preloaded:
150
+
151
+ ````md
152
+ ```sql {"type":"duckfn","show":"svg","option":{"height":"140px"}}
153
+ SELECT '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40"/></svg>';
154
+ ```
155
+
156
+ ```sql {"type":"duckfn","show":"table","extensions":["inet"]}
157
+ SELECT '127.0.0.1'::INET::VARCHAR AS ip;
158
+ ```
159
+ ````
160
+
161
+ A diagram built by a query (the same element a ```` ```mermaid ```` fence produces):
162
+
163
+ ````md
164
+ ```sql {"type":"duckfn","show":"mermaid"}
165
+ SELECT 'flowchart LR' || chr(10)
166
+ || ' A["a SELECT"] --> B["one result cell"]' AS diagram;
167
+ ```
168
+ ````
169
+
170
+ ### Mistakes to check for when reviewing a page
171
+
172
+ - Several independent examples stacked in one block — only the last result is visible. **Split
173
+ them into one block per example.**
174
+ - `show: "html"` / `"iframe"` / `"svg"` / `"mermaid"` with a multi-column result and no `field`.
175
+ - No `tab_name`, so the preview tabs read `Row 1`, `Row 2`, … instead of something meaningful.
176
+ - A bare ```` ```sql ```` block where a runnable one was intended (it renders as a plain listing).
177
+ - Declaring `extensions` for the extension the site already preloads.
178
+ - A block that depends on a table created on a *different* page.
179
+
180
+ ## Mermaid diagrams (`remarkMermaid`)
181
+
182
+ ````ts
183
+ // docusaurus.config.ts, in the docs preset's `remarkPlugins`
184
+ remarkPlugins: [remarkMermaid],
185
+ ````
186
+
187
+ A ```` ```mermaid ```` fence becomes a `<dfk-mermaid>` element that renders the diagram **in the
188
+ browser** (mermaid is a lazy `import()`, so a page with no diagram never downloads it). Do **not**
189
+ also install `@docusaurus/theme-mermaid` or list it in `themes`, and do not set `markdown.mermaid`:
190
+ the kit's element replaces both, and two renderers on one page would fight.
191
+
192
+ What the reader gets, in the element's top-right corner on hover: **reset zoom**, **fullscreen**,
193
+ **edit the source** in a CodeMirror dialog, and **download SVG**. Zoom and pan are off until the
194
+ diagram is expanded — fullscreen is what turns the wheel into a zoom and a drag into a pan.
195
+
196
+ - **The file is named after the section it sits in.** `1. Expansion.svg`, not
197
+ `mermaid-diagram.svg`, in a cascade that walks from the most specific source to the most general:
198
+ the diagram's own title (mermaid frontmatter, `---\ntitle: …\n---`) → the nearest heading above
199
+ it → the document title → `mermaid-diagram.svg`. So a diagram is best named by the heading it
200
+ lives under; give it a frontmatter `title:` when the heading is not the name you want. The name
201
+ goes through `filenamify`, so nothing a filesystem chokes on (`:`, `?`, `*`, `|`, …) reaches the
202
+ file.
203
+ - **The diagram on the page is a picture, not a viewport.** The cursor is the browser's (an I-beam
204
+ over a label), the wheel scrolls the page, and dragging selects text — labels are still text, and
205
+ copying one works. Expanding the diagram is what turns the pointer into a `grab` hand and gives a
206
+ drag to panning; **Reset zoom** returns it to fit without leaving fullscreen, and zooming is off
207
+ again the moment the diagram is back inline. There is no select/drag mode to remember.
208
+ - **A `show: "mermaid"` result reuses the same element**, in an *embedded* mode: the result panel
209
+ already draws the frame and the strip, so the element renders neither a frame nor a floating
210
+ cluster, and **reset zoom** / **edit source** move into the tab strip. See *The strip at the right
211
+ of the tabs*.
212
+
213
+ - **The palette is a site choice, not a page one.** The kit's default is the `neo` look with
214
+ `redux-color` / `redux-dark-color`; a site overrides it in its own config, which also keeps the
215
+ kit fork-free for downstream docs sites:
216
+
217
+ ````ts
218
+ remarkMermaid({config: {theme: {light: 'neutral', dark: 'dark'}, options: {look: 'classic'}}})
219
+ ````
220
+
221
+ `theme` is per colour mode (the element re-renders on a theme switch); `look` has no light/dark
222
+ counterpart and lives in `options`. Mermaid silently ignores a value it does not recognise, so
223
+ check a diagram in a browser — a build proves nothing here.
224
+ - **Diagrams render per page, in the page's own colour mode** — read from `<html data-theme>` (the
225
+ attribute written before first paint), not from a framework hook. That is what keeps a dark-mode
226
+ first load from painting a light diagram and then a dark one.
227
+ - **Labels**: keep them quoted (`A["text"]`), use `<br/>` for a line break, and avoid a bare `#` or
228
+ an unescaped `&`. A syntax error shows up in the page, not in the build.
229
+ - A ```` ```mermaid ```` fence inside a longer fence (documenting it, as here) is *not* turned into
230
+ a diagram — it is text inside the outer code block.
231
+
232
+ ## Preloading extensions (the `dfkExtensions` plugin)
233
+
234
+ The site declares an ordered preload list; the kit fetches the release assets at dev/build startup,
235
+ injects the list into every page and loads them — in order — while DuckDB initialises. Blocks can
236
+ then call into those extensions without declaring anything.
237
+
238
+ Three source kinds:
239
+
240
+ | Entry | Meaning |
241
+ | --- | --- |
242
+ | `'json'` | A name: `LOAD json` from the official repository. |
243
+ | `{name: 'h3', repository: 'community'}` | `community`, `core` or a repository URL. On wasm `INSTALL` only records *where* a later `LOAD` fetches from. |
244
+ | `{url: 'duckdb-extensions/x.duckdb_extension.wasm', release: {repository, asset}}` | The site serves the file itself; with `release`, the build fetches that asset from the repository's latest release (cached by sha256 in `<siteDir>/.cache/duckfn-docs-kit/`). |
245
+
246
+ Constraints that bite:
247
+
248
+ - **The file name is a contract**: the text before the first dot is the entry symbol DuckDB looks
249
+ up, so a release asset named `duckfn-wasm_eh.duckdb_extension.wasm` must be served as
250
+ `duckfn.duckdb_extension.wasm` (the `url` decides the file name).
251
+ - **Platforms must match**: a `wasm_eh` extension needs a runtime bundle on the `eh` platform. Pin
252
+ `@duckdb/duckdb-wasm` to the exact version whose bundled DuckDB is ABI-compatible with the
253
+ extension build.
254
+ - **Unsigned third-party extensions need `allowUnsignedExtensions: true`** (the WebAssembly
255
+ equivalent of `duckdb -unsigned`). Community extensions are signed and load without it.
256
+
257
+ ## Testing the blocks (`duckfn-sql-verify`)
258
+
259
+ The kit runs every block in a **real browser**, on the same runtime the page uses, so what CI
260
+ checks is what a reader gets:
261
+
262
+ ```bash
263
+ duckfn-sql-verify --site . # or: npx duckfn-sql-verify --site .
264
+ ```
265
+
266
+ It collects every runnable block (`docs/` plus each `i18n/<locale>/…/current/` by default), runs it
267
+ in DuckDB-Wasm **in a headless browser** (driven with `playwright-core`, launching your system
268
+ Chrome/Edge via `executablePath` — so no browser download) with the site's extension loaded, and
269
+ fails the process when a block does not behave as it declares. It is fully offline: the engine and
270
+ the extension are served from local files. Wire it into `package.json` as
271
+ `"test": "duckfn-sql-verify --site ."`.
272
+
273
+ - **A block that demonstrates a failure must say so**: `{"type":"duckfn","expect":"error"}`. The
274
+ check is two-way — a block that declares `error` and starts succeeding is reported too — and a
275
+ `-- error:` comment in the SQL is *not* read; only the metadata counts.
276
+ - Useful options: `--extension <path|url>`, `--platform eh|mvp`, `--content <dir>` (repeatable),
277
+ `--browser <path>` (the Chrome/Edge executable; otherwise a detected one or `DFK_BROWSER`),
278
+ `--timeout <ms>`, `--report <file>`, `--quiet`. It needs `playwright-core`, which the kit lists
279
+ as a dependency; unlike `playwright` it never downloads a browser.
280
+ - The default extension is the single file under `static/duckdb-extensions/`.
281
+ - **File-system examples stay plain code blocks.** Under DuckDB-Wasm the raw file system is not
282
+ POSIX-faithful: `dfn_file_exists` reads a missing file as "opened" (always `true`), writes/append
283
+ byte order is wrong, and there is no existence primitive in DuckDB's C API to correct it. So keep
284
+ any `COPY … TO` / `output_dir` / file-write demo as a non-runnable block and note the reason.
285
+
286
+ ## TOC collapse control (`dfkTocToggle()`)
287
+
288
+ ```ts
289
+ plugins: [dfkTocToggle()],
290
+ ```
291
+
292
+ Adds a collapse button to the desktop table of contents and remembers the choice in
293
+ `localStorage` (`duckfn:toc-collapsed`). Custom labels come from the plugin's `labels` option,
294
+ keyed by a lower-cased `html-lang` prefix (`{en: {hide, show}, 'zh-hans': {…}}`).
295
+
296
+ ## Version placeholder (`remarkVersionPlaceholder`)
297
+
298
+ ```ts
299
+ remarkPlugins: [[remarkVersionPlaceholder, {version: DUCKFN_VERSION}]],
300
+ ```
301
+
302
+ Replaces `{{DUCKFN_VERSION}}` inside `text`, `inlineCode` and `code` nodes, so a release updates one
303
+ file instead of every page. It only touches that exact placeholder — anything else is left alone.
304
+
305
+ ## Home-page components
306
+
307
+ `<dfk-hero>`, `<dfk-features>`, `<dfk-next-steps>` (along with `<dfk-sql>` and `<dfk-mermaid>`,
308
+ which the plugins generate) are registered by the barrel import (`duckfn-docs-kit`). The home
309
+ components are **setter-driven and do not reflect attributes**:
310
+ give them named setters (`setTitle`, `setTagline`, `setFeatures`, …), not markup content. They
311
+ render into shadow roots, inherit `--duckfn-*` / `--ifm-*` CSS variables from the page, and are
312
+ safe to call before the element is connected.
313
+
314
+ ## Troubleshooting
315
+
316
+ | Symptom | Likely cause |
317
+ | --- | --- |
318
+ | A code block has no Run button | Its info string is not JSON with `"type":"duckfn"`. |
319
+ | The block runs but nothing appears in the preview | `show: "html"` / `"iframe"` / `"svg"` without `field` on a multi-column result. |
320
+ | The preview tabs are labelled `Row 1`, `Row 2`, … | No `tab_name`. |
321
+ | Clicking Run shows an error like `Table with name … does not exist` | The block depends on something created on another page, or on a statement that is no longer the last one in its block. |
322
+ | `LOAD` fails with a signature error | `allowUnsignedExtensions: true` missing for a third-party asset. |
323
+ | A preloaded extension fails to load | The served file name's pre-dot part does not match the extension's entry symbol, or the platform does not match the runtime bundle. |
324
+ | Only the last of several examples shows a result | That is the contract — split the block. |
325
+ | A ```` ```mermaid ```` fence renders as a plain code block | `remarkMermaid` is not in the docs preset's `remarkPlugins`; and if `@docusaurus/theme-mermaid` is still installed, remove it and `markdown.mermaid`. |
326
+ | A diagram is blank, or shows a message instead | The mermaid source does not parse — the message carries mermaid's own error text. |
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # duckfn-docs-kit
2
2
 
3
3
  Shared building blocks for [duckfn](https://github.com/shijianjs/duckfn)-family
4
- DuckDB extension documentation sites: runnable SQL blocks, extension preloading,
5
- a TOC collapse control, the home-page web components and the version-placeholder
6
- remark plugin. Each one is a self-contained entry point that a Docusaurus 3 site
7
- wires into its own config — the alternative is copying the same glue into every
8
- extension's docs site.
4
+ DuckDB extension documentation sites: runnable SQL blocks, mermaid diagrams,
5
+ extension preloading, a TOC collapse control, the home-page web components and
6
+ the version-placeholder remark plugin. Each one is a self-contained entry point
7
+ that a Docusaurus 3 site wires into its own config — the alternative is copying
8
+ the same glue into every extension's docs site.
9
9
 
10
10
  Plain TypeScript over the native DOM: no React and no UI framework of its own.
11
11
  The components are retained-mode classes — they build their DOM once, expose
@@ -22,9 +22,10 @@ npm install duckfn-docs-kit
22
22
 
23
23
  | Import | Runs in | What it provides |
24
24
  | --- | --- | --- |
25
- | `duckfn-docs-kit` | browser | Home-page custom elements (`<dfk-hero>`, `<dfk-features>`, `<dfk-next-steps>`, `<dfk-sql>`), `registerDfkElements()` and the value types their setters accept |
25
+ | `duckfn-docs-kit` | browser | Custom elements (`<dfk-hero>`, `<dfk-features>`, `<dfk-next-steps>`, `<dfk-sql>`, `<dfk-mermaid>`), `registerDfkElements()` and the value types their setters accept |
26
26
  | `duckfn-docs-kit/remark` | Node (build) | `remarkVersionPlaceholder`: replaces `{{DUCKFN_VERSION}}` inside `text` / `inlineCode` / `code` nodes |
27
27
  | `duckfn-docs-kit/sql/remark` | Node (build) | `remarkRunnableSql`: turns fenced `sql {"type":"duckfn",…}` blocks into `<dfk-sql>` elements |
28
+ | `duckfn-docs-kit/mermaid/remark` | Node (build) | `remarkMermaid`: turns ```` ```mermaid ```` fences into `<dfk-mermaid>` diagrams |
28
29
  | `duckfn-docs-kit/sql/extensions` | Node (build) | `dfkExtensions()` Docusaurus plugin: preloads a site's DuckDB extensions before the first block runs |
29
30
  | `duckfn-docs-kit/toc-toggle/plugin` | Node (build) | `dfkTocToggle()` Docusaurus plugin: adds the TOC collapse control |
30
31
  | `duckfn-docs-kit/toc-toggle/TocToggle` | browser | The TOC collapse class, for a site that drives it itself |
@@ -43,6 +44,7 @@ import {dfkExtensions} from 'duckfn-docs-kit/sql/extensions';
43
44
  import {dfkTocToggle} from 'duckfn-docs-kit/toc-toggle/plugin';
44
45
  import {remarkVersionPlaceholder} from 'duckfn-docs-kit/remark';
45
46
  import {remarkRunnableSql} from 'duckfn-docs-kit/sql/remark';
47
+ import {remarkMermaid} from 'duckfn-docs-kit/mermaid/remark';
46
48
  import {DUCKFN_VERSION} from './duckfn-version';
47
49
 
48
50
  export default {
@@ -54,6 +56,8 @@ export default {
54
56
  remarkPlugins: [
55
57
  [remarkVersionPlaceholder, {version: DUCKFN_VERSION}],
56
58
  remarkRunnableSql,
59
+ // ```mermaid fences become diagrams; no @docusaurus/theme-mermaid.
60
+ remarkMermaid,
57
61
  ],
58
62
  },
59
63
  },