diff --git a/apps/web/package.json b/apps/web/package.json index 2bc47c8d2cb..1e6b5a8d85e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -148,6 +148,7 @@ "@types/file-saver": "^2.0.3", "@types/glob-to-regexp": "^0.4.1", "@types/jest": "30.0.0", + "@types/jest-image-snapshot": "^6.4.1", "@types/jitsi-meet": "^2.0.2", "@types/jsrsasign": "^10.5.4", "@types/lodash": "^4.14.168", @@ -171,6 +172,7 @@ "babel-loader": "^10.0.0", "babel-plugin-jsx-remove-data-test-id": "^3.0.0", "blob-polyfill": "^9.0.0", + "canvas": "^3.0.0", "copy-webpack-plugin": "^14.0.0", "css-loader": "^7.0.0", "css-minimizer-webpack-plugin": "^8.0.0", @@ -196,6 +198,7 @@ "jest-canvas-mock": "^2.5.2", "jest-environment-jsdom": "^30.2.0", "jest-fixed-jsdom": "^0.0.11", + "jest-image-snapshot": "^6.5.1", "jest-mock": "^30.0.0", "jest-raw-loader": "^1.0.1", "jsqr": "^1.4.0", diff --git a/apps/web/src/favicon.ts b/apps/web/src/favicon.ts index 8d014157f6f..652f17e41d5 100644 --- a/apps/web/src/favicon.ts +++ b/apps/web/src/favicon.ts @@ -98,21 +98,32 @@ abstract class IconRenderer { const opt = this.options(n, params); let more = false; + // Font-height multiplier applied to opt.h to ensure text fits the badge. + let fontScale: number; if (!this.baseImage) { - // If we omit the background, assume the entire canvas is our target. + // If we omit the background, the entire canvas is our target. + // Keep it a circle inside the square canvas (favicons / overlay + // icons are square) and shrink the text for multi-digit counts + // so it doesn't overflow the fixed-width circle. opt.x = 0; opt.y = 0; opt.w = this.canvas.width; opt.h = this.canvas.height; - } - if (opt.len === 2) { + if (opt.len === 2) fontScale = 0.65; + else if (opt.len >= 3) fontScale = 0.5; + else fontScale = 1; + } else if (opt.len === 2) { opt.x = opt.x - opt.w * 0.4; opt.w = opt.w * 1.4; more = true; + fontScale = 1; } else if (opt.len >= 3) { opt.x = opt.x - opt.w * 0.65; opt.w = opt.w * 1.65; more = true; + fontScale = 0.85; + } else { + fontScale = 1; } this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); @@ -120,7 +131,7 @@ abstract class IconRenderer { this.context.drawImage(this.baseImage, 0, 0, this.canvas.width, this.canvas.height); } this.context.beginPath(); - const fontSize = Math.floor(opt.h * (typeof opt.n === "number" && opt.n > 99 ? 0.85 : 1)) + "px"; + const fontSize = Math.floor(opt.h * fontScale) + "px"; this.context.font = `${params.fontWeight} ${fontSize} ${params.fontFamily}`; this.context.textAlign = "center"; @@ -145,12 +156,19 @@ abstract class IconRenderer { this.context.stroke(); this.context.fillStyle = params.textColor; - if (typeof opt.n === "number" && opt.n > 999) { - const count = (opt.n > 9999 ? 9 : Math.floor(opt.n / 1000)) + "k+"; - this.context.fillText(count, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.2)); - } else { - this.context.fillText("" + opt.n, Math.floor(opt.x + opt.w / 2), Math.floor(opt.y + opt.h - opt.h * 0.15)); - } + const text = + typeof opt.n === "number" && opt.n > 999 + ? (opt.n > 9999 ? 9 : Math.floor(opt.n / 1000)) + "k+" + : "" + opt.n; + // Centre the glyph vertically on the badge using its measured bounding + // box. `actualBoundingBoxAscent` is the distance from the alphabetic + // baseline up to the top of the glyph, so placing the baseline at + // `centre + ascent/2` puts the glyph's visual centre at the badge centre, + // regardless of font size or font metrics. + const metrics = this.context.measureText(text); + const textX = Math.floor(opt.x + opt.w / 2); + const textY = Math.floor(opt.y + opt.h / 2 + metrics.actualBoundingBoxAscent / 2); + this.context.fillText(text, textX, textY); this.context.closePath(); } @@ -176,7 +194,14 @@ export class BadgeOverlayRenderer extends IconRenderer { return null; } - this.circle(contents, { ...(bgColor ? { bgColor } : undefined) }); + // Windows native notification badges clamp at "99+" + // (https://learn.microsoft.com/en-us/windows/apps/develop/notifications/badges), + // and the overlay canvas is only 16x16, so we follow the same convention here. + // This only affects the Windows taskbar overlay; browser-tab favicons + // (Favicon below) still render larger counts as e.g. "1k+". + const clamped = typeof contents === "number" && contents > 99 ? "99+" : contents; + + this.circle(clamped, { ...(bgColor ? { bgColor } : undefined) }); return new Promise((resolve, reject) => { this.canvas.toBlob( (blob) => { diff --git a/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-1-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-1-1-snap.png new file mode 100644 index 00000000000..6a573b608d8 Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-1-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-10-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-10-1-snap.png new file mode 100644 index 00000000000..9dd9f2aeda0 Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-10-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-100-clamped-to-99-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-100-clamped-to-99-1-snap.png new file mode 100644 index 00000000000..89b3449dde6 Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-100-clamped-to-99-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-9-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-9-1-snap.png new file mode 100644 index 00000000000..1519f3e0a90 Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-9-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-99-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-99-1-snap.png new file mode 100644 index 00000000000..3156198650f Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-99-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-9999-clamped-to-99-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-9999-clamped-to-99-1-snap.png new file mode 100644 index 00000000000..89b3449dde6 Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-9999-clamped-to-99-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-error-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-error-1-snap.png new file mode 100644 index 00000000000..91241fbbdcd Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/badge-overlay-test-ts-badge-overlay-renderer-renders-count-error-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-1-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-1-1-snap.png new file mode 100644 index 00000000000..dabab8adb47 Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-1-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-10-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-10-1-snap.png new file mode 100644 index 00000000000..4a8ecc6a6fc Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-10-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-100-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-100-1-snap.png new file mode 100644 index 00000000000..24794f715f0 Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-100-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-1000-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-1000-1-snap.png new file mode 100644 index 00000000000..e7218877b25 Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-1000-1-snap.png differ diff --git a/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-99-1-snap.png b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-99-1-snap.png new file mode 100644 index 00000000000..79fec5bb51e Binary files /dev/null and b/apps/web/test/unit-tests/__image_snapshots__/favicon-image-test-ts-favicon-over-base-image-badges-favicon-with-99-1-snap.png differ diff --git a/apps/web/test/unit-tests/badge-overlay-test.ts b/apps/web/test/unit-tests/badge-overlay-test.ts new file mode 100644 index 00000000000..df87f11c54c --- /dev/null +++ b/apps/web/test/unit-tests/badge-overlay-test.ts @@ -0,0 +1,104 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +/* + * Tests for BadgeOverlayRenderer (the 16x16 PNG used as the Windows taskbar overlay). + * + * Approach: + * 1. Swap jest-canvas-mock's HTMLCanvasElement stubs for shims backed by + * node-canvas so real pixels come out of toBlob(). + * 2. Drive the real BadgeOverlayRenderer and compare the PNG bytes against a + * stored baseline via jest-image-snapshot. + */ + +import { createCanvas, type Canvas } from "canvas"; +import { toMatchImageSnapshot } from "jest-image-snapshot"; + +import { BadgeOverlayRenderer } from "../../src/favicon"; + +expect.extend({ toMatchImageSnapshot }); + +// jest-canvas-mock (configured globally via setupFiles) replaces +// HTMLCanvasElement with stubs that record calls but don't rasterise. For +// pixel-accurate output we re-patch the prototype to delegate to node-canvas. +beforeAll(() => { + const backing = new WeakMap(); + const get = (el: HTMLCanvasElement): Canvas => { + let c = backing.get(el); + if (!c) { + c = createCanvas(1, 1); + backing.set(el, c); + } + return c; + }; + + // Mirror width/height onto the node-canvas. Setting either on a real + // HTMLCanvasElement resizes and clears it; node-canvas behaves the same + // when you assign to its width/height. + Object.defineProperty(HTMLCanvasElement.prototype, "width", { + configurable: true, + get(): number { + return get(this).width; + }, + set(v: number): void { + get(this).width = v; + }, + }); + Object.defineProperty(HTMLCanvasElement.prototype, "height", { + configurable: true, + get(): number { + return get(this).height; + }, + set(v: number): void { + get(this).height = v; + }, + }); + + HTMLCanvasElement.prototype.getContext = function ( + this: HTMLCanvasElement, + type: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): any { + return get(this).getContext(type as "2d"); + }; + HTMLCanvasElement.prototype.toBlob = function ( + this: HTMLCanvasElement, + cb: BlobCallback, + type = "image/png", + ): void { + const buf = get(this).toBuffer("image/png"); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + cb(new Blob([buf as any], { type })); + }; +}); + +// The overlay only ships on Windows and matches the platform's own +// notification badges, which clamp at "99+". Anything above 99 should +// render identically, so we just snapshot the boundary cases. +const SAMPLES: Array<{ name: string; value: number | string; bgColor?: string }> = [ + { name: "1", value: 1 }, + { name: "9", value: 9 }, + { name: "10", value: 10 }, + { name: "99", value: 99 }, + { name: "100 (clamped to 99+)", value: 100 }, + { name: "9999 (clamped to 99+)", value: 9999 }, + { name: "error", value: "×", bgColor: "#f00" }, +]; + +describe("BadgeOverlayRenderer", () => { + it.each(SAMPLES)("renders count $name", async ({ value, bgColor }) => { + const renderer = new BadgeOverlayRenderer(); + const buf = await renderer.render(value, bgColor); + expect(buf).not.toBeNull(); + expect(Buffer.from(buf!)).toMatchImageSnapshot(); + }); + + it("returns null when count is 0", async () => { + const renderer = new BadgeOverlayRenderer(); + expect(await renderer.render(0)).toBeNull(); + }); +}); diff --git a/apps/web/test/unit-tests/favicon-image-test.ts b/apps/web/test/unit-tests/favicon-image-test.ts new file mode 100644 index 00000000000..331160ec18c --- /dev/null +++ b/apps/web/test/unit-tests/favicon-image-test.ts @@ -0,0 +1,150 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +/* + * Tests for the Favicon class (browser-tab favicon with a badge drawn over + * the Element logo). + * + * Approach: + * 1. Swap jest-canvas-mock's HTMLCanvasElement stubs for shims backed by + * node-canvas so real pixels come out. + * 2. Pre-load the real Element favicon (apps/web/res/vector-icons/144.png) + * as a node-canvas Image, then have document.createElement("img") return + * it so Favicon's internal baseImage is already populated. + * 3. Drive the real Favicon class, run timers, then read the badged PNG out + * of the 's href and compare via jest-image-snapshot. + */ + +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { createCanvas, loadImage, type Canvas, type Image } from "canvas"; +import { toMatchImageSnapshot } from "jest-image-snapshot"; + +import Favicon from "../../src/favicon"; + +expect.extend({ toMatchImageSnapshot }); +jest.useFakeTimers(); + +const BASE_FAVICON = path.resolve(__dirname, "../../res/vector-icons/144.png"); +let baseImage: Image; + +beforeAll(async () => { + baseImage = await loadImage(await fs.readFile(BASE_FAVICON)); + + // Replace jest-canvas-mock's HTMLCanvasElement stubs with shims backed by + // node-canvas so toDataURL produces real PNG bytes. + const backing = new WeakMap(); + const get = (el: HTMLCanvasElement): Canvas => { + let c = backing.get(el); + if (!c) { + c = createCanvas(1, 1); + backing.set(el, c); + } + return c; + }; + + Object.defineProperty(HTMLCanvasElement.prototype, "width", { + configurable: true, + get(): number { + return get(this).width; + }, + set(v: number): void { + get(this).width = v; + }, + }); + Object.defineProperty(HTMLCanvasElement.prototype, "height", { + configurable: true, + get(): number { + return get(this).height; + }, + set(v: number): void { + get(this).height = v; + }, + }); + + HTMLCanvasElement.prototype.getContext = function ( + this: HTMLCanvasElement, + type: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ): any { + return get(this).getContext(type as "2d"); + }; + HTMLCanvasElement.prototype.toDataURL = function (this: HTMLCanvasElement, type = "image/png"): string { + return "data:" + type + ";base64," + get(this).toBuffer("image/png").toString("base64"); + }; +}); + +beforeEach(() => { + // Reset the document between tests so each starts from a clean slate. + document.getElementsByTagName("head")[0]?.remove(); + const head = document.createElement("head"); + document.documentElement.prepend(head); + + // Add a for Favicon to discover. + const link = document.createElement("link"); + link.rel = "icon"; + link.href = "favicon.png"; + document.head.appendChild(link); + + // Intercept Favicon's internal document.createElement("img") and return + // the pre-loaded node-canvas Image so its bytes are ready for drawImage. + // We add the jsdom-flavoured shims (setAttribute, onload trigger) that + // Favicon relies on. + const originalCreateElement = document.createElement.bind(document); + jest.spyOn(document, "createElement").mockImplementation((tag: string) => { + if (tag !== "img") return originalCreateElement(tag); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const img = baseImage as any; + img.setAttribute = (name: string, _value: string): void => { + if (name === "src" && typeof img.onload === "function") { + // src is already loaded; fire the next-tick onload Favicon expects. + queueMicrotask(() => img.onload?.()); + } + }; + return img; + }); +}); + +afterEach(() => { + jest.restoreAllMocks(); +}); + +const SAMPLES = [ + { name: "1", value: 1 }, + { name: "10", value: 10 }, + { name: "99", value: 99 }, + { name: "100", value: 100 }, + { name: "1000", value: 1000 }, +]; + +const linkHref = (): string => document.querySelector("link[rel='icon']")!.getAttribute("href")!; + +describe("Favicon (over base image)", () => { + it.each(SAMPLES)("badges favicon with $name", async ({ value }) => { + const fav = new Favicon(); + fav.badge(value); + await jest.runAllTimersAsync(); + + const href = linkHref(); + expect(href).toMatch(/^data:image\/png;base64,/); + const png = Buffer.from(href.split(",")[1], "base64"); + expect(png).toMatchImageSnapshot(); + }); + + it("badge(0) clears the badge and rewrites the link", async () => { + const fav = new Favicon(); + fav.badge(5); + await jest.runAllTimersAsync(); + const withBadge = linkHref(); + + fav.badge(0); + await jest.runAllTimersAsync(); + const cleared = linkHref(); + + expect(withBadge).not.toEqual(cleared); + }); +}); diff --git a/package.json b/package.json index e55184abf81..20646b8e794 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ }, "pnpm": { "onlyBuiltDependencies": [ + "canvas", "matrix-js-sdk" ], "ignoredBuiltDependencies": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10136ebc28d..2c364813c10 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -327,7 +327,7 @@ importers: version: 6.0.3 vitest: specifier: 'catalog:' - version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) vitest-sonar-reporter: specifier: 'catalog:' version: 3.0.0(vitest@4.1.7) @@ -468,7 +468,7 @@ importers: version: 1.0.3 matrix-js-sdk: specifier: github:matrix-org/matrix-js-sdk#develop - version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ec54b54a8964e87832421a8e1faf60ede9364798 + version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e1a7f6532b82638dcd3ceabe67e5f136e2d54e0d matrix-widget-api: specifier: ^1.17.0 version: 1.17.0 @@ -662,6 +662,9 @@ importers: '@types/jest': specifier: 30.0.0 version: 30.0.0 + '@types/jest-image-snapshot': + specifier: ^6.4.1 + version: 6.4.1 '@types/jitsi-meet': specifier: ^2.0.2 version: 2.0.5 @@ -731,6 +734,9 @@ importers: blob-polyfill: specifier: ^9.0.0 version: 9.0.20240710 + canvas: + specifier: ^3.0.0 + version: 3.2.3 copy-webpack-plugin: specifier: ^14.0.0 version: 14.0.0(webpack@5.107.1) @@ -802,10 +808,13 @@ importers: version: 2.5.2 jest-environment-jsdom: specifier: ^30.2.0 - version: 30.4.1 + version: 30.4.1(canvas@3.2.3) jest-fixed-jsdom: specifier: ^0.0.11 - version: 0.0.11(patch_hash=b4bc876eca343b57d231c1cf9817ddb13ff04503ece7794d1690f342f6289ad3)(jest-environment-jsdom@30.4.1) + version: 0.0.11(patch_hash=b4bc876eca343b57d231c1cf9817ddb13ff04503ece7794d1690f342f6289ad3)(jest-environment-jsdom@30.4.1(canvas@3.2.3)) + jest-image-snapshot: + specifier: ^6.5.1 + version: 6.5.2(jest@30.4.2(@types/node@18.19.130)(babel-plugin-macros@3.1.0)) jest-mock: specifier: ^30.0.0 version: 30.4.1 @@ -974,7 +983,7 @@ importers: version: 8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4) vitest: specifier: 'catalog:' - version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) vitest-sonar-reporter: specifier: 'catalog:' version: 3.0.0(vitest@4.1.7) @@ -1237,7 +1246,7 @@ importers: version: 0.28.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) vitest: specifier: 'catalog:' - version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) vitest-sonar-reporter: specifier: 'catalog:' version: 3.0.0(vitest@4.1.7) @@ -1249,7 +1258,7 @@ importers: version: 4.1.7(@vitest/browser@4.1.7)(vitest@4.1.7) vitest: specifier: 'catalog:' - version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + version: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) vitest-sonar-reporter: specifier: 'catalog:' version: 3.0.0(vitest@4.1.7) @@ -5636,6 +5645,9 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/jest-image-snapshot@6.4.1': + resolution: {integrity: sha512-pj3Sdc7Cx5mMLUttPprazSDQCur2cr512Dm38e9aAHI55LDxEhqdyqzK9myC4EmEy7sPAF2nGJ8zifX4qso7sQ==} + '@types/jest@30.0.0': resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==} @@ -5729,6 +5741,9 @@ packages: '@types/picomatch@4.0.2': resolution: {integrity: sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==} + '@types/pixelmatch@5.2.6': + resolution: {integrity: sha512-wC83uexE5KGuUODn6zkm9gMzTwdY5L0chiK+VrKcDfEjzxh1uadlWTvOmAbCpnM9zx/Ww3f8uKlYQVnO/TrqVg==} + '@types/plist@3.0.5': resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} @@ -7007,6 +7022,10 @@ packages: caniuse-lite@1.0.30001793: resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + canvas@3.2.3: + resolution: {integrity: sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==} + engines: {node: ^18.12.0 || >= 20.9.0} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -7744,6 +7763,10 @@ packages: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -8456,6 +8479,10 @@ packages: resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} engines: {node: '>= 0.8.0'} + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -8766,6 +8793,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stdin@5.0.1: + resolution: {integrity: sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==} + engines: {node: '>=0.12.0'} + get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -8781,6 +8812,9 @@ packages: get-tsconfig@4.14.0: resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + github-markdown-css@5.9.0: resolution: {integrity: sha512-tmT5sY+zvg2302XLYEfH2mtkViIM1SWf2nvYoF5N1ZsO0V6B2qZTiw3GOzw4vpjLygK/KG35qRlPFweHqfzz5w==} engines: {node: '>=10'} @@ -8850,6 +8884,9 @@ packages: globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + glur@1.1.2: + resolution: {integrity: sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -9579,6 +9616,15 @@ packages: resolution: {integrity: sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-image-snapshot@6.5.2: + resolution: {integrity: sha512-frenWThr5ddnnokcX5N4gwi41hA5TiUOdhv/JoGcJrOaktHjrk4/7XbiHKW52lgKX+vei6QkRlgM7fkYQ15nPg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + jest: '>=20 <31' + peerDependenciesMeta: + jest: + optional: true + jest-leak-detector@30.4.1: resolution: {integrity: sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -10115,8 +10161,8 @@ packages: matrix-events-sdk@0.0.1: resolution: {integrity: sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ec54b54a8964e87832421a8e1faf60ede9364798: - resolution: {gitHosted: true, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ec54b54a8964e87832421a8e1faf60ede9364798} + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e1a7f6532b82638dcd3ceabe67e5f136e2d54e0d: + resolution: {gitHosted: true, integrity: sha512-gEbgqEiUhz1iryI39+yuAmygIvM9yP2Cbz8c77AQvIqB2dXiA95qK+DhxAt9rkubw40f2fG3Dy55WUWG88skaQ==, tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e1a7f6532b82638dcd3ceabe67e5f136e2d54e0d} version: 41.6.0 engines: {node: '>=22.0.0'} @@ -10416,6 +10462,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + napi-postinstall@0.3.4: resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -10449,6 +10498,9 @@ packages: node-addon-api@1.7.2: resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} + node-api-version@0.2.1: resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==} @@ -10851,6 +10903,10 @@ packages: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} + pixelmatch@5.3.0: + resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} + hasBin: true + pixelmatch@7.2.0: resolution: {integrity: sha512-xhcb4yHu9sM/G7foGzoLtXYcC0zHEaOXXjRKhGup0fw78Nf2Tkiapv4EQyMzrbcmQPsllAI7DbFY2UT7PlI9Pg==} hasBin: true @@ -10902,10 +10958,18 @@ packages: engines: {node: '>=20'} hasBin: true + pngjs@3.4.0: + resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} + engines: {node: '>=4.0.0'} + pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} + pngjs@6.0.0: + resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} + engines: {node: '>=12.13.0'} + pngjs@7.0.0: resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} engines: {node: '>=14.19.0'} @@ -11372,6 +11436,12 @@ packages: preact@10.28.3: resolution: {integrity: sha512-tCmoRkPQLpBeWzpmbhryairGnhW9tKV6c6gr/w+RhoRoKEJwsjzipwp//1oCpGPOchvSLaAPlpcJi9MwMmoPyA==} + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available. + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -11541,6 +11611,10 @@ packages: peerDependencies: webpack: ^4.0.0 || ^5.0.0 + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + re-resizable@6.11.2: resolution: {integrity: sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==} peerDependencies: @@ -12128,6 +12202,12 @@ packages: resolution: {integrity: sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==} engines: {node: ^20.17.0 || >=22.9.0} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -12406,6 +12486,10 @@ packages: resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} engines: {node: '>=12'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -12804,6 +12888,9 @@ packages: resolution: {integrity: sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==} engines: {node: ^20.17.0 || >=22.9.0} + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + tunnel@0.0.6: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} @@ -15441,7 +15528,7 @@ snapshots: '@fetch-mock/vitest@0.2.18(vitest@4.1.7)': dependencies: fetch-mock: 12.6.0 - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) '@figspec/components@2.1.0': {} @@ -15635,7 +15722,7 @@ snapshots: '@jest/diff-sequences@30.4.0': {} - '@jest/environment-jsdom-abstract@30.4.1(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))': + '@jest/environment-jsdom-abstract@30.4.1(canvas@3.2.3)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))': dependencies: '@jest/environment': 30.4.1 '@jest/fake-timers': 30.4.1 @@ -15644,7 +15731,9 @@ snapshots: '@types/node': 18.19.130 jest-mock: 30.4.1 jest-util: 30.4.1 - jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f) + jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3) + optionalDependencies: + canvas: 3.2.3 '@jest/environment@30.4.1': dependencies: @@ -17969,7 +18058,7 @@ snapshots: '@vitest/browser': 4.1.7(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.7) '@vitest/browser-playwright': 4.1.7(playwright@1.60.0)(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.7) '@vitest/runner': 4.1.7 - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) transitivePeerDependencies: - react - react-dom @@ -18494,6 +18583,12 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 + '@types/jest-image-snapshot@6.4.1': + dependencies: + '@types/jest': 30.0.0 + '@types/pixelmatch': 5.2.6 + ssim.js: 3.5.0 + '@types/jest@30.0.0': dependencies: expect: 30.4.1 @@ -18598,6 +18693,10 @@ snapshots: '@types/picomatch@4.0.2': {} + '@types/pixelmatch@5.2.6': + dependencies: + '@types/node': 18.19.130 + '@types/plist@3.0.5': dependencies: '@types/node': 18.19.130 @@ -18954,7 +19053,7 @@ snapshots: '@vitest/mocker': 4.1.7(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) playwright: 1.60.0 tinyrainbow: 3.1.0 - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) transitivePeerDependencies: - bufferutil - msw @@ -18970,7 +19069,7 @@ snapshots: pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -18990,7 +19089,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) optionalDependencies: '@vitest/browser': 4.1.7(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.7) @@ -20164,6 +20263,11 @@ snapshots: caniuse-lite@1.0.30001793: {} + canvas@3.2.3: + dependencies: + node-addon-api: 7.1.1 + prebuild-install: 7.1.3 + ccount@2.0.1: {} chai@5.3.3: @@ -20936,6 +21040,8 @@ snapshots: deep-eql@5.0.2: {} + deep-extend@0.6.0: {} + deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -21864,6 +21970,8 @@ snapshots: exit-x@0.2.2: {} + expand-template@2.0.3: {} + expect-type@1.3.0: {} expect@30.4.1: @@ -22244,6 +22352,8 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stdin@5.0.1: {} + get-stream@5.2.0: dependencies: pump: 3.0.4 @@ -22260,6 +22370,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + github-from-package@0.0.0: {} + github-markdown-css@5.9.0: {} gl-matrix@3.4.4: {} @@ -22346,6 +22458,8 @@ snapshots: globrex@0.1.2: {} + glur@1.1.2: {} + gopd@1.2.0: {} got@11.8.6: @@ -23113,11 +23227,13 @@ snapshots: jest-util: 30.4.1 pretty-format: 30.4.1 - jest-environment-jsdom@30.4.1: + jest-environment-jsdom@30.4.1(canvas@3.2.3): dependencies: '@jest/environment': 30.4.1 - '@jest/environment-jsdom-abstract': 30.4.1(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)) - jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f) + '@jest/environment-jsdom-abstract': 30.4.1(canvas@3.2.3)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3)) + jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3) + optionalDependencies: + canvas: 3.2.3 transitivePeerDependencies: - bufferutil - supports-color @@ -23133,9 +23249,9 @@ snapshots: jest-util: 30.4.1 jest-validate: 30.4.1 - jest-fixed-jsdom@0.0.11(patch_hash=b4bc876eca343b57d231c1cf9817ddb13ff04503ece7794d1690f342f6289ad3)(jest-environment-jsdom@30.4.1): + jest-fixed-jsdom@0.0.11(patch_hash=b4bc876eca343b57d231c1cf9817ddb13ff04503ece7794d1690f342f6289ad3)(jest-environment-jsdom@30.4.1(canvas@3.2.3)): dependencies: - jest-environment-jsdom: 30.4.1 + jest-environment-jsdom: 30.4.1(canvas@3.2.3) jest-haste-map@30.4.1: dependencies: @@ -23152,6 +23268,18 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + jest-image-snapshot@6.5.2(jest@30.4.2(@types/node@18.19.130)(babel-plugin-macros@3.1.0)): + dependencies: + chalk: 4.1.2 + get-stdin: 5.0.1 + glur: 1.1.2 + lodash: 4.18.1 + pixelmatch: 5.3.0 + pngjs: 3.4.0 + ssim.js: 3.5.0 + optionalDependencies: + jest: 30.4.2(@types/node@18.19.130)(babel-plugin-macros@3.1.0) + jest-leak-detector@30.4.1: dependencies: '@jest/get-type': 30.1.0 @@ -23374,7 +23502,7 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f): + jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3): dependencies: cssstyle: 4.6.0 data-urls: 5.0.0 @@ -23396,6 +23524,8 @@ snapshots: whatwg-url: 14.2.0 ws: 8.21.0 xml-name-validator: 5.0.0 + optionalDependencies: + canvas: 3.2.3 transitivePeerDependencies: - bufferutil - supports-color @@ -23819,7 +23949,7 @@ snapshots: matrix-events-sdk@0.0.1: {} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/ec54b54a8964e87832421a8e1faf60ede9364798: + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/e1a7f6532b82638dcd3ceabe67e5f136e2d54e0d: dependencies: '@babel/runtime': 7.29.2 '@matrix-org/matrix-sdk-crypto-wasm': 18.2.0 @@ -24141,6 +24271,8 @@ snapshots: nanoid@3.3.12: {} + napi-build-utils@2.0.0: {} + napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} @@ -24165,6 +24297,8 @@ snapshots: node-addon-api@1.7.2: optional: true + node-addon-api@7.1.1: {} + node-api-version@0.2.1: dependencies: semver: 7.8.1 @@ -24863,6 +24997,10 @@ snapshots: pirates@4.0.7: {} + pixelmatch@5.3.0: + dependencies: + pngjs: 6.0.0 + pixelmatch@7.2.0: dependencies: pngjs: 7.0.0 @@ -24924,8 +25062,12 @@ snapshots: minimist: 1.2.8 pngjs: 7.0.0 + pngjs@3.4.0: {} + pngjs@5.0.0: {} + pngjs@6.0.0: {} + pngjs@7.0.0: {} points-on-curve@0.2.0: {} @@ -25441,6 +25583,21 @@ snapshots: preact@10.28.3: {} + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 4.31.0 + pump: 3.0.4 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + prelude-ls@1.2.1: {} prettier@2.8.8: {} @@ -25616,6 +25773,13 @@ snapshots: schema-utils: 3.3.0 webpack: 5.107.1(lightningcss@1.32.0)(postcss@8.5.15)(webpack-cli@7.0.2) + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + re-resizable@6.11.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: react: 19.2.6 @@ -26362,6 +26526,14 @@ snapshots: transitivePeerDependencies: - supports-color + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-update-notifier@2.0.0: dependencies: semver: 7.8.1 @@ -26558,7 +26730,7 @@ snapshots: pathe: 2.0.3 storybook: 10.4.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@testing-library/dom@10.4.1)(@types/react@19.2.14)(prettier@3.8.3)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) type-plus: 8.0.0-beta.8(typescript@6.0.3) - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) vitest-plugin-vis: 5.1.1(@vitest/browser-playwright@4.1.7)(@vitest/browser@4.1.7)(babel-plugin-macros@3.1.0)(typescript@6.0.3)(vitest@4.1.7) transitivePeerDependencies: - '@vitest/browser-playwright' @@ -26731,6 +26903,8 @@ snapshots: strip-indent@4.1.1: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} strip-json-comments@5.0.3: {} @@ -27199,6 +27373,10 @@ snapshots: transitivePeerDependencies: - supports-color + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + tunnel@0.0.6: {} tweetnacl@0.14.5: {} @@ -27659,7 +27837,7 @@ snapshots: rimraf: 6.1.3 ssim.js: 3.5.0 type-plus: 8.0.0-beta.8(typescript@6.0.3) - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) optionalDependencies: '@vitest/browser-playwright': 4.1.7(playwright@1.60.0)(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.7) transitivePeerDependencies: @@ -27668,9 +27846,9 @@ snapshots: vitest-sonar-reporter@3.0.0(vitest@4.1.7): dependencies: - vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) + vitest: 4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) - vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)): + vitest@4.1.7(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.7)(@vitest/coverage-v8@4.1.7)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3))(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)): dependencies: '@vitest/expect': 4.1.7 '@vitest/mocker': 4.1.7(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4)) @@ -27697,7 +27875,7 @@ snapshots: '@types/node': 18.19.130 '@vitest/browser-playwright': 4.1.7(playwright@1.60.0)(vite@8.0.13(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.15))(terser@5.48.0)(tsx@4.21.0)(yaml@2.8.4))(vitest@4.1.7) '@vitest/coverage-v8': 4.1.7(@vitest/browser@4.1.7)(vitest@4.1.7) - jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f) + jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f)(canvas@3.2.3) transitivePeerDependencies: - msw