@ably/ui 18.0.0 → 18.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/AGENTS.md +48 -425
  2. package/README.md +1 -1
  3. package/core/Accordion.js +1 -1
  4. package/core/Accordion.js.map +1 -1
  5. package/core/Code.js.map +1 -1
  6. package/core/CodeSnippet.js +1 -1
  7. package/core/CodeSnippet.js.map +1 -1
  8. package/core/Flash.js.map +1 -1
  9. package/core/Header/HeaderLinks.js +1 -1
  10. package/core/Header/HeaderLinks.js.map +1 -1
  11. package/core/Header.js +1 -1
  12. package/core/Header.js.map +1 -1
  13. package/core/Icon/components/icon-tech-perplexity-mono.js +2 -0
  14. package/core/Icon/components/icon-tech-perplexity-mono.js.map +1 -0
  15. package/core/Icon/components/index.js +1 -1
  16. package/core/Icon/components/index.js.map +1 -1
  17. package/core/Icon/computed-icons/tech-icons.js +1 -1
  18. package/core/Icon/computed-icons/tech-icons.js.map +1 -1
  19. package/core/Loader.js.map +1 -1
  20. package/core/Meganav/data.js +1 -1
  21. package/core/Meganav/data.js.map +1 -1
  22. package/core/Meganav/images/cust-logo-dialpad-dark.png +0 -0
  23. package/core/Meganav/images/cust-logo-dialpad-light.png +0 -0
  24. package/core/Meganav.js +1 -1
  25. package/core/Meganav.js.map +1 -1
  26. package/core/Notice.js.map +1 -1
  27. package/core/Slider.js +1 -1
  28. package/core/Slider.js.map +1 -1
  29. package/core/icons/tech/icon-tech-perplexity-mono.svg +3 -0
  30. package/core/sprites-tech.svg +1 -1
  31. package/core/styles/dropdowns.css +2 -2
  32. package/core/utils/sanitize-html.js +2 -0
  33. package/core/utils/sanitize-html.js.map +1 -0
  34. package/core/utils/sanitize-html.test.js +2 -0
  35. package/core/utils/sanitize-html.test.js.map +1 -0
  36. package/index.d.ts +78 -3
  37. package/package.json +25 -25
  38. package/core/Meganav/images/cust-logo-doxy-dark.png +0 -0
  39. package/core/Meganav/images/cust-logo-doxy-light.png +0 -0
@@ -32,11 +32,11 @@
32
32
  }
33
33
 
34
34
  .ui-dropdown-select2-wrapper .select2-selection--single {
35
- @apply !h-auto;
35
+ @apply !h-auto !flex !items-center !min-h-[42px];
36
36
  }
37
37
 
38
38
  .ui-dropdown-select2-wrapper .select2-selection__rendered {
39
- @apply !leading-relaxed !px-0;
39
+ @apply !leading-normal !px-0;
40
40
  }
41
41
 
42
42
  .ui-dropdown-select2-wrapper .select2-selection__arrow {
@@ -0,0 +1,2 @@
1
+ import DOMPurify from"dompurify";const RELATIVE_URI=/^\/[^/\\]/;const DANGEROUS_URI_SCHEMES=/^\s*(data|javascript|vbscript|file|blob):/i;const URI_BEARING_ATTRS=new Set(["src","href","xlink:href","action","formaction","background","poster","srcset"]);DOMPurify.addHook("uponSanitizeAttribute",(_node,data)=>{if(URI_BEARING_ATTRS.has(data.attrName)&&DANGEROUS_URI_SCHEMES.test(data.attrValue)){data.keepAttr=false}});export const sanitizeInlineMarkup=input=>DOMPurify.sanitize(input??"",{ALLOWED_TAGS:["a"],ALLOWED_ATTR:["href","data-method"],ALLOWED_URI_REGEXP:RELATIVE_URI});export const sanitizeRichText=input=>DOMPurify.sanitize(input??"",{ALLOWED_TAGS:["a","b","br","em","i","p","strong"],ALLOWED_ATTR:["href"],ALLOWED_URI_REGEXP:RELATIVE_URI});export const sanitizeMarketingHtml=input=>DOMPurify.sanitize(input??"",{ALLOWED_TAGS:["a","b","blockquote","br","code","em","figcaption","figure","h2","h3","h4","i","img","li","ol","p","pre","span","strong","table","tbody","td","th","thead","tr","ul"],ALLOWED_ATTR:["alt","class","href","src","title"],ALLOWED_URI_REGEXP:/^(https?:\/\/|\/[^/\\])/});
2
+ //# sourceMappingURL=sanitize-html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/utils/sanitize-html.ts"],"sourcesContent":["import DOMPurify from \"dompurify\";\n\n// Restricts hrefs to same-origin paths (no scheme, no protocol-relative).\n// Matches the rule applied historically in Flash and Notice, with one\n// hardening: the exclusion class also rejects backslash. Per WHATWG URL,\n// browsers parsing a special-scheme URL treat `/\\` as an authority delimiter\n// identical to `//`, so `href=\"/\\evil.com\"` resolves to `http://evil.com`.\n// DOMPurify checks ALLOWED_URI_REGEXP against the raw attribute string, not\n// the resolved URL, so the exclusion has to do the work.\nconst RELATIVE_URI = /^\\/[^/\\\\]/;\n\n// DOMPurify hard-codes a data: URI allowance on `img`, `audio`, `video`,\n// `source`, `image`, and `track` regardless of ALLOWED_URI_REGEXP - intended\n// to support inline base64 images. We don't want that exception: a\n// `data:image/svg+xml,<svg onload=alert(1)>` URI on an <img> still executes\n// in some contexts, and we'd rather force authors to host their images. The\n// hook below catches dangerous schemes on every URI-bearing attribute and\n// drops the attribute, regardless of which built-in safe-list DOMPurify\n// would normally apply.\nconst DANGEROUS_URI_SCHEMES = /^\\s*(data|javascript|vbscript|file|blob):/i;\nconst URI_BEARING_ATTRS = new Set([\n \"src\",\n \"href\",\n \"xlink:href\",\n \"action\",\n \"formaction\",\n \"background\",\n \"poster\",\n \"srcset\",\n]);\n\nDOMPurify.addHook(\"uponSanitizeAttribute\", (_node, data) => {\n if (\n URI_BEARING_ATTRS.has(data.attrName) &&\n DANGEROUS_URI_SCHEMES.test(data.attrValue)\n ) {\n data.keepAttr = false;\n }\n});\n\n/**\n * sanitizeInlineMarkup — tightest allowlist, intended for short pieces of\n * trusted-but-defence-in-depthed text (flash messages, banner bodies that\n * already went through Rails sanitisation). Only inline links to same-origin\n * paths survive.\n *\n * Use this for surfaces where the producer (Rails helper, backend flash)\n * already sanitised the input and React-side sanitisation is the second\n * layer of defence.\n */\nexport const sanitizeInlineMarkup = (input: string | null | undefined) =>\n DOMPurify.sanitize(input ?? \"\", {\n ALLOWED_TAGS: [\"a\"],\n ALLOWED_ATTR: [\"href\", \"data-method\"],\n ALLOWED_URI_REGEXP: RELATIVE_URI,\n });\n\n/**\n * sanitizeRichText — mirror of DashboardNoticeHelper::SANITISE_TAGS on the\n * Rails side. Use for content that an admin types as light HTML (banner\n * body text, dashboard notices). Keeps href so links work; same-origin URI\n * restriction prevents the URI from carrying a script payload.\n *\n * Tag set deliberately matches the server-side allowlist so the trust\n * boundary is identical regardless of which side did the rendering.\n */\nexport const sanitizeRichText = (input: string | null | undefined) =>\n DOMPurify.sanitize(input ?? \"\", {\n ALLOWED_TAGS: [\"a\", \"b\", \"br\", \"em\", \"i\", \"p\", \"strong\"],\n ALLOWED_ATTR: [\"href\"],\n ALLOWED_URI_REGEXP: RELATIVE_URI,\n });\n\n/**\n * sanitizeMarketingHtml — wider allowlist for Contentful-sourced marketing\n * content (the `ContentfulBlockHtml` escape-hatch field). Trust boundary is\n * \"compromised Contentful editor account\" — wider than inline/rich-text but\n * still blocks scripts, event handlers, and non-http(s) URIs.\n *\n * Not suitable for the Ghost blog body. A survey of all 396 published Ably\n * blog posts (built locally against Ghost) shows the body legitimately\n * contains HubSpot CTA scripts (~70 posts), Twitter widget scripts, YouTube\n * and Vimeo iframes (~50), and inline SVG diagrams (~130). Sanitising those\n * with this allowlist would silently break the site. Ghost body is treated\n * as a trusted-CMS surface in the ADR — editor account control is the\n * security boundary.\n *\n * Marketing copy needs headings, lists, code blocks, blockquotes, and\n * external links, so the URI rule allows http(s) instead of being clamped\n * to relative paths.\n */\nexport const sanitizeMarketingHtml = (input: string | null | undefined) =>\n DOMPurify.sanitize(input ?? \"\", {\n ALLOWED_TAGS: [\n \"a\",\n \"b\",\n \"blockquote\",\n \"br\",\n \"code\",\n \"em\",\n \"figcaption\",\n \"figure\",\n \"h2\",\n \"h3\",\n \"h4\",\n \"i\",\n \"img\",\n \"li\",\n \"ol\",\n \"p\",\n \"pre\",\n \"span\",\n \"strong\",\n \"table\",\n \"tbody\",\n \"td\",\n \"th\",\n \"thead\",\n \"tr\",\n \"ul\",\n ],\n ALLOWED_ATTR: [\"alt\", \"class\", \"href\", \"src\", \"title\"],\n // Either a full http(s):// URL, or a same-origin relative path whose\n // second character is neither `/` nor `\\`. The earlier `/^(https?:)?\\//`\n // was anchored only at the start, so `//evil.com` (protocol-relative)\n // survived: the regex matched the leading `/` and left the rest\n // unanchored. Browsers resolve `//evil.com` to the page scheme, so this\n // was an external-redirect bypass exactly in the threat model the\n // marketing sanitiser is meant to harden against (compromised Contentful\n // editor sneaking off-site links past review).\n ALLOWED_URI_REGEXP: /^(https?:\\/\\/|\\/[^/\\\\])/,\n });\n"],"names":["DOMPurify","RELATIVE_URI","DANGEROUS_URI_SCHEMES","URI_BEARING_ATTRS","Set","addHook","_node","data","has","attrName","test","attrValue","keepAttr","sanitizeInlineMarkup","input","sanitize","ALLOWED_TAGS","ALLOWED_ATTR","ALLOWED_URI_REGEXP","sanitizeRichText","sanitizeMarketingHtml"],"mappings":"AAAA,OAAOA,cAAe,WAAY,CASlC,MAAMC,aAAe,YAUrB,MAAMC,sBAAwB,6CAC9B,MAAMC,kBAAoB,IAAIC,IAAI,CAChC,MACA,OACA,aACA,SACA,aACA,aACA,SACA,SACD,EAEDJ,UAAUK,OAAO,CAAC,wBAAyB,CAACC,MAAOC,QACjD,GACEJ,kBAAkBK,GAAG,CAACD,KAAKE,QAAQ,GACnCP,sBAAsBQ,IAAI,CAACH,KAAKI,SAAS,EACzC,CACAJ,KAAKK,QAAQ,CAAG,KAClB,CACF,EAYA,QAAO,MAAMC,qBAAuB,AAACC,OACnCd,UAAUe,QAAQ,CAACD,OAAS,GAAI,CAC9BE,aAAc,CAAC,IAAI,CACnBC,aAAc,CAAC,OAAQ,cAAc,CACrCC,mBAAoBjB,YACtB,EAAG,AAWL,QAAO,MAAMkB,iBAAmB,AAACL,OAC/Bd,UAAUe,QAAQ,CAACD,OAAS,GAAI,CAC9BE,aAAc,CAAC,IAAK,IAAK,KAAM,KAAM,IAAK,IAAK,SAAS,CACxDC,aAAc,CAAC,OAAO,CACtBC,mBAAoBjB,YACtB,EAAG,AAoBL,QAAO,MAAMmB,sBAAwB,AAACN,OACpCd,UAAUe,QAAQ,CAACD,OAAS,GAAI,CAC9BE,aAAc,CACZ,IACA,IACA,aACA,KACA,OACA,KACA,aACA,SACA,KACA,KACA,KACA,IACA,MACA,KACA,KACA,IACA,MACA,OACA,SACA,QACA,QACA,KACA,KACA,QACA,KACA,KACD,CACDC,aAAc,CAAC,MAAO,QAAS,OAAQ,MAAO,QAAQ,CAStDC,mBAAoB,yBACtB,EAAG"}
@@ -0,0 +1,2 @@
1
+ import{describe,expect,it}from"vitest";import{sanitizeInlineMarkup,sanitizeMarketingHtml,sanitizeRichText}from"./sanitize-html";const parseDom=html=>{const wrapper=document.createElement("div");wrapper.innerHTML=html;return wrapper};const eventHandlerAttrsOf=root=>{const offenders=[];root.querySelectorAll("*").forEach(el=>{for(const attr of Array.from(el.attributes)){if(attr.name.toLowerCase().startsWith("on")){offenders.push(`${el.tagName}:${attr.name}`)}}});return offenders};const dangerousProtocols=/^(javascript|data|vbscript|file|blob):/i;const dangerousHrefsIn=root=>{const offenders=[];root.querySelectorAll("[href]").forEach(el=>{if(dangerousProtocols.test(el.getAttribute("href")??"")){offenders.push(el.getAttribute("href")??"")}});root.querySelectorAll("[src]").forEach(el=>{if(dangerousProtocols.test(el.getAttribute("src")??"")){offenders.push(el.getAttribute("src")??"")}});return offenders};const scriptTagsIn=root=>Array.from(root.querySelectorAll("script"));describe("sanitizeInlineMarkup",()=>{describe("baseline allowlist behaviour",()=>{it("keeps same-origin relative links",()=>{expect(sanitizeInlineMarkup('<a href="/dashboard">go</a>')).toBe('<a href="/dashboard">go</a>')});it("keeps a data-method attribute on a link (Rails UJS)",()=>{const out=sanitizeInlineMarkup('<a href="/logout" data-method="delete">Log out</a>');expect(out).toContain('data-method="delete"')});it("strips disallowed tags but keeps their text content",()=>{expect(sanitizeInlineMarkup("<b>bold</b>")).toBe("bold")});it("returns an empty string for null and undefined",()=>{expect(sanitizeInlineMarkup(null)).toBe("");expect(sanitizeInlineMarkup(undefined)).toBe("")});it("returns an empty string for empty input",()=>{expect(sanitizeInlineMarkup("")).toBe("")})});describe("script-tag injection",()=>{it.each(["<script>alert(1)</script>","<SCRIPT>alert(1)</SCRIPT>","<ScRiPt>alert(1)</ScRiPt>","<script src=//evil.com/x.js></script>","<script\nsrc=//evil.com/x.js></script>","<script type='text/javascript'>alert(1)</script>","<script >alert(1)</script>"])("strips '%s'",payload=>{const out=sanitizeInlineMarkup(payload+"safe");expect(scriptTagsIn(parseDom(out))).toHaveLength(0);expect(out).toContain("safe")})});describe("event-handler injection",()=>{it.each(['<a href="/x" onmouseover="alert(1)">l</a>','<a href="/x" onclick="alert(1)">l</a>','<a href="/x" onfocus="alert(1)" autofocus>l</a>','<a href="/x" oNmOuSeOvEr="alert(1)">l</a>',"<a href=\"/x\" onmouseover='alert(1)'>l</a>","<a href=/x onmouseover=alert(1)>l</a>","<a href=/x onmouseover=alert(1)>l</a>","<a href=/x\nonmouseover=alert(1)>l</a>","<a href=/x onmouseover=`alert(1)`>l</a>"])("strips event handlers in '%s'",payload=>{const out=sanitizeInlineMarkup(payload);expect(eventHandlerAttrsOf(parseDom(out))).toEqual([])})});describe("javascript:/data:/vbscript: URI bypasses",()=>{it.each(["javascript:alert(1)","JaVaScRiPt:alert(1)","java script:alert(1)","java\nscript:alert(1)","java\rscript:alert(1)","java\0script:alert(1)","javascript&colon;alert(1)","&#x6A;avascript:alert(1)","&#0000106;avascript:alert(1)","vbscript:msgbox(1)","data:text/html,<script>alert(1)</script>","data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==","file:///etc/passwd","blob:https://example.com/abc"])("strips href='%s'",href=>{const out=sanitizeInlineMarkup(`<a href="${href}">x</a>`);expect(dangerousHrefsIn(parseDom(out))).toEqual([])})});describe("authority-delimiter / protocol-relative bypasses",()=>{it.each(["//evil.com","///evil.com","/\\evil.com","/\\\\evil.com","/\\/evil.com","/%5cevil.com"])("strips href='%s'",href=>{const out=sanitizeInlineMarkup(`<a href="${href}">click</a>`);const root=parseDom(out);root.querySelectorAll("a[href]").forEach(a=>{const raw=a.getAttribute("href")??"";expect(raw).toMatch(/^\/[^/\\]/)})})});describe("DOMPurify historical mXSS / mutation vectors",()=>{it("strips noscript-wrapped HTML injection",()=>{const payload='<noscript><p title="</noscript><img src=x onerror=alert(1)>">';const out=sanitizeInlineMarkup(payload);const root=parseDom(out);expect(scriptTagsIn(root)).toHaveLength(0);expect(eventHandlerAttrsOf(root)).toEqual([]);expect(root.querySelector("img")).toBeNull()});it("strips template / svg foreignObject smuggling",()=>{const payload="<template><svg><foreignObject><body onload=alert(1)>x</body></foreignObject></svg></template>";const out=sanitizeInlineMarkup(payload);expect(eventHandlerAttrsOf(parseDom(out))).toEqual([])});it("strips MathML annotation-xml encoded HTML",()=>{const payload='<math><annotation-xml encoding="text/html"><iframe src="javascript:alert(1)"></iframe></annotation-xml></math>';const out=sanitizeInlineMarkup(payload);const root=parseDom(out);expect(root.querySelector("iframe")).toBeNull();expect(dangerousHrefsIn(root)).toEqual([])})})});describe("sanitizeRichText",()=>{describe("baseline allowlist behaviour",()=>{it("keeps the allowlisted inline tags",()=>{const input='<p>Hello <strong>world</strong>, <em>see</em> <a href="/x">x</a><br>and <b>more</b>.</p>';expect(sanitizeRichText(input)).toBe(input)});it("strips img tags (not in allowlist) but keeps trailing text",()=>{const out=sanitizeRichText('<img src=x onerror="alert(1)">trail');const root=parseDom(out);expect(root.querySelector("img")).toBeNull();expect(eventHandlerAttrsOf(root)).toEqual([]);expect(out).toContain("trail")})});describe("script + event-handler corpus (symmetric with sanitizeInlineMarkup)",()=>{it.each(["<script>alert(1)</script>","<SCRIPT>alert(1)</SCRIPT>",'<p onclick="alert(1)">x</p>','<a href="/x" onmouseover="alert(1)">l</a>','<strong onerror="alert(1)">x</strong>'])("strips '%s'",payload=>{const out=sanitizeRichText(payload+"tail");const root=parseDom(out);expect(scriptTagsIn(root)).toHaveLength(0);expect(eventHandlerAttrsOf(root)).toEqual([]);expect(out).toContain("tail")})});describe("URI bypasses (symmetric with sanitizeInlineMarkup)",()=>{it.each(["javascript:alert(1)","JaVaScRiPt:alert(1)","java script:alert(1)","javascript&colon;alert(1)","&#x6A;avascript:alert(1)","data:text/html,<script>alert(1)</script>","vbscript:msgbox(1)","https://evil.com","//evil.com","///evil.com","/\\evil.com","/\\\\evil.com"])("strips href='%s'",href=>{const out=sanitizeRichText(`<a href="${href}">x</a>`);const root=parseDom(out);expect(dangerousHrefsIn(root)).toEqual([]);root.querySelectorAll("a[href]").forEach(a=>{expect(a.getAttribute("href")).toMatch(/^\/[^/\\]/)})})});describe("disallowed-but-tempting tags",()=>{it.each(["<iframe src=javascript:alert(1)></iframe>","<object data=javascript:alert(1)></object>","<embed src=javascript:alert(1)></embed>","<svg><script>alert(1)</script></svg>","<svg onload=alert(1)></svg>","<details ontoggle=alert(1) open>x</details>","<marquee onstart=alert(1)>x</marquee>","<video><source onerror=alert(1)></video>","<form><input type=image src=x onerror=alert(1)></form>","<isindex action=javascript:alert(1) type=image>","<base href=javascript:alert(1)//>","<meta http-equiv=refresh content=0;url=javascript:alert(1)>"])("strips '%s'",payload=>{const out=sanitizeRichText(payload+"ok");const root=parseDom(out);expect(scriptTagsIn(root)).toHaveLength(0);expect(eventHandlerAttrsOf(root)).toEqual([]);expect(dangerousHrefsIn(root)).toEqual([]);["iframe","object","embed","svg","details","marquee","video","source","form","input","isindex","base","meta"].forEach(t=>expect(root.querySelector(t)).toBeNull())})})});describe("sanitizeMarketingHtml",()=>{describe("baseline allowlist behaviour",()=>{it("keeps headings, lists, code, blockquote",()=>{const input="<h2>Title</h2><ul><li>One</li></ul><pre><code>code</code></pre><blockquote>quote</blockquote>";expect(sanitizeMarketingHtml(input)).toBe(input)});it("allows external https links",()=>{const out=sanitizeMarketingHtml('<a href="https://ably.com">go</a>');expect(out).toContain('href="https://ably.com"')});it("allows http and relative URIs on img src",()=>{const out=sanitizeMarketingHtml('<img src="https://ably.com/x.png" alt="x">');expect(out).toContain('src="https://ably.com/x.png"')});it("preserves figure/figcaption around images (Ghost-style)",()=>{const input='<figure><img src="/x.png" alt="x"><figcaption>caption</figcaption></figure>';expect(sanitizeMarketingHtml(input)).toBe(input)})});describe("script and embed stripping",()=>{it.each(["<script>alert(1)</script>",'<script src="https://js.hscta.net/cta/current.js"></script>','<script type="application/ld+json">{"x":1}</script>',"<iframe src=https://youtube.com></iframe>","<iframe src=javascript:alert(1)></iframe>",'<object data="https://evil.com"></object>',"<embed src=javascript:alert(1)>","<form action=javascript:alert(1)><input></form>","<svg><script>alert(1)</script></svg>","<svg onload=alert(1)></svg>","<math><mtext><img src=x onerror=alert(1)></mtext></math>"])("strips '%s' while keeping trailing copy",payload=>{const out=sanitizeMarketingHtml(payload+"copy");const root=parseDom(out);expect(scriptTagsIn(root)).toHaveLength(0);expect(eventHandlerAttrsOf(root)).toEqual([]);expect(dangerousHrefsIn(root)).toEqual([]);["iframe","object","embed","form","input","svg","script","math"].forEach(t=>expect(root.querySelector(t)).toBeNull());expect(out).toContain("copy")})});describe("URL-protocol bypasses on allowed-tag attributes",()=>{it.each(["javascript:alert(1)","JaVaScRiPt:alert(1)","java script:alert(1)","java\nscript:alert(1)","javascript&colon;alert(1)","&#x6A;avascript:alert(1)","&#0000106;avascript:alert(1)","data:text/html,<script>alert(1)</script>","vbscript:msgbox(1)","file:///etc/passwd","blob:https://example.com/abc"])("strips href='%s' on <a>",href=>{const out=sanitizeMarketingHtml(`<a href="${href}">x</a>`);expect(dangerousHrefsIn(parseDom(out))).toEqual([])});it.each(["javascript:alert(1)","data:image/svg+xml,<svg onload=alert(1)>","vbscript:msgbox(1)"])("strips img src='%s'",src=>{const out=sanitizeMarketingHtml(`<img src="${src}" alt="x">`);expect(dangerousHrefsIn(parseDom(out))).toEqual([])})});describe("authority-delimiter / protocol-relative bypasses",()=>{it.each(["//evil.com","///evil.com","/\\evil.com","/\\\\evil.com","/\\/evil.com","/%5cevil.com"])("strips href='%s' on <a>",href=>{const out=sanitizeMarketingHtml(`<a href="${href}">click</a>`);const root=parseDom(out);root.querySelectorAll("a[href]").forEach(a=>{const raw=a.getAttribute("href")??"";expect(raw).toMatch(/^(https?:\/\/|\/[^/\\])/)})});it.each(["//evil.com/x.png","/\\evil.com/x.png","/\\\\evil.com/x.png"])("strips img src='%s'",src=>{const out=sanitizeMarketingHtml(`<img src="${src}" alt="x">`);const root=parseDom(out);root.querySelectorAll("img[src]").forEach(img=>{expect(img.getAttribute("src")).toMatch(/^(https?:\/\/|\/[^/\\])/)})})});describe("style-based payloads",()=>{it("strips style attribute entirely (we do not allow it)",()=>{const out=sanitizeMarketingHtml('<p style="background:url(javascript:alert(1))">x</p>');expect(out).not.toContain("style");expect(out).not.toContain("javascript:")});it("strips inline style with expression()",()=>{const out=sanitizeMarketingHtml('<p style="width:expression(alert(1))">x</p>');expect(out).not.toContain("style");expect(out).not.toContain("expression")})});describe("event-handler corpus on allowed tags",()=>{it.each(['<p onclick="alert(1)">x</p>','<a href="/x" onmouseover="alert(1)">l</a>','<img src="/x.png" onerror="alert(1)" alt="x">','<h2 onmouseenter="alert(1)">t</h2>','<table onclick="alert(1)"><tbody><tr><td>x</td></tr></tbody></table>'])("strips event handlers in '%s'",payload=>{const out=sanitizeMarketingHtml(payload);expect(eventHandlerAttrsOf(parseDom(out))).toEqual([])})});describe("mXSS / parser-confusion vectors",()=>{it("strips noscript-wrapped img injection",()=>{const payload='<noscript><p title="</noscript><img src=x onerror=alert(1)>">';const out=sanitizeMarketingHtml(payload);const root=parseDom(out);expect(eventHandlerAttrsOf(root)).toEqual([]);const img=root.querySelector("img");if(img){expect(img.getAttribute("onerror")).toBeNull();expect(img.getAttribute("src")).not.toBe("x")}});it("strips xlink:href javascript on svg use",()=>{const out=sanitizeMarketingHtml('<svg><use xlink:href="javascript:alert(1)"></use></svg>');const root=parseDom(out);expect(root.querySelector("svg")).toBeNull();expect(dangerousHrefsIn(root)).toEqual([])});it("strips title-attribute escape inside an allowed tag",()=>{const payload='<a href="/x" title=\'"><script>alert(1)</script><p \'>link</a>';const out=sanitizeMarketingHtml(payload);expect(scriptTagsIn(parseDom(out))).toHaveLength(0)})})});
2
+ //# sourceMappingURL=sanitize-html.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/core/utils/sanitize-html.test.ts"],"sourcesContent":["/**\n * @vitest-environment jsdom\n *\n * Adversarial test corpus for the sanitize-html primitives. Cases are sourced\n * from public XSS cheatsheets (OWASP, PortSwigger, html5sec.org) and from\n * past DOMPurify CVEs / advisories. They exercise the wrappers, not DOMPurify\n * itself — the assertion in every case is \"the rendered string does not\n * carry an executable payload through to a DOM that would fire on hydration\".\n *\n * The shape of each assertion is intentionally strict: we don't trust that\n * \"alert\" or \"javascript\" not appearing as a substring means safety, so most\n * checks combine \"tag/attr stripped\" + \"no live-handler attribute survives\"\n * + \"raw text content may survive but is rendered as text\".\n */\n\nimport { describe, expect, it } from \"vitest\";\n\nimport {\n sanitizeInlineMarkup,\n sanitizeMarketingHtml,\n sanitizeRichText,\n} from \"./sanitize-html\";\n\n// Helper: parse the sanitised string into a real DOM, then assert no element\n// in the resulting tree carries an attribute whose name starts with `on`,\n// nor an `href`/`src` whose value resolves to anything beyond the allowlist.\nconst parseDom = (html: string) => {\n const wrapper = document.createElement(\"div\");\n wrapper.innerHTML = html;\n return wrapper;\n};\n\nconst eventHandlerAttrsOf = (root: HTMLElement) => {\n const offenders: string[] = [];\n root.querySelectorAll(\"*\").forEach((el) => {\n for (const attr of Array.from(el.attributes)) {\n if (attr.name.toLowerCase().startsWith(\"on\")) {\n offenders.push(`${el.tagName}:${attr.name}`);\n }\n }\n });\n return offenders;\n};\n\nconst dangerousProtocols = /^(javascript|data|vbscript|file|blob):/i;\nconst dangerousHrefsIn = (root: HTMLElement) => {\n const offenders: string[] = [];\n root.querySelectorAll<HTMLAnchorElement>(\"[href]\").forEach((el) => {\n if (dangerousProtocols.test(el.getAttribute(\"href\") ?? \"\")) {\n offenders.push(el.getAttribute(\"href\") ?? \"\");\n }\n });\n root.querySelectorAll<HTMLImageElement>(\"[src]\").forEach((el) => {\n if (dangerousProtocols.test(el.getAttribute(\"src\") ?? \"\")) {\n offenders.push(el.getAttribute(\"src\") ?? \"\");\n }\n });\n return offenders;\n};\n\nconst scriptTagsIn = (root: HTMLElement) =>\n Array.from(root.querySelectorAll(\"script\"));\n\ndescribe(\"sanitizeInlineMarkup\", () => {\n describe(\"baseline allowlist behaviour\", () => {\n it(\"keeps same-origin relative links\", () => {\n expect(sanitizeInlineMarkup('<a href=\"/dashboard\">go</a>')).toBe(\n '<a href=\"/dashboard\">go</a>',\n );\n });\n\n it(\"keeps a data-method attribute on a link (Rails UJS)\", () => {\n const out = sanitizeInlineMarkup(\n '<a href=\"/logout\" data-method=\"delete\">Log out</a>',\n );\n expect(out).toContain('data-method=\"delete\"');\n });\n\n it(\"strips disallowed tags but keeps their text content\", () => {\n expect(sanitizeInlineMarkup(\"<b>bold</b>\")).toBe(\"bold\");\n });\n\n it(\"returns an empty string for null and undefined\", () => {\n expect(sanitizeInlineMarkup(null)).toBe(\"\");\n expect(sanitizeInlineMarkup(undefined)).toBe(\"\");\n });\n\n it(\"returns an empty string for empty input\", () => {\n expect(sanitizeInlineMarkup(\"\")).toBe(\"\");\n });\n });\n\n describe(\"script-tag injection\", () => {\n it.each([\n \"<script>alert(1)</script>\",\n \"<SCRIPT>alert(1)</SCRIPT>\",\n \"<ScRiPt>alert(1)</ScRiPt>\",\n \"<script src=//evil.com/x.js></script>\",\n \"<script\\nsrc=//evil.com/x.js></script>\",\n \"<script type='text/javascript'>alert(1)</script>\",\n \"<script\t>alert(1)</script>\",\n ])(\"strips '%s'\", (payload) => {\n const out = sanitizeInlineMarkup(payload + \"safe\");\n expect(scriptTagsIn(parseDom(out))).toHaveLength(0);\n expect(out).toContain(\"safe\");\n });\n });\n\n describe(\"event-handler injection\", () => {\n it.each([\n '<a href=\"/x\" onmouseover=\"alert(1)\">l</a>',\n '<a href=\"/x\" onclick=\"alert(1)\">l</a>',\n '<a href=\"/x\" onfocus=\"alert(1)\" autofocus>l</a>',\n '<a href=\"/x\" oNmOuSeOvEr=\"alert(1)\">l</a>',\n \"<a href=\\\"/x\\\" onmouseover='alert(1)'>l</a>\",\n // Unquoted attribute value\n \"<a href=/x onmouseover=alert(1)>l</a>\",\n // Tab between attribute name and equals\n \"<a\\thref=/x\\tonmouseover=alert(1)>l</a>\",\n // Newline between attribute name and value\n \"<a href=/x\\nonmouseover=alert(1)>l</a>\",\n // Backtick around value (some parsers accept)\n \"<a href=/x onmouseover=`alert(1)`>l</a>\",\n ])(\"strips event handlers in '%s'\", (payload) => {\n const out = sanitizeInlineMarkup(payload);\n expect(eventHandlerAttrsOf(parseDom(out))).toEqual([]);\n });\n });\n\n describe(\"javascript:/data:/vbscript: URI bypasses\", () => {\n it.each([\n \"javascript:alert(1)\",\n \"JaVaScRiPt:alert(1)\",\n // Tab in scheme (browsers normalise away whitespace inside URLs)\n \"java\\tscript:alert(1)\",\n // Newline in scheme\n \"java\\nscript:alert(1)\",\n // Carriage-return in scheme\n \"java\\rscript:alert(1)\",\n // Null byte in scheme\n \"java\\0script:alert(1)\",\n // HTML-entity-encoded colon\n \"javascript&colon;alert(1)\",\n // HTML decimal entity\n \"&#x6A;avascript:alert(1)\",\n // Long form decimal\n \"&#0000106;avascript:alert(1)\",\n // vbscript\n \"vbscript:msgbox(1)\",\n // data: with embedded script\n \"data:text/html,<script>alert(1)</script>\",\n \"data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg==\",\n // file:\n \"file:///etc/passwd\",\n // blob:\n \"blob:https://example.com/abc\",\n ])(\"strips href='%s'\", (href) => {\n const out = sanitizeInlineMarkup(`<a href=\"${href}\">x</a>`);\n expect(dangerousHrefsIn(parseDom(out))).toEqual([]);\n });\n });\n\n describe(\"authority-delimiter / protocol-relative bypasses\", () => {\n it.each([\n \"//evil.com\",\n \"///evil.com\",\n \"/\\\\evil.com\", // CRITICAL: slash-backslash is authority-delimiter in WHATWG\n \"/\\\\\\\\evil.com\", // slash + two backslashes\n \"/\\\\/evil.com\", // mixed slash-backslash\n // URL-encoded backslash - browsers may or may not normalise\n \"/%5cevil.com\",\n ])(\"strips href='%s'\", (href) => {\n const out = sanitizeInlineMarkup(`<a href=\"${href}\">click</a>`);\n const root = parseDom(out);\n // Either the href is gone entirely, or it doesn't resolve to a foreign host\n root.querySelectorAll<HTMLAnchorElement>(\"a[href]\").forEach((a) => {\n const raw = a.getAttribute(\"href\") ?? \"\";\n // A surviving href must start with a single slash followed by a non-slash,\n // non-backslash character.\n expect(raw).toMatch(/^\\/[^/\\\\]/);\n });\n });\n });\n\n describe(\"DOMPurify historical mXSS / mutation vectors\", () => {\n // Mutation XSS via noscript - DOMPurify has had advisories around these\n it(\"strips noscript-wrapped HTML injection\", () => {\n const payload =\n '<noscript><p title=\"</noscript><img src=x onerror=alert(1)>\">';\n const out = sanitizeInlineMarkup(payload);\n const root = parseDom(out);\n expect(scriptTagsIn(root)).toHaveLength(0);\n expect(eventHandlerAttrsOf(root)).toEqual([]);\n expect(root.querySelector(\"img\")).toBeNull();\n });\n\n it(\"strips template / svg foreignObject smuggling\", () => {\n const payload =\n \"<template><svg><foreignObject><body onload=alert(1)>x</body></foreignObject></svg></template>\";\n const out = sanitizeInlineMarkup(payload);\n expect(eventHandlerAttrsOf(parseDom(out))).toEqual([]);\n });\n\n it(\"strips MathML annotation-xml encoded HTML\", () => {\n const payload =\n '<math><annotation-xml encoding=\"text/html\"><iframe src=\"javascript:alert(1)\"></iframe></annotation-xml></math>';\n const out = sanitizeInlineMarkup(payload);\n const root = parseDom(out);\n expect(root.querySelector(\"iframe\")).toBeNull();\n expect(dangerousHrefsIn(root)).toEqual([]);\n });\n });\n});\n\ndescribe(\"sanitizeRichText\", () => {\n describe(\"baseline allowlist behaviour\", () => {\n it(\"keeps the allowlisted inline tags\", () => {\n const input =\n '<p>Hello <strong>world</strong>, <em>see</em> <a href=\"/x\">x</a><br>and <b>more</b>.</p>';\n expect(sanitizeRichText(input)).toBe(input);\n });\n\n it(\"strips img tags (not in allowlist) but keeps trailing text\", () => {\n const out = sanitizeRichText('<img src=x onerror=\"alert(1)\">trail');\n const root = parseDom(out);\n expect(root.querySelector(\"img\")).toBeNull();\n expect(eventHandlerAttrsOf(root)).toEqual([]);\n expect(out).toContain(\"trail\");\n });\n });\n\n describe(\"script + event-handler corpus (symmetric with sanitizeInlineMarkup)\", () => {\n it.each([\n \"<script>alert(1)</script>\",\n \"<SCRIPT>alert(1)</SCRIPT>\",\n '<p onclick=\"alert(1)\">x</p>',\n '<a href=\"/x\" onmouseover=\"alert(1)\">l</a>',\n '<strong onerror=\"alert(1)\">x</strong>',\n ])(\"strips '%s'\", (payload) => {\n const out = sanitizeRichText(payload + \"tail\");\n const root = parseDom(out);\n expect(scriptTagsIn(root)).toHaveLength(0);\n expect(eventHandlerAttrsOf(root)).toEqual([]);\n expect(out).toContain(\"tail\");\n });\n });\n\n describe(\"URI bypasses (symmetric with sanitizeInlineMarkup)\", () => {\n it.each([\n \"javascript:alert(1)\",\n \"JaVaScRiPt:alert(1)\",\n \"java\\tscript:alert(1)\",\n \"javascript&colon;alert(1)\",\n \"&#x6A;avascript:alert(1)\",\n \"data:text/html,<script>alert(1)</script>\",\n \"vbscript:msgbox(1)\",\n \"https://evil.com\",\n \"//evil.com\",\n \"///evil.com\",\n \"/\\\\evil.com\",\n \"/\\\\\\\\evil.com\",\n ])(\"strips href='%s'\", (href) => {\n const out = sanitizeRichText(`<a href=\"${href}\">x</a>`);\n const root = parseDom(out);\n expect(dangerousHrefsIn(root)).toEqual([]);\n root.querySelectorAll<HTMLAnchorElement>(\"a[href]\").forEach((a) => {\n expect(a.getAttribute(\"href\")).toMatch(/^\\/[^/\\\\]/);\n });\n });\n });\n\n describe(\"disallowed-but-tempting tags\", () => {\n it.each([\n \"<iframe src=javascript:alert(1)></iframe>\",\n \"<object data=javascript:alert(1)></object>\",\n \"<embed src=javascript:alert(1)></embed>\",\n \"<svg><script>alert(1)</script></svg>\",\n \"<svg onload=alert(1)></svg>\",\n \"<details ontoggle=alert(1) open>x</details>\",\n \"<marquee onstart=alert(1)>x</marquee>\",\n \"<video><source onerror=alert(1)></video>\",\n \"<form><input type=image src=x onerror=alert(1)></form>\",\n \"<isindex action=javascript:alert(1) type=image>\",\n \"<base href=javascript:alert(1)//>\",\n \"<meta http-equiv=refresh content=0;url=javascript:alert(1)>\",\n ])(\"strips '%s'\", (payload) => {\n const out = sanitizeRichText(payload + \"ok\");\n const root = parseDom(out);\n expect(scriptTagsIn(root)).toHaveLength(0);\n expect(eventHandlerAttrsOf(root)).toEqual([]);\n expect(dangerousHrefsIn(root)).toEqual([]);\n [\n \"iframe\",\n \"object\",\n \"embed\",\n \"svg\",\n \"details\",\n \"marquee\",\n \"video\",\n \"source\",\n \"form\",\n \"input\",\n \"isindex\",\n \"base\",\n \"meta\",\n ].forEach((t) => expect(root.querySelector(t)).toBeNull());\n });\n });\n});\n\ndescribe(\"sanitizeMarketingHtml\", () => {\n describe(\"baseline allowlist behaviour\", () => {\n it(\"keeps headings, lists, code, blockquote\", () => {\n const input =\n \"<h2>Title</h2><ul><li>One</li></ul><pre><code>code</code></pre><blockquote>quote</blockquote>\";\n expect(sanitizeMarketingHtml(input)).toBe(input);\n });\n\n it(\"allows external https links\", () => {\n const out = sanitizeMarketingHtml('<a href=\"https://ably.com\">go</a>');\n expect(out).toContain('href=\"https://ably.com\"');\n });\n\n it(\"allows http and relative URIs on img src\", () => {\n const out = sanitizeMarketingHtml(\n '<img src=\"https://ably.com/x.png\" alt=\"x\">',\n );\n expect(out).toContain('src=\"https://ably.com/x.png\"');\n });\n\n it(\"preserves figure/figcaption around images (Ghost-style)\", () => {\n const input =\n '<figure><img src=\"/x.png\" alt=\"x\"><figcaption>caption</figcaption></figure>';\n expect(sanitizeMarketingHtml(input)).toBe(input);\n });\n });\n\n describe(\"script and embed stripping\", () => {\n it.each([\n \"<script>alert(1)</script>\",\n '<script src=\"https://js.hscta.net/cta/current.js\"></script>',\n '<script type=\"application/ld+json\">{\"x\":1}</script>',\n \"<iframe src=https://youtube.com></iframe>\",\n \"<iframe src=javascript:alert(1)></iframe>\",\n '<object data=\"https://evil.com\"></object>',\n \"<embed src=javascript:alert(1)>\",\n \"<form action=javascript:alert(1)><input></form>\",\n \"<svg><script>alert(1)</script></svg>\",\n \"<svg onload=alert(1)></svg>\",\n \"<math><mtext><img src=x onerror=alert(1)></mtext></math>\",\n ])(\"strips '%s' while keeping trailing copy\", (payload) => {\n const out = sanitizeMarketingHtml(payload + \"copy\");\n const root = parseDom(out);\n expect(scriptTagsIn(root)).toHaveLength(0);\n expect(eventHandlerAttrsOf(root)).toEqual([]);\n expect(dangerousHrefsIn(root)).toEqual([]);\n [\n \"iframe\",\n \"object\",\n \"embed\",\n \"form\",\n \"input\",\n \"svg\",\n \"script\",\n \"math\",\n ].forEach((t) => expect(root.querySelector(t)).toBeNull());\n expect(out).toContain(\"copy\");\n });\n });\n\n describe(\"URL-protocol bypasses on allowed-tag attributes\", () => {\n it.each([\n \"javascript:alert(1)\",\n \"JaVaScRiPt:alert(1)\",\n \"java\\tscript:alert(1)\",\n \"java\\nscript:alert(1)\",\n \"javascript&colon;alert(1)\",\n \"&#x6A;avascript:alert(1)\",\n \"&#0000106;avascript:alert(1)\",\n \"data:text/html,<script>alert(1)</script>\",\n \"vbscript:msgbox(1)\",\n \"file:///etc/passwd\",\n \"blob:https://example.com/abc\",\n ])(\"strips href='%s' on <a>\", (href) => {\n const out = sanitizeMarketingHtml(`<a href=\"${href}\">x</a>`);\n expect(dangerousHrefsIn(parseDom(out))).toEqual([]);\n });\n\n it.each([\n \"javascript:alert(1)\",\n \"data:image/svg+xml,<svg onload=alert(1)>\",\n \"vbscript:msgbox(1)\",\n ])(\"strips img src='%s'\", (src) => {\n const out = sanitizeMarketingHtml(`<img src=\"${src}\" alt=\"x\">`);\n expect(dangerousHrefsIn(parseDom(out))).toEqual([]);\n });\n });\n\n describe(\"authority-delimiter / protocol-relative bypasses\", () => {\n // Same corpus as the inline/rich-text wrappers - the marketing wrapper\n // earlier permitted `//evil.com` because the URI regex was unanchored\n // past the leading slash. Symmetric coverage protects against regression.\n it.each([\n \"//evil.com\",\n \"///evil.com\",\n \"/\\\\evil.com\",\n \"/\\\\\\\\evil.com\",\n \"/\\\\/evil.com\",\n \"/%5cevil.com\",\n ])(\"strips href='%s' on <a>\", (href) => {\n const out = sanitizeMarketingHtml(`<a href=\"${href}\">click</a>`);\n const root = parseDom(out);\n root.querySelectorAll<HTMLAnchorElement>(\"a[href]\").forEach((a) => {\n const raw = a.getAttribute(\"href\") ?? \"\";\n // A surviving href must be either a full http(s):// URL or a\n // single-slash same-origin path. No protocol-relative variants.\n expect(raw).toMatch(/^(https?:\\/\\/|\\/[^/\\\\])/);\n });\n });\n\n it.each([\"//evil.com/x.png\", \"/\\\\evil.com/x.png\", \"/\\\\\\\\evil.com/x.png\"])(\n \"strips img src='%s'\",\n (src) => {\n const out = sanitizeMarketingHtml(`<img src=\"${src}\" alt=\"x\">`);\n const root = parseDom(out);\n root.querySelectorAll<HTMLImageElement>(\"img[src]\").forEach((img) => {\n expect(img.getAttribute(\"src\")).toMatch(/^(https?:\\/\\/|\\/[^/\\\\])/);\n });\n },\n );\n });\n\n describe(\"style-based payloads\", () => {\n it(\"strips style attribute entirely (we do not allow it)\", () => {\n const out = sanitizeMarketingHtml(\n '<p style=\"background:url(javascript:alert(1))\">x</p>',\n );\n expect(out).not.toContain(\"style\");\n expect(out).not.toContain(\"javascript:\");\n });\n\n it(\"strips inline style with expression()\", () => {\n const out = sanitizeMarketingHtml(\n '<p style=\"width:expression(alert(1))\">x</p>',\n );\n expect(out).not.toContain(\"style\");\n expect(out).not.toContain(\"expression\");\n });\n });\n\n describe(\"event-handler corpus on allowed tags\", () => {\n it.each([\n '<p onclick=\"alert(1)\">x</p>',\n '<a href=\"/x\" onmouseover=\"alert(1)\">l</a>',\n '<img src=\"/x.png\" onerror=\"alert(1)\" alt=\"x\">',\n '<h2 onmouseenter=\"alert(1)\">t</h2>',\n '<table onclick=\"alert(1)\"><tbody><tr><td>x</td></tr></tbody></table>',\n ])(\"strips event handlers in '%s'\", (payload) => {\n const out = sanitizeMarketingHtml(payload);\n expect(eventHandlerAttrsOf(parseDom(out))).toEqual([]);\n });\n });\n\n describe(\"mXSS / parser-confusion vectors\", () => {\n it(\"strips noscript-wrapped img injection\", () => {\n const payload =\n '<noscript><p title=\"</noscript><img src=x onerror=alert(1)>\">';\n const out = sanitizeMarketingHtml(payload);\n const root = parseDom(out);\n expect(eventHandlerAttrsOf(root)).toEqual([]);\n const img = root.querySelector(\"img\");\n // If an <img> survives (it's allowed in marketing) it must not carry onerror\n if (img) {\n expect(img.getAttribute(\"onerror\")).toBeNull();\n expect(img.getAttribute(\"src\")).not.toBe(\"x\"); // src=x with no scheme is fine, but the onerror must be gone\n }\n });\n\n it(\"strips xlink:href javascript on svg use\", () => {\n const out = sanitizeMarketingHtml(\n '<svg><use xlink:href=\"javascript:alert(1)\"></use></svg>',\n );\n const root = parseDom(out);\n expect(root.querySelector(\"svg\")).toBeNull();\n expect(dangerousHrefsIn(root)).toEqual([]);\n });\n\n it(\"strips title-attribute escape inside an allowed tag\", () => {\n const payload =\n '<a href=\"/x\" title=\\'\"><script>alert(1)</script><p \\'>link</a>';\n const out = sanitizeMarketingHtml(payload);\n expect(scriptTagsIn(parseDom(out))).toHaveLength(0);\n });\n });\n});\n"],"names":["describe","expect","it","sanitizeInlineMarkup","sanitizeMarketingHtml","sanitizeRichText","parseDom","html","wrapper","document","createElement","innerHTML","eventHandlerAttrsOf","root","offenders","querySelectorAll","forEach","el","attr","Array","from","attributes","name","toLowerCase","startsWith","push","tagName","dangerousProtocols","dangerousHrefsIn","test","getAttribute","scriptTagsIn","toBe","out","toContain","undefined","each","payload","toHaveLength","toEqual","href","a","raw","toMatch","querySelector","toBeNull","input","t","src","img","not"],"mappings":"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"}
package/index.d.ts CHANGED
@@ -4315,6 +4315,17 @@ export default ForwardRef;
4315
4315
  //# sourceMappingURL=icon-tech-parse-server.d.ts.map
4316
4316
  }
4317
4317
 
4318
+ declare module '@ably/ui/core/Icon/components/icon-tech-perplexity-mono' {
4319
+ import * as React from "react";
4320
+ interface SVGRProps {
4321
+ title?: string;
4322
+ titleId?: string;
4323
+ }
4324
+ const ForwardRef: React.ForwardRefExoticComponent<Omit<React.SVGProps<SVGSVGElement> & SVGRProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
4325
+ export default ForwardRef;
4326
+ //# sourceMappingURL=icon-tech-perplexity-mono.d.ts.map
4327
+ }
4328
+
4318
4329
  declare module '@ably/ui/core/Icon/components/icon-tech-php' {
4319
4330
  import * as React from "react";
4320
4331
  interface SVGRProps {
@@ -5342,6 +5353,7 @@ import IconTechNodejs from "@ably/ui/core/icon-tech-nodejs";
5342
5353
  import IconTechObjectivec from "@ably/ui/core/icon-tech-objectivec";
5343
5354
  import IconTechOpenai from "@ably/ui/core/icon-tech-openai";
5344
5355
  import IconTechParseServer from "@ably/ui/core/icon-tech-parse-server";
5356
+ import IconTechPerplexityMono from "@ably/ui/core/icon-tech-perplexity-mono";
5345
5357
  import IconTechPhp from "@ably/ui/core/icon-tech-php";
5346
5358
  import IconTechPlanetscale from "@ably/ui/core/icon-tech-planetscale";
5347
5359
  import IconTechPostgres from "@ably/ui/core/icon-tech-postgres";
@@ -5407,7 +5419,7 @@ import IconTechXhrStreaming from "@ably/ui/core/icon-tech-xhr-streaming";
5407
5419
  import IconTechXmpp from "@ably/ui/core/icon-tech-xmpp";
5408
5420
  import IconTechZapier from "@ably/ui/core/icon-tech-zapier";
5409
5421
  import IconTechZeromq from "@ably/ui/core/icon-tech-zeromq";
5410
- export { IconTechAblyApiStreamer, IconTechAblyFirehose, IconTechAblyNative, IconTechAbly, IconTechActivemq, IconTechActivitypub, IconTechAerospike, IconTechAkka, IconTechAmazonEc2, IconTechAmazonEventBridge, IconTechAmqp091, IconTechAmqp10, IconTechAndroidFull, IconTechAndroidHead, IconTechAngular, IconTechAnycable, IconTechApacheCassandra, IconTechApacheCordova, IconTechApacheKafka, IconTechApacheSpark, IconTechApachepulsar, IconTechApachestorm, IconTechApns, IconTechAssemblyai, IconTechAtmosphere, IconTechAwsAppSync, IconTechAwsAurora, IconTechAwsGatewayWebsockets, IconTechAwsSns, IconTechAwsSqs, IconTechAws, IconTechAwsiot, IconTechAwskinesis, IconTechAwslambda, IconTechAwssqs, IconTechAzureApi, IconTechAzureArchiveApi, IconTechAzureBus, IconTechAzureCosmos, IconTechAzureEventHub, IconTechAzureFunctions, IconTechAzureSearch, IconTechAzureStaticWebApp, IconTechAzureStaticWebApps, IconTechAzureStorage, IconTechAzureWebPubsub, IconTechAzurefunctions, IconTechAzureservicebus, IconTechAzuresignalR, IconTechBayeux, IconTechC, IconTechCentrifugo, IconTechClaudeMono, IconTechClaude, IconTechClientSideFrameworks, IconTechClojure, IconTechCloudflareDurableObjects, IconTechCloudflareworkers, IconTechCocoa, IconTechConfluent, IconTechCord, IconTechCsharp, IconTechCurl, IconTechCustomwebhooks, IconTechDatadog, IconTechDesignPatterns, IconTechDevplatforms, IconTechDiffusionData, IconTechDjango, IconTechEngineio, IconTechEventDrivenServers, IconTechFanoutIo, IconTechFastApi, IconTechFauna, IconTechFeatherjs, IconTechFirebaseCloudMessaging, IconTechFirebase, IconTechFlutter, IconTechGcloudbigquery, IconTechGclouddataflow, IconTechGcloudfunctions, IconTechGcloudpubsub, IconTechGo, IconTechGrpc, IconTechHivemq, IconTechHttp2, IconTechHttp3, IconTechHttprest, IconTechIdempotency, IconTechIfttt, IconTechIntegrations, IconTechIosGeneric, IconTechIos, IconTechIpados, IconTechIpfs, IconTechIronmq, IconTechJava, IconTechJavascript, IconTechJetpack, IconTechJms, IconTechJsonWebTokens, IconTechJson, IconTechKaazing, IconTechKotlin, IconTechKsqlDb, IconTechKubernetes, IconTechLaravelBroadcast, IconTechLaravelEcho, IconTechLightstreamer, IconTechLiveblocks, IconTechLongpolling, IconTechMacos, IconTechMatrix, IconTechMeteor, IconTechMongoDb, IconTechMono, IconTechMqtt, IconTechMysql, IconTechNativeScript, IconTechNet, IconTechNetlify, IconTechNextjs, IconTechNkn, IconTechNodejs, IconTechObjectivec, IconTechOpenai, IconTechParseServer, IconTechPhp, IconTechPlanetscale, IconTechPostgres, IconTechPrisma, IconTechProgramminglanguages, IconTechProtcolAdaptors, IconTechProtocols, IconTechPubSub, IconTechPubnub, IconTechPushTechnology, IconTechPusher, IconTechPython, IconTechQuic, IconTechRabbitMQ, IconTechRailsactioncable, IconTechReactApp, IconTechReact, IconTechReactnative, IconTechRedis, IconTechRedpanda, IconTechReplicache, IconTechRethinkdb, IconTechRocketmq, IconTechRuby, IconTechScala, IconTechScaledrone, IconTechServersentevents, IconTechServersideframeworks, IconTechSignalR, IconTechSnowflake, IconTechSocketio, IconTechSockjs, IconTechSolace, IconTechSpring, IconTechStomp, IconTechStreamdataIo, IconTechStreamr, IconTechSwift, IconTechSymfonyMercure, IconTechSymfony, IconTechTcpIp, IconTechTenefit, IconTechTerraformOutline, IconTechTerraform, IconTechTvos, IconTechTwilio, IconTechTypescript, IconTechUdpProtocol, IconTechUnity, IconTechVercel, IconTechVscode, IconTechVuejs, IconTechWamp, IconTechWatchos, IconTechWebPush, IconTechWeb, IconTechWebhooks, IconTechWebrtc, IconTechWebsockets, IconTechWebsub, IconTechXamarin, IconTechXhrStreaming, IconTechXmpp, IconTechZapier, IconTechZeromq, };
5422
+ export { IconTechAblyApiStreamer, IconTechAblyFirehose, IconTechAblyNative, IconTechAbly, IconTechActivemq, IconTechActivitypub, IconTechAerospike, IconTechAkka, IconTechAmazonEc2, IconTechAmazonEventBridge, IconTechAmqp091, IconTechAmqp10, IconTechAndroidFull, IconTechAndroidHead, IconTechAngular, IconTechAnycable, IconTechApacheCassandra, IconTechApacheCordova, IconTechApacheKafka, IconTechApacheSpark, IconTechApachepulsar, IconTechApachestorm, IconTechApns, IconTechAssemblyai, IconTechAtmosphere, IconTechAwsAppSync, IconTechAwsAurora, IconTechAwsGatewayWebsockets, IconTechAwsSns, IconTechAwsSqs, IconTechAws, IconTechAwsiot, IconTechAwskinesis, IconTechAwslambda, IconTechAwssqs, IconTechAzureApi, IconTechAzureArchiveApi, IconTechAzureBus, IconTechAzureCosmos, IconTechAzureEventHub, IconTechAzureFunctions, IconTechAzureSearch, IconTechAzureStaticWebApp, IconTechAzureStaticWebApps, IconTechAzureStorage, IconTechAzureWebPubsub, IconTechAzurefunctions, IconTechAzureservicebus, IconTechAzuresignalR, IconTechBayeux, IconTechC, IconTechCentrifugo, IconTechClaudeMono, IconTechClaude, IconTechClientSideFrameworks, IconTechClojure, IconTechCloudflareDurableObjects, IconTechCloudflareworkers, IconTechCocoa, IconTechConfluent, IconTechCord, IconTechCsharp, IconTechCurl, IconTechCustomwebhooks, IconTechDatadog, IconTechDesignPatterns, IconTechDevplatforms, IconTechDiffusionData, IconTechDjango, IconTechEngineio, IconTechEventDrivenServers, IconTechFanoutIo, IconTechFastApi, IconTechFauna, IconTechFeatherjs, IconTechFirebaseCloudMessaging, IconTechFirebase, IconTechFlutter, IconTechGcloudbigquery, IconTechGclouddataflow, IconTechGcloudfunctions, IconTechGcloudpubsub, IconTechGo, IconTechGrpc, IconTechHivemq, IconTechHttp2, IconTechHttp3, IconTechHttprest, IconTechIdempotency, IconTechIfttt, IconTechIntegrations, IconTechIosGeneric, IconTechIos, IconTechIpados, IconTechIpfs, IconTechIronmq, IconTechJava, IconTechJavascript, IconTechJetpack, IconTechJms, IconTechJsonWebTokens, IconTechJson, IconTechKaazing, IconTechKotlin, IconTechKsqlDb, IconTechKubernetes, IconTechLaravelBroadcast, IconTechLaravelEcho, IconTechLightstreamer, IconTechLiveblocks, IconTechLongpolling, IconTechMacos, IconTechMatrix, IconTechMeteor, IconTechMongoDb, IconTechMono, IconTechMqtt, IconTechMysql, IconTechNativeScript, IconTechNet, IconTechNetlify, IconTechNextjs, IconTechNkn, IconTechNodejs, IconTechObjectivec, IconTechOpenai, IconTechParseServer, IconTechPerplexityMono, IconTechPhp, IconTechPlanetscale, IconTechPostgres, IconTechPrisma, IconTechProgramminglanguages, IconTechProtcolAdaptors, IconTechProtocols, IconTechPubSub, IconTechPubnub, IconTechPushTechnology, IconTechPusher, IconTechPython, IconTechQuic, IconTechRabbitMQ, IconTechRailsactioncable, IconTechReactApp, IconTechReact, IconTechReactnative, IconTechRedis, IconTechRedpanda, IconTechReplicache, IconTechRethinkdb, IconTechRocketmq, IconTechRuby, IconTechScala, IconTechScaledrone, IconTechServersentevents, IconTechServersideframeworks, IconTechSignalR, IconTechSnowflake, IconTechSocketio, IconTechSockjs, IconTechSolace, IconTechSpring, IconTechStomp, IconTechStreamdataIo, IconTechStreamr, IconTechSwift, IconTechSymfonyMercure, IconTechSymfony, IconTechTcpIp, IconTechTenefit, IconTechTerraformOutline, IconTechTerraform, IconTechTvos, IconTechTwilio, IconTechTypescript, IconTechUdpProtocol, IconTechUnity, IconTechVercel, IconTechVscode, IconTechVuejs, IconTechWamp, IconTechWatchos, IconTechWebPush, IconTechWeb, IconTechWebhooks, IconTechWebrtc, IconTechWebsockets, IconTechWebsub, IconTechXamarin, IconTechXhrStreaming, IconTechXmpp, IconTechZapier, IconTechZeromq, };
5411
5423
  //# sourceMappingURL=index.d.ts.map
5412
5424
  }
5413
5425
 
@@ -5432,7 +5444,7 @@ export const socialIcons: readonly ["icon-social-discord", "icon-social-discord-
5432
5444
  }
5433
5445
 
5434
5446
  declare module '@ably/ui/core/Icon/computed-icons/tech-icons' {
5435
- export const techIcons: readonly ["icon-tech-ably", "icon-tech-ably-api-streamer", "icon-tech-ably-firehose", "icon-tech-ably-native", "icon-tech-activemq", "icon-tech-activitypub", "icon-tech-aerospike", "icon-tech-akka", "icon-tech-amazon-ec2", "icon-tech-amazon-event-bridge", "icon-tech-amqp091", "icon-tech-amqp10", "icon-tech-android-full", "icon-tech-android-head", "icon-tech-angular", "icon-tech-anycable", "icon-tech-apache-cassandra", "icon-tech-apache-cordova", "icon-tech-apache-kafka", "icon-tech-apache-spark", "icon-tech-apachepulsar", "icon-tech-apachestorm", "icon-tech-apns", "icon-tech-assemblyai", "icon-tech-atmosphere", "icon-tech-aws", "icon-tech-aws-app-sync", "icon-tech-aws-aurora", "icon-tech-aws-gateway-websockets", "icon-tech-aws-sns", "icon-tech-aws-sqs", "icon-tech-awsiot", "icon-tech-awskinesis", "icon-tech-awslambda", "icon-tech-awssqs", "icon-tech-azure-api", "icon-tech-azure-archive-api", "icon-tech-azure-bus", "icon-tech-azure-cosmos", "icon-tech-azure-event-hub", "icon-tech-azure-functions", "icon-tech-azure-search", "icon-tech-azure-static-web-app", "icon-tech-azure-static-web-apps", "icon-tech-azure-storage", "icon-tech-azure-web-pubsub", "icon-tech-azurefunctions", "icon-tech-azureservicebus", "icon-tech-azuresignalR", "icon-tech-bayeux", "icon-tech-c++", "icon-tech-centrifugo", "icon-tech-claude", "icon-tech-claude-mono", "icon-tech-client-side-frameworks", "icon-tech-clojure", "icon-tech-cloudflare-durable-objects", "icon-tech-cloudflareworkers", "icon-tech-cocoa", "icon-tech-confluent", "icon-tech-cord", "icon-tech-csharp", "icon-tech-curl", "icon-tech-customwebhooks", "icon-tech-datadog", "icon-tech-design-patterns", "icon-tech-devplatforms", "icon-tech-diffusion-data", "icon-tech-django", "icon-tech-engineio", "icon-tech-event-driven-servers", "icon-tech-fanout-io", "icon-tech-fast-api", "icon-tech-fauna", "icon-tech-featherjs", "icon-tech-firebase", "icon-tech-firebase-cloud-messaging", "icon-tech-flutter", "icon-tech-gcloudbigquery", "icon-tech-gclouddataflow", "icon-tech-gcloudfunctions", "icon-tech-gcloudpubsub", "icon-tech-go", "icon-tech-grpc", "icon-tech-hivemq", "icon-tech-http2", "icon-tech-http3", "icon-tech-httprest", "icon-tech-idempotency", "icon-tech-ifttt", "icon-tech-integrations", "icon-tech-ios", "icon-tech-ios-generic", "icon-tech-ipados", "icon-tech-ipfs", "icon-tech-ironmq", "icon-tech-java", "icon-tech-javascript", "icon-tech-jetpack", "icon-tech-jms", "icon-tech-json", "icon-tech-json-web-tokens", "icon-tech-kaazing", "icon-tech-kotlin", "icon-tech-ksql-db", "icon-tech-kubernetes", "icon-tech-laravel-broadcast", "icon-tech-laravel-echo", "icon-tech-lightstreamer", "icon-tech-liveblocks", "icon-tech-longpolling", "icon-tech-macos", "icon-tech-matrix", "icon-tech-meteor", "icon-tech-mongo-db", "icon-tech-mono", "icon-tech-mqtt", "icon-tech-mysql", "icon-tech-native-script", "icon-tech-net", "icon-tech-netlify", "icon-tech-nextjs", "icon-tech-nkn", "icon-tech-nodejs", "icon-tech-objectivec", "icon-tech-openai", "icon-tech-parse-server", "icon-tech-php", "icon-tech-planetscale", "icon-tech-postgres", "icon-tech-prisma", "icon-tech-programminglanguages", "icon-tech-protcol-adaptors", "icon-tech-protocols", "icon-tech-pub-sub", "icon-tech-pubnub", "icon-tech-push-technology", "icon-tech-pusher", "icon-tech-python", "icon-tech-quic", "icon-tech-rabbitMQ", "icon-tech-railsactioncable", "icon-tech-react", "icon-tech-react-app", "icon-tech-reactnative", "icon-tech-redis", "icon-tech-redpanda", "icon-tech-replicache", "icon-tech-rethinkdb", "icon-tech-rocketmq", "icon-tech-ruby", "icon-tech-scala", "icon-tech-scaledrone", "icon-tech-serversentevents", "icon-tech-serversideframeworks", "icon-tech-signalR", "icon-tech-snowflake", "icon-tech-socketio", "icon-tech-sockjs", "icon-tech-solace", "icon-tech-spring", "icon-tech-stomp", "icon-tech-streamdata-io", "icon-tech-streamr", "icon-tech-swift", "icon-tech-symfony", "icon-tech-symfony-mercure", "icon-tech-tcp-ip", "icon-tech-tenefit", "icon-tech-terraform", "icon-tech-terraform-outline", "icon-tech-tvos", "icon-tech-twilio", "icon-tech-typescript", "icon-tech-udp-protocol", "icon-tech-unity", "icon-tech-vercel", "icon-tech-vscode", "icon-tech-vuejs", "icon-tech-wamp", "icon-tech-watchos", "icon-tech-web", "icon-tech-web-push", "icon-tech-webhooks", "icon-tech-webrtc", "icon-tech-websockets", "icon-tech-websub", "icon-tech-xamarin", "icon-tech-xhr-streaming", "icon-tech-xmpp", "icon-tech-zapier", "icon-tech-zeromq"];
5447
+ export const techIcons: readonly ["icon-tech-ably", "icon-tech-ably-api-streamer", "icon-tech-ably-firehose", "icon-tech-ably-native", "icon-tech-activemq", "icon-tech-activitypub", "icon-tech-aerospike", "icon-tech-akka", "icon-tech-amazon-ec2", "icon-tech-amazon-event-bridge", "icon-tech-amqp091", "icon-tech-amqp10", "icon-tech-android-full", "icon-tech-android-head", "icon-tech-angular", "icon-tech-anycable", "icon-tech-apache-cassandra", "icon-tech-apache-cordova", "icon-tech-apache-kafka", "icon-tech-apache-spark", "icon-tech-apachepulsar", "icon-tech-apachestorm", "icon-tech-apns", "icon-tech-assemblyai", "icon-tech-atmosphere", "icon-tech-aws", "icon-tech-aws-app-sync", "icon-tech-aws-aurora", "icon-tech-aws-gateway-websockets", "icon-tech-aws-sns", "icon-tech-aws-sqs", "icon-tech-awsiot", "icon-tech-awskinesis", "icon-tech-awslambda", "icon-tech-awssqs", "icon-tech-azure-api", "icon-tech-azure-archive-api", "icon-tech-azure-bus", "icon-tech-azure-cosmos", "icon-tech-azure-event-hub", "icon-tech-azure-functions", "icon-tech-azure-search", "icon-tech-azure-static-web-app", "icon-tech-azure-static-web-apps", "icon-tech-azure-storage", "icon-tech-azure-web-pubsub", "icon-tech-azurefunctions", "icon-tech-azureservicebus", "icon-tech-azuresignalR", "icon-tech-bayeux", "icon-tech-c++", "icon-tech-centrifugo", "icon-tech-claude", "icon-tech-claude-mono", "icon-tech-client-side-frameworks", "icon-tech-clojure", "icon-tech-cloudflare-durable-objects", "icon-tech-cloudflareworkers", "icon-tech-cocoa", "icon-tech-confluent", "icon-tech-cord", "icon-tech-csharp", "icon-tech-curl", "icon-tech-customwebhooks", "icon-tech-datadog", "icon-tech-design-patterns", "icon-tech-devplatforms", "icon-tech-diffusion-data", "icon-tech-django", "icon-tech-engineio", "icon-tech-event-driven-servers", "icon-tech-fanout-io", "icon-tech-fast-api", "icon-tech-fauna", "icon-tech-featherjs", "icon-tech-firebase", "icon-tech-firebase-cloud-messaging", "icon-tech-flutter", "icon-tech-gcloudbigquery", "icon-tech-gclouddataflow", "icon-tech-gcloudfunctions", "icon-tech-gcloudpubsub", "icon-tech-go", "icon-tech-grpc", "icon-tech-hivemq", "icon-tech-http2", "icon-tech-http3", "icon-tech-httprest", "icon-tech-idempotency", "icon-tech-ifttt", "icon-tech-integrations", "icon-tech-ios", "icon-tech-ios-generic", "icon-tech-ipados", "icon-tech-ipfs", "icon-tech-ironmq", "icon-tech-java", "icon-tech-javascript", "icon-tech-jetpack", "icon-tech-jms", "icon-tech-json", "icon-tech-json-web-tokens", "icon-tech-kaazing", "icon-tech-kotlin", "icon-tech-ksql-db", "icon-tech-kubernetes", "icon-tech-laravel-broadcast", "icon-tech-laravel-echo", "icon-tech-lightstreamer", "icon-tech-liveblocks", "icon-tech-longpolling", "icon-tech-macos", "icon-tech-matrix", "icon-tech-meteor", "icon-tech-mongo-db", "icon-tech-mono", "icon-tech-mqtt", "icon-tech-mysql", "icon-tech-native-script", "icon-tech-net", "icon-tech-netlify", "icon-tech-nextjs", "icon-tech-nkn", "icon-tech-nodejs", "icon-tech-objectivec", "icon-tech-openai", "icon-tech-parse-server", "icon-tech-perplexity-mono", "icon-tech-php", "icon-tech-planetscale", "icon-tech-postgres", "icon-tech-prisma", "icon-tech-programminglanguages", "icon-tech-protcol-adaptors", "icon-tech-protocols", "icon-tech-pub-sub", "icon-tech-pubnub", "icon-tech-push-technology", "icon-tech-pusher", "icon-tech-python", "icon-tech-quic", "icon-tech-rabbitMQ", "icon-tech-railsactioncable", "icon-tech-react", "icon-tech-react-app", "icon-tech-reactnative", "icon-tech-redis", "icon-tech-redpanda", "icon-tech-replicache", "icon-tech-rethinkdb", "icon-tech-rocketmq", "icon-tech-ruby", "icon-tech-scala", "icon-tech-scaledrone", "icon-tech-serversentevents", "icon-tech-serversideframeworks", "icon-tech-signalR", "icon-tech-snowflake", "icon-tech-socketio", "icon-tech-sockjs", "icon-tech-solace", "icon-tech-spring", "icon-tech-stomp", "icon-tech-streamdata-io", "icon-tech-streamr", "icon-tech-swift", "icon-tech-symfony", "icon-tech-symfony-mercure", "icon-tech-tcp-ip", "icon-tech-tenefit", "icon-tech-terraform", "icon-tech-terraform-outline", "icon-tech-tvos", "icon-tech-twilio", "icon-tech-typescript", "icon-tech-udp-protocol", "icon-tech-unity", "icon-tech-vercel", "icon-tech-vscode", "icon-tech-vuejs", "icon-tech-wamp", "icon-tech-watchos", "icon-tech-web", "icon-tech-web-push", "icon-tech-webhooks", "icon-tech-webrtc", "icon-tech-websockets", "icon-tech-websub", "icon-tech-xamarin", "icon-tech-xhr-streaming", "icon-tech-xmpp", "icon-tech-zapier", "icon-tech-zeromq"];
5436
5448
  //# sourceMappingURL=tech-icons.d.ts.map
5437
5449
  }
5438
5450
 
@@ -5441,7 +5453,7 @@ export const iconNames: {
5441
5453
  gui: readonly ["icon-gui-ably-badge", "icon-gui-check-circled-fill", "icon-gui-check-lotus-circled", "icon-gui-checklist-checked", "icon-gui-code-doc", "icon-gui-cursor", "icon-gui-expand", "icon-gui-filter-flow-step-0", "icon-gui-filter-flow-step-1", "icon-gui-filter-flow-step-2", "icon-gui-filter-flow-step-3", "icon-gui-flower-growth", "icon-gui-further-reading", "icon-gui-glasses", "icon-gui-heartbeat-outline", "icon-gui-heartbeat-solid", "icon-gui-history", "icon-gui-live-chat", "icon-gui-mouse", "icon-gui-partial", "icon-gui-pitfall", "icon-gui-prod-ai-transport-outline", "icon-gui-prod-ai-transport-solid", "icon-gui-prod-chat-outline", "icon-gui-prod-chat-solid", "icon-gui-prod-liveobjects-outline", "icon-gui-prod-liveobjects-solid", "icon-gui-prod-livesync-outline", "icon-gui-prod-livesync-solid", "icon-gui-prod-pubsub-outline", "icon-gui-prod-pubsub-solid", "icon-gui-prod-spaces-outline", "icon-gui-prod-spaces-solid", "icon-gui-quote-marks-fill", "icon-gui-refresh", "icon-gui-resources", "icon-gui-spinner-dark", "icon-gui-spinner-light"];
5442
5454
  display: readonly ["icon-display-48hrs", "icon-display-ably-channels", "icon-display-about-ably-col", "icon-display-api", "icon-display-api-keys", "icon-display-architectural-guidance", "icon-display-authentication", "icon-display-avatar-stack", "icon-display-browser", "icon-display-calendar", "icon-display-call-mobile", "icon-display-careers-col", "icon-display-case-studies-col", "icon-display-chat-col", "icon-display-chat-mono", "icon-display-chat-stack", "icon-display-chat-stack-col", "icon-display-cloud-servers", "icon-display-cloud-servers-mono", "icon-display-compare-tech-col", "icon-display-connection-state-recovery", "icon-display-consumer-groups", "icon-display-custom", "icon-display-custom-cname", "icon-display-customers-col", "icon-display-data-broadcast-col", "icon-display-data-broadcast-mono", "icon-display-data-integrity", "icon-display-data-synchronization-col", "icon-display-database-connector", "icon-display-dedicated-cluster", "icon-display-deltas", "icon-display-docs-col", "icon-display-documentation", "icon-display-dynamic-channel-groups", "icon-display-edge-network", "icon-display-elasticity", "icon-display-ephemeral-messages", "icon-display-ephemeral-messages-dark-col", "icon-display-equalisers-mono", "icon-display-events-col", "icon-display-exactly-once-delivery", "icon-display-examples-col", "icon-display-fan-out", "icon-display-firehose", "icon-display-gdpr", "icon-display-general-comms", "icon-display-granular-permissions", "icon-display-hipaa", "icon-display-hipaa-mono", "icon-display-history", "icon-display-integrations", "icon-display-integrations-col", "icon-display-it-support-access", "icon-display-it-support-helpdesk", "icon-display-kafka-at-the-edge-col", "icon-display-laptop", "icon-display-last-seen", "icon-display-lightbulb-col", "icon-display-live-chat", "icon-display-live-updates", "icon-display-live-updates-results-metrics-col", "icon-display-map-pin", "icon-display-message", "icon-display-message-annotations", "icon-display-message-annotations-dark-col", "icon-display-message-batching", "icon-display-message-persistence", "icon-display-message-queues", "icon-display-multi-user-spaces", "icon-display-multi-user-spaces-col", "icon-display-observe-analytics", "icon-display-padlock-closed", "icon-display-platform", "icon-display-play", "icon-display-premium-support", "icon-display-privacy-shield-framework", "icon-display-private-link", "icon-display-push-notifications", "icon-display-push-notifications-col", "icon-display-push-notifications-mono", "icon-display-quickstart-guides-col", "icon-display-reactions", "icon-display-read-receipts", "icon-display-resources-col", "icon-display-rewind", "icon-display-sdks", "icon-display-sdks-col", "icon-display-send-received-messages", "icon-display-servers", "icon-display-shopping-cart", "icon-display-sla", "icon-display-soc2-type2", "icon-display-soc2-type2-mono", "icon-display-something-else", "icon-display-something-else-mono", "icon-display-subscription-filters", "icon-display-support-chat-mono", "icon-display-system-metadata", "icon-display-tech-account-comms", "icon-display-tutorials-demos-col", "icon-display-ui", "icon-display-ui-mono", "icon-display-virtual-events", "icon-display-virtual-events-col"];
5443
5455
  social: readonly ["icon-social-discord", "icon-social-discord-mono", "icon-social-facebook", "icon-social-facebook-mono", "icon-social-github", "icon-social-github-mono", "icon-social-glassdoor", "icon-social-glassdoor-mono", "icon-social-google", "icon-social-google-mono", "icon-social-linkedin", "icon-social-linkedin-mono", "icon-social-slack", "icon-social-slack-mono", "icon-social-stackoverflow", "icon-social-stackoverflow-mono", "icon-social-twitter", "icon-social-twitter-mono", "icon-social-x", "icon-social-x-mono", "icon-social-youtube", "icon-social-youtube-mono"];
5444
- tech: readonly ["icon-tech-ably", "icon-tech-ably-api-streamer", "icon-tech-ably-firehose", "icon-tech-ably-native", "icon-tech-activemq", "icon-tech-activitypub", "icon-tech-aerospike", "icon-tech-akka", "icon-tech-amazon-ec2", "icon-tech-amazon-event-bridge", "icon-tech-amqp091", "icon-tech-amqp10", "icon-tech-android-full", "icon-tech-android-head", "icon-tech-angular", "icon-tech-anycable", "icon-tech-apache-cassandra", "icon-tech-apache-cordova", "icon-tech-apache-kafka", "icon-tech-apache-spark", "icon-tech-apachepulsar", "icon-tech-apachestorm", "icon-tech-apns", "icon-tech-assemblyai", "icon-tech-atmosphere", "icon-tech-aws", "icon-tech-aws-app-sync", "icon-tech-aws-aurora", "icon-tech-aws-gateway-websockets", "icon-tech-aws-sns", "icon-tech-aws-sqs", "icon-tech-awsiot", "icon-tech-awskinesis", "icon-tech-awslambda", "icon-tech-awssqs", "icon-tech-azure-api", "icon-tech-azure-archive-api", "icon-tech-azure-bus", "icon-tech-azure-cosmos", "icon-tech-azure-event-hub", "icon-tech-azure-functions", "icon-tech-azure-search", "icon-tech-azure-static-web-app", "icon-tech-azure-static-web-apps", "icon-tech-azure-storage", "icon-tech-azure-web-pubsub", "icon-tech-azurefunctions", "icon-tech-azureservicebus", "icon-tech-azuresignalR", "icon-tech-bayeux", "icon-tech-c++", "icon-tech-centrifugo", "icon-tech-claude", "icon-tech-claude-mono", "icon-tech-client-side-frameworks", "icon-tech-clojure", "icon-tech-cloudflare-durable-objects", "icon-tech-cloudflareworkers", "icon-tech-cocoa", "icon-tech-confluent", "icon-tech-cord", "icon-tech-csharp", "icon-tech-curl", "icon-tech-customwebhooks", "icon-tech-datadog", "icon-tech-design-patterns", "icon-tech-devplatforms", "icon-tech-diffusion-data", "icon-tech-django", "icon-tech-engineio", "icon-tech-event-driven-servers", "icon-tech-fanout-io", "icon-tech-fast-api", "icon-tech-fauna", "icon-tech-featherjs", "icon-tech-firebase", "icon-tech-firebase-cloud-messaging", "icon-tech-flutter", "icon-tech-gcloudbigquery", "icon-tech-gclouddataflow", "icon-tech-gcloudfunctions", "icon-tech-gcloudpubsub", "icon-tech-go", "icon-tech-grpc", "icon-tech-hivemq", "icon-tech-http2", "icon-tech-http3", "icon-tech-httprest", "icon-tech-idempotency", "icon-tech-ifttt", "icon-tech-integrations", "icon-tech-ios", "icon-tech-ios-generic", "icon-tech-ipados", "icon-tech-ipfs", "icon-tech-ironmq", "icon-tech-java", "icon-tech-javascript", "icon-tech-jetpack", "icon-tech-jms", "icon-tech-json", "icon-tech-json-web-tokens", "icon-tech-kaazing", "icon-tech-kotlin", "icon-tech-ksql-db", "icon-tech-kubernetes", "icon-tech-laravel-broadcast", "icon-tech-laravel-echo", "icon-tech-lightstreamer", "icon-tech-liveblocks", "icon-tech-longpolling", "icon-tech-macos", "icon-tech-matrix", "icon-tech-meteor", "icon-tech-mongo-db", "icon-tech-mono", "icon-tech-mqtt", "icon-tech-mysql", "icon-tech-native-script", "icon-tech-net", "icon-tech-netlify", "icon-tech-nextjs", "icon-tech-nkn", "icon-tech-nodejs", "icon-tech-objectivec", "icon-tech-openai", "icon-tech-parse-server", "icon-tech-php", "icon-tech-planetscale", "icon-tech-postgres", "icon-tech-prisma", "icon-tech-programminglanguages", "icon-tech-protcol-adaptors", "icon-tech-protocols", "icon-tech-pub-sub", "icon-tech-pubnub", "icon-tech-push-technology", "icon-tech-pusher", "icon-tech-python", "icon-tech-quic", "icon-tech-rabbitMQ", "icon-tech-railsactioncable", "icon-tech-react", "icon-tech-react-app", "icon-tech-reactnative", "icon-tech-redis", "icon-tech-redpanda", "icon-tech-replicache", "icon-tech-rethinkdb", "icon-tech-rocketmq", "icon-tech-ruby", "icon-tech-scala", "icon-tech-scaledrone", "icon-tech-serversentevents", "icon-tech-serversideframeworks", "icon-tech-signalR", "icon-tech-snowflake", "icon-tech-socketio", "icon-tech-sockjs", "icon-tech-solace", "icon-tech-spring", "icon-tech-stomp", "icon-tech-streamdata-io", "icon-tech-streamr", "icon-tech-swift", "icon-tech-symfony", "icon-tech-symfony-mercure", "icon-tech-tcp-ip", "icon-tech-tenefit", "icon-tech-terraform", "icon-tech-terraform-outline", "icon-tech-tvos", "icon-tech-twilio", "icon-tech-typescript", "icon-tech-udp-protocol", "icon-tech-unity", "icon-tech-vercel", "icon-tech-vscode", "icon-tech-vuejs", "icon-tech-wamp", "icon-tech-watchos", "icon-tech-web", "icon-tech-web-push", "icon-tech-webhooks", "icon-tech-webrtc", "icon-tech-websockets", "icon-tech-websub", "icon-tech-xamarin", "icon-tech-xhr-streaming", "icon-tech-xmpp", "icon-tech-zapier", "icon-tech-zeromq"];
5456
+ tech: readonly ["icon-tech-ably", "icon-tech-ably-api-streamer", "icon-tech-ably-firehose", "icon-tech-ably-native", "icon-tech-activemq", "icon-tech-activitypub", "icon-tech-aerospike", "icon-tech-akka", "icon-tech-amazon-ec2", "icon-tech-amazon-event-bridge", "icon-tech-amqp091", "icon-tech-amqp10", "icon-tech-android-full", "icon-tech-android-head", "icon-tech-angular", "icon-tech-anycable", "icon-tech-apache-cassandra", "icon-tech-apache-cordova", "icon-tech-apache-kafka", "icon-tech-apache-spark", "icon-tech-apachepulsar", "icon-tech-apachestorm", "icon-tech-apns", "icon-tech-assemblyai", "icon-tech-atmosphere", "icon-tech-aws", "icon-tech-aws-app-sync", "icon-tech-aws-aurora", "icon-tech-aws-gateway-websockets", "icon-tech-aws-sns", "icon-tech-aws-sqs", "icon-tech-awsiot", "icon-tech-awskinesis", "icon-tech-awslambda", "icon-tech-awssqs", "icon-tech-azure-api", "icon-tech-azure-archive-api", "icon-tech-azure-bus", "icon-tech-azure-cosmos", "icon-tech-azure-event-hub", "icon-tech-azure-functions", "icon-tech-azure-search", "icon-tech-azure-static-web-app", "icon-tech-azure-static-web-apps", "icon-tech-azure-storage", "icon-tech-azure-web-pubsub", "icon-tech-azurefunctions", "icon-tech-azureservicebus", "icon-tech-azuresignalR", "icon-tech-bayeux", "icon-tech-c++", "icon-tech-centrifugo", "icon-tech-claude", "icon-tech-claude-mono", "icon-tech-client-side-frameworks", "icon-tech-clojure", "icon-tech-cloudflare-durable-objects", "icon-tech-cloudflareworkers", "icon-tech-cocoa", "icon-tech-confluent", "icon-tech-cord", "icon-tech-csharp", "icon-tech-curl", "icon-tech-customwebhooks", "icon-tech-datadog", "icon-tech-design-patterns", "icon-tech-devplatforms", "icon-tech-diffusion-data", "icon-tech-django", "icon-tech-engineio", "icon-tech-event-driven-servers", "icon-tech-fanout-io", "icon-tech-fast-api", "icon-tech-fauna", "icon-tech-featherjs", "icon-tech-firebase", "icon-tech-firebase-cloud-messaging", "icon-tech-flutter", "icon-tech-gcloudbigquery", "icon-tech-gclouddataflow", "icon-tech-gcloudfunctions", "icon-tech-gcloudpubsub", "icon-tech-go", "icon-tech-grpc", "icon-tech-hivemq", "icon-tech-http2", "icon-tech-http3", "icon-tech-httprest", "icon-tech-idempotency", "icon-tech-ifttt", "icon-tech-integrations", "icon-tech-ios", "icon-tech-ios-generic", "icon-tech-ipados", "icon-tech-ipfs", "icon-tech-ironmq", "icon-tech-java", "icon-tech-javascript", "icon-tech-jetpack", "icon-tech-jms", "icon-tech-json", "icon-tech-json-web-tokens", "icon-tech-kaazing", "icon-tech-kotlin", "icon-tech-ksql-db", "icon-tech-kubernetes", "icon-tech-laravel-broadcast", "icon-tech-laravel-echo", "icon-tech-lightstreamer", "icon-tech-liveblocks", "icon-tech-longpolling", "icon-tech-macos", "icon-tech-matrix", "icon-tech-meteor", "icon-tech-mongo-db", "icon-tech-mono", "icon-tech-mqtt", "icon-tech-mysql", "icon-tech-native-script", "icon-tech-net", "icon-tech-netlify", "icon-tech-nextjs", "icon-tech-nkn", "icon-tech-nodejs", "icon-tech-objectivec", "icon-tech-openai", "icon-tech-parse-server", "icon-tech-perplexity-mono", "icon-tech-php", "icon-tech-planetscale", "icon-tech-postgres", "icon-tech-prisma", "icon-tech-programminglanguages", "icon-tech-protcol-adaptors", "icon-tech-protocols", "icon-tech-pub-sub", "icon-tech-pubnub", "icon-tech-push-technology", "icon-tech-pusher", "icon-tech-python", "icon-tech-quic", "icon-tech-rabbitMQ", "icon-tech-railsactioncable", "icon-tech-react", "icon-tech-react-app", "icon-tech-reactnative", "icon-tech-redis", "icon-tech-redpanda", "icon-tech-replicache", "icon-tech-rethinkdb", "icon-tech-rocketmq", "icon-tech-ruby", "icon-tech-scala", "icon-tech-scaledrone", "icon-tech-serversentevents", "icon-tech-serversideframeworks", "icon-tech-signalR", "icon-tech-snowflake", "icon-tech-socketio", "icon-tech-sockjs", "icon-tech-solace", "icon-tech-spring", "icon-tech-stomp", "icon-tech-streamdata-io", "icon-tech-streamr", "icon-tech-swift", "icon-tech-symfony", "icon-tech-symfony-mercure", "icon-tech-tcp-ip", "icon-tech-tenefit", "icon-tech-terraform", "icon-tech-terraform-outline", "icon-tech-tvos", "icon-tech-twilio", "icon-tech-typescript", "icon-tech-udp-protocol", "icon-tech-unity", "icon-tech-vercel", "icon-tech-vscode", "icon-tech-vuejs", "icon-tech-wamp", "icon-tech-watchos", "icon-tech-web", "icon-tech-web-push", "icon-tech-webhooks", "icon-tech-webrtc", "icon-tech-websockets", "icon-tech-websub", "icon-tech-xamarin", "icon-tech-xhr-streaming", "icon-tech-xmpp", "icon-tech-zapier", "icon-tech-zeromq"];
5445
5457
  product: readonly ["icon-product-ai-transport", "icon-product-ai-transport-mono", "icon-product-chat", "icon-product-chat-mono", "icon-product-liveobjects", "icon-product-liveobjects-dark", "icon-product-liveobjects-mono", "icon-product-livesync", "icon-product-livesync-mono", "icon-product-platform", "icon-product-platform-mono", "icon-product-pubsub", "icon-product-pubsub-mono", "icon-product-spaces", "icon-product-spaces-mono"];
5446
5458
  };
5447
5459
  type HeroiconVariant = "outline" | "solid" | "mini" | "micro";
@@ -6590,6 +6602,69 @@ export const componentMaxHeight: (...heights: number[]) => string;
6590
6602
  //# sourceMappingURL=heights.d.ts.map
6591
6603
  }
6592
6604
 
6605
+ declare module '@ably/ui/core/utils/sanitize-html' {
6606
+ /**
6607
+ * sanitizeInlineMarkup — tightest allowlist, intended for short pieces of
6608
+ * trusted-but-defence-in-depthed text (flash messages, banner bodies that
6609
+ * already went through Rails sanitisation). Only inline links to same-origin
6610
+ * paths survive.
6611
+ *
6612
+ * Use this for surfaces where the producer (Rails helper, backend flash)
6613
+ * already sanitised the input and React-side sanitisation is the second
6614
+ * layer of defence.
6615
+ */
6616
+ export const sanitizeInlineMarkup: (input: string | null | undefined) => string;
6617
+ /**
6618
+ * sanitizeRichText — mirror of DashboardNoticeHelper::SANITISE_TAGS on the
6619
+ * Rails side. Use for content that an admin types as light HTML (banner
6620
+ * body text, dashboard notices). Keeps href so links work; same-origin URI
6621
+ * restriction prevents the URI from carrying a script payload.
6622
+ *
6623
+ * Tag set deliberately matches the server-side allowlist so the trust
6624
+ * boundary is identical regardless of which side did the rendering.
6625
+ */
6626
+ export const sanitizeRichText: (input: string | null | undefined) => string;
6627
+ /**
6628
+ * sanitizeMarketingHtml — wider allowlist for Contentful-sourced marketing
6629
+ * content (the `ContentfulBlockHtml` escape-hatch field). Trust boundary is
6630
+ * "compromised Contentful editor account" — wider than inline/rich-text but
6631
+ * still blocks scripts, event handlers, and non-http(s) URIs.
6632
+ *
6633
+ * Not suitable for the Ghost blog body. A survey of all 396 published Ably
6634
+ * blog posts (built locally against Ghost) shows the body legitimately
6635
+ * contains HubSpot CTA scripts (~70 posts), Twitter widget scripts, YouTube
6636
+ * and Vimeo iframes (~50), and inline SVG diagrams (~130). Sanitising those
6637
+ * with this allowlist would silently break the site. Ghost body is treated
6638
+ * as a trusted-CMS surface in the ADR — editor account control is the
6639
+ * security boundary.
6640
+ *
6641
+ * Marketing copy needs headings, lists, code blocks, blockquotes, and
6642
+ * external links, so the URI rule allows http(s) instead of being clamped
6643
+ * to relative paths.
6644
+ */
6645
+ export const sanitizeMarketingHtml: (input: string | null | undefined) => string;
6646
+ //# sourceMappingURL=sanitize-html.d.ts.map
6647
+ }
6648
+
6649
+ declare module '@ably/ui/core/utils/sanitize-html.test' {
6650
+ /**
6651
+ * @vitest-environment jsdom
6652
+ *
6653
+ * Adversarial test corpus for the sanitize-html primitives. Cases are sourced
6654
+ * from public XSS cheatsheets (OWASP, PortSwigger, html5sec.org) and from
6655
+ * past DOMPurify CVEs / advisories. They exercise the wrappers, not DOMPurify
6656
+ * itself — the assertion in every case is "the rendered string does not
6657
+ * carry an executable payload through to a DOM that would fire on hydration".
6658
+ *
6659
+ * The shape of each assertion is intentionally strict: we don't trust that
6660
+ * "alert" or "javascript" not appearing as a substring means safety, so most
6661
+ * checks combine "tag/attr stripped" + "no live-handler attribute survives"
6662
+ * + "raw text content may survive but is rendered as text".
6663
+ */
6664
+ export {};
6665
+ //# sourceMappingURL=sanitize-html.test.d.ts.map
6666
+ }
6667
+
6593
6668
  declare module '@ably/ui/core/utils/syntax-highlighter-registry' {
6594
6669
  export default registry;
6595
6670
  const registry: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ably/ui",
3
- "version": "18.0.0",
3
+ "version": "18.2.0",
4
4
  "description": "Home of the Ably design system library ([design.ably.com](https://design.ably.com)). It provides a showcase, development/test environment and a publishing pipeline for different distributables.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,10 +21,10 @@
21
21
  "workerDirectory": "./public"
22
22
  },
23
23
  "devDependencies": {
24
- "@storybook/addon-docs": "^10.2.0",
25
- "@storybook/addon-themes": "^10.2.0",
26
- "@storybook/addon-vitest": "^10.2.0",
27
- "@storybook/react-vite": "^10.2.0",
24
+ "@storybook/addon-docs": "^10.4.0",
25
+ "@storybook/addon-themes": "^10.4.0",
26
+ "@storybook/addon-vitest": "^10.4.0",
27
+ "@storybook/react-vite": "^10.4.0",
28
28
  "@svgr/core": "^8.1.0",
29
29
  "@svgr/plugin-jsx": "^8.1.0",
30
30
  "@svgr/plugin-svgo": "^8.1.0",
@@ -37,34 +37,34 @@
37
37
  "@types/react": "^18.3.1",
38
38
  "@types/react-dom": "^18.3.0",
39
39
  "@types/svg-sprite": "^0.0.39",
40
- "@typescript-eslint/eslint-plugin": "^8.56.0",
41
- "@typescript-eslint/parser": "^8.56.0",
42
- "@vitejs/plugin-react-swc": "^4.0.1",
40
+ "@typescript-eslint/eslint-plugin": "^8.59.0",
41
+ "@typescript-eslint/parser": "^8.59.0",
42
+ "@vitejs/plugin-react-swc": "^4.3.0",
43
43
  "@vitest/browser": "3.2.4",
44
44
  "@vitest/coverage-v8": "3.2.4",
45
- "autoprefixer": "^10.0.2",
45
+ "autoprefixer": "^10.5.0",
46
46
  "eslint": "^8.57.1",
47
47
  "eslint-config-prettier": "^10.1.8",
48
48
  "eslint-plugin-jsx-a11y": "^6.10.2",
49
49
  "eslint-plugin-react": "^7.35.0",
50
- "eslint-plugin-react-hooks": "^7.0.0",
50
+ "eslint-plugin-react-hooks": "^7.1.1",
51
51
  "eslint-plugin-react-perf": "^3.3.3",
52
- "eslint-plugin-storybook": "^10.2.0",
52
+ "eslint-plugin-storybook": "^10.3.3",
53
53
  "heroicons": "^2.2.0",
54
54
  "http-server": "14.1.1",
55
- "jsdom": "^28.0.0",
56
- "mixpanel-browser": "^2.74.0",
57
- "msw": "2.12.0",
58
- "msw-storybook-addon": "^2.0.5",
59
- "playwright": "^1.58.0",
60
- "posthog-js": "1.359.0",
55
+ "jsdom": "^29.1.0",
56
+ "mixpanel-browser": "^2.79.0",
57
+ "msw": "2.14.3",
58
+ "msw-storybook-addon": "^2.0.7",
59
+ "playwright": "^1.59.1",
60
+ "posthog-js": "1.375.0",
61
61
  "prettier": "^3.8.0",
62
- "storybook": "^10.2.0",
62
+ "storybook": "^10.4.0",
63
63
  "svg-sprite": "^2.0.4",
64
64
  "tailwindcss": "^3.3.6",
65
65
  "ts-node": "^10.9.2",
66
- "typescript": "5.9.2",
67
- "vite": "^7.3.0",
66
+ "typescript": "6.0.2",
67
+ "vite": "^7.3.2",
68
68
  "vitest": "^3.2.4"
69
69
  },
70
70
  "dependencies": {
@@ -78,19 +78,19 @@
78
78
  "@radix-ui/react-tooltip": "^1.2.8",
79
79
  "array-flat-polyfill": "^1.0.1",
80
80
  "clsx": "^2.1.1",
81
- "dompurify": "^3.3.2",
81
+ "dompurify": "^3.4.0",
82
82
  "embla-carousel": "^8.6.0",
83
83
  "embla-carousel-autoplay": "^8.6.0",
84
84
  "embla-carousel-react": "^8.6.0",
85
- "es-toolkit": "^1.44.0",
85
+ "es-toolkit": "^1.46.0",
86
86
  "highlight.js": "^11.11.1",
87
87
  "highlightjs-curl": "^1.3.0",
88
- "js-cookie": "^3.0.5",
88
+ "js-cookie": "^3.0.7",
89
89
  "react": "^18.2.0",
90
90
  "react-dom": "^18.3.1",
91
91
  "scroll-lock": "^2.1.4",
92
92
  "swr": "^2.4.0",
93
- "tailwind-merge": "^3.4.1"
93
+ "tailwind-merge": "^3.6.0"
94
94
  },
95
95
  "peerDependencies": {
96
96
  "mixpanel-browser": "^2.60.0 || <3",
@@ -122,7 +122,7 @@
122
122
  "pre-release": "./scripts/pre-release.sh",
123
123
  "release": "./scripts/release.sh",
124
124
  "start": "vite --port 5000",
125
- "storybook": "pnpm build && storybook dev -p 6006",
125
+ "storybook": "pnpm build && storybook dev -p ${STORYBOOK_UI_PORT:-3200}",
126
126
  "build-storybook": "pnpm build && storybook build --quiet -o preview",
127
127
  "heroku-postbuild": "pnpm build-storybook && ./scripts/setup-auth.sh",
128
128
  "test": "vitest --run",