# Conflicts: # website/package-lock.json # website/package.json
- commit
- ecffc0b3ee5f4523b8c5caf3fdb213dc0ebc0300
- parent
- 9cad411e2eaed58a5c3d4327c1324ebea291bfc1
- parent
- bf09e5926e048b223d3ecd9f6a239fc7facc8ca5
- author
- Dami <dami.etoile@gmail.com>
- kind
- build risk score 1.5 of 3, Jev's estimate the message may not describe the diff
| M | docs/ | 2 | +- |
| M | docs/ | 2 | +- |
| M | docs/ | 9 | +- |
| M | website/ | 311 | ++++++++++++++++++++++++++++++ |
| M | website/ | 4 | +- |
| A | website/ | 79 | ++++++++ |
| M | website/ | 64 | ++++++- |
@@ -70,7 +70,7 @@ sequenceDiagram
7070 S-->>A: welcome: the workbook, the scope7171 H->>S: tools/call write_cells (through A)7272 S->>R: a turn: the change made on a copy, checked against the scope73- R-->>S: a suggestion on the board; your screen marks its cells73+ R-->>S: a suggestion on the board, its cells marked on your screen7474 S-->>H: the suggestion's number7575 Note over S: you accept (Ctrl+Alt+A, Enter)7676 S->>R: your turn: the cells set, one step in the agent's name@@ -781,7 +781,7 @@ sequenceDiagram
781781 Nu->>Nu: let files = (open --raw $env.NU012_TABLE_0 | from nuon)782782 Nu-->>R: the pipeline's value, to nuon783783 R-->>UI: the NUON, up to 512 MB784- UI->>UI: the cell's output, not an undo step; sent on to its region784+ UI->>UI: the cell's output, not an undo step, sent on to its region785785 ```786786 787787 `Runner` is the seam: tests hand the UI a fake, the e2e@@ -59,7 +59,8 @@ this site's.
5959 ## The build is a docs check6060 6161 `make site` fails on a link to a page that doesn't exist, an anchor no62-heading makes, a missing image or two pages at one address. It checks62+heading makes, a missing image, two pages at one address or a63+[diagram](#diagrams) Mermaid can't parse. It checks6364 the same links `scripts/doccheck` does, as the site resolves them, so a6465 page that passes `make lint` but breaks on the site shows up here.6566 @@ -138,6 +139,12 @@ picture ([Pictures of 012](#pictures-of-012)).
138139 (Docusaurus's component, swizzled) draws with Mermaid's `base` theme139140 and variables read from the palette in `custom.css` for the color140141 mode shown, so diagrams don't set their own styles.142+- `make site` fails on a diagram Mermaid can't parse, which the site143+ would draw as an error: `website/scripts/check-mermaid.mjs` (`npm run144+ diagrams` in `website/`) parses every `mermaid` block in `docs/` with145+ the Mermaid the site draws with, and names the page, the line and146+ Mermaid's error. A `;` in a sequence diagram's message ends the147+ statement, so write a comma, or `#59;` where the semicolon matters.141148 142149 ## Pictures of 012143150 @@ -23,6 +23,7 @@
2323 "@docusaurus/module-type-aliases": "3.10.2",2424 "@docusaurus/tsconfig": "3.10.2",2525 "@docusaurus/types": "3.10.2",26+ "jsdom": "26.1.0",2627 "typescript": "5.9.3",2728 "wrangler": "4.144.0"2829 },@@ -291,6 +292,27 @@
291292 "url": "https://github.com/sponsors/antfu"292293 }293294 },295+ "node_modules/@asamuzakjp/css-color": {296+ "version": "3.2.0",297+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",298+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",299+ "dev": true,300+ "license": "MIT",301+ "dependencies": {302+ "@csstools/css-calc": "^2.1.3",303+ "@csstools/css-color-parser": "^3.0.9",304+ "@csstools/css-parser-algorithms": "^3.0.4",305+ "@csstools/css-tokenizer": "^3.0.3",306+ "lru-cache": "^10.4.3"307+ }308+ },309+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {310+ "version": "10.4.3",311+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",312+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",313+ "dev": true,314+ "license": "ISC"315+ },294316 "node_modules/@babel/code-frame": {295317 "version": "7.29.7",296318 "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",@@ -7984,6 +8006,16 @@
79848006 "node": ">= 16.0.0"79858007 }79868008 },8009+ "node_modules/agent-base": {8010+ "version": "7.1.4",8011+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",8012+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",8013+ "dev": true,8014+ "license": "MIT",8015+ "engines": {8016+ "node": ">= 14"8017+ }8018+ },79878019 "node_modules/aggregate-error": {79888020 "version": "3.1.0",79898021 "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",@@ -9829,6 +9861,20 @@
98299861 "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",98309862 "license": "CC0-1.0"98319863 },9864+ "node_modules/cssstyle": {9865+ "version": "4.6.0",9866+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",9867+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",9868+ "dev": true,9869+ "license": "MIT",9870+ "dependencies": {9871+ "@asamuzakjp/css-color": "^3.2.0",9872+ "rrweb-cssom": "^0.8.0"9873+ },9874+ "engines": {9875+ "node": ">=18"9876+ }9877+ },98329878 "node_modules/csstype": {98339879 "version": "3.2.3",98349880 "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",@@ -10355,6 +10401,20 @@
1035510401 "lodash-es": "^4.17.21"1035610402 }1035710403 },10404+ "node_modules/data-urls": {10405+ "version": "5.0.0",10406+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",10407+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",10408+ "dev": true,10409+ "license": "MIT",10410+ "dependencies": {10411+ "whatwg-mimetype": "^4.0.0",10412+ "whatwg-url": "^14.0.0"10413+ },10414+ "engines": {10415+ "node": ">=18"10416+ }10417+ },1035810418 "node_modules/dayjs": {1035910419 "version": "1.11.23",1036010420 "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.23.tgz",@@ -10384,6 +10444,13 @@
1038410444 }1038510445 }1038610446 },10447+ "node_modules/decimal.js": {10448+ "version": "10.6.0",10449+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",10450+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",10451+ "dev": true,10452+ "license": "MIT"10453+ },1038710454 "node_modules/decode-named-character-reference": {1038810455 "version": "1.3.0",1038910456 "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",@@ -12202,6 +12269,19 @@
1220212269 "safe-buffer": "~5.1.0"1220312270 }1220412271 },12272+ "node_modules/html-encoding-sniffer": {12273+ "version": "4.0.0",12274+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",12275+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",12276+ "dev": true,12277+ "license": "MIT",12278+ "dependencies": {12279+ "whatwg-encoding": "^3.1.1"12280+ },12281+ "engines": {12282+ "node": ">=18"12283+ }12284+ },1220512285 "node_modules/html-escaper": {1220612286 "version": "2.0.2",1220712287 "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",@@ -12393,6 +12473,20 @@
1239312473 "node": ">=8.0.0"1239412474 }1239512475 },12476+ "node_modules/http-proxy-agent": {12477+ "version": "7.0.2",12478+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",12479+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",12480+ "dev": true,12481+ "license": "MIT",12482+ "dependencies": {12483+ "agent-base": "^7.1.0",12484+ "debug": "^4.3.4"12485+ },12486+ "engines": {12487+ "node": ">= 14"12488+ }12489+ },1239612490 "node_modules/http-proxy-middleware": {1239712491 "version": "2.0.10",1239812492 "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz",@@ -12442,6 +12536,20 @@
1244212536 "node": ">=10.19.0"1244312537 }1244412538 },12539+ "node_modules/https-proxy-agent": {12540+ "version": "7.0.6",12541+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",12542+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",12543+ "dev": true,12544+ "license": "MIT",12545+ "dependencies": {12546+ "agent-base": "^7.1.2",12547+ "debug": "4"12548+ },12549+ "engines": {12550+ "node": ">= 14"12551+ }12552+ },1244512553 "node_modules/human-signals": {1244612554 "version": "2.1.0",1244712555 "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",@@ -12888,6 +12996,13 @@
1288812996 "node": ">=0.10.0"1288912997 }1289012998 },12999+ "node_modules/is-potential-custom-element-name": {13000+ "version": "1.0.1",13001+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",13002+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",13003+ "dev": true,13004+ "license": "MIT"13005+ },1289113006 "node_modules/is-regexp": {1289213007 "version": "1.0.0",1289313008 "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",@@ -13054,6 +13169,68 @@
1305413169 "js-yaml": "bin/js-yaml.js"1305513170 }1305613171 },13172+ "node_modules/jsdom": {13173+ "version": "26.1.0",13174+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz",13175+ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",13176+ "dev": true,13177+ "license": "MIT",13178+ "dependencies": {13179+ "cssstyle": "^4.2.1",13180+ "data-urls": "^5.0.0",13181+ "decimal.js": "^10.5.0",13182+ "html-encoding-sniffer": "^4.0.0",13183+ "http-proxy-agent": "^7.0.2",13184+ "https-proxy-agent": "^7.0.6",13185+ "is-potential-custom-element-name": "^1.0.1",13186+ "nwsapi": "^2.2.16",13187+ "parse5": "^7.2.1",13188+ "rrweb-cssom": "^0.8.0",13189+ "saxes": "^6.0.0",13190+ "symbol-tree": "^3.2.4",13191+ "tough-cookie": "^5.1.1",13192+ "w3c-xmlserializer": "^5.0.0",13193+ "webidl-conversions": "^7.0.0",13194+ "whatwg-encoding": "^3.1.1",13195+ "whatwg-mimetype": "^4.0.0",13196+ "whatwg-url": "^14.1.1",13197+ "ws": "^8.18.0",13198+ "xml-name-validator": "^5.0.0"13199+ },13200+ "engines": {13201+ "node": ">=18"13202+ },13203+ "peerDependencies": {13204+ "canvas": "^3.0.0"13205+ },13206+ "peerDependenciesMeta": {13207+ "canvas": {13208+ "optional": true13209+ }13210+ }13211+ },13212+ "node_modules/jsdom/node_modules/ws": {13213+ "version": "8.22.0",13214+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz",13215+ "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==",13216+ "dev": true,13217+ "license": "MIT",13218+ "engines": {13219+ "node": ">=10.0.0"13220+ },13221+ "peerDependencies": {13222+ "bufferutil": "^4.0.1",13223+ "utf-8-validate": ">=5.0.2"13224+ },13225+ "peerDependenciesMeta": {13226+ "bufferutil": {13227+ "optional": true13228+ },13229+ "utf-8-validate": {13230+ "optional": true13231+ }13232+ }13233+ },1305713234 "node_modules/jsesc": {1305813235 "version": "3.1.0",1305913236 "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",@@ -16244,6 +16421,13 @@
1624416421 "url": "https://opencollective.com/webpack"1624516422 }1624616423 },16424+ "node_modules/nwsapi": {16425+ "version": "2.2.28",16426+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.28.tgz",16427+ "integrity": "sha512-IlVB7OS7qrOsVYlpnFIkETjMwT9jwvmocJmmM+GZU/PAB3uGi9Ezd7vcWhWBUnSc0ya4ppmQITOyP1ez9gg8cg==",16428+ "dev": true,16429+ "license": "MIT"16430+ },1624716431 "node_modules/object-assign": {1624816432 "version": "4.1.1",1624916433 "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",@@ -19207,6 +19391,13 @@
1920719391 "points-on-path": "^0.2.1"1920819392 }1920919393 },19394+ "node_modules/rrweb-cssom": {19395+ "version": "0.8.0",19396+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",19397+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",19398+ "dev": true,19399+ "license": "MIT"19400+ },1921019401 "node_modules/rtlcss": {1921119402 "version": "4.3.0",1921219403 "resolved": "https://registry.npmjs.org/rtlcss/-/rtlcss-4.3.0.tgz",@@ -19301,6 +19492,19 @@
1930119492 "node": ">=11.0.0"1930219493 }1930319494 },19495+ "node_modules/saxes": {19496+ "version": "6.0.0",19497+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",19498+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",19499+ "dev": true,19500+ "license": "ISC",19501+ "dependencies": {19502+ "xmlchars": "^2.2.0"19503+ },19504+ "engines": {19505+ "node": ">=v12.22.7"19506+ }19507+ },1930419508 "node_modules/scheduler": {1930519509 "version": "0.28.0",1930619510 "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz",@@ -20215,6 +20419,13 @@
2021520419 "node": ">= 10"2021620420 }2021720421 },20422+ "node_modules/symbol-tree": {20423+ "version": "3.2.4",20424+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",20425+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",20426+ "dev": true,20427+ "license": "MIT"20428+ },2021820429 "node_modules/tapable": {2021920430 "version": "2.3.3",2022020431 "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",@@ -20393,6 +20604,26 @@
2039320604 "node": "^18.0.0 || >=20.0.0"2039420605 }2039520606 },20607+ "node_modules/tldts": {20608+ "version": "6.1.86",20609+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",20610+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",20611+ "dev": true,20612+ "license": "MIT",20613+ "dependencies": {20614+ "tldts-core": "^6.1.86"20615+ },20616+ "bin": {20617+ "tldts": "bin/cli.js"20618+ }20619+ },20620+ "node_modules/tldts-core": {20621+ "version": "6.1.86",20622+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",20623+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",20624+ "dev": true,20625+ "license": "MIT"20626+ },2039620627 "node_modules/to-regex-range": {2039720628 "version": "5.0.1",2039820629 "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",@@ -20423,6 +20654,32 @@
2042320654 "node": ">=6"2042420655 }2042520656 },20657+ "node_modules/tough-cookie": {20658+ "version": "5.1.2",20659+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",20660+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",20661+ "dev": true,20662+ "license": "BSD-3-Clause",20663+ "dependencies": {20664+ "tldts": "^6.1.32"20665+ },20666+ "engines": {20667+ "node": ">=16"20668+ }20669+ },20670+ "node_modules/tr46": {20671+ "version": "5.1.1",20672+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",20673+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",20674+ "dev": true,20675+ "license": "MIT",20676+ "dependencies": {20677+ "punycode": "^2.3.1"20678+ },20679+ "engines": {20680+ "node": ">=18"20681+ }20682+ },2042620683 "node_modules/tree-dump": {2042720684 "version": "1.1.0",2042820685 "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz",@@ -21075,6 +21332,19 @@
2107521332 "url": "https://opencollective.com/unified"2107621333 }2107721334 },21335+ "node_modules/w3c-xmlserializer": {21336+ "version": "5.0.0",21337+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",21338+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",21339+ "dev": true,21340+ "license": "MIT",21341+ "dependencies": {21342+ "xml-name-validator": "^5.0.0"21343+ },21344+ "engines": {21345+ "node": ">=18"21346+ }21347+ },2107821348 "node_modules/watchpack": {2107921349 "version": "2.5.2",2108021350 "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz",@@ -21106,6 +21376,16 @@
2110621376 "url": "https://github.com/sponsors/wooorm"2110721377 }2110821378 },21379+ "node_modules/webidl-conversions": {21380+ "version": "7.0.0",21381+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",21382+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",21383+ "dev": true,21384+ "license": "BSD-2-Clause",21385+ "engines": {21386+ "node": ">=12"21387+ }21388+ },2110921389 "node_modules/webpack": {2111021390 "version": "5.111.1",2111121391 "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.111.1.tgz",@@ -21472,6 +21752,20 @@
2147221752 "node": ">=18"2147321753 }2147421754 },21755+ "node_modules/whatwg-url": {21756+ "version": "14.2.0",21757+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",21758+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",21759+ "dev": true,21760+ "license": "MIT",21761+ "dependencies": {21762+ "tr46": "^5.1.0",21763+ "webidl-conversions": "^7.0.0"21764+ },21765+ "engines": {21766+ "node": ">=18"21767+ }21768+ },2147521769 "node_modules/which": {2147621770 "version": "2.0.2",2147721771 "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",@@ -21715,6 +22009,23 @@
2171522009 "xml-js": "bin/cli.js"2171622010 }2171722011 },22012+ "node_modules/xml-name-validator": {22013+ "version": "5.0.0",22014+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",22015+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",22016+ "dev": true,22017+ "license": "Apache-2.0",22018+ "engines": {22019+ "node": ">=18"22020+ }22021+ },22022+ "node_modules/xmlchars": {22023+ "version": "2.2.0",22024+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",22025+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",22026+ "dev": true,22027+ "license": "MIT"22028+ },2171822029 "node_modules/yallist": {2171922030 "version": "3.1.1",2172022031 "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",@@ -4,7 +4,8 @@
44 "description": "The 012 documentation site: Docusaurus over ../docs",55 "scripts": {66 "start": "docusaurus start",7- "build": "docusaurus build",7+ "build": "npm run diagrams && docusaurus build",8+ "diagrams": "node scripts/check-mermaid.mjs",89 "serve": "docusaurus serve",910 "clear": "docusaurus clear",1011 "typecheck": "tsc"@@ -27,6 +28,7 @@
2728 "@docusaurus/module-type-aliases": "3.10.2",2829 "@docusaurus/tsconfig": "3.10.2",2930 "@docusaurus/types": "3.10.2",31+ "jsdom": "26.1.0",3032 "typescript": "5.9.3",3133 "wrangler": "4.144.0"3234 },@@ -0,0 +1,79 @@
1+// Parses every mermaid code block in docs/ with the Mermaid the site2+// draws with, and fails on one it can't read: in the browser such a3+// block is drawn as an error, not a diagram. `npm run build` (and so4+// `make site`) runs it first.5+//6+// Mermaid's parser runs in Node, but it sanitizes labels with DOMPurify,7+// which needs a DOM: jsdom gives it one, set up before Mermaid loads.8+9+import {readFileSync, readdirSync} from 'node:fs';10+import {join, relative} from 'node:path';11+import {fileURLToPath} from 'node:url';12+import {JSDOM} from 'jsdom';13+14+const docs = fileURLToPath(new URL('../../docs', import.meta.url));15+16+const {window} = new JSDOM('<!doctype html><html><body></body></html>');17+globalThis.window = window;18+globalThis.document = window.document;19+globalThis.DOMParser = window.DOMParser;20+globalThis.Element = window.Element;21+const {default: mermaid} = await import('mermaid');22+mermaid.initialize({startOnLoad: false});23+24+function markdownFiles(dir) {25+ return readdirSync(dir, {withFileTypes: true}).flatMap((e) => {26+ const path = join(dir, e.name);27+ if (e.isDirectory()) {28+ return markdownFiles(path);29+ }30+ return /\.mdx?$/.test(e.name) ? [path] : [];31+ });32+}33+34+// The mermaid blocks of a page, as CommonMark reads its fences: a block35+// inside a longer fence (an example of Markdown) is text, not a diagram.36+function mermaidBlocks(text) {37+ const blocks = [];38+ let fence = null;39+ text.split('\n').forEach((line, i) => {40+ const m = /^(\s*)(`{3,}|~{3,})\s*([^`\s]*)/.exec(line);41+ if (fence === null) {42+ if (m) {43+ fence = {marker: m[2], indent: m[1].length, mermaid: m[3] === 'mermaid', line: i + 1, body: []};44+ }45+ return;46+ }47+ const close = /^\s*(`{3,}|~{3,})\s*$/.exec(line);48+ if (close && close[1][0] === fence.marker[0] && close[1].length >= fence.marker.length) {49+ if (fence.mermaid) {50+ blocks.push({line: fence.line, text: fence.body.join('\n')});51+ }52+ fence = null;53+ return;54+ }55+ fence.body.push(line.slice(Math.min(fence.indent, line.search(/\S|$/))));56+ });57+ return blocks;58+}59+60+let count = 0;61+const failures = [];62+for (const file of markdownFiles(docs).sort()) {63+ for (const block of mermaidBlocks(readFileSync(file, 'utf8'))) {64+ count++;65+ try {66+ await mermaid.parse(block.text);67+ } catch (err) {68+ const first = block.text.trim().split('\n')[0];69+ failures.push(`docs/${relative(docs, file)}:${block.line}: ${first}\n${String(err.message ?? err)}`);70+ }71+ }72+}73+74+if (failures.length > 0) {75+ console.error(`${failures.length} of ${count} mermaid diagrams don't parse:\n`);76+ console.error(failures.join('\n\n'));77+ process.exit(1);78+}79+console.log(`${count} mermaid diagrams parse`);@@ -2,15 +2,15 @@
22 // palette: Mermaid's base theme, its variables read from the colors33 // custom.css defines for the color mode shown, so the palette has one44 // home. Mermaid needs literal colors, not CSS variables, which is why5-// they are read from the page rather than passed through.5+// they are read from the page rather than passed through. It draws with6+// its own hook rather than Docusaurus's, which can leave a diagram's box7+// empty when the color mode changes quickly (see draw).68 79 import React, {useEffect, useMemo, useRef, useState, type ReactNode} from 'react';810 import ErrorBoundary from '@docusaurus/ErrorBoundary';911 import {ErrorBoundaryErrorMessageFallback, useColorMode} from '@docusaurus/theme-common';10-import {11- MermaidContainerClassName,12- useMermaidRenderResult,13-} from '@docusaurus/theme-mermaid/client';12+import {MermaidContainerClassName} from '@docusaurus/theme-mermaid/client';13+import {loadMermaid} from '@docusaurus/theme-mermaid/lib/client/loadMermaid.js';1414 import type {MermaidConfig, RenderResult} from 'mermaid';1515 1616 type Props = {value: string};@@ -157,9 +157,61 @@ function MermaidRenderResult({renderResult}: {renderResult: RenderResult}): Reac
157157 );158158 }159159 160+// Mermaid is one object with one configuration, and it draws a diagram161+// through an element of the page named by the id it is given. So each162+// drawing gets its configuration set just before it and runs after the163+// one before has finished, and gets an id of its own: under the id of164+// the drawing shown, Mermaid would take that one off the page first, and165+// a drawing identical to the one React last put there (the color mode166+// switched twice quickly) would leave the box empty.167+let drawing: Promise<unknown> = Promise.resolve();168+let drawn = 0;169+170+function draw(text: string, config: MermaidConfig): Promise<RenderResult> {171+ const id = `mermaid-svg-${++drawn}`;172+ const result = drawing.then(async () => {173+ const mermaid = await loadMermaid();174+ mermaid.initialize(config);175+ try {176+ return await mermaid.render(id, text);177+ } catch (e) {178+ // On an error Mermaid leaves its error drawing on the page.179+ document.querySelector(`#d${id}`)?.remove();180+ throw e;181+ }182+ });183+ drawing = result.catch(() => undefined);184+ return result;185+}186+187+// The drawing for the latest configuration: one asked for before the188+// color mode changed again is dropped when it arrives. An error goes to189+// the error boundary, which shows it in place of the diagram.190+function useDrawing(text: string, config: MermaidConfig | undefined): RenderResult | null {191+ const [result, setResult] = useState<RenderResult | null>(null);192+ useEffect(() => {193+ if (config === undefined) {194+ return undefined;195+ }196+ let live = true;197+ draw(text, config).then(198+ (r) => live && setResult(r),199+ (e: unknown) =>200+ live &&201+ setResult(() => {202+ throw e;203+ }),204+ );205+ return () => {206+ live = false;207+ };208+ }, [text, config]);209+ return result;210+}211+160212 function MermaidRenderer({value}: Props): ReactNode {161213 const config = useMermaidConfig();162- const renderResult = useMermaidRenderResult({text: value, config});214+ const renderResult = useDrawing(value, config);163215 if (renderResult === null) {164216 return null;165217 }