diff --git a/package.json b/package.json index 543053148..581bf1b35 100644 --- a/package.json +++ b/package.json @@ -137,10 +137,10 @@ "lru-cache": "^11.2.7", "maath": "^0.10.8", "match-sorter": "^8.3.0", - "mermaid": "^11.14.0", "octane": "0.1.13", "parse5": "^7.3.0", "parse5-parser-stream": "^7.1.2", + "pele": "1.12.0", "postgres": "^3.4.8", "react": "19.2.3", "react-colorful": "^5.6.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e285ed8e..ed6a03f0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -316,9 +316,6 @@ importers: match-sorter: specifier: ^8.3.0 version: 8.3.0 - mermaid: - specifier: ^11.14.0 - version: 11.14.0 motion: specifier: ^13.4.6 version: 13.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -331,6 +328,9 @@ importers: parse5-parser-stream: specifier: ^7.1.2 version: 7.1.2 + pele: + specifier: 1.12.0 + version: 1.12.0 postgres: specifier: ^3.4.8 version: 3.4.8(patch_hash=3dffa5c1d54f190ed733b23ed8c2fadd0e61c1939e5a8ba8affad5d96e13f033) @@ -598,9 +598,6 @@ packages: resolution: {integrity: sha512-xpwefe4fCOWnZgXCbkGpqQY6jgBSCf2hmgnySbyzZIccrv3SoashHKGPE4x6vVG+gdHrGciMTAcDo9HOZwH22Q==} engines: {node: '>= 14.0.0'} - '@antfu/install-pkg@1.1.0': - resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - '@anthropic-ai/sdk@0.97.1': resolution: {integrity: sha512-wOf7AUeJPitcVpvKO4UMu63mWH5SaVipkGd7OOQJt/G6VYGlV8D2Gp9dLxOrttDJh/9gqPqdaBwDGcBevumeAg==} hasBin: true @@ -759,27 +756,9 @@ packages: '@types/react': optional: true - '@braintree/sanitize-url@7.1.2': - resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} - '@cfworker/json-schema@4.1.1': resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} - '@chevrotain/cst-dts-gen@11.1.2': - resolution: {integrity: sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==} - - '@chevrotain/gast@11.1.2': - resolution: {integrity: sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==} - - '@chevrotain/regexp-to-ast@11.1.2': - resolution: {integrity: sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==} - - '@chevrotain/types@11.1.2': - resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} - - '@chevrotain/utils@11.1.2': - resolution: {integrity: sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==} - '@cloudflare/kv-asset-handler@0.5.0': resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} engines: {node: '>=22.0.0'} @@ -1699,12 +1678,6 @@ packages: peerDependencies: hono: ^4 - '@iconify/types@2.0.0': - resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - - '@iconify/utils@3.1.0': - resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} - '@img/colour@1.1.0': resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} engines: {node: '>=18'} @@ -2084,9 +2057,6 @@ packages: '@mediapipe/tasks-vision@0.10.17': resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} - '@mermaid-js/parser@1.1.0': - resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} - '@microsoft/fetch-event-source@2.0.1': resolution: {integrity: sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==} @@ -4632,9 +4602,6 @@ packages: '@uploadthing/shared@7.1.10': resolution: {integrity: sha512-R/XSA3SfCVnLIzFpXyGaKPfbwlYlWYSTuGjTFHuJhdAomuBuhopAHLh2Ois5fJibAHzi02uP1QCKbgTAdmArqg==} - '@upsetjs/venn.js@2.0.0': - resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} - '@use-gesture/core@10.3.1': resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} @@ -5034,14 +5001,6 @@ packages: resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==} engines: {node: '>=20.18.1'} - chevrotain-allstar@0.3.1: - resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} - peerDependencies: - chevrotain: ^11.0.0 - - chevrotain@11.1.2: - resolution: {integrity: sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==} - chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -5116,10 +5075,6 @@ packages: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -5131,9 +5086,6 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -5170,12 +5122,6 @@ packages: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} - cose-base@1.0.3: - resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} - - cose-base@2.2.0: - resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} - crelt@1.0.7: resolution: {integrity: sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==} @@ -5206,20 +5152,6 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - cytoscape-cose-bilkent@4.1.0: - resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} - peerDependencies: - cytoscape: ^3.2.0 - - cytoscape-fcose@2.2.0: - resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} - peerDependencies: - cytoscape: ^3.2.0 - - cytoscape@3.33.1: - resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} - engines: {node: '>=0.10'} - d3-array@2.12.1: resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} @@ -5362,9 +5294,6 @@ packages: resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} engines: {node: '>=12'} - dagre-d3-es@7.0.14: - resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} - data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -5460,9 +5389,6 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.3.3: - resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==} - domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} @@ -6071,9 +5997,6 @@ packages: crossws: optional: true - hachure-fill@0.5.2: - resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -6461,16 +6384,9 @@ packages: jws@4.0.1: resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - katex@0.16.44: - resolution: {integrity: sha512-EkxoDTk8ufHqHlf9QxGwcxeLkWRR3iOuYfRpfORgYfqc8s13bgb+YtRY59NK5ZpRaCwq1kqA6a5lpX8C/eLphQ==} - hasBin: true - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - khroma@2.1.0: - resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -6479,19 +6395,9 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - langium@4.2.1: - resolution: {integrity: sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==} - engines: {node: '>=20.10.0', npm: '>=10.2.3'} - launch-editor@2.13.2: resolution: {integrity: sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==} - layout-base@1.0.2: - resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} - - layout-base@2.0.1: - resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} - lazy-val@1.0.5: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} @@ -6666,9 +6572,6 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.17.23: - resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} - lodash.escaperegexp@4.1.2: resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} @@ -6724,11 +6627,6 @@ packages: react: optional: true - marked@16.4.2: - resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} - engines: {node: '>= 20'} - hasBin: true - match-sorter@8.3.0: resolution: {integrity: sha512-8Py1GbZi5zsclYSFcPAH4H5xfTbeD0bOREA7qP/t8bW4MbOSlPl8sbqHOedEV7O+Bxyvxm6xs/v6BXJGe+JDNA==} @@ -6752,9 +6650,6 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} - mermaid@11.14.0: - resolution: {integrity: sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==} - meshline@3.3.1: resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==} peerDependencies: @@ -6834,9 +6729,6 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true - mlly@1.8.2: - resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - module-details-from-path@1.0.4: resolution: {integrity: sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==} @@ -7066,9 +6958,6 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - package-manager-detector@1.6.0: - resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} - pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -7100,9 +6989,6 @@ packages: partial-json@0.1.7: resolution: {integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==} - path-data-parser@0.1.0: - resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -7139,6 +7025,10 @@ packages: resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==} engines: {node: '>=12', npm: '>=6'} + pele@1.12.0: + resolution: {integrity: sha512-3OdOK6rvkaTB569R4/I8U0H82MqPRxj7vfkbowa2rRhgSBPFhvWlfFy5eae0jP+2A6hE8abE17nsdyxJz2VBiQ==} + engines: {node: '>=20'} + pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} @@ -7178,9 +7068,6 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pkijs@3.4.1: resolution: {integrity: sha512-Oo/NZcSWccq8KyoG7gLE9fnltgHns+pNCjCAp/WmjsUySi+sX7y4z4Xqu4fVb42CDHzRPl33fjzT15V1wvcyhA==} engines: {node: '>=16.0.0'} @@ -7198,12 +7085,6 @@ packages: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - points-on-curve@0.2.0: - resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} - - points-on-path@0.2.1: - resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} - possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -7510,9 +7391,6 @@ packages: rou3@0.8.1: resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==} - roughjs@4.6.6: - resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} - router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -7818,9 +7696,6 @@ packages: style-mod@4.1.3: resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} - stylis@4.3.6: - resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} - sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} @@ -7964,10 +7839,6 @@ packages: ts-algebra@2.0.0: resolution: {integrity: sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==} - ts-dedent@2.2.0: - resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} - engines: {node: '>=6.10'} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -8023,9 +7894,6 @@ packages: uc.micro@3.0.0: resolution: {integrity: sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw==} - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - ufo@1.6.4: resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} @@ -8192,10 +8060,6 @@ packages: resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - valibot@1.3.1: resolution: {integrity: sha512-sfdRir/QFM0JaF22hqTroPc5xy4DimuGQVKFrzF1YfGwaS1nJot3Y8VqMdLO2Lg27fMzat2yD3pY5PbAYO39Gg==} peerDependencies: @@ -8307,26 +8171,6 @@ packages: jsdom: optional: true - vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - - vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - - vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - - vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} @@ -8660,11 +8504,6 @@ snapshots: dependencies: '@algolia/client-common': 5.50.0 - '@antfu/install-pkg@1.1.0': - dependencies: - package-manager-detector: 1.6.0 - tinyexec: 1.0.4 - '@anthropic-ai/sdk@0.97.1(zod@4.3.6)': dependencies: json-schema-to-ts: 3.1.1 @@ -8876,27 +8715,8 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 - '@braintree/sanitize-url@7.1.2': {} - '@cfworker/json-schema@4.1.1': {} - '@chevrotain/cst-dts-gen@11.1.2': - dependencies: - '@chevrotain/gast': 11.1.2 - '@chevrotain/types': 11.1.2 - lodash-es: 4.17.23 - - '@chevrotain/gast@11.1.2': - dependencies: - '@chevrotain/types': 11.1.2 - lodash-es: 4.17.23 - - '@chevrotain/regexp-to-ast@11.1.2': {} - - '@chevrotain/types@11.1.2': {} - - '@chevrotain/utils@11.1.2': {} - '@cloudflare/kv-asset-handler@0.5.0': {} '@cloudflare/unenv-preset@2.16.2(unenv@2.0.0-rc.24)(workerd@1.20261001.1)': @@ -9636,14 +9456,6 @@ snapshots: dependencies: hono: 4.12.9 - '@iconify/types@2.0.0': {} - - '@iconify/utils@3.1.0': - dependencies: - '@antfu/install-pkg': 1.1.0 - '@iconify/types': 2.0.0 - mlly: 1.8.2 - '@img/colour@1.1.0': {} '@img/sharp-darwin-arm64@0.34.5': @@ -9933,10 +9745,6 @@ snapshots: '@mediapipe/tasks-vision@0.10.17': {} - '@mermaid-js/parser@1.1.0': - dependencies: - langium: 4.2.1 - '@microsoft/fetch-event-source@2.0.1': {} '@modelcontextprotocol/sdk@1.31.0(@cfworker/json-schema@4.1.1)(zod@4.3.6)': @@ -12472,11 +12280,6 @@ snapshots: effect: 3.17.7 sqids: 0.3.0 - '@upsetjs/venn.js@2.0.0': - optionalDependencies: - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - '@use-gesture/core@10.3.1': {} '@use-gesture/react@10.3.1(react@19.2.3)': @@ -12942,20 +12745,6 @@ snapshots: undici: 7.24.7 whatwg-mimetype: 4.0.0 - chevrotain-allstar@0.3.1(chevrotain@11.1.2): - dependencies: - chevrotain: 11.1.2 - lodash-es: 4.17.23 - - chevrotain@11.1.2: - dependencies: - '@chevrotain/cst-dts-gen': 11.1.2 - '@chevrotain/gast': 11.1.2 - '@chevrotain/regexp-to-ast': 11.1.2 - '@chevrotain/types': 11.1.2 - '@chevrotain/utils': 11.1.2 - lodash-es: 4.17.23 - chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -13034,8 +12823,6 @@ snapshots: commander@7.2.0: {} - commander@8.3.0: {} - commander@9.5.0: optional: true @@ -13043,8 +12830,6 @@ snapshots: concat-map@0.0.1: {} - confbox@0.1.8: {} - content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -13068,14 +12853,6 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cose-base@1.0.3: - dependencies: - layout-base: 1.0.2 - - cose-base@2.2.0: - dependencies: - layout-base: 2.0.1 - crelt@1.0.7: {} cross-dirname@0.1.0: @@ -13105,18 +12882,6 @@ snapshots: csstype@3.2.3: {} - cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): - dependencies: - cose-base: 1.0.3 - cytoscape: 3.33.1 - - cytoscape-fcose@2.2.0(cytoscape@3.33.1): - dependencies: - cose-base: 2.2.0 - cytoscape: 3.33.1 - - cytoscape@3.33.1: {} - d3-array@2.12.1: dependencies: internmap: 1.0.1 @@ -13286,11 +13051,6 @@ snapshots: d3-transition: 3.0.1(d3-selection@3.0.0) d3-zoom: 3.0.0 - dagre-d3-es@7.0.14: - dependencies: - d3: 7.9.0 - lodash-es: 4.17.23 - data-uri-to-buffer@4.0.1: {} data-view-buffer@1.0.2: @@ -13387,10 +13147,6 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.3.3: - optionalDependencies: - '@types/trusted-types': 2.0.7 - domutils@3.2.2: dependencies: dom-serializer: 2.0.0 @@ -14156,8 +13912,6 @@ snapshots: rou3: 0.8.1 srvx: 0.11.22 - hachure-fill@0.5.2: {} - has-bigints@1.1.0: {} has-flag@3.0.0: {} @@ -14549,37 +14303,19 @@ snapshots: jwa: 2.0.1 safe-buffer: 5.1.2 - katex@0.16.44: - dependencies: - commander: 8.3.0 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 - khroma@2.1.0: {} - kind-of@6.0.3: {} kleur@4.1.5: {} - langium@4.2.1: - dependencies: - chevrotain: 11.1.2 - chevrotain-allstar: 0.3.1(chevrotain@11.1.2) - vscode-languageserver: 9.0.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.1.0 - launch-editor@2.13.2: dependencies: picocolors: 1.1.1 shell-quote: 1.8.3 - layout-base@1.0.2: {} - - layout-base@2.0.1: {} - lazy-val@1.0.5: {} lie@3.3.0: @@ -14715,8 +14451,6 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.17.23: {} - lodash.escaperegexp@4.1.2: {} lodash.isequal@4.5.0: {} @@ -14756,8 +14490,6 @@ snapshots: optionalDependencies: react: 19.2.3 - marked@16.4.2: {} - match-sorter@8.3.0: dependencies: '@babel/runtime': 7.29.2 @@ -14776,30 +14508,6 @@ snapshots: merge-descriptors@2.0.0: {} - mermaid@11.14.0: - dependencies: - '@braintree/sanitize-url': 7.1.2 - '@iconify/utils': 3.1.0 - '@mermaid-js/parser': 1.1.0 - '@types/d3': 7.4.3 - '@upsetjs/venn.js': 2.0.0 - cytoscape: 3.33.1 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) - cytoscape-fcose: 2.2.0(cytoscape@3.33.1) - d3: 7.9.0 - d3-sankey: 0.12.3 - dagre-d3-es: 7.0.14 - dayjs: 1.11.20 - dompurify: 3.3.3 - katex: 0.16.44 - khroma: 2.1.0 - lodash-es: 4.17.23 - marked: 16.4.2 - roughjs: 4.6.6 - stylis: 4.3.6 - ts-dedent: 2.2.0 - uuid: 11.1.0 - meshline@3.3.1(three@0.183.2): dependencies: three: 0.183.2 @@ -14880,13 +14588,6 @@ snapshots: dependencies: minimist: 1.2.8 - mlly@1.8.2: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.3 - module-details-from-path@1.0.4: {} motion-dom@13.5.0: @@ -15168,8 +14869,6 @@ snapshots: package-json-from-dist@1.0.1: {} - package-manager-detector@1.6.0: {} - pako@1.0.11: {} parse-gitignore@2.0.0: {} @@ -15198,8 +14897,6 @@ snapshots: partial-json@0.1.7: {} - path-data-parser@0.1.0: {} - path-exists@4.0.0: {} path-key@3.1.1: {} @@ -15225,6 +14922,8 @@ snapshots: pe-library@0.4.1: {} + pele@1.12.0: {} + pg-int8@1.0.1: {} pg-protocol@1.13.0: {} @@ -15251,12 +14950,6 @@ snapshots: pkce-challenge@5.0.1: {} - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.2 - pathe: 2.0.3 - pkijs@3.4.1: dependencies: '@noble/hashes': 1.8.0 @@ -15276,13 +14969,6 @@ snapshots: pluralize@8.0.0: {} - points-on-curve@0.2.0: {} - - points-on-path@0.2.1: - dependencies: - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - possible-typed-array-names@1.1.0: {} postal-mime@2.7.4: {} @@ -15633,13 +15319,6 @@ snapshots: rou3@0.8.1: {} - roughjs@4.6.6: - dependencies: - hachure-fill: 0.5.2 - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - points-on-path: 0.2.1 - router@2.2.0: dependencies: debug: 4.4.3 @@ -16029,8 +15708,6 @@ snapshots: style-mod@4.1.3: {} - stylis@4.3.6: {} - sumchecker@3.0.1: dependencies: debug: 4.4.3 @@ -16190,8 +15867,6 @@ snapshots: ts-algebra@2.0.0: {} - ts-dedent@2.2.0: {} - tslib@2.8.1: {} tsx@4.21.0: @@ -16262,8 +15937,6 @@ snapshots: uc.micro@3.0.0: {} - ufo@1.6.3: {} - ufo@1.6.4: {} unbox-primitive@1.1.0: @@ -16371,8 +16044,6 @@ snapshots: uuid@10.0.0: {} - uuid@11.1.0: {} - valibot@1.3.1(typescript@6.0.2): optionalDependencies: typescript: 6.0.2 @@ -16433,23 +16104,6 @@ snapshots: transitivePeerDependencies: - msw - vscode-jsonrpc@8.2.0: {} - - vscode-languageserver-protocol@3.17.5: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - - vscode-languageserver-textdocument@1.0.12: {} - - vscode-languageserver-types@3.17.5: {} - - vscode-languageserver@9.0.1: - dependencies: - vscode-languageserver-protocol: 3.17.5 - - vscode-uri@3.1.0: {} - w3c-keyname@2.2.8: {} web-streams-polyfill@3.3.3: {} diff --git a/src/blog/tanstack-db-1.0-rc1-going-all-in-on-oracles.md b/src/blog/tanstack-db-1.0-rc1-going-all-in-on-oracles.md index 60de63b42..58607ba80 100644 --- a/src/blog/tanstack-db-1.0-rc1-going-all-in-on-oracles.md +++ b/src/blog/tanstack-db-1.0-rc1-going-all-in-on-oracles.md @@ -85,8 +85,8 @@ flowchart TD P["Explicit design"] --> G["Generate permitted changes"] P --> M["Independent model"] P --> C["Compare promised public behavior"] - G --> M G --> D["Real DB"] + G --> M M -->|Expected result| C D -->|Actual result| C ``` diff --git a/src/blog/your-mcp-your-way.md b/src/blog/your-mcp-your-way.md index 826dd06e1..9dd98cdd1 100644 --- a/src/blog/your-mcp-your-way.md +++ b/src/blog/your-mcp-your-way.md @@ -46,11 +46,11 @@ Everything else in this post is a variation on that line: how you build the clie ```mermaid flowchart LR S1[MCP server] -->|callTool| C1[createMCPClient] + CLI[npx @tanstack/ai-mcp generate] -.->|compile-time types| C1 S2[MCP server] --> P[createMCPClients pool] S3[MCP server] --> P C1 -->|ServerTool array| CHAT["chat({ tools }) or chat({ mcp })"] P -->|prefixed ServerTool array| CHAT - CLI[npx @tanstack/ai-mcp generate] -.->|compile-time types| C1 CLI -.->|compile-time types| P CHAT --> ADP[any adapter: OpenAI / Anthropic / Gemini / Ollama] ``` diff --git a/src/components/markdown/CodeBlock.tsx b/src/components/markdown/CodeBlock.tsx index 59a7f99b9..ddd888ebe 100644 --- a/src/components/markdown/CodeBlock.tsx +++ b/src/components/markdown/CodeBlock.tsx @@ -1,24 +1,48 @@ import { defaultHighlighter } from '@tanstack/highlight' import { renderCodeFence } from '@tanstack/highlight/markdown' import * as React from 'react' +import { ClientOnly } from '@tanstack/react-router' import { CodeBlockView } from './CodeBlockView' -import { MermaidBlock } from './MermaidBlock' -import { extractCodeBlockData, type CodeBlockProps } from './codeBlock.shared' +import { + buildPlainCodeBlockHtml, + extractCodeBlockData, + type CodeBlockProps, +} from './codeBlock.shared' + +const LazyMermaidBlock = React.lazy(() => + import('./MermaidBlock').then((m) => ({ default: m.MermaidBlock })), +) export function CodeBlock(props: CodeBlockProps) { const { code, lang, title } = extractCodeBlockData(props) if (lang === 'mermaid') { - return ( - ) + + return ( + + + + + + ) } return ( diff --git a/src/components/markdown/MermaidBlock.tsx b/src/components/markdown/MermaidBlock.tsx index e39ac60ea..571b5f3ac 100644 --- a/src/components/markdown/MermaidBlock.tsx +++ b/src/components/markdown/MermaidBlock.tsx @@ -1,142 +1,49 @@ +import { mount, register } from 'pele/core' +import flowchart from 'pele/diagrams/flowchart' import * as React from 'react' import { twMerge } from 'tailwind-merge' -import { CodeBlockView } from './CodeBlockView' -import { buildPlainCodeBlockHtml } from './codeBlock.shared' -// Mermaid shares configuration and a temporary DOM across renders. -let mermaidRenderQueue = Promise.resolve() -let mermaidRenderId = 0 - -type MermaidRenderState = - | { - status: 'loading' - svg?: undefined - } - | { - status: 'rendered' - svg: string - } - | { - status: 'error' - svg?: undefined - } - -function getIsDarkMode() { - return document.documentElement.classList.contains('dark') -} - -function useIsDarkMode() { - const [isDark, setIsDark] = React.useState(false) - - React.useEffect(() => { - const updateIsDark = () => setIsDark(getIsDarkMode()) - updateIsDark() - - const observer = new MutationObserver(updateIsDark) - observer.observe(document.documentElement, { - attributeFilter: ['class'], - attributes: true, - }) - - return () => observer.disconnect() - }, []) - - return isDark -} +register(flowchart) export function MermaidBlock({ className, code, - isEmbedded, - showTypeCopyButton, style, title, }: { className?: string code: string - isEmbedded?: boolean - showTypeCopyButton?: boolean style?: React.CSSProperties title?: string }) { - const isDark = useIsDarkMode() - const [renderState, setRenderState] = React.useState({ - status: 'loading', - }) - - React.useEffect(() => { - let cancelled = false - - setRenderState({ status: 'loading' }) - - async function renderMermaid() { - if (cancelled) return - - const container = document.createElement('div') - container.style.visibility = 'hidden' - container.style.position = 'absolute' - document.body.append(container) - - try { - const mermaid = (await import('mermaid')).default - if (cancelled) return - - mermaid.initialize({ - securityLevel: 'strict', - startOnLoad: false, - theme: isDark ? 'dark' : 'default', - }) - - const { svg } = await mermaid.render( - `mermaid-${++mermaidRenderId}`, - code, - container, - ) - - if (!cancelled) { - setRenderState({ status: 'rendered', svg }) - } - } catch { - if (!cancelled) { - setRenderState({ status: 'error' }) - } - } finally { - container.remove() + const mountDiagram = React.useCallback( + (container: HTMLDivElement | null) => { + if (!container) return + + const diagram = mount(container, code, { + // Keep diagrams from navigating or loading remote images. + links: false, + images: false, + zoom: false, + }) + + return () => { + diagram.destroy() } - } - - mermaidRenderQueue = mermaidRenderQueue.then(renderMermaid) - - return () => { - cancelled = true - } - }, [code, isDark]) - - if (renderState.status !== 'rendered') { - return ( - - ) - } + }, + [code], + ) return ( ) } diff --git a/src/styles/app.css b/src/styles/app.css index acbcbeaf0..ac055a6f8 100644 --- a/src/styles/app.css +++ b/src/styles/app.css @@ -3056,3 +3056,48 @@ License: The WebFont(s) listed in this document must follow the YouWorkForThem font-style: normal; font-display: swap; } + +/* Pele keeps the same SVG when the site's theme changes. */ +.mermaid-block { + --pele-bg: var(--color-white); + --pele-fg: var(--color-gray-900); + --pele-muted: var(--color-gray-600); + --pele-line: var(--color-gray-600); + --pele-surface: var(--color-gray-100); + --pele-surface-alt: var(--color-gray-200); + --pele-border: var(--color-gray-500); + --pele-accent: var(--color-blue-600); + --pele-critical: var(--color-red-700); + --pele-error: var(--color-red-700); + --pele-series-1: var(--color-blue-700); + --pele-series-2: var(--color-orange-700); + --pele-series-3: var(--color-green-700); + --pele-series-4: var(--color-red-700); + --pele-series-5: var(--color-cyan-700); + --pele-series-6: var(--color-yellow-700); + --pele-series-7: var(--color-purple-700); + --pele-series-8: var(--color-pink-700); + --pele-font: var(--font-sans); + --pele-font-mono: var(--font-mono); +} + +.dark .mermaid-block { + --pele-bg: var(--color-gray-950); + --pele-fg: var(--color-gray-200); + --pele-muted: var(--color-gray-400); + --pele-line: var(--color-gray-400); + --pele-surface: var(--color-gray-800); + --pele-surface-alt: var(--color-gray-900); + --pele-border: var(--color-gray-500); + --pele-accent: var(--color-blue-400); + --pele-critical: var(--color-red-400); + --pele-error: var(--color-red-400); + --pele-series-1: var(--color-blue-400); + --pele-series-2: var(--color-orange-400); + --pele-series-3: var(--color-green-400); + --pele-series-4: var(--color-red-400); + --pele-series-5: var(--color-cyan-400); + --pele-series-6: var(--color-yellow-400); + --pele-series-7: var(--color-purple-400); + --pele-series-8: var(--color-pink-400); +} diff --git a/tests/mermaid-rendering.test.ts b/tests/mermaid-rendering.test.ts index 44dac4092..9ac769cb4 100644 --- a/tests/mermaid-rendering.test.ts +++ b/tests/mermaid-rendering.test.ts @@ -1,9 +1,13 @@ import assert from 'node:assert/strict' -import { existsSync, readFileSync } from 'node:fs' +import { existsSync, readFileSync, readdirSync } from 'node:fs' import path from 'node:path' import test from 'node:test' import { build } from 'esbuild' import { chromium } from 'playwright-core' +import { register, render } from 'pele/core' +import flowchart from 'pele/diagrams/flowchart' + +register(flowchart) const repoRoot = path.resolve(import.meta.dirname, '..') const chromePath = [ @@ -14,18 +18,35 @@ const chromePath = [ '/usr/bin/chromium', chromium.executablePath(), ].find((candidate) => candidate && existsSync(candidate)) -const post = readFileSync( - path.join( - repoRoot, - 'src/blog/tanstack-db-1.0-rc1-going-all-in-on-oracles.md', - ), - 'utf8', +const diagrams = readdirSync(path.join(repoRoot, 'src/blog')).flatMap((file) => + [ + ...readFileSync(path.join(repoRoot, 'src/blog', file), 'utf8').matchAll( + /```mermaid\n([\s\S]*?)```/g, + ), + ].map((match) => match[1]), ) -const diagram = post.match(/```mermaid\n([\s\S]*?)```/)?.[1] -assert.ok(diagram) +const appCss = readFileSync(path.join(repoRoot, 'src/styles/app.css'), 'utf8') +const diagramCss = appCss.slice(appCss.indexOf('/* Pele keeps')) +const siteNeutralCss = appCss.match(/:root,\n:host \{[\s\S]*?\n\}/)?.[0] +assert.ok(siteNeutralCss) +const themeCss = readFileSync( + path.join(repoRoot, 'node_modules/tailwindcss/theme.css'), + 'utf8', +).replace('@theme default', ':root') + +test('Pele renders every existing article diagram at desktop and phone widths', () => { + assert.ok(diagrams.length >= 5) + for (const code of diagrams) { + for (const maxWidth of [844, 308]) { + const result = render(code, { maxWidth }) + assert.ok(result.width > 0 && result.height > 0) + assert.match(result.svg, / { const bundle = await build({ @@ -34,78 +55,16 @@ test( import React from 'react' import { createRoot } from 'react-dom/client' import { MermaidBlock } from './src/components/markdown/MermaidBlock' - const root = createRoot(document.getElementById('root')) - window.mermaidTest = { active: 0, configRaces: 0, visibleScratch: 0 } - window.holdRender = () => { - window.mermaidTest.gate = new Promise(resolve => { - window.releaseRender = () => { - window.mermaidTest.gate = undefined - resolve() - } - }) - } window.setDiagrams = codes => root.render( React.createElement(React.StrictMode, null, - codes.map((code, index) => React.createElement(MermaidBlock, { key: index, code })) + codes.map((code, index) => React.createElement(MermaidBlock, { key: index, code, title: 'Article diagram' })) ) ) - new MutationObserver(() => { - for (const svg of document.querySelectorAll('body svg')) { - if (!svg.closest('#root') && getComputedStyle(svg).visibility !== 'hidden') { - window.mermaidTest.visibleScratch++ - } - } - }).observe(document.body, { childList: true, subtree: true }) `, loader: 'tsx', resolveDir: repoRoot, }, - plugins: [ - { - name: 'mermaid-render-harness', - setup(build) { - // Isolate the renderer from the fallback's unrelated toast providers. - build.onResolve({ filter: /^\.\/CodeBlockView$/ }, () => ({ - path: 'fallback', - namespace: 'mermaid-test', - })) - build.onResolve({ filter: /^mermaid$/ }, () => ({ - path: 'mermaid', - namespace: 'mermaid-test', - })) - build.onLoad( - { filter: /.*/, namespace: 'mermaid-test' }, - (args) => ({ - contents: - args.path === 'fallback' - ? `import React from 'react' - export function CodeBlockView({ copyText }) { - return React.createElement('pre', null, copyText) - }` - : `import mermaid from ${JSON.stringify(path.join(repoRoot, 'node_modules/mermaid/dist/mermaid.core.mjs'))} - export default { - initialize(config) { - if (window.mermaidTest.active) window.mermaidTest.configRaces++ - mermaid.initialize(config) - }, - async render(...args) { - window.mermaidTest.active++ - try { - await window.mermaidTest.gate - return await mermaid.render(...args) - } finally { - window.mermaidTest.active-- - } - } - }`, - loader: 'jsx', - resolveDir: repoRoot, - }), - ) - }, - }, - ], bundle: true, format: 'iife', platform: 'browser', @@ -116,125 +75,382 @@ test( headless: true, }) try { - const page = await browser.newPage() + const page = await browser.newPage({ + viewport: { width: 1200, height: 900 }, + }) const errors: string[] = [] page.on('pageerror', (error) => errors.push(error.message)) - await page.setContent( - '', - ) + page.on('console', (message) => { + if (message.type() === 'error') errors.push(message.text()) + }) + await page.setContent(``) await page.addScriptTag({ content: bundle.outputFiles[0].text }) const run = (source: string) => page.evaluate(source) - await run('window.holdRender()') - await run(`window.setDiagrams([${JSON.stringify(diagram)}])`) - await page.waitForFunction('window.mermaidTest.active > 0') + const setDiagrams = (codes: string[]) => + run(`window.setDiagrams(${JSON.stringify(codes)})`) + const waitForDiagrams = (count: number) => + page.waitForFunction( + (count) => + document.querySelectorAll('.mermaid-block:not([hidden]) > svg') + .length === count, + count, + ) + await setDiagrams(diagrams) + await waitForDiagrams(diagrams.length) + assert.equal(await page.locator('#root pre').count(), 0) + const ids = await page + .locator('.mermaid-block [id]') + .evaluateAll((els) => els.map((el) => el.id)) + assert.equal( + ids.length, + new Set(ids).size, + 'IDs must be unique across diagrams', + ) await run( - `window.setDiagrams([${JSON.stringify(diagram)}, 'flowchart TD; A[Second diagram] --> B[Result]'])`, + 'window.initialSvg = document.querySelector(".mermaid-block > svg")', + ) + const label = page.locator('.pele-node .pele-label').first() + const darkFill = await label.evaluate((el) => getComputedStyle(el).fill) + await run('document.documentElement.classList.remove("dark")') + const lightFill = await label.evaluate((el) => getComputedStyle(el).fill) + assert.notEqual(lightFill, darkFill) + assert.equal( + await run( + 'window.initialSvg === document.querySelector(".mermaid-block > svg")', + ), + true, ) - // Flush React effects while the first render is deliberately held. + await run('document.documentElement.classList.add("dark")') + assert.equal( + await label.evaluate((el) => getComputedStyle(el).fill), + darkFill, + ) + + await setDiagrams([ + 'flowchart LR\n A[First] --> B[Second] --> C[Third] --> D[Fourth] --> E[Fifth]', + ]) + await waitForDiagrams(1) await page.waitForFunction( - () => document.querySelectorAll('#root pre').length === 2, + () => document.querySelector('.pele-label')?.textContent === 'First', ) - await run('window.releaseRender()') + const wideHeight = await page + .locator('.mermaid-block > svg') + .evaluate((svg) => svg.getBoundingClientRect().height) + await run('document.getElementById("root").style.width = "308px"') await page.waitForFunction( - () => - document.querySelectorAll('#root .mermaid-block svg').length === 2, + (height) => + document + .querySelector('.mermaid-block > svg')! + .getBoundingClientRect().height > height, + wideHeight, ) + assert.equal(await page.locator('.pele-node').count(), 5) - const assertDiagrams = async (count: number) => { - await page.waitForFunction( - (count) => - document.querySelectorAll('#root .mermaid-block svg').length === - count, - count, - ) - await page.waitForFunction('window.mermaidTest.active === 0') - assert.equal(await page.locator('body > div:not(#root)').count(), 0) - assert.deepEqual( - await page - .locator('.mermaid-block') - .first() - .locator('.nodeLabel') - .allTextContents(), - [ - 'Explicit design', - 'Generate permitted changes', - 'Independent model', - 'Compare promised public behavior', - 'Real DB', - ], - ) - assert.equal( - await run('window.mermaidTest.configRaces'), - 0, - 'Mermaid configuration must stay fixed during a render', - ) - assert.equal( - await run('window.mermaidTest.visibleScratch'), - 0, - 'Temporary diagrams must never be visible outside their blocks', + // Responsive layout remains enabled, but diagrams have no zoom UI. + await setDiagrams([ + 'flowchart TD\n A[Root] --> B[Long left label]\n A --> C[Long middle label]\n A --> D[Long right label]', + ]) + await waitForDiagrams(1) + await page.waitForFunction( + () => document.querySelector('.pele-label')?.textContent === 'Root', + ) + assert.equal(await page.locator('.pele-zoom').count(), 0) + assert.equal( + await page + .getByRole('button', { name: 'Zoom in', exact: true }) + .count(), + 0, + ) + assert.equal( + await page.locator('.mermaid-block > svg').getAttribute('tabindex'), + null, + ) + + // Explicit source fills retain contrasting labels in either site theme. + await setDiagrams([ + 'flowchart LR\n A[Light fill] --> B[Dark fill]\n style A fill:#ffffff\n style B fill:#000000', + ]) + await waitForDiagrams(1) + await page.waitForFunction( + () => + document.querySelector('.pele-label')?.textContent === 'Light fill', + ) + for (const dark of [false, true]) { + await page.evaluate( + (dark) => document.documentElement.classList.toggle('dark', dark), + dark, ) - assert.deepEqual(errors, []) + const fills = await page + .locator('.pele-node .pele-label') + .evaluateAll((els) => els.map((el) => getComputedStyle(el).fill)) + assert.deepEqual(fills, ['rgb(31, 31, 31)', 'rgb(255, 255, 255)']) } - await assertDiagrams(2) - const initialId = await page - .locator('.mermaid-block svg') - .first() - .getAttribute('id') + + // Pie values use the diagram background as their text color. Every + // categorical fill must contrast with it, in both site themes. for (const dark of [false, true]) { - const previousId = await page - .locator('.mermaid-block svg') - .first() - .getAttribute('id') await page.evaluate( (dark) => document.documentElement.classList.toggle('dark', dark), dark, ) - await page.waitForFunction( - (id) => document.querySelector('.mermaid-block svg')?.id !== id, - previousId, - ) - await assertDiagrams(2) - assert.equal( - await page - .locator('.mermaid-block .node rect') - .first() - .evaluate((rect) => getComputedStyle(rect).fill), - dark ? 'rgb(31, 32, 32)' : 'rgb(236, 236, 255)', + const contrasts = await page + .locator('.mermaid-block') + .evaluate((el) => { + const css = getComputedStyle(el) + const canvas = document.createElement('canvas') + canvas.width = canvas.height = 1 + const context = canvas.getContext('2d')! + const colorMath = { + luminance(color: string) { + context.fillStyle = color + context.fillRect(0, 0, 1, 1) + const rgb = context.getImageData(0, 0, 1, 1).data + const linear = [rgb[0], rgb[1], rgb[2]].map((value) => { + const channel = value / 255 + return channel <= 0.04045 + ? channel / 12.92 + : ((channel + 0.055) / 1.055) ** 2.4 + }) + return ( + linear[0] * 0.2126 + linear[1] * 0.7152 + linear[2] * 0.0722 + ) + }, + } + const bg = colorMath.luminance(css.getPropertyValue('--pele-bg')) + return Array.from({ length: 8 }, (_, i) => { + const fill = colorMath.luminance( + css.getPropertyValue('--pele-series-' + (i + 1)), + ) + return (Math.max(bg, fill) + 0.05) / (Math.min(bg, fill) + 0.05) + }) + }) + assert.ok( + contrasts.every((ratio) => ratio >= 4.5), + JSON.stringify(contrasts), ) } - assert.notEqual( - await page.locator('.mermaid-block svg').first().getAttribute('id'), - initialId, + + // A diagram mounted inside a collapsed article section must recover on reveal. + await run('document.getElementById("root").style.display = "none"') + await setDiagrams([ + 'flowchart LR\n A[Initially hidden] --> B[Visible later]', + ]) + await waitForDiagrams(1) + await run('document.getElementById("root").style.display = "block"') + await page.waitForFunction( + () => + document + .querySelector('.mermaid-block > svg')! + .getBoundingClientRect().width > 0, + ) + assert.match( + await page.locator('.mermaid-block').innerText(), + /Initially hidden/, ) - await run( - `window.setDiagrams([${JSON.stringify(diagram)}, 'not a diagram'])`, + await setDiagrams(['not a diagram ']) + await page.locator('.pele-error').waitFor() + assert.match( + await page.locator('.pele-error').innerText(), + /No diagram type detected/, ) - await page.waitForFunction( - () => document.querySelectorAll('#root pre').length === 1, + assert.equal(await page.locator('.mermaid-block > svg').count(), 0) + assert.equal(await page.locator('#root img').count(), 0) + await setDiagrams(['sequenceDiagram\n Alice->>Bob: Not registered']) + await page.locator('.pele-error').waitFor() + assert.match( + await page.locator('.pele-error').innerText(), + /not supported/, ) - await page.waitForFunction('window.mermaidTest.active === 0') - await assertDiagrams(1) - await run( - `window.setDiagrams([${JSON.stringify(diagram)}, 'flowchart TD; A[Recovered] --> B[Result]'])`, + await setDiagrams(['flowchart LR\n A[Recovered] --> B[Result]']) + await waitForDiagrams(1) + assert.match( + await page.locator('.mermaid-block').innerText(), + /Recovered/, ) - await assertDiagrams(2) - await run('window.holdRender()') - await run( - `window.setDiagrams([${JSON.stringify(diagram + '\n%% updated')}])`, + await setDiagrams([ + 'flowchart LR\n A[Link] --> B[End]\n click A "https://example.com"', + ]) + await waitForDiagrams(1) + await page.waitForFunction( + () => document.querySelector('.pele-label')?.textContent === 'Link', ) - await page.waitForFunction('window.mermaidTest.active > 0') - await run('window.setDiagrams([])') + assert.equal(await page.locator('.mermaid-block a').count(), 0) + await setDiagrams([]) await page.waitForFunction( () => document.querySelector('#root')?.childElementCount === 0, ) - await run('window.releaseRender()') - await page.waitForFunction('window.mermaidTest.active === 0') - assert.equal(await page.locator('body svg').count(), 0) + await page.setViewportSize({ width: 400, height: 800 }) + await setDiagrams(['flowchart TD\n A[Remounted] --> B[Result]']) + await waitForDiagrams(1) + assert.match( + await page.locator('.mermaid-block').innerText(), + /Remounted/, + ) assert.equal(await page.locator('body > div:not(#root)').count(), 0) + assert.equal( + await page + .getByText('Navigation and sponsors', { exact: true }) + .isVisible(), + true, + ) + assert.deepEqual(errors, []) + } finally { + await browser.close() + } + }, +) + +test( + 'diagram source renders on the server and stays visible until its lazy chunk loads', + { skip: chromePath ? false : 'Chrome is not installed', timeout: 60_000 }, + async () => { + const source = 'flowchart TD; A[Loaded] --> B[Result]' + // Isolate the existing code-block UI from unrelated toast providers. + const fallbackPlugin = { + name: 'code-block-fallback', + setup(pluginBuild: import('esbuild').PluginBuild) { + pluginBuild.onResolve({ filter: /^\.\/CodeBlockView$/ }, () => ({ + path: 'fallback', + namespace: 'test', + })) + pluginBuild.onLoad({ filter: /.*/, namespace: 'test' }, () => ({ + contents: `import React from 'react'; export function CodeBlockView({copyText,htmlMarkup}) { return React.createElement('section', {'data-copy':copyText, dangerouslySetInnerHTML:{__html:htmlMarkup}}) }`, + loader: 'jsx', + resolveDir: repoRoot, + })) + }, + } + const server = await build({ + stdin: { + contents: ` + import React from 'react' + import { renderToString } from 'react-dom/server' + import { CodeBlock } from './src/components/markdown/CodeBlock' + export const html = renderToString(React.createElement(CodeBlock, null, + React.createElement('code', {className:'language-mermaid'}, ${JSON.stringify(source)}) + )) + `, + resolveDir: repoRoot, + }, + plugins: [ + fallbackPlugin, + { + name: 'external-node-packages', + setup(pluginBuild) { + pluginBuild.onResolve({ filter: /^[^./]/ }, (args) => ({ + path: import.meta.resolve(args.path), + external: true, + })) + }, + }, + ], + bundle: true, + platform: 'node', + format: 'esm', + write: false, + }) + const { html } = await import( + `data:text/javascript;base64,${Buffer.from(server.outputFiles[0].text).toString('base64')}` + ) + assert.match(html, / setBlock({language,code}) + return React.createElement('main', null, + React.createElement('aside', null, 'Navigation and sponsors'), + React.createElement(CodeBlock, null, + React.createElement('code', {className:'language-'+block.language}, block.code) + ) + ) + } + root.render(React.createElement(Page)) + `, + loader: 'tsx', + resolveDir: repoRoot, + }, + plugins: [fallbackPlugin], + bundle: true, + splitting: true, + format: 'esm', + platform: 'browser', + outdir: '/tmp/pele-loading-test', + write: false, + }) + const browser = await chromium.launch({ + executablePath: chromePath, + headless: true, + }) + let release = () => {} + const gate = new Promise((resolve) => { + release = () => resolve(undefined) + }) + let diagramRequests = 0 + try { + const page = await browser.newPage() + const errors: string[] = [] + page.on('pageerror', (error) => errors.push(error.message)) + await page.route('https://pele-test.invalid/**', async (route) => { + const filename = new URL(route.request().url()).pathname.slice(1) + if (!filename) { + await route.fulfill({ + contentType: 'text/html', + body: '', + }) + return + } + if (filename.startsWith('MermaidBlock-')) { + diagramRequests++ + await gate + } + const file = bundle.outputFiles.find( + (file) => path.basename(file.path) === filename, + ) + assert.ok(file, filename) + await route.fulfill({ contentType: 'text/javascript', body: file.text }) + }) + await page.goto('https://pele-test.invalid/') + await page.getByText('const value = 1', { exact: true }).waitFor() + assert.equal(diagramRequests, 0) + await page.evaluate( + `window.showCode('mermaid',${JSON.stringify(source)})`, + ) + await page.getByText(source, { exact: true }).waitFor() + assert.equal( + await page + .getByText('Navigation and sponsors', { exact: true }) + .isVisible(), + true, + ) + assert.equal(await page.locator('.mermaid-block > svg').count(), 0) + release() + await page.locator('.mermaid-block > svg').waitFor() + assert.equal(diagramRequests, 1) + assert.equal(await page.locator('#root pre').count(), 0) + assert.equal( + await page + .getByText('Navigation and sponsors', { exact: true }) + .isVisible(), + true, + ) assert.deepEqual(errors, []) } finally { + release() await browser.close() } },
setBlock({language,code}) + return React.createElement('main', null, + React.createElement('aside', null, 'Navigation and sponsors'), + React.createElement(CodeBlock, null, + React.createElement('code', {className:'language-'+block.language}, block.code) + ) + ) + } + root.render(React.createElement(Page)) + `, + loader: 'tsx', + resolveDir: repoRoot, + }, + plugins: [fallbackPlugin], + bundle: true, + splitting: true, + format: 'esm', + platform: 'browser', + outdir: '/tmp/pele-loading-test', + write: false, + }) + const browser = await chromium.launch({ + executablePath: chromePath, + headless: true, + }) + let release = () => {} + const gate = new Promise((resolve) => { + release = () => resolve(undefined) + }) + let diagramRequests = 0 + try { + const page = await browser.newPage() + const errors: string[] = [] + page.on('pageerror', (error) => errors.push(error.message)) + await page.route('https://pele-test.invalid/**', async (route) => { + const filename = new URL(route.request().url()).pathname.slice(1) + if (!filename) { + await route.fulfill({ + contentType: 'text/html', + body: '', + }) + return + } + if (filename.startsWith('MermaidBlock-')) { + diagramRequests++ + await gate + } + const file = bundle.outputFiles.find( + (file) => path.basename(file.path) === filename, + ) + assert.ok(file, filename) + await route.fulfill({ contentType: 'text/javascript', body: file.text }) + }) + await page.goto('https://pele-test.invalid/') + await page.getByText('const value = 1', { exact: true }).waitFor() + assert.equal(diagramRequests, 0) + await page.evaluate( + `window.showCode('mermaid',${JSON.stringify(source)})`, + ) + await page.getByText(source, { exact: true }).waitFor() + assert.equal( + await page + .getByText('Navigation and sponsors', { exact: true }) + .isVisible(), + true, + ) + assert.equal(await page.locator('.mermaid-block > svg').count(), 0) + release() + await page.locator('.mermaid-block > svg').waitFor() + assert.equal(diagramRequests, 1) + assert.equal(await page.locator('#root pre').count(), 0) + assert.equal( + await page + .getByText('Navigation and sponsors', { exact: true }) + .isVisible(), + true, + ) assert.deepEqual(errors, []) } finally { + release() await browser.close() } },