From 0c23b39ae13f9702dc25558a34f98c676d3b56e8 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Tue, 3 Mar 2026 17:36:38 -0800 Subject: [PATCH 01/10] feat(react): add shared markdown message renderer --- package-lock.json | 1579 ++++++++++++++++- .../src/components/MessageCard.tsx | 9 +- .../src/components/ThreadPanel.tsx | 9 +- packages/react/package.json | 5 +- .../src/__tests__/messageMarkdown.test.tsx | 32 + .../react/src/components/MessageMarkdown.tsx | 147 ++ packages/react/src/index.ts | 2 + 7 files changed, 1708 insertions(+), 75 deletions(-) create mode 100644 packages/react/src/__tests__/messageMarkdown.test.tsx create mode 100644 packages/react/src/components/MessageMarkdown.tsx diff --git a/package-lock.json b/package-lock.json index 12c9bf10..5c02eaec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4669,6 +4669,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -4680,9 +4689,17 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/@types/express": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", @@ -4708,6 +4725,15 @@ "@types/send": "*" } }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -4722,6 +4748,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -4729,6 +4764,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.19.35", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.35.tgz", @@ -4743,7 +4784,6 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, "license": "MIT" }, "node_modules/@types/qs": { @@ -4764,7 +4804,6 @@ "version": "18.3.28", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -4826,6 +4865,12 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.56.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", @@ -5108,6 +5153,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@vitest/expect": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", @@ -5415,6 +5466,16 @@ "dev": true, "license": "MIT" }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5582,6 +5643,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", @@ -5609,6 +5680,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", @@ -5679,6 +5790,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", @@ -5811,7 +5932,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/data-urls": { @@ -5852,6 +5972,19 @@ "dev": true, "license": "MIT" }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -5892,9 +6025,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -5909,6 +6040,19 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -6406,6 +6550,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -6568,6 +6722,12 @@ "dev": true, "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6940,6 +7100,46 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hono": { "version": "4.12.3", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.3.tgz", @@ -6962,6 +7162,16 @@ "node": ">=18" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -7066,6 +7276,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, "node_modules/ip-address": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", @@ -7084,6 +7300,40 @@ "node": ">= 0.10" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7107,6 +7357,28 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -7309,7 +7581,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7331,7 +7602,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7353,7 +7623,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7375,7 +7644,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7397,7 +7665,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7419,7 +7686,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7441,7 +7707,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7463,7 +7728,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7485,7 +7749,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7507,7 +7770,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7529,7 +7791,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -7579,6 +7840,16 @@ "dev": true, "license": "MIT" }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7635,6 +7906,16 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -7644,37 +7925,896 @@ "node": ">= 0.4" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.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.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-newline-to-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz", + "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-find-and-replace": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "micromark-util-types": "^2.0.0" } }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -8040,6 +9180,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -8214,6 +9379,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -8342,6 +9517,114 @@ "license": "MIT", "peer": true }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/remark-breaks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", + "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-newline-to-break": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -8741,6 +10024,16 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -8772,6 +10065,20 @@ "node": ">=10.0.0" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -8817,6 +10124,24 @@ ], "license": "MIT" }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -9046,6 +10371,26 @@ "node": ">=18" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -9098,7 +10443,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -9116,7 +10460,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -9134,7 +10477,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -9152,7 +10494,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -9170,7 +10511,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -9188,7 +10528,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -9206,7 +10545,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -9224,7 +10562,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -9242,7 +10579,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -9260,7 +10596,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -9278,7 +10613,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -9296,7 +10630,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -9314,7 +10647,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -9332,7 +10664,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -9350,7 +10681,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -9368,7 +10698,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -9386,7 +10715,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -9404,7 +10732,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -9422,7 +10749,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -9440,7 +10766,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -9458,7 +10783,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -9476,7 +10800,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -9494,7 +10817,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -9512,7 +10834,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -9530,7 +10851,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -9548,7 +10868,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -9821,6 +11140,93 @@ "pathe": "^2.0.3" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -9849,6 +11255,34 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", @@ -11253,6 +12687,16 @@ "zod": "^3.25 || ^4" } }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "packages/mcp": { "name": "@relaycast/mcp", "version": "0.4.2", @@ -11551,7 +12995,10 @@ "license": "Apache-2.0", "dependencies": { "@relaycast/sdk": "0.4.2", - "@relaycast/types": "0.4.2" + "@relaycast/types": "0.4.2", + "react-markdown": "^10.1.0", + "remark-breaks": "^4.0.0", + "remark-gfm": "^4.0.1" }, "devDependencies": { "@testing-library/react": "^16.0.0", diff --git a/packages/observer-dashboard/src/components/MessageCard.tsx b/packages/observer-dashboard/src/components/MessageCard.tsx index cdc2e70c..5a9e5127 100644 --- a/packages/observer-dashboard/src/components/MessageCard.tsx +++ b/packages/observer-dashboard/src/components/MessageCard.tsx @@ -1,6 +1,6 @@ 'use client'; -import { formatReplyCountLabel } from '@relaycast/react'; +import { formatReplyCountLabel, MessageMarkdown } from '@relaycast/react'; import { AgentAvatar } from './AgentAvatar'; import type { MessageWithMeta } from '@relaycast/sdk'; @@ -41,9 +41,10 @@ export function MessageCard({ message, compact = false, onOpenThread }: MessageC )} -

- {message.text} -

+ {((message.reactions?.length ?? 0) > 0 || message.replyCount > 0) && (
{(message.reactions ?? []).map((r) => ( diff --git a/packages/observer-dashboard/src/components/ThreadPanel.tsx b/packages/observer-dashboard/src/components/ThreadPanel.tsx index f9063749..8d32b6b8 100644 --- a/packages/observer-dashboard/src/components/ThreadPanel.tsx +++ b/packages/observer-dashboard/src/components/ThreadPanel.tsx @@ -1,7 +1,7 @@ 'use client'; import { X, MessageSquare } from 'lucide-react'; -import { useThread } from '@relaycast/react'; +import { MessageMarkdown, useThread } from '@relaycast/react'; import { AgentAvatar } from './AgentAvatar'; import type { MessageWithMeta } from '@relaycast/sdk'; @@ -30,9 +30,10 @@ function ThreadMessageRow({ msg }: { msg: MessageWithMeta }) { {relativeTime(msg.createdAt)}
-

- {msg.text} -

+ ); diff --git a/packages/react/package.json b/packages/react/package.json index abda415f..8451a64c 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -34,7 +34,10 @@ }, "dependencies": { "@relaycast/sdk": "0.4.2", - "@relaycast/types": "0.4.2" + "@relaycast/types": "0.4.2", + "react-markdown": "^10.1.0", + "remark-breaks": "^4.0.0", + "remark-gfm": "^4.0.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" diff --git a/packages/react/src/__tests__/messageMarkdown.test.tsx b/packages/react/src/__tests__/messageMarkdown.test.tsx new file mode 100644 index 00000000..1a2b7440 --- /dev/null +++ b/packages/react/src/__tests__/messageMarkdown.test.tsx @@ -0,0 +1,32 @@ +import { render } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import { MessageMarkdown } from '../components/MessageMarkdown.js'; + +describe('MessageMarkdown', () => { + it('renders markdown with soft line breaks and list items', () => { + const { container } = render( + , + ); + + const strong = container.querySelector('strong'); + expect(strong?.textContent).toBe('bold'); + expect(container.querySelectorAll('br')).toHaveLength(1); + expect(Array.from(container.querySelectorAll('li')).map((node) => node.textContent)).toEqual([ + 'one', + 'two', + ]); + }); + + it('applies secure attributes to external links', () => { + const { container } = render( + , + ); + + const links = Array.from(container.querySelectorAll('a')); + expect(links[0]?.getAttribute('href')).toBe('https://example.com'); + expect(links[0]?.getAttribute('target')).toBe('_blank'); + expect(links[0]?.getAttribute('rel')).toBe('noopener noreferrer nofollow'); + expect(links[1]?.getAttribute('href')).toBe('/general'); + expect(links[1]?.getAttribute('target')).toBeNull(); + }); +}); diff --git a/packages/react/src/components/MessageMarkdown.tsx b/packages/react/src/components/MessageMarkdown.tsx new file mode 100644 index 00000000..07cf1ffe --- /dev/null +++ b/packages/react/src/components/MessageMarkdown.tsx @@ -0,0 +1,147 @@ +import type { CSSProperties } from 'react'; +import ReactMarkdown, { type Components } from 'react-markdown'; +import remarkBreaks from 'remark-breaks'; +import remarkGfm from 'remark-gfm'; + +const BLOCK_SPACING = '0.35rem'; + +const PARAGRAPH_STYLE: CSSProperties = { + margin: `${BLOCK_SPACING} 0`, + overflowWrap: 'anywhere', +}; + +const LIST_STYLE: CSSProperties = { + margin: `${BLOCK_SPACING} 0`, + paddingLeft: '1.25rem', +}; + +const PRE_STYLE: CSSProperties = { + margin: `${BLOCK_SPACING} 0`, + padding: '0.5rem 0.625rem', + borderRadius: '0.375rem', + overflowX: 'auto', + backgroundColor: 'rgba(127, 127, 127, 0.12)', +}; + +const CODE_STYLE: CSSProperties = { + fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', + fontSize: '0.875em', +}; + +const INLINE_CODE_STYLE: CSSProperties = { + ...CODE_STYLE, + borderRadius: '0.25rem', + padding: '0.075rem 0.3rem', + backgroundColor: 'rgba(127, 127, 127, 0.16)', +}; + +const BLOCKQUOTE_STYLE: CSSProperties = { + margin: `${BLOCK_SPACING} 0`, + paddingLeft: '0.75rem', + borderLeft: '2px solid rgba(127, 127, 127, 0.35)', + opacity: 0.92, +}; + +const TABLE_STYLE: CSSProperties = { + width: '100%', + margin: `${BLOCK_SPACING} 0`, + borderCollapse: 'collapse', +}; + +const TABLE_CELL_STYLE: CSSProperties = { + border: '1px solid rgba(127, 127, 127, 0.28)', + padding: '0.2rem 0.4rem', + textAlign: 'left', + verticalAlign: 'top', +}; + +function isExternalHref(href: string | undefined): boolean { + if (!href) return false; + return /^(https?:)?\/\//i.test(href); +} + +const DEFAULT_COMPONENTS: Components = { + p({ children }) { + return

{children}

; + }, + ul({ children }) { + return ( +
    + {children} +
+ ); + }, + ol({ children }) { + return ( +
    + {children} +
+ ); + }, + li({ children }) { + return
  • {children}
  • ; + }, + a({ href, children, ...props }) { + const external = isExternalHref(href); + return ( + + {children} + + ); + }, + pre({ children }) { + return
    {children}
    ; + }, + code({ className, children }) { + const hasLanguageClass = typeof className === 'string' && className.includes('language-'); + return ( + + {children} + + ); + }, + blockquote({ children }) { + return
    {children}
    ; + }, + hr() { + return
    ; + }, + table({ children }) { + return {children}
    ; + }, + th({ children }) { + return {children}; + }, + td({ children }) { + return {children}; + }, +}; + +export interface MessageMarkdownProps { + text: string; + className?: string; + components?: Components; +} + +export function MessageMarkdown({ text, className, components }: MessageMarkdownProps) { + return ( +
    + + {text} + +
    + ); +} diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 2a173902..21895df1 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -32,11 +32,13 @@ export { sortMessagesChronologically, formatReplyCountLabel, } from './adapters/messages.js'; +export { MessageMarkdown } from './components/MessageMarkdown.js'; export type { ThreadSummaryViewModel, ChannelMessageViewModel, ChannelMessageAdapterOptions, } from './adapters/messages.js'; +export type { MessageMarkdownProps } from './components/MessageMarkdown.js'; // Types export type { From ed2348cfd631fd9bddcbc978a280dd92f3c209be Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Tue, 3 Mar 2026 17:43:30 -0800 Subject: [PATCH 02/10] test(e2e): include markdown channel messages --- scripts/e2e.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/scripts/e2e.ts b/scripts/e2e.ts index 1dfbc298..b9491d43 100644 --- a/scripts/e2e.ts +++ b/scripts/e2e.ts @@ -141,9 +141,25 @@ const CHANNEL_MESSAGES: Array<{ from: string; text: string }> = [ { from: BACKEND, text: 'Will do. I will watch for the deploy and kick off the suite.' }, { from: INFRA, text: 'Fix is pushed. Staging deploy rolling out now.' }, { from: BACKEND, text: 'Integration suite passed — all 247 tests green.' }, + { + from: LEAD, + text: '### Rollout Notes\n- health timeout: `30s`\n- retries: **enabled**\n- owner: @InfraAgent', + }, + { + from: BACKEND, + text: 'Validation checklist:\n1. smoke test ✅\n2. integration suite ✅\n3. canary logs ✅', + }, + { + from: INFRA, + text: 'Deployment command used: `kubectl rollout status deployment/api -n staging`', + }, { from: LEAD, text: 'Great work team. Merging to main.' }, ]; +const MARKDOWN_CHANNEL_MESSAGES = CHANNEL_MESSAGES.filter((message) => + /[*_`#[\]\n]/.test(message.text), +); + // --------------------------------------------------------------------------- // Main // --------------------------------------------------------------------------- @@ -433,6 +449,20 @@ ${B}${CYAN}╔══════════════════════ } console.log(`${DIM}${'─'.repeat(60)}${R}`); + await run('Verify markdown messages in #engineering', async () => { + const messages = await lead.messages(channelName, { limit: CHANNEL_MESSAGES.length + 10 }); + const textSet = new Set(messages.map((message) => message.text)); + const missing = MARKDOWN_CHANNEL_MESSAGES + .map((message) => message.text) + .filter((text) => !textSet.has(text)); + + if (missing.length > 0) { + throw new Error(`Missing markdown message(s): ${missing.map((text) => JSON.stringify(text)).join(', ')}`); + } + + log('🧪', `Verified ${MARKDOWN_CHANNEL_MESSAGES.length} markdown message(s) in #${channelName}`); + }); + // ── 7. Direct messages — Lead ↔ InfraAgent ───────────────────────── step('Direct messages'); console.log(`${DIM}${'─'.repeat(60)}${R}`); From 9ad8e2b7a9073a0616f9314b81cdcaaff394953f Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Tue, 3 Mar 2026 17:51:30 -0800 Subject: [PATCH 03/10] fix(react): address markdown review feedback --- packages/react/src/__tests__/messageMarkdown.test.tsx | 1 + packages/react/src/components/MessageMarkdown.tsx | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/react/src/__tests__/messageMarkdown.test.tsx b/packages/react/src/__tests__/messageMarkdown.test.tsx index 1a2b7440..ec09c745 100644 --- a/packages/react/src/__tests__/messageMarkdown.test.tsx +++ b/packages/react/src/__tests__/messageMarkdown.test.tsx @@ -28,5 +28,6 @@ describe('MessageMarkdown', () => { expect(links[0]?.getAttribute('rel')).toBe('noopener noreferrer nofollow'); expect(links[1]?.getAttribute('href')).toBe('/general'); expect(links[1]?.getAttribute('target')).toBeNull(); + expect(links[1]?.getAttribute('rel')).toBeNull(); }); }); diff --git a/packages/react/src/components/MessageMarkdown.tsx b/packages/react/src/components/MessageMarkdown.tsx index 07cf1ffe..e71320b9 100644 --- a/packages/react/src/components/MessageMarkdown.tsx +++ b/packages/react/src/components/MessageMarkdown.tsx @@ -55,6 +55,8 @@ const TABLE_CELL_STYLE: CSSProperties = { verticalAlign: 'top', }; +const REMARK_PLUGINS = [remarkGfm, remarkBreaks]; + function isExternalHref(href: string | undefined): boolean { if (!href) return false; return /^(https?:)?\/\//i.test(href); @@ -81,15 +83,15 @@ const DEFAULT_COMPONENTS: Components = { li({ children }) { return
  • {children}
  • ; }, - a({ href, children, ...props }) { + a({ href, children, node: _node, ...props }) { const external = isExternalHref(href); return ( {children} @@ -137,7 +139,7 @@ export function MessageMarkdown({ text, className, components }: MessageMarkdown
    {text} From 0f23d09acefac6e07cb78413fe71e8e653ff00c2 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Tue, 3 Mar 2026 18:07:20 -0800 Subject: [PATCH 04/10] feat(react): add code highlighting and copy button --- package-lock.json | 20 ++ .../src/components/MessageCard.tsx | 1 + .../src/components/ThreadPanel.tsx | 1 + packages/react/package.json | 1 + .../src/__tests__/messageMarkdown.test.tsx | 31 ++- .../react/src/components/MessageMarkdown.tsx | 214 ++++++++++++++++-- 6 files changed, 252 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c02eaec..d20f07e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4780,6 +4780,12 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "license": "MIT" + }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", @@ -9379,6 +9385,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -12996,6 +13015,7 @@ "dependencies": { "@relaycast/sdk": "0.4.2", "@relaycast/types": "0.4.2", + "prism-react-renderer": "^2.4.1", "react-markdown": "^10.1.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.1" diff --git a/packages/observer-dashboard/src/components/MessageCard.tsx b/packages/observer-dashboard/src/components/MessageCard.tsx index 5a9e5127..1cc75d51 100644 --- a/packages/observer-dashboard/src/components/MessageCard.tsx +++ b/packages/observer-dashboard/src/components/MessageCard.tsx @@ -44,6 +44,7 @@ export function MessageCard({ message, compact = false, onOpenThread }: MessageC {((message.reactions?.length ?? 0) > 0 || message.replyCount > 0) && (
    diff --git a/packages/observer-dashboard/src/components/ThreadPanel.tsx b/packages/observer-dashboard/src/components/ThreadPanel.tsx index 8d32b6b8..fee0307b 100644 --- a/packages/observer-dashboard/src/components/ThreadPanel.tsx +++ b/packages/observer-dashboard/src/components/ThreadPanel.tsx @@ -33,6 +33,7 @@ function ThreadMessageRow({ msg }: { msg: MessageWithMeta }) {
    diff --git a/packages/react/package.json b/packages/react/package.json index 8451a64c..6e84164c 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -35,6 +35,7 @@ "dependencies": { "@relaycast/sdk": "0.4.2", "@relaycast/types": "0.4.2", + "prism-react-renderer": "^2.4.1", "react-markdown": "^10.1.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.1" diff --git a/packages/react/src/__tests__/messageMarkdown.test.tsx b/packages/react/src/__tests__/messageMarkdown.test.tsx index ec09c745..836af5d6 100644 --- a/packages/react/src/__tests__/messageMarkdown.test.tsx +++ b/packages/react/src/__tests__/messageMarkdown.test.tsx @@ -1,5 +1,5 @@ -import { render } from '@testing-library/react'; -import { describe, it, expect } from 'vitest'; +import { fireEvent, render, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi } from 'vitest'; import { MessageMarkdown } from '../components/MessageMarkdown.js'; describe('MessageMarkdown', () => { @@ -30,4 +30,31 @@ describe('MessageMarkdown', () => { expect(links[1]?.getAttribute('target')).toBeNull(); expect(links[1]?.getAttribute('rel')).toBeNull(); }); + + it('renders highlighted fenced code and supports copy button', async () => { + const writeText = vi.fn().mockResolvedValue(undefined); + Object.defineProperty(navigator, 'clipboard', { + value: { writeText }, + configurable: true, + }); + + const { container, getByRole } = render( + , + ); + + const code = container.querySelector('pre code'); + expect(code).not.toBeNull(); + expect(code?.querySelectorAll('span').length ?? 0).toBeGreaterThan(2); + + const copyButton = getByRole('button', { name: 'Copy code' }); + fireEvent.click(copyButton); + + await waitFor(() => { + expect(writeText).toHaveBeenCalledWith('const answer: number = 42;'); + expect(copyButton.textContent).toBe('Copied'); + }); + }); }); diff --git a/packages/react/src/components/MessageMarkdown.tsx b/packages/react/src/components/MessageMarkdown.tsx index e71320b9..c646f048 100644 --- a/packages/react/src/components/MessageMarkdown.tsx +++ b/packages/react/src/components/MessageMarkdown.tsx @@ -1,4 +1,5 @@ -import type { CSSProperties } from 'react'; +import type { CSSProperties, MouseEvent, ReactNode } from 'react'; +import { Highlight, Prism, themes, type Language } from 'prism-react-renderer'; import ReactMarkdown, { type Components } from 'react-markdown'; import remarkBreaks from 'remark-breaks'; import remarkGfm from 'remark-gfm'; @@ -28,6 +29,18 @@ const CODE_STYLE: CSSProperties = { fontSize: '0.875em', }; +const BLOCK_CODE_STYLE: CSSProperties = { + ...CODE_STYLE, + display: 'block', + whiteSpace: 'pre', + lineHeight: 1.5, +}; + +const BLOCK_CODE_STYLE_WITH_COPY: CSSProperties = { + ...BLOCK_CODE_STYLE, + paddingTop: '1.15rem', +}; + const INLINE_CODE_STYLE: CSSProperties = { ...CODE_STYLE, borderRadius: '0.25rem', @@ -56,13 +69,179 @@ const TABLE_CELL_STYLE: CSSProperties = { }; const REMARK_PLUGINS = [remarkGfm, remarkBreaks]; +const CODE_THEME = themes.vsDark; + +const CODE_BLOCK_CONTAINER_STYLE: CSSProperties = { + display: 'block', + position: 'relative', +}; + +const COPY_BUTTON_STYLE: CSSProperties = { + position: 'absolute', + top: '0.15rem', + right: '0.15rem', + borderRadius: '0.25rem', + border: '1px solid rgba(255, 255, 255, 0.2)', + backgroundColor: 'rgba(255, 255, 255, 0.08)', + color: 'inherit', + fontSize: '0.72rem', + fontFamily: 'inherit', + lineHeight: 1.1, + padding: '0.2rem 0.45rem', + cursor: 'pointer', +}; + +const LANGUAGE_ALIASES: Record = { + js: 'javascript', + ts: 'typescript', + shell: 'bash', + sh: 'bash', + zsh: 'bash', + yml: 'yaml', + md: 'markdown', + py: 'python', + text: 'plain', + plaintext: 'plain', +}; function isExternalHref(href: string | undefined): boolean { if (!href) return false; return /^(https?:)?\/\//i.test(href); } -const DEFAULT_COMPONENTS: Components = { +function toCodeText(children: ReactNode): string { + if (Array.isArray(children)) { + return children.join(''); + } + return String(children ?? ''); +} + +function normalizeLanguage(raw: string | null): string { + if (!raw) return 'text'; + const normalized = raw.toLowerCase(); + return LANGUAGE_ALIASES[normalized] ?? normalized; +} + +function extractLanguageFromClassName(className: string | undefined): string | null { + if (!className) return null; + const match = /language-([A-Za-z0-9_-]+)/.exec(className); + return match ? normalizeLanguage(match[1]) : null; +} + +function supportsLanguage(language: string): boolean { + return Boolean((Prism.languages as Record)[language]); +} + +async function copyTextToClipboard(text: string): Promise { + if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(text); + return true; + } + + if (typeof document !== 'undefined') { + const textArea = document.createElement('textarea'); + textArea.value = text; + textArea.style.position = 'fixed'; + textArea.style.opacity = '0'; + textArea.style.pointerEvents = 'none'; + document.body.appendChild(textArea); + textArea.select(); + const copied = document.execCommand('copy'); + document.body.removeChild(textArea); + return copied; + } + + return false; +} + +async function handleCodeCopy(event: MouseEvent, code: string): Promise { + const button = event.currentTarget; + const previousLabel = button.textContent ?? 'Copy'; + button.disabled = true; + + try { + const copied = await copyTextToClipboard(code); + button.textContent = copied ? 'Copied' : 'Copy failed'; + } catch { + button.textContent = 'Copy failed'; + } + + window.setTimeout(() => { + button.textContent = previousLabel; + button.disabled = false; + }, 1400); +} + +function renderCodeBlock( + codeText: string, + language: string, + className: string | undefined, + showCodeCopyButton: boolean, +): ReactNode { + const blockCodeStyle = showCodeCopyButton ? BLOCK_CODE_STYLE_WITH_COPY : BLOCK_CODE_STYLE; + const canHighlight = supportsLanguage(language); + const trimmedCode = codeText.replace(/\n$/, ''); + + return ( + + {showCodeCopyButton && ( + + )} + {canHighlight ? ( + + {({ tokens, getLineProps, getTokenProps }) => ( + + {tokens.map((line, lineIndex) => { + const lineProps = getLineProps({ line }); + return ( + + {line.map((token, tokenIndex) => { + const tokenProps = getTokenProps({ token }); + return ( + + {tokenProps.children} + + ); + })} + {line.length === 0 ? '\n' : null} + + ); + })} + + )} + + ) : ( + + {trimmedCode} + + )} + + ); +} + +function buildDefaultComponents(showCodeCopyButton: boolean): Components { + return { p({ children }) { return

    {children}

    ; }, @@ -101,15 +280,15 @@ const DEFAULT_COMPONENTS: Components = { return
    {children}
    ; }, code({ className, children }) { - const hasLanguageClass = typeof className === 'string' && className.includes('language-'); - return ( - - {children} - - ); + const codeText = toCodeText(children); + const language = extractLanguageFromClassName(className); + const isBlockCode = language !== null || codeText.includes('\n'); + + if (isBlockCode) { + return renderCodeBlock(codeText, language ?? 'text', className, showCodeCopyButton); + } + + return {children}; }, blockquote({ children }) { return
    {children}
    ; @@ -126,19 +305,26 @@ const DEFAULT_COMPONENTS: Components = { td({ children }) { return {children}; }, -}; + }; +} export interface MessageMarkdownProps { text: string; className?: string; components?: Components; + showCodeCopyButton?: boolean; } -export function MessageMarkdown({ text, className, components }: MessageMarkdownProps) { +export function MessageMarkdown({ + text, + className, + components, + showCodeCopyButton = false, +}: MessageMarkdownProps) { return (
    From 656381f084083de43a538df48ac26dc6320ef13c Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Tue, 3 Mar 2026 18:10:26 -0800 Subject: [PATCH 05/10] test(e2e): add typescript fenced code-block message --- scripts/e2e.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/e2e.ts b/scripts/e2e.ts index b9491d43..f577bf12 100644 --- a/scripts/e2e.ts +++ b/scripts/e2e.ts @@ -153,6 +153,10 @@ const CHANNEL_MESSAGES: Array<{ from: string; text: string }> = [ from: INFRA, text: 'Deployment command used: `kubectl rollout status deployment/api -n staging`', }, + { + from: BACKEND, + text: 'TypeScript patch preview:\n```ts\nconst deployConfig: { timeoutSeconds: number; retries: number } = {\n timeoutSeconds: 30,\n retries: 2,\n};\n```', + }, { from: LEAD, text: 'Great work team. Merging to main.' }, ]; From fc116402312f82b8f10773b3e52cf95401e842a3 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Tue, 3 Mar 2026 18:21:49 -0800 Subject: [PATCH 06/10] fix(observer): move member badge to channel header --- .../src/components/AgentSidebar.tsx | 5 ----- .../src/components/ChatFeed.tsx | 20 ++++++++++++++++--- .../src/components/DashboardLayout.tsx | 5 +++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/observer-dashboard/src/components/AgentSidebar.tsx b/packages/observer-dashboard/src/components/AgentSidebar.tsx index b7eb7f92..d2508e61 100644 --- a/packages/observer-dashboard/src/components/AgentSidebar.tsx +++ b/packages/observer-dashboard/src/components/AgentSidebar.tsx @@ -109,11 +109,6 @@ export function AgentSidebar({ > {ch.name} - {(ch.memberCount ?? 0) > 0 && ( - - {ch.memberCount} - - )} ))} {regularChannels.length === 0 && ( diff --git a/packages/observer-dashboard/src/components/ChatFeed.tsx b/packages/observer-dashboard/src/components/ChatFeed.tsx index 8fe404b4..db08e8d8 100644 --- a/packages/observer-dashboard/src/components/ChatFeed.tsx +++ b/packages/observer-dashboard/src/components/ChatFeed.tsx @@ -1,18 +1,24 @@ 'use client'; import { useEffect, useRef, useState } from 'react'; -import { Hash, MessageSquare } from 'lucide-react'; +import { Hash, MessageSquare, UserRound } from 'lucide-react'; import { useMessages, sortMessagesChronologically } from '@relaycast/react'; import { MessageCard } from './MessageCard'; import type { MessageWithMeta } from '@relaycast/sdk'; interface ChatFeedProps { selectedChannel: string | null; + selectedChannelMemberCount?: number | null; dmLabel?: string; onOpenThread?: (messageId: string) => void; } -export function ChatFeed({ selectedChannel, dmLabel, onOpenThread }: ChatFeedProps) { +export function ChatFeed({ + selectedChannel, + selectedChannelMemberCount, + dmLabel, + onOpenThread, +}: ChatFeedProps) { const isDm = selectedChannel?.startsWith('dm:'); const channelName = selectedChannel && !isDm ? selectedChannel : null; const dmId = isDm ? selectedChannel!.slice(3) : null; @@ -22,6 +28,8 @@ export function ChatFeed({ selectedChannel, dmLabel, onOpenThread }: ChatFeedPro ? dmLabel || 'Direct Message' : `#${selectedChannel}` : 'Select a channel'; + const memberCount = selectedChannelMemberCount ?? 0; + const showMemberBadge = !!channelName; return (
    @@ -32,7 +40,13 @@ export function ChatFeed({ selectedChannel, dmLabel, onOpenThread }: ChatFeedPro ) : ( )} -

    {title}

    +

    {title}

    + {showMemberBadge && ( + + + {memberCount} + + )}
    {/* Messages */} diff --git a/packages/observer-dashboard/src/components/DashboardLayout.tsx b/packages/observer-dashboard/src/components/DashboardLayout.tsx index 85c0fe46..064876da 100644 --- a/packages/observer-dashboard/src/components/DashboardLayout.tsx +++ b/packages/observer-dashboard/src/components/DashboardLayout.tsx @@ -113,6 +113,10 @@ export function DashboardLayout() { const selectedAgentData: ApiAgent | null = selectedAgent ? agents.find((a) => a.name === selectedAgent) ?? null : null; + const selectedChannelMemberCount = + selectedChannel && !selectedChannel.startsWith('dm:') + ? (channels.find((ch) => ch.name === selectedChannel)?.memberCount ?? 0) + : null; let rightPanel: React.ReactNode = null; if (selectedAgentData) { @@ -192,6 +196,7 @@ export function DashboardLayout() {
    `dm:${dm.id}` === selectedChannel)?.name ?? undefined From 807d1315acd90d1b55d10be0fe507dab7a19b915 Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Tue, 3 Mar 2026 18:26:21 -0800 Subject: [PATCH 07/10] fix(observer): show full participant list for DMs --- .../src/components/AgentSidebar.tsx | 6 +++++- .../src/components/DashboardLayout.tsx | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/packages/observer-dashboard/src/components/AgentSidebar.tsx b/packages/observer-dashboard/src/components/AgentSidebar.tsx index d2508e61..337a44c7 100644 --- a/packages/observer-dashboard/src/components/AgentSidebar.tsx +++ b/packages/observer-dashboard/src/components/AgentSidebar.tsx @@ -126,7 +126,11 @@ export function AgentSidebar({ {conversations.map((dm) => { const dmKey = `dm:${dm.id}`; - const dmLabel = dm.name || dm.participants.map((p) => p.agentName).join(', '); + const participantLabel = dm.participants + .map((p) => p.agentName.trim()) + .filter((name) => name.length > 0) + .join(', '); + const dmLabel = participantLabel || dm.name || 'Direct Message'; return ( ))} {regularChannels.length === 0 && ( diff --git a/packages/observer-dashboard/src/components/DashboardLayout.tsx b/packages/observer-dashboard/src/components/DashboardLayout.tsx index cd932c82..f60bfd47 100644 --- a/packages/observer-dashboard/src/components/DashboardLayout.tsx +++ b/packages/observer-dashboard/src/components/DashboardLayout.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from 'react'; import { Activity, AlertTriangle } from 'lucide-react'; -import { usePresence, useChannels, useWebSocket } from '@relaycast/react'; +import { useEvent, usePresence, useChannels, useWebSocket } from '@relaycast/react'; import { AgentSidebar } from './AgentSidebar'; import { ChatFeed } from './ChatFeed'; import { ActivityLog } from './ActivityLog'; @@ -10,7 +10,7 @@ import { ThreadPanel } from './ThreadPanel'; import { AgentPanel } from './AgentPanel'; import { cn } from '../lib/utils'; import { useWorkspaceDMs } from '../hooks/use-workspace-dms'; -import type { Agent as ApiAgent } from '@relaycast/sdk'; +import type { Agent as ApiAgent, MessageCreatedEvent } from '@relaycast/sdk'; export function DashboardLayout() { const { agents: rawAgents } = usePresence(); @@ -19,6 +19,7 @@ export function DashboardLayout() { const { status: wsStatus } = useWebSocket(); const [selectedChannel, setSelectedChannel] = useState(null); const [selectedAgent, setSelectedAgent] = useState(null); + const [unreadChannelCounts, setUnreadChannelCounts] = useState>({}); const [activityOpen, setActivityOpen] = useState(true); const [streamEnabled, setStreamEnabled] = useState(null); const [streamMessage, setStreamMessage] = useState(''); @@ -32,6 +33,37 @@ export function DashboardLayout() { }, [selectedChannel, channels]); const [threadMessageId, setThreadMessageId] = useState(null); + useEffect(() => { + const knownChannels = new Set(channels.map((ch) => ch.name)); + setUnreadChannelCounts((prev) => { + let changed = false; + const next: Record = {}; + for (const [name, count] of Object.entries(prev)) { + if (knownChannels.has(name)) { + next[name] = count; + } else { + changed = true; + } + } + return changed ? next : prev; + }); + }, [channels]); + + useEvent('message.created', (evt) => { + const event = evt as MessageCreatedEvent; + const channelName = event.channel; + if (!channelName) return; + + if (selectedChannel === channelName) { + return; + } + + setUnreadChannelCounts((prev) => ({ + ...prev, + [channelName]: (prev[channelName] ?? 0) + 1, + })); + }); + // Filter out the dashboard observer agent and stale offline agents (offline > 5 min) const STALE_OFFLINE_MS = 5 * 60 * 1000; const agents = rawAgents.filter((a) => { @@ -107,6 +139,13 @@ export function DashboardLayout() { setSelectedChannel(name); setSelectedAgent(null); setThreadMessageId(null); + if (name && !name.startsWith('dm:')) { + setUnreadChannelCounts((prev) => + prev[name] && prev[name] > 0 + ? { ...prev, [name]: 0 } + : prev, + ); + } } // Determine right panel priority: agent panel > thread > activity @@ -160,6 +199,7 @@ export function DashboardLayout() { conversations={conversations} selectedChannel={selectedChannel} selectedAgent={selectedAgent} + unreadChannelCounts={unreadChannelCounts} wsStatus={wsStatus} onSelectChannel={handleSelectChannel} onSelectAgent={handleSelectAgent} From e70cbe7e1d071c1183b786ae412242e1bdd021bf Mon Sep 17 00:00:00 2001 From: Will Washburn Date: Tue, 3 Mar 2026 22:20:56 -0800 Subject: [PATCH 10/10] fix(pr): address valid review feedback --- .../src/components/AgentSidebar.tsx | 9 +- .../src/components/DashboardLayout.tsx | 17 +- packages/observer-dashboard/src/lib/utils.ts | 18 +++ .../src/__tests__/messageMarkdown.test.tsx | 14 ++ .../react/src/components/MessageMarkdown.tsx | 153 ++++++++++-------- 5 files changed, 131 insertions(+), 80 deletions(-) diff --git a/packages/observer-dashboard/src/components/AgentSidebar.tsx b/packages/observer-dashboard/src/components/AgentSidebar.tsx index 37b9114d..484944fb 100644 --- a/packages/observer-dashboard/src/components/AgentSidebar.tsx +++ b/packages/observer-dashboard/src/components/AgentSidebar.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { Hash, MessageSquare, LogOut, Sun, Moon } from 'lucide-react'; -import { cn } from '../lib/utils'; +import { cn, formatDmLabel } from '../lib/utils'; import { AgentAvatar } from './AgentAvatar'; import { clearAuth } from '../lib/auth'; import { useRouter } from 'next/navigation'; @@ -133,12 +133,7 @@ export function AgentSidebar({ {conversations.map((dm) => { const dmKey = `dm:${dm.id}`; - const participantLabel = dm.participants - .map((p) => p.agentName.trim()) - .filter((name) => name.length > 0) - .sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })) - .join(', '); - const dmLabel = participantLabel || dm.name || 'Direct Message'; + const dmLabel = formatDmLabel(dm.participants, dm.name); return (