From b866f53dd70e1e93dda0aa0065a1d677b13a2628 Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Sun, 7 Jun 2026 11:43:32 -0400 Subject: [PATCH 01/40] docs: add superchat plan for multi-agent/multi-human chat --- superchat-plan.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 superchat-plan.md diff --git a/superchat-plan.md b/superchat-plan.md new file mode 100644 index 00000000..58e4b720 --- /dev/null +++ b/superchat-plan.md @@ -0,0 +1,40 @@ +# SuperChat — Multi-Participant Chat Plan + +> **Status:** Draft / placeholder. To be fleshed out. + +## Goal + +Enhance the existing chat component (`AIChat` / `AIMessageDisplay` in +[src/components/AI](src/components/AI)) to support **multiple participants** in a +single conversation — any mix of **multiple AI agents** and **multiple humans** — +rather than the current single-assistant / single-user model. + +## Motivation + +Today the chat assumes a 1:1 exchange (one user ↔ one assistant). Real workflows +involve several actors at once: a care team collaborating with one or more AI +assistants, agent-to-agent hand-offs, human escalation/oversight, and shared +context across participants. + +## Open questions (to flesh out) + +- **Participant model** — how are agents vs. humans identified, named, avatared, + and color-coded? Roles/permissions per participant? +- **Message attribution** — extend `AIMessage.role` beyond `user`/`assistant`? + Add a `participantId` / `author` concept? +- **Turn-taking & routing** — who responds when? @-mentions to address a specific + agent/human? Concurrent streaming from multiple agents? +- **Presence & typing** — show who's active / generating. +- **Agent-to-agent** — visualizing hand-offs and tool calls across agents. +- **Backwards compatibility** — keep the existing single-assistant API working; + multi-participant as an additive layer. +- **Reuse vs. build** — what can be reused from the Messaging module + (`MessageBubble` / `MessageList` / composer) vs. net-new. + +## Related + +- Existing AI module: [src/components/AI](src/components/AI) +- Maintainer notes: [src/components/AI/MAINTAINERS.md](src/components/AI/MAINTAINERS.md) +- Extension seam for rich rendering: `renderTextContent` (see AI types) + +_TODO: detailed design, component API, data model, milestones._ From 02aba9f193d9dbfd9ee94b427be77748c33345c9 Mon Sep 17 00:00:00 2001 From: Doug Horner Date: Sun, 7 Jun 2026 22:39:14 -0400 Subject: [PATCH 02/40] feat(SuperChat): multi-participant chat with pluggable markdown pipeline Add a native SuperChat module that composes the AI module's renderTextContent seam (Decisions 1-3 of superchat-plan.md): - Participant model (human/agent/system) generalizing AI + chat-component roles; chat-component-compatible conversation/thread/ref/linkBuilder shape - createMarkdownRenderer: composes render plugins into one renderTextContent; Markdown core (remark-gfm) with rehype-sanitize on untrusted output - Opt-in rich plugins behind a subpath: code (rehype-highlight + copy), math (KaTeX), genui (fenced JSON -> host-registered, lazy, schema-validated widget registry with component-vs-data prefetch) - SuperChat shell: sidebar/thread/composer, controlled props, timestamp-ordered interleaved replies, per-participant color/avatar cues, read-only - Composer @-mention menu (keyboard + mouse) and password-manager opt-out attrs - Stories (Markdown core / rich plugins / read-only), MAINTAINERS.md, 9 tests - Wire package.json optional peers + tsup subpath entries; not in base bundle --- package.json | 35 + pnpm-lock.yaml | 1102 ++++++++++++++++- src/components/SuperChat/MAINTAINERS.md | 90 ++ .../SuperChat/SuperChat.stories.tsx | 271 ++++ src/components/SuperChat/SuperChat.test.tsx | 136 ++ src/components/SuperChat/SuperChat.tsx | 683 ++++++++++ src/components/SuperChat/index.ts | 52 + src/components/SuperChat/plugins/code.tsx | 70 ++ src/components/SuperChat/plugins/genui.tsx | 324 +++++ src/components/SuperChat/plugins/index.ts | 23 + src/components/SuperChat/plugins/math.tsx | 73 ++ .../render/createMarkdownRenderer.tsx | 233 ++++ .../SuperChat/render/renderContext.ts | 22 + src/components/SuperChat/types.ts | 247 ++++ superchat-plan.md | 351 +++++- tsup.config.ts | 9 + 16 files changed, 3683 insertions(+), 38 deletions(-) create mode 100644 src/components/SuperChat/MAINTAINERS.md create mode 100644 src/components/SuperChat/SuperChat.stories.tsx create mode 100644 src/components/SuperChat/SuperChat.test.tsx create mode 100644 src/components/SuperChat/SuperChat.tsx create mode 100644 src/components/SuperChat/index.ts create mode 100644 src/components/SuperChat/plugins/code.tsx create mode 100644 src/components/SuperChat/plugins/genui.tsx create mode 100644 src/components/SuperChat/plugins/index.ts create mode 100644 src/components/SuperChat/plugins/math.tsx create mode 100644 src/components/SuperChat/render/createMarkdownRenderer.tsx create mode 100644 src/components/SuperChat/render/renderContext.ts create mode 100644 src/components/SuperChat/types.ts diff --git a/package.json b/package.json index 2f445d4b..e38ade50 100644 --- a/package.json +++ b/package.json @@ -185,10 +185,17 @@ "ag-grid-react": ">=32.0.0", "datavis-ace": "=4.0.0-PRE.2", "js-yaml": ">=4.0.0", + "katex": ">=0.16.0", "mermaid": ">=10.0.0", "papaparse": ">=5.0.0", "react": ">=18.0.0", "react-dom": ">=18.0.0", + "react-markdown": ">=9.0.0", + "rehype-highlight": ">=7.0.0", + "rehype-katex": ">=7.0.0", + "rehype-sanitize": ">=6.0.0", + "remark-gfm": ">=4.0.0", + "remark-math": ">=6.0.0", "wavesurfer.js": ">=7.0.0" }, "peerDependenciesMeta": { @@ -219,6 +226,27 @@ "papaparse": { "optional": true }, + "react-markdown": { + "optional": true + }, + "remark-gfm": { + "optional": true + }, + "remark-math": { + "optional": true + }, + "rehype-katex": { + "optional": true + }, + "katex": { + "optional": true + }, + "rehype-sanitize": { + "optional": true + }, + "rehype-highlight": { + "optional": true + }, "react": { "optional": false }, @@ -298,6 +326,7 @@ "eslint-plugin-storybook": "^10.2.11", "js-yaml": "^4.1.1", "jsdom": "^26.1.0", + "katex": "^0.17.0", "mermaid": "^11.12.3", "papaparse": "^5.5.3", "postcss": "^8.5.6", @@ -305,6 +334,12 @@ "prettier-plugin-tailwindcss": "^0.6.14", "react": "^19.2.4", "react-dom": "^19.2.4", + "react-markdown": "^10.1.0", + "rehype-highlight": "^7.0.2", + "rehype-katex": "^7.0.1", + "rehype-sanitize": "^6.0.0", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", "sass": "1.100.0", "sortablejs": "^1.15.7", "storybook": "^10.2.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a58c1fea..68b41a36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -152,7 +152,7 @@ importers: version: 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@vitest/coverage-v8': specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0)) ag-grid-community: specifier: ^35.1.0 version: 35.1.0 @@ -216,6 +216,9 @@ importers: jsdom: specifier: ^26.1.0 version: 26.1.0 + katex: + specifier: ^0.17.0 + version: 0.17.0 mermaid: specifier: ^11.12.3 version: 11.15.0 @@ -237,6 +240,24 @@ importers: react-dom: specifier: ^19.2.4 version: 19.2.4(react@19.2.4) + react-markdown: + specifier: ^10.1.0 + version: 10.1.0(@types/react@19.2.14)(react@19.2.4) + rehype-highlight: + specifier: ^7.0.2 + version: 7.0.2 + rehype-katex: + specifier: ^7.0.1 + version: 7.0.1 + rehype-sanitize: + specifier: ^6.0.0 + version: 6.0.0 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 sass: specifier: 1.100.0 version: 1.100.0 @@ -260,7 +281,7 @@ importers: version: 7.3.1(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.100.0) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0) + version: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0) wavesurfer.js: specifier: ^7.12.1 version: 7.12.1 @@ -1673,12 +1694,18 @@ packages: '@types/d3@7.4.3': resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1688,6 +1715,9 @@ packages: '@types/google-libphonenumber@7.4.30': resolution: {integrity: sha512-Td1X1ayRxePEm6/jPHUBs2tT6TzW1lrVB6ZX7ViPGellyzO/0xMNi+wx5nH6jEitjznq276VGIqjK5qAju0XVw==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -1703,12 +1733,21 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + '@types/luxon@3.7.1': resolution: {integrity: sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdx@2.0.13': resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@22.19.11': resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==} @@ -1732,6 +1771,12 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/wait-on@5.3.4': resolution: {integrity: sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==} @@ -1802,6 +1847,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -2135,6 +2181,9 @@ packages: peerDependencies: '@babel/core': ^7.11.0 || ^8.0.0-beta.1 + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2218,6 +2267,9 @@ packages: caniuse-lite@1.0.30001774: resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} engines: {node: '>=18'} @@ -2238,6 +2290,18 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.3: resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} engines: {node: '>= 16'} @@ -2302,6 +2366,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@12.1.0: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} @@ -2580,6 +2647,9 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + dedent@1.7.2: resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} peerDependencies: @@ -2642,6 +2712,9 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diffable-html@4.1.0: resolution: {integrity: sha512-++kyNek+YBLH8cLXS+iTj/Hiy2s5qkRJEJ8kgu/WHbFrVY2vz9xPFUT+fii2zGF0m1CaojDlQJjkfrCt7YWM1g==} @@ -2798,6 +2871,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + eslint-plugin-jsx-a11y@6.10.2: resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} engines: {node: '>=4.0'} @@ -2873,6 +2950,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} @@ -2917,6 +2997,9 @@ packages: ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -3148,6 +3231,39 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-from-dom@5.0.1: + resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} + + hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-sanitize@5.0.2: + resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + highlight.js@11.11.1: resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==} engines: {node: '>=12.0.0'} @@ -3166,6 +3282,9 @@ packages: html-parse-stringify@3.0.1: resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + html-url-attributes@3.0.1: + resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} + htmlparser2@3.10.1: resolution: {integrity: sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==} @@ -3234,6 +3353,9 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -3245,6 +3367,12 @@ packages: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -3280,6 +3408,9 @@ packages: resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@3.0.0: resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -3309,6 +3440,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} @@ -3326,6 +3460,10 @@ packages: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -3652,6 +3790,10 @@ packages: resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} hasBin: true + katex@0.17.0: + resolution: {integrity: sha512-Vdw0ATsQ9V+LuegM/BTwQqV/6cTl5lbGcIrU+BCgLxyf6bo38ybOr372tuSIxir3CN720flu1meYR6XzNMwQnw==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3792,6 +3934,9 @@ packages: resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} engines: {node: '>= 0.6.0'} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -3799,6 +3944,9 @@ packages: loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lowlight@3.3.0: + resolution: {integrity: sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -3844,6 +3992,9 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marked@14.0.0: resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} engines: {node: '>= 18'} @@ -3863,12 +4014,147 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} mermaid@11.15.0: resolution: {integrity: sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==} + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -4071,6 +4357,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -4271,6 +4560,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + proxy-from-env@2.1.0: resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} engines: {node: '>=10'} @@ -4321,6 +4613,12 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-markdown@10.1.0: + resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} + peerDependencies: + '@types/react': '>=18' + react: '>=18' + react@19.2.4: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} @@ -4353,10 +4651,34 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + rehype-highlight@7.0.2: + resolution: {integrity: sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==} + + rehype-katex@7.0.1: + resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} + + rehype-sanitize@6.0.0: + resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} + release-zalgo@1.0.0: resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} engines: {node: '>=4'} + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -4536,6 +4858,9 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spawn-wrap@2.0.0: resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} engines: {node: '>=8'} @@ -4617,6 +4942,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4655,6 +4983,12 @@ packages: style-mod@4.1.3: resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + stylis@4.4.0: resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} @@ -4762,6 +5096,12 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-api-utils@2.4.0: resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} engines: {node: '>=18.12'} @@ -4857,6 +5197,30 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + unplugin@2.3.11: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} @@ -4893,6 +5257,15 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -4993,6 +5366,9 @@ packages: wavesurfer.js@7.12.1: resolution: {integrity: sha512-NswPjVHxk0Q1F/VMRemCPUzSojjuHHisQrBqQiRXg7MVbe3f5vQ6r0rTTXA/a/neC/4hnOEC4YpXca4LpH0SUg==} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webidl-conversions@7.0.0: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} @@ -5149,6 +5525,9 @@ packages: use-sync-external-store: optional: true + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@adobe/css-tools@4.4.4': {} @@ -6616,16 +6995,28 @@ snapshots: '@types/d3-transition': 3.0.9 '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} '@types/doctrine@0.0.9': {} + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + '@types/estree@1.0.8': {} '@types/geojson@7946.0.16': {} '@types/google-libphonenumber@7.4.30': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -6640,10 +7031,18 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/katex@0.16.8': {} + '@types/luxon@3.7.1': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/mdx@2.0.13': {} + '@types/ms@2.1.0': {} + '@types/node@22.19.11': dependencies: undici-types: 6.21.0 @@ -6667,6 +7066,10 @@ snapshots: '@types/trusted-types@2.0.7': optional: true + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + '@types/wait-on@5.3.4': dependencies: '@types/node': 22.19.11 @@ -6834,7 +7237,7 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0))': + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0))': dependencies: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 @@ -6849,7 +7252,7 @@ snapshots: std-env: 3.10.0 test-exclude: 7.0.2 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0) + vitest: 3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0) transitivePeerDependencies: - supports-color @@ -7129,6 +7532,8 @@ snapshots: babel-plugin-jest-hoist: 30.3.0 babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + bail@2.0.2: {} + balanced-match@1.0.2: {} balanced-match@4.0.4: {} @@ -7207,6 +7612,8 @@ snapshots: caniuse-lite@1.0.30001774: {} + ccount@2.0.1: {} + chai@5.3.3: dependencies: assertion-error: 2.0.1 @@ -7230,6 +7637,14 @@ snapshots: char-regex@1.0.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + check-error@2.1.3: {} chokidar@4.0.3: @@ -7294,6 +7709,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + commander@12.1.0: {} commander@3.0.2: {} @@ -7599,6 +8016,10 @@ snapshots: decimal.js@10.6.0: {} + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + dedent@1.7.2: {} deep-eql@5.0.2: {} @@ -7644,6 +8065,10 @@ snapshots: detect-newline@3.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + diffable-html@4.1.0: dependencies: htmlparser2: 3.10.1 @@ -7883,6 +8308,8 @@ snapshots: escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.3(jiti@2.6.1)): dependencies: aria-query: 5.3.2 @@ -8014,6 +8441,8 @@ snapshots: estraverse@5.3.0: {} + estree-util-is-identifier-name@3.0.0: {} + estree-walker@2.0.2: {} estree-walker@3.0.3: @@ -8064,6 +8493,8 @@ snapshots: dependencies: type: 2.7.3 + extend@3.0.2: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -8295,6 +8726,92 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html-isomorphic@2.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-dom: 5.0.1 + hast-util-from-html: 2.0.3 + unist-util-remove-position: 5.0.0 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.2.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-sanitize@5.0.2: + dependencies: + '@types/hast': 3.0.4 + '@ungap/structured-clone': 1.3.0 + unist-util-position: 5.0.0 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + highlight.js@11.11.1: {} homedir-polyfill@1.0.3: @@ -8311,6 +8828,8 @@ snapshots: dependencies: void-elements: 3.1.0 + html-url-attributes@3.0.1: {} + htmlparser2@3.10.1: dependencies: domelementtype: 1.3.1 @@ -8375,6 +8894,8 @@ snapshots: ini@1.3.8: {} + inline-style-parser@0.2.7: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -8385,6 +8906,13 @@ snapshots: internmap@2.0.3: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -8427,6 +8955,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-decimal@2.0.1: {} + is-docker@3.0.0: {} is-extglob@2.1.1: {} @@ -8451,6 +8981,8 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -8464,6 +8996,8 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 + is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} is-regex@1.2.1: @@ -9026,6 +9560,10 @@ snapshots: dependencies: commander: 8.3.0 + katex@0.17.0: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -9124,12 +9662,20 @@ snapshots: loglevel@1.9.2: {} + longest-streak@3.1.0: {} + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 loupe@3.2.1: {} + lowlight@3.3.0: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + highlight.js: 11.11.1 + lru-cache@10.4.3: {} lru-cache@11.2.6: {} @@ -9172,6 +9718,8 @@ snapshots: dependencies: tmpl: 1.0.5 + markdown-table@3.0.4: {} + marked@14.0.0: {} marked@16.4.2: {} @@ -9180,6 +9728,171 @@ snapshots: math-intrinsics@1.1.0: {} + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.3: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.3 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-math@3.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.3 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + merge-stream@2.0.0: {} mermaid@11.15.0: @@ -9206,6 +9919,207 @@ snapshots: ts-dedent: 2.2.0 uuid: 14.0.0 + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.8 + devlop: 1.1.0 + katex: 0.16.47 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + mime-db@1.52.0: {} mime-types@2.1.35: @@ -9432,6 +10346,16 @@ snapshots: dependencies: callsites: 3.1.0 + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.29.0 @@ -9554,6 +10478,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + property-information@7.2.0: {} + proxy-from-env@2.1.0: {} punycode@2.3.1: {} @@ -9601,6 +10527,24 @@ snapshots: react-is@18.3.1: {} + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.14 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.4 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + react@19.2.4: {} readable-stream@3.6.2: @@ -9646,10 +10590,76 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + rehype-highlight@7.0.2: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-text: 4.0.2 + lowlight: 3.3.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + rehype-katex@7.0.1: + dependencies: + '@types/hast': 3.0.4 + '@types/katex': 0.16.8 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.47 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + + rehype-sanitize@6.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-sanitize: 5.0.2 + release-zalgo@1.0.0: dependencies: es6-error: 4.1.1 + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-math@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.3 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + require-directory@2.1.1: {} require-main-filename@2.0.0: {} @@ -9860,6 +10870,8 @@ snapshots: source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + spawn-wrap@2.0.0: dependencies: foreground-child: 2.0.0 @@ -9995,6 +11007,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -10023,6 +11040,14 @@ snapshots: style-mod@4.1.3: {} + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + stylis@4.4.0: {} sucrase@3.35.1: @@ -10118,6 +11143,10 @@ snapshots: tree-kill@1.2.2: {} + trim-lines@3.0.1: {} + + trough@2.2.0: {} + ts-api-utils@2.4.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -10227,6 +11256,49 @@ snapshots: undici-types@6.21.0: {} + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.1.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + unplugin@2.3.11: dependencies: '@jridgewell/remapping': 2.3.5 @@ -10284,6 +11356,21 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite-node@3.2.4(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.100.0): dependencies: cac: 6.7.14 @@ -10320,7 +11407,7 @@ snapshots: lightningcss: 1.31.1 sass: 1.100.0 - vitest@3.2.4(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0): + vitest@3.2.4(@types/debug@4.1.13)(@types/node@22.19.11)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.31.1)(sass@1.100.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 @@ -10346,6 +11433,7 @@ snapshots: vite-node: 3.2.4(@types/node@22.19.11)(jiti@2.6.1)(lightningcss@1.31.1)(sass@1.100.0) why-is-node-running: 2.3.0 optionalDependencies: + '@types/debug': 4.1.13 '@types/node': 22.19.11 jsdom: 26.1.0 transitivePeerDependencies: @@ -10394,6 +11482,8 @@ snapshots: wavesurfer.js@7.12.1: {} + web-namespaces@2.0.1: {} + webidl-conversions@7.0.0: {} webpack-virtual-modules@0.6.2: {} @@ -10557,3 +11647,5 @@ snapshots: '@types/react': 19.2.14 react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) + + zwitch@2.0.4: {} diff --git a/src/components/SuperChat/MAINTAINERS.md b/src/components/SuperChat/MAINTAINERS.md new file mode 100644 index 00000000..4e87f994 --- /dev/null +++ b/src/components/SuperChat/MAINTAINERS.md @@ -0,0 +1,90 @@ +# SuperChat — Maintainer Notes + +> **Provider notes** — how to *change* the SuperChat module. Consumers should read +> the Storybook autodocs (Product › Feature Modules › SuperChat) and +> [superchat-plan.md](../../../superchat-plan.md). General conventions live in +> [CONTRIBUTING.md](../../../CONTRIBUTING.md). This module **composes** the AI +> module — see [../AI/MAINTAINERS.md](../AI/MAINTAINERS.md). + +## What's in here + +| Surface | File | Role | +|---------|------|------| +| `SuperChat` | [SuperChat.tsx](SuperChat.tsx) | Native shell: sidebar + thread + composer (controlled props) | +| `createMarkdownRenderer` | [render/createMarkdownRenderer.tsx](render/createMarkdownRenderer.tsx) | Composes render plugins → one `renderTextContent` (Markdown core) | +| render context | [render/renderContext.ts](render/renderContext.ts) | Threads `messageId`/`streaming` into custom nodes (GenUI) | +| code / math / genui plugins | [plugins/](plugins) | Opt-in rich plugins (subpath entry) | +| types | [types.ts](types.ts) | Participant model + chat-component-compatible data model + plugin/GenUI contracts | + +## Architecture (3 decisions from the plan) + +1. **Native reimplementation** preserving the `chat-component` prop/data-model + shape (`SuperChatConversation` / `SuperChatMessage` with `participantId`, + `channel`, `ref`, `linkBuilder`, callbacks). No bundled React / `tw-` prefix. +2. **Participant model** (`Participant { id, kind, name, color?, … }`) unifies the + AI module's `user`/`assistant` and chat-component's `external`/`internal`/`system`. + The thread is append-only and **ordered by `time`**; concurrent agent replies + interleave and are disambiguated by per-participant `color`/avatar/name. +3. **Pluggable Markdown pipeline** wired through the AI module's + `renderTextContent` seam. **The host owns sanitization** — untrusted output is + run through `rehype-sanitize` with an allow-list extended per plugin. + +## Bundle / entry layout + +- `@mieweb/ui/components/SuperChat` ships the shell **+ Markdown core** only + (`react-markdown` + `remark-gfm` + `rehype-sanitize`). +- `@mieweb/ui/components/SuperChat/plugins` ships `code` / `math` / `genui`. Each + rich dependency (`rehype-highlight`, `rehype-katex`/`katex`) is an **optional + peer dependency** — not in the base bundle. tsup entries: + `components/SuperChat/index` and `components/SuperChat/plugins/index`. +- SuperChat is intentionally **not** re-exported from the top-level `src/index.ts` + (same pattern as `datavis` / `ag-grid`) so the main bundle stays light. + +## Render plugin contract (read before adding a plugin) + +A `SuperChatRenderPlugin` contributes `remarkPlugins`, `rehypePlugins`, +`components` (node → React), `widgets` (GenUI), and a `sanitizeSchema` fragment. +The composer: + +- always prepends `remark-gfm`; +- appends `rehype-sanitize` **last** (after highlight/katex) so their classNames + exist to be allow-listed — order matters, don't reshuffle; +- merges each plugin's `sanitizeSchema` (tagNames union, per-tag attribute concat). + +**Gotchas** + +- The base schema broadens `className` on `code`/`pre`/`span`/`div` so `.hljs-*` + syntax tokens survive. If you tighten this you will strip highlight colors. +- The math plugin must allow KaTeX's HTML+MathML tags — see `KATEX_TAGS`. +- Consumers of the math plugin must import `katex/dist/katex.min.css` themselves. + +## GenUI widgets + +- Wire format is a **fenced ```genui JSON block**, not inline. A small rehype + transformer rewrites `
` → `` *before* sanitize (the tag/attr are allow-listed), which
+  avoids `pre`/`code` component-override conflicts with the code plugin.
+- Widgets are **host-registered, lazy, schema-validated**. Unknown widget →
+  inert code-block fallback (never arbitrary HTML).
+- Prefetch is split: **component (code)** may load while streaming per policy
+  (`eager`/`visible`/`idle`); **data** validation/prefetch runs only once the
+  payload parses *and* the message has stopped streaming. **Registry policy
+  overrides the wire hint.**
+- Versioning: key the registry by base name; resolve `version` explicitly (do not
+  bake the version into the lookup key).
+
+## Not yet implemented (tracked against the plan)
+
+- **NITRO tables** plugin (GFM table → `DataVisNITRO` grid) — reuse the
+  `@mieweb/ui/datavis` entry; see [../DataVisNITRO/MAINTAINERS.md](../DataVisNITRO/MAINTAINERS.md).
+- **Mermaid** plugin (lazy `mermaid` on ` ```mermaid ` fences).
+- **Image lightbox** reuse from Messaging.
+- Composer `@`-mention **autocomplete** (mentions are currently detected on send;
+  see `detectMentions`).
+
+## Testing
+
+- Stories: [SuperChat.stories.tsx](SuperChat.stories.tsx) drives the autodocs page
+  (Markdown core / rich plugins / read-only).
+- When adding a plugin, add a story exercising it and confirm sanitized output
+  still renders.
diff --git a/src/components/SuperChat/SuperChat.stories.tsx b/src/components/SuperChat/SuperChat.stories.tsx
new file mode 100644
index 00000000..348434ec
--- /dev/null
+++ b/src/components/SuperChat/SuperChat.stories.tsx
@@ -0,0 +1,271 @@
+import * as React from 'react';
+import type { Meta, StoryObj } from '@storybook/react-vite';
+import { SuperChat } from './index';
+import { createCodePlugin, createMathPlugin, createGenUIPlugin } from './plugins';
+import type {
+  GenUIRegistry,
+  GenUIWidgetProps,
+  SuperChatConversation,
+} from './index';
+import 'katex/dist/katex.min.css';
+
+// ============================================================================
+// Sample data
+// ============================================================================
+
+const participants = {
+  me: { id: 'u1', kind: 'human' as const, name: 'Dr. Alice Reyes', role: 'Provider', color: '#0e7490' },
+  nurse: { id: 'u2', kind: 'human' as const, name: 'Sam Carter', role: 'Nurse', color: '#9333ea' },
+  triage: { id: 'a1', kind: 'agent' as const, name: 'Triage Agent', color: '#2563eb' },
+  coder: { id: 'a2', kind: 'agent' as const, name: 'Coding Agent', color: '#16a34a' },
+  system: { id: 's1', kind: 'system' as const, name: 'System' },
+};
+
+const conversation: SuperChatConversation = {
+  id: 'c1',
+  title: 'Patient 4821 — Intake review',
+  reference_id: 'patient/4821',
+  unread: 0,
+  participants: Object.values(participants),
+  thread: [
+    {
+      id: 'm0',
+      type: 'system',
+      participantId: 's1',
+      text: 'Triage Agent and Coding Agent joined the conversation.',
+      time: '2026-06-07T09:00:00Z',
+    },
+    {
+      id: 'm1',
+      participantId: 'u1',
+      text: '@Triage can you summarize the **chief complaint** and flag anything urgent?',
+      mentions: ['a1'],
+      time: '2026-06-07T09:01:00Z',
+    },
+    {
+      id: 'm2',
+      participantId: 'a1',
+      text: [
+        'Summary of the intake:',
+        '',
+        '- **Chief complaint:** chest tightness on exertion',
+        '- **Duration:** 3 days',
+        '- **Risk flags:** family history of CAD',
+        '',
+        '> Recommend prioritizing an ECG.',
+      ].join('\n'),
+      time: '2026-06-07T09:01:30Z',
+    },
+    {
+      id: 'm3',
+      participantId: 'u2',
+      text: 'Thanks. @Coding what CPT applies to a 12-lead ECG with interpretation?',
+      mentions: ['a2'],
+      time: '2026-06-07T09:02:00Z',
+    },
+    {
+      id: 'm4',
+      participantId: 'a2',
+      text: [
+        'For a 12-lead ECG with interpretation and report, use **93000**.',
+        '',
+        '```javascript',
+        "const code = '93000';",
+        "console.log(`CPT ${code}: ECG, complete`);",
+        '```',
+      ].join('\n'),
+      time: '2026-06-07T09:02:30Z',
+    },
+    {
+      id: 'm5',
+      participantId: 'a1',
+      text: [
+        'Risk score uses the standard quadratic term:',
+        '',
+        '$$ risk = \\beta_0 + \\beta_1 x + \\beta_2 x^2 $$',
+        '',
+        'Inline too: the threshold is $x > 0.7$.',
+      ].join('\n'),
+      time: '2026-06-07T09:03:00Z',
+    },
+    {
+      id: 'm6',
+      participantId: 'a2',
+      text: [
+        'Here is an interactive widget:',
+        '',
+        '```genui',
+        '{ "widget": "kpi_card", "version": 1, "prefetch": "eager", "props": { "label": "Risk", "value": "High", "trend": "+12%" } }',
+        '```',
+      ].join('\n'),
+      time: '2026-06-07T09:03:30Z',
+    },
+    {
+      id: 'm7',
+      type: 'ref',
+      participantId: 'a1',
+      ref: { refType: 'doc', refId: 'doc-991', title: 'ECG protocol (PDF)' },
+      time: '2026-06-07T09:04:00Z',
+    },
+  ],
+};
+
+// ---------------------------------------------------------------------------
+// Sample host-registered GenUI widget (lazy, inline for the story).
+// ---------------------------------------------------------------------------
+
+function KpiCard({ data }: GenUIWidgetProps<{ label: string; value: string; trend?: string }>) {
+  return (
+    
+ {data.label} + {data.value} + {data.trend && {data.trend}} +
+ ); +} + +const registry: GenUIRegistry = { + kpi_card: { + component: () => Promise.resolve({ default: KpiCard as React.ComponentType }), + prefetch: 'eager', + }, +}; + +// ============================================================================ +// Meta +// ============================================================================ + +const meta: Meta = { + title: 'Product/Feature Modules/SuperChat/SuperChat', + component: SuperChat, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + docs: { + description: { + component: [ + '`SuperChat` is a native, multi-participant chat surface: any mix of multiple **AI agents**', + 'and multiple **humans** in one conversation. It preserves the `chat-component` API shape', + '(conversation/thread, sidebar, compose, read-only, `linkBuilder`, callbacks) and generalizes', + 'roles into a **participant** model.', + '', + '### Rich Markdown', + 'Message text renders through a pluggable Markdown pipeline (`createMarkdownRenderer`). The base', + 'ships Markdown core (GFM) with sanitization of untrusted output. Opt into `code`, `math`, and', + '`genui` plugins from `@mieweb/ui/components/SuperChat/plugins`.', + '', + '### Participant cues', + 'Concurrent / interleaved agent replies stay legible via per-participant `color`, avatar, and name.', + ].join('\n'), + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// ============================================================================ +// Stateful demo wrapper +// ============================================================================ +// SuperChat is controlled (the host owns conversation state). This wrapper +// shows the expected host wiring: append the sent message to the active +// conversation's thread, and simulate a reply from any @-mentioned agent. + +function InteractiveSuperChat( + props: Omit, 'conversations'> & { + initial: SuperChatConversation[]; + } +) { + const { initial, ...rest } = props; + const [conversations, setConversations] = React.useState(initial); + + const appendMessage = (conversationId: string, message: SuperChatConversation['thread'][number]) => { + setConversations((prev) => + prev.map((c) => + c.id === conversationId + ? { ...c, thread: [...c.thread, message], lastActivity: message.time } + : c + ) + ); + }; + + return ( + { + const now = new Date().toISOString(); + appendMessage(meta.conversation.id, { + id: `m-${Date.now()}`, + participantId: props.currentParticipantId ?? 'u1', + text, + time: now, + }); + // Simulate each mentioned agent replying shortly after. + meta.conversation.participants + .filter((p) => p.kind === 'agent' && meta.mentions.includes(p.id)) + .forEach((agent, i) => { + window.setTimeout( + () => + appendMessage(meta.conversation.id, { + id: `a-${Date.now()}-${agent.id}`, + participantId: agent.id, + text: `On it — responding to **${text.slice(0, 40)}**.`, + time: new Date().toISOString(), + }), + 500 * (i + 1) + ); + }); + }} + /> + ); +} + +// ============================================================================ +// Stories +// ============================================================================ + +export const MarkdownCore: Story = { + render: () => ( +
+ console.log('opened', c.id)} + onReferenceClick={(ref) => console.log('ref', ref)} + linkBuilder={(ref) => `#/${ref.refType}/${ref.refId}`} + /> +
+ ), +}; + +export const WithRichPlugins: Story = { + render: () => ( +
+ `#/${ref.refType}/${ref.refId}`} + /> +
+ ), +}; + +export const ReadOnly: Story = { + render: () => ( +
+ +
+ ), +}; diff --git a/src/components/SuperChat/SuperChat.test.tsx b/src/components/SuperChat/SuperChat.test.tsx new file mode 100644 index 00000000..4d457af9 --- /dev/null +++ b/src/components/SuperChat/SuperChat.test.tsx @@ -0,0 +1,136 @@ +import * as React from 'react'; +import { describe, it, expect, vi } from 'vitest'; +import { render, screen, waitFor } from '@testing-library/react'; +import { createMarkdownRenderer } from './render/createMarkdownRenderer'; +import { createCodePlugin } from './plugins/code'; +import { createGenUIPlugin } from './plugins/genui'; +import { SuperChat } from './SuperChat'; +import type { + GenUIRegistry, + GenUIWidgetProps, + SuperChatConversation, +} from './index'; + +function renderText(node: React.ReactNode) { + return render(
{node}
); +} + +describe('createMarkdownRenderer', () => { + it('renders GFM markdown (bold + lists)', () => { + const r = createMarkdownRenderer(); + renderText(r('**bold** and\n\n- one\n- two', { messageId: 'm1', streaming: false, role: 'assistant' })); + expect(screen.getByText('bold').tagName).toBe('STRONG'); + expect(screen.getByText('one')).toBeInTheDocument(); + expect(screen.getByText('two')).toBeInTheDocument(); + }); + + it('sanitizes untrusted HTML / script by default', () => { + const r = createMarkdownRenderer(); + const { container } = renderText( + r('hi ', { + messageId: 'm2', + streaming: false, + role: 'assistant', + }) + ); + expect(container.querySelector('script')).toBeNull(); + // react-markdown does not render raw HTML, so no sink survives at all. + expect(container.querySelector('img')).toBeNull(); + }); + + it('keeps syntax-highlight token classes through sanitization (code plugin)', () => { + const r = createMarkdownRenderer({ plugins: [createCodePlugin()] }); + const { container } = renderText( + r('```js\nconst x = 1;\n```', { messageId: 'm3', streaming: false, role: 'assistant' }) + ); + expect(container.querySelector('code.hljs, code[class*="language-"]')).not.toBeNull(); + }); +}); + +describe('GenUI plugin', () => { + const KpiCard = ({ data }: GenUIWidgetProps<{ label: string; value: string }>) => ( +
{data.label}: {data.value}
+ ); + const registry: GenUIRegistry = { + kpi_card: { + component: () => Promise.resolve({ default: KpiCard as React.ComponentType }), + prefetch: 'eager', + }, + }; + + it('renders a registered widget from a fenced genui block', async () => { + const r = createMarkdownRenderer({ plugins: [createGenUIPlugin(registry)] }); + renderText( + r('```genui\n{ "widget": "kpi_card", "props": { "label": "Risk", "value": "High" } }\n```', { + messageId: 'm4', + streaming: false, + role: 'assistant', + }) + ); + await waitFor(() => expect(screen.getByTestId('kpi')).toHaveTextContent('Risk: High')); + }); + + it('falls back to an inert block for unknown widgets', () => { + const r = createMarkdownRenderer({ plugins: [createGenUIPlugin(registry)] }); + const { container } = renderText( + r('```genui\n{ "widget": "nope", "props": {} }\n```', { + messageId: 'm5', + streaming: false, + role: 'assistant', + }) + ); + expect(container.querySelector('[data-slot="superchat-genui-fallback"]')).not.toBeNull(); + }); +}); + +describe('SuperChat', () => { + const conversation: SuperChatConversation = { + id: 'c1', + title: 'Intake', + participants: [ + { id: 'u1', kind: 'human', name: 'Alice Reyes' }, + { id: 'a1', kind: 'agent', name: 'Triage Agent', color: '#2563eb' }, + ], + thread: [ + { id: 'm1', participantId: 'u1', text: 'hello @Triage', time: '2026-06-07T09:00:00Z', mentions: ['a1'] }, + { id: 'm2', participantId: 'a1', text: '**hi** Alice', time: '2026-06-07T09:00:30Z' }, + ], + }; + + it('renders participants and markdown messages', () => { + render(
); + // 'Intake' appears in both the sidebar and the thread header. + expect(screen.getAllByText('Intake').length).toBeGreaterThan(0); + expect(screen.getAllByText('Triage Agent').length).toBeGreaterThan(0); + expect(screen.getByText('hi').tagName).toBe('STRONG'); + }); + + it('fires onMessageSent with detected mentions', async () => { + const onMessageSent = vi.fn(); + const { default: userEvent } = await import('@testing-library/user-event'); + const user = userEvent.setup(); + render(); + const input = screen.getByLabelText('Message'); + await user.type(input, 'ping @Triage'); + await user.click(screen.getByLabelText('Send message')); + expect(onMessageSent).toHaveBeenCalledWith('ping @Triage', expect.objectContaining({ mentions: ['a1'] })); + }); + + it('disables the composer when readOnly', () => { + render(); + expect(screen.getByLabelText('Message')).toBeDisabled(); + }); + + it('opens an @-mention menu and inserts the chosen participant', async () => { + const { default: userEvent } = await import('@testing-library/user-event'); + const user = userEvent.setup(); + render(); + const input = screen.getByLabelText('Message') as HTMLTextAreaElement; + await user.type(input, 'hello @Tri'); + const menu = screen.getByRole('listbox', { name: 'Mention a participant' }); + expect(menu).toBeInTheDocument(); + await user.click(screen.getByRole('option', { name: /Triage Agent/ })); + expect(input.value).toBe('hello @Triage '); + expect(screen.queryByRole('listbox')).toBeNull(); + }); +}); diff --git a/src/components/SuperChat/SuperChat.tsx b/src/components/SuperChat/SuperChat.tsx new file mode 100644 index 00000000..4681816e --- /dev/null +++ b/src/components/SuperChat/SuperChat.tsx @@ -0,0 +1,683 @@ +/** + * SuperChat — a native, multi-participant chat surface for `@mieweb/ui`. + * + * Preserves the `chat-component` API shape (conversation/thread, sidebar, + * compose, read-only, `linkBuilder`, callbacks) while generalizing roles into a + * {@link Participant} model (any mix of multiple agents + multiple humans). + * Message text renders through the pluggable Markdown pipeline + * ({@link createMarkdownRenderer}); rich plugins (code/math/genui/…) are opt-in. + * + * Controlled-props: the host owns conversation state. Concurrent agent replies + * interleave by `time`; per-participant `color`/avatar/name keep them legible. + */ + +import * as React from 'react'; +import { cva } from 'class-variance-authority'; +import { cn } from '../../utils/cn'; +import { MCPToolCallDisplay } from '../AI/MCPToolCall'; +import { SendIcon, SparklesIcon, CloseIcon } from '../AI/icons'; +import { createMarkdownRenderer } from './render/createMarkdownRenderer'; +import type { + AIRenderTextContent, + Participant, + SuperChatConversation, + SuperChatLinkBuilder, + SuperChatMessage, + SuperChatRef, + SuperChatRenderPlugin, +} from './types'; + +// ============================================================================ +// Helpers +// ============================================================================ + +function initials(name: string): string { + return name + .split(' ') + .map((n) => n[0]) + .filter(Boolean) + .join('') + .slice(0, 2) + .toUpperCase(); +} + +function formatTime(time: Date | string): string { + return new Date(time).toLocaleTimeString(undefined, { + hour: 'numeric', + minute: '2-digit', + }); +} + +function byTime(a: SuperChatMessage, b: SuperChatMessage): number { + return new Date(a.time).getTime() - new Date(b.time).getTime(); +} + +function lastActivityOf(c: SuperChatConversation): number { + if (c.lastActivity) return new Date(c.lastActivity).getTime(); + const last = c.thread[c.thread.length - 1]; + return last ? new Date(last.time).getTime() : 0; +} + +/** Compute mentioned participant ids from `@Name` tokens in the draft. */ +function detectMentions(text: string, participants: Participant[]): string[] { + const ids: string[] = []; + for (const p of participants) { + const token = '@' + p.name.split(' ')[0]; + if (text.toLowerCase().includes(token.toLowerCase())) ids.push(p.id); + } + return ids; +} + +// ============================================================================ +// Avatar +// ============================================================================ + +function ParticipantAvatar({ + participant, + size = 'md', +}: { + participant?: Participant; + size?: 'sm' | 'md'; +}) { + const dim = size === 'sm' ? 'h-6 w-6 text-[10px]' : 'h-8 w-8 text-xs'; + if (participant?.avatar) { + return ( + {participant.name} + ); + } + const isAgent = participant?.kind === 'agent'; + return ( + + ); +} + +// ============================================================================ +// Reference chip (chat-component `ref` thread items) +// ============================================================================ + +function ReferenceChip({ + reference, + linkBuilder, + onReferenceClick, +}: { + reference: SuperChatRef; + linkBuilder?: SuperChatLinkBuilder; + onReferenceClick?: (ref: SuperChatRef) => void; +}) { + const href = linkBuilder?.(reference); + const content = ( + <> + + {reference.refType} + + {reference.title} + + ); + const className = + 'inline-flex max-w-full items-center gap-2 rounded-lg border border-neutral-200 bg-white px-3 py-1.5 text-sm text-neutral-700 hover:border-primary-300 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-200'; + if (href) { + return ( + onReferenceClick?.(reference)}> + {content} + + ); + } + return ( + + ); +} + +// ============================================================================ +// Message row +// ============================================================================ + +interface MessageRowProps { + message: SuperChatMessage; + participant?: Participant; + isSelf: boolean; + renderText: AIRenderTextContent; + linkBuilder?: SuperChatLinkBuilder; + onReferenceClick?: (ref: SuperChatRef) => void; +} + +function MessageRow({ + message, + participant, + isSelf, + renderText, + linkBuilder, + onReferenceClick, +}: MessageRowProps) { + const streaming = message.status === 'streaming'; + + if (message.type === 'system') { + return ( +
+ {message.text} +
+ ); + } + + if (message.type === 'ref' && message.ref) { + return ( +
+ +
+ ); + } + + const accent = participant?.color; + + return ( +
+ +
+
+ + {participant?.name ?? 'Unknown'} + + {participant?.role && ( + {participant.role} + )} + {formatTime(message.time)} +
+ +
+ {/* Rich content blocks (tool calls etc.) reused from the AI module. */} + {message.content?.map((block, i) => { + if (block.type === 'tool_use' && block.toolCall) { + return ; + } + if ((block.type === 'text' || block.type === 'thinking') && block.text) { + return ( +
+ {renderText(block.text, { + messageId: message.id, + streaming, + role: participant?.kind === 'human' ? 'user' : 'assistant', + })} +
+ ); + } + return null; + })} + + {/* Plain `text` body (the common case). */} + {message.text && ( +
+ {renderText(message.text, { + messageId: message.id, + streaming, + role: participant?.kind === 'human' ? 'user' : 'assistant', + })} +
+ )} +
+
+
+ ); +} + +// ============================================================================ +// Composer +// ============================================================================ + +/** Match a trailing `@token` immediately before the caret. */ +function activeMentionQuery( + value: string, + caret: number +): { query: string; start: number } | null { + const upToCaret = value.slice(0, caret); + const match = /(^|\s)@([^\s@]*)$/.exec(upToCaret); + if (!match) return null; + const query = match[2]; + return { query, start: caret - query.length - 1 }; +} + +function Composer({ + participants, + disabled, + onSend, +}: { + participants: Participant[]; + disabled?: boolean; + onSend: (text: string, mentions: string[]) => void; +}) { + const [draft, setDraft] = React.useState(''); + const [mention, setMention] = React.useState<{ query: string; start: number } | null>( + null + ); + const [highlight, setHighlight] = React.useState(0); + const textareaRef = React.useRef>(null); + + // Agents/humans you can address (exclude the system participant). + const mentionable = React.useMemo( + () => participants.filter((p) => p.kind !== 'system'), + [participants] + ); + + const suggestions = React.useMemo(() => { + if (!mention) return []; + const q = mention.query.toLowerCase(); + return mentionable.filter((p) => p.name.toLowerCase().includes(q)); + }, [mention, mentionable]); + + const menuOpen = mention !== null && suggestions.length > 0; + + const syncMention = (value: string, caret: number) => { + const next = activeMentionQuery(value, caret); + setMention(next); + setHighlight(0); + }; + + const insertMention = (participant: Participant) => { + if (!mention) return; + const first = participant.name.split(' ')[0]; + const before = draft.slice(0, mention.start); + const after = draft.slice(mention.start + 1 + mention.query.length); + const insert = `@${first} `; + const nextValue = before + insert + after; + setDraft(nextValue); + setMention(null); + // Restore caret just after the inserted mention. + const caret = before.length + insert.length; + requestAnimationFrame(() => { + const el = textareaRef.current; + if (el) { + el.focus(); + el.setSelectionRange(caret, caret); + } + }); + }; + + const submit = () => { + const text = draft.trim(); + if (!text) return; + onSend(text, detectMentions(text, participants)); + setDraft(''); + setMention(null); + }; + + return ( +
+ {menuOpen && ( +
    + {suggestions.map((p, i) => ( +
  • + +
  • + ))} +
+ )} +