From 9a5f94078767dbc9a41c80f330a13aa416ec0d81 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:14:05 +0100 Subject: [PATCH 01/23] Update dependency mailpit-api to v2 (#33317) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/playwright-common/package.json | 2 +- pnpm-lock.yaml | 56 ++++--------------------- 2 files changed, 10 insertions(+), 48 deletions(-) diff --git a/packages/playwright-common/package.json b/packages/playwright-common/package.json index 978f924392..6effc87d1d 100644 --- a/packages/playwright-common/package.json +++ b/packages/playwright-common/package.json @@ -31,7 +31,7 @@ "@testcontainers/postgresql": "^11.0.0", "glob": "^13.0.5", "lodash-es": "^4.17.23", - "mailpit-api": "^1.2.0", + "mailpit-api": "^2.0.0", "strip-ansi": "^7.1.0", "testcontainers": "^11.0.0", "wait-on": "^9.0.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f038a47476..d4d40a11e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -446,7 +446,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/c09d3ca9e60b32e09111a83c6c7462d5442c6774 + version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/60993dd10ada731e37db81873fede2a0afa93a6b matrix-widget-api: specifier: ^1.17.0 version: 1.17.0 @@ -975,8 +975,8 @@ importers: specifier: ^4.17.23 version: 4.18.1 mailpit-api: - specifier: ^1.2.0 - version: 1.9.0(react@19.2.5) + specifier: ^2.0.0 + version: 2.0.0 playwright-core: specifier: 'catalog:' version: 1.59.1 @@ -8162,9 +8162,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - event-target-polyfill@0.0.4: - resolution: {integrity: sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==} - event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -9218,11 +9215,6 @@ packages: resolution: {integrity: sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==} engines: {node: '>=10'} - isomorphic-ws@5.0.0: - resolution: {integrity: sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==} - peerDependencies: - ws: '*' - istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -9810,8 +9802,8 @@ packages: magicast@0.5.2: resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} - mailpit-api@1.9.0: - resolution: {integrity: sha512-KoZIR7vfE7boEBLZ7fOw6SpGxn2pF207eLEWCoYePKAayFe3zYxGUgxNwxzEAp0hwVYSoe0lJQdeprHLEuelhA==} + mailpit-api@2.0.0: + resolution: {integrity: sha512-zK68B0CRVZaJS1PaX7omFhKUKSLp4JMMpAUTRYbCmcrgJ9nguIh4sS+RRUyFeW1nykDNH8PVlRz/WSXsq9ZqlQ==} engines: {node: '>=18.0.0'} make-dir@4.0.0: @@ -9863,8 +9855,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/c09d3ca9e60b32e09111a83c6c7462d5442c6774: - resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c09d3ca9e60b32e09111a83c6c7462d5442c6774} + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/60993dd10ada731e37db81873fede2a0afa93a6b: + resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/60993dd10ada731e37db81873fede2a0afa93a6b} version: 41.4.0 engines: {node: '>=22.0.0'} @@ -10486,14 +10478,6 @@ packages: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} - partysocket@1.1.18: - resolution: {integrity: sha512-SyuvH9VavWOSa14v6dYdp3yfSUDII4BQB1+TkGOFBkjfZKjnDBiba4fhdhwBlqGBkqw4ea3gTA1DYhSffX24Wg==} - peerDependencies: - react: '>=17' - peerDependenciesMeta: - react: - optional: true - pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} @@ -21410,8 +21394,6 @@ snapshots: etag@1.8.1: {} - event-target-polyfill@0.0.4: {} - event-target-shim@5.0.1: {} eventemitter3@4.0.7: {} @@ -22540,10 +22522,6 @@ snapshots: isomorphic-timers-promises@1.0.1: {} - isomorphic-ws@5.0.0(ws@8.20.0): - dependencies: - ws: 8.20.0 - istanbul-lib-coverage@3.2.2: {} istanbul-lib-instrument@6.0.3: @@ -23323,17 +23301,7 @@ snapshots: '@babel/types': 7.29.0 source-map-js: 1.2.1 - mailpit-api@1.9.0(react@19.2.5): - dependencies: - axios: 1.15.2 - isomorphic-ws: 5.0.0(ws@8.20.0) - partysocket: 1.1.18(react@19.2.5) - ws: 8.20.0 - transitivePeerDependencies: - - bufferutil - - debug - - react - - utf-8-validate + mailpit-api@2.0.0: {} make-dir@4.0.0: dependencies: @@ -23424,7 +23392,7 @@ snapshots: matrix-events-sdk@0.0.1: {} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/c09d3ca9e60b32e09111a83c6c7462d5442c6774: + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/60993dd10ada731e37db81873fede2a0afa93a6b: dependencies: '@babel/runtime': 7.29.2 '@matrix-org/matrix-sdk-crypto-wasm': 18.2.0 @@ -24242,12 +24210,6 @@ snapshots: parseurl@1.3.3: {} - partysocket@1.1.18(react@19.2.5): - dependencies: - event-target-polyfill: 0.0.4 - optionalDependencies: - react: 19.2.5 - pascal-case@3.1.2: dependencies: no-case: 3.0.4 From e9ce54928f18219fd3c0fa35d3e09b660e38245e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 14:14:23 +0100 Subject: [PATCH 02/23] Update pnpm to v10.33.2 (#33314) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/desktop/package.json | 2 +- apps/web/package.json | 2 +- package.json | 2 +- packages/shared-components/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 3b1f145d0c..fdc5fb0a54 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -105,7 +105,7 @@ "hakDependencies": { "matrix-seshat": "4.2.0" }, - "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", + "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8", "nx": { "includedScripts": [] } diff --git a/apps/web/package.json b/apps/web/package.json index 605b44c9bb..31bbb3aa79 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -244,6 +244,6 @@ "engines": { "node": ">=22.18" }, - "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", + "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8", "private": true } diff --git a/package.json b/package.json index 644e3bf96d..e6979bc72c 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,6 @@ "engines": { "node": ">=22.18" }, - "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", + "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8", "private": true } diff --git a/packages/shared-components/package.json b/packages/shared-components/package.json index 5f834d5432..f3cd793f4a 100644 --- a/packages/shared-components/package.json +++ b/packages/shared-components/package.json @@ -123,7 +123,7 @@ "engines": { "node": ">=20.0.0" }, - "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319", + "packageManager": "pnpm@10.33.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8", "peerDependencies": { "@vector-im/compound-web": "^8.3.5 || ^9.0.0" } From 980a405f76ea7ceecd22d7804f797260944f1d03 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:21:00 +0000 Subject: [PATCH 03/23] Update dependency nx to v22.7.0 (#33316) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- pnpm-lock.yaml | 342 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 324 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index e6979bc72c..27cc32d0b7 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "lodash": "^4.17.21", "mermaid": "^11.13.0", "minimist": "^1.2.6", - "nx": "22.6.5", + "nx": "22.7.0", "prettier": "3.8.3", "typescript": "catalog:", "vitepress": "^1.6.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d4d40a11e7..88aac9c171 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -124,7 +124,7 @@ importers: version: 0.6.0 '@nx-tools/nx-container': specifier: ^7.2.1 - version: 7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(@nx/js@22.6.5(@babel/traverse@7.29.0)(nx@22.6.5))(dotenv@17.4.2)(nx@22.6.5)(tslib@2.8.1) + version: 7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(@nx/js@22.6.5(@babel/traverse@7.29.0)(nx@22.7.0))(dotenv@17.4.2)(nx@22.7.0)(tslib@2.8.1) '@playwright/test': specifier: 'catalog:' version: 1.59.1 @@ -156,8 +156,8 @@ importers: specifier: ^1.2.6 version: 1.2.8 nx: - specifier: 22.6.5 - version: 22.6.5 + specifier: 22.7.0 + version: 22.7.0 prettier: specifier: 3.8.3 version: 3.8.3 @@ -2479,15 +2479,24 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.4.5': + resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} + '@emnapi/core@1.9.2': resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.4.5': + resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} + '@emnapi/runtime@1.9.2': resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + '@emnapi/wasi-threads@1.0.4': + resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} + '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} @@ -2822,6 +2831,10 @@ packages: node-notifier: optional: true + '@jest/diff-sequences@30.0.1': + resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/diff-sequences@30.3.0': resolution: {integrity: sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -3286,55 +3299,109 @@ packages: cpu: [arm64] os: [darwin] + '@nx/nx-darwin-arm64@22.7.0': + resolution: {integrity: sha512-WczfOkv7bVSi0i4bLFmMLqdcRllJr+CLO0ibVapGHxdaOkNPCgSDxBVYC/0eg8yyMsPXQu8daaTvOlODNJw9GQ==} + cpu: [arm64] + os: [darwin] + '@nx/nx-darwin-x64@22.6.5': resolution: {integrity: sha512-9jICxb7vfJ56y/7Yuh3b/n1QJqWxO9xnXKYEs6SO8xPoW/KomVckILGc1C6RQSs6/3ixVJC7k1Dh1wm5tKPFrg==} cpu: [x64] os: [darwin] + '@nx/nx-darwin-x64@22.7.0': + resolution: {integrity: sha512-KGBq43lJbDjIpIAU5/sJFJQokGgCu/0KVe5pO2u9hMO2A9VaVtud5bGbDpkBiWoYBK9BLM3YpbG/RB74unxWxg==} + cpu: [x64] + os: [darwin] + '@nx/nx-freebsd-x64@22.6.5': resolution: {integrity: sha512-6B1wEKpqz5dI3AGMqttAVnA6M3DB/besAtuGyQiymK9ROlta1iuWgCcIYwcCQyhLn2Rx7vqj447KKcgCa8HlVw==} cpu: [x64] os: [freebsd] + '@nx/nx-freebsd-x64@22.7.0': + resolution: {integrity: sha512-DKOHaFC7Ko4+nlin/us8FEfBBgnpifZie4LELgo2nI044GlazGHJArtRSdLq3qKfWXqtieOKO3R4YV09Ax37Rg==} + cpu: [x64] + os: [freebsd] + '@nx/nx-linux-arm-gnueabihf@22.6.5': resolution: {integrity: sha512-xV50B8mnDPboct7JkAHftajI02s+8FszA8WTzhore+YGR+lEKHTLpucwGEaQuMlSdLplH7pQix4B4uK5pcMhZw==} cpu: [arm] os: [linux] + '@nx/nx-linux-arm-gnueabihf@22.7.0': + resolution: {integrity: sha512-LaZSE4FGk8KFyKF8grlhcoBWJabxkn7moO2QfULm3i1GY1AYu7bbxVZG42cAdGENqliCB8V2ARBWrMKq6mqW2g==} + cpu: [arm] + os: [linux] + '@nx/nx-linux-arm64-gnu@22.6.5': resolution: {integrity: sha512-2JkWuMGj+HpW6oPAvU5VdAx1afTnEbiM10Y3YOrl3fipWV4BiP5VDx762QTrfCraP4hl6yqTgvTe7F9xaby+jQ==} cpu: [arm64] os: [linux] libc: [glibc] + '@nx/nx-linux-arm64-gnu@22.7.0': + resolution: {integrity: sha512-g0GJ2avp0wuxHCQ2fjlMVjez4TWBtfj0jLCzO2I9M3B6ui0V+9Wh66DO5pLwyQFGbxODgwz9vwrEEZKlbDrchA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@nx/nx-linux-arm64-musl@22.6.5': resolution: {integrity: sha512-Z/zMqFClnEyqDXouJKEPoWVhMQIif5F0YuECWBYjd3ZLwQsXGTItoh+6Wm3XF/nGMA2uLOHyTq/X7iFXQY3RzA==} cpu: [arm64] os: [linux] libc: [musl] + '@nx/nx-linux-arm64-musl@22.7.0': + resolution: {integrity: sha512-xb72G+LrhZNrWXOAd6aoDQmhzO5GKq6dkAYqhtOTAklCwIo5/4dkvqvFEe5RIQ7pk6RzdD2cUZMTJTr99rSGTg==} + cpu: [arm64] + os: [linux] + libc: [musl] + '@nx/nx-linux-x64-gnu@22.6.5': resolution: {integrity: sha512-FlotSyqNnaXSn0K+yWw+hRdYBwusABrPgKLyixfJIYRzsy+xPKN6pON6vZfqGwzuWF/9mEGReRz+iM8PiW0XSg==} cpu: [x64] os: [linux] libc: [glibc] + '@nx/nx-linux-x64-gnu@22.7.0': + resolution: {integrity: sha512-win7DkwxThhGWJlJ0s9HLxPzam/wz5MbQhKTQmLehHz8mgFJOu6MqzrccDxuT1E93dODedtxJuAyW4c80mpp4A==} + cpu: [x64] + os: [linux] + libc: [glibc] + '@nx/nx-linux-x64-musl@22.6.5': resolution: {integrity: sha512-RVOe2qcwhoIx6mxQURPjUfAW5SEOmT2gdhewvdcvX9ICq1hj5B2VarmkhTg0qroO7xiyqOqwq26mCzoV2I3NgQ==} cpu: [x64] os: [linux] libc: [musl] + '@nx/nx-linux-x64-musl@22.7.0': + resolution: {integrity: sha512-lU7yT+CsZFPCH3dZAVynhyJxa5w8rwCVtbDYbBa703DkNgb8/CQAthyE7NN2fjfzeQ0YpXoV3O/iX6sVY1sdjA==} + cpu: [x64] + os: [linux] + libc: [musl] + '@nx/nx-win32-arm64-msvc@22.6.5': resolution: {integrity: sha512-ZqurqI8VuYnsr2Kn4K4t+Gx6j/BZdf6qz/6Tv4A7XQQ6oNYVQgTqoNEFj+CCkVaIe6aIdCWpousFLqs+ZgBqYQ==} cpu: [arm64] os: [win32] + '@nx/nx-win32-arm64-msvc@22.7.0': + resolution: {integrity: sha512-e8P0ZSPJpaAjiIitcJAjdSCeCxAMjt5OqM6kZYUiwpHVnB31YAEe8feNvQcfvhyMjx/AXFQlBztHh8kANvdlLw==} + cpu: [arm64] + os: [win32] + '@nx/nx-win32-x64-msvc@22.6.5': resolution: {integrity: sha512-i2QFBJIuaYg9BHxrrnBV4O7W9rVL2k0pSIdk/rRp3EYJEU93iUng+qbZiY9wh1xvmXuUCE2G7TRd+8/SG/RFKg==} cpu: [x64] os: [win32] + '@nx/nx-win32-x64-msvc@22.7.0': + resolution: {integrity: sha512-NWLezNDRoZpqs8DudLCGBj214fIIG3cDhkez7eKW/i+s27O3laVCO9QgaFi07P+RSnrnZVJPfOISkE0Ql7gdAw==} + cpu: [x64] + os: [win32] + '@nx/workspace@22.6.5': resolution: {integrity: sha512-/CZtv1ESSfZ1MVqSlCsmnBWysU1z5VdNlwANlqL6BV2X6RUHKDPVj4YuNPvCK+0LsqyzfJdUt3pcnBYxnT5TIg==} @@ -6472,6 +6539,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.3: + resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==} + engines: {node: 20 || >=22} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} @@ -6594,6 +6665,10 @@ packages: brace-expansion@2.1.0: resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@5.0.2: + resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} + engines: {node: 20 || >=22} + brace-expansion@5.0.5: resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} engines: {node: 18 || 20 || >=22} @@ -7697,6 +7772,10 @@ packages: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} + dotenv-expand@12.0.3: + resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==} + engines: {node: '>=12'} + dotenv@16.4.7: resolution: {integrity: sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==} engines: {node: '>=12'} @@ -8662,6 +8741,10 @@ packages: resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==} engines: {node: '>=20'} + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + hasown@2.0.3: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} @@ -10295,6 +10378,18 @@ packages: '@swc/core': optional: true + nx@22.7.0: + resolution: {integrity: sha512-zjySvwwAfexdKN8utZlq5IkoRZOki/gB1KTrY7OkMjSehuiimD6A7DTySiOUkPPMDwgqxMr+eOjIIbC1uWbp8Q==} + hasBin: true + peerDependencies: + '@swc-node/register': ^1.11.1 + '@swc/core': ^1.15.8 + peerDependenciesMeta: + '@swc-node/register': + optional: true + '@swc/core': + optional: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -12396,6 +12491,10 @@ packages: tmp-promise@3.0.3: resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} + tmp@0.2.4: + resolution: {integrity: sha512-UdiSoX6ypifLmrfQ/XfiawN6hkjSBpCjhKxxZcWlUUmoXLaCKQU0bx4HF/tdDK2uzRuchf1txGvrWBzYREssoQ==} + engines: {node: '>=14.14'} + tmp@0.2.5: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} @@ -13322,6 +13421,11 @@ packages: resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} engines: {node: '>= 6'} + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} + hasBin: true + yaml@2.8.3: resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} engines: {node: '>= 14.6'} @@ -14934,6 +15038,11 @@ snapshots: '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 + '@emnapi/core@1.4.5': + dependencies: + '@emnapi/wasi-threads': 1.0.4 + tslib: 2.8.1 + '@emnapi/core@1.9.2': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -14944,11 +15053,19 @@ snapshots: dependencies: tslib: 2.8.1 + '@emnapi/runtime@1.4.5': + dependencies: + tslib: 2.8.1 + '@emnapi/runtime@1.9.2': dependencies: tslib: 2.8.1 optional: true + '@emnapi/wasi-threads@1.0.4': + dependencies: + tslib: 2.8.1 + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 @@ -15262,6 +15379,8 @@ snapshots: - supports-color - ts-node + '@jest/diff-sequences@30.0.1': {} + '@jest/diff-sequences@30.3.0': {} '@jest/environment-jsdom-abstract@30.3.0(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))': @@ -15841,10 +15960,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nx-tools/ci-context@7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(tslib@2.8.1)': + '@nx-tools/ci-context@7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(tslib@2.8.1)': dependencies: '@actions/github': 8.0.1 - '@nx-tools/core': 7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(tslib@2.8.1) + '@nx-tools/core': 7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(tslib@2.8.1) '@octokit/openapi-types': 22.2.0 properties-file: 3.6.4 std-env: 3.10.0 @@ -15852,11 +15971,11 @@ snapshots: transitivePeerDependencies: - '@nx/devkit' - '@nx-tools/container-metadata@7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(tslib@2.8.1)': + '@nx-tools/container-metadata@7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(tslib@2.8.1)': dependencies: - '@nx-tools/ci-context': 7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(tslib@2.8.1) - '@nx-tools/core': 7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(tslib@2.8.1) - '@nx/devkit': 22.6.5(nx@22.6.5) + '@nx-tools/ci-context': 7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(tslib@2.8.1) + '@nx-tools/core': 7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(tslib@2.8.1) + '@nx/devkit': 22.6.5(nx@22.7.0) '@renovatebot/pep440': 4.2.1 csv-parse: 5.6.0 handlebars: 4.7.9 @@ -15864,26 +15983,26 @@ snapshots: semver: 7.7.4 tslib: 2.8.1 - '@nx-tools/core@7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(tslib@2.8.1)': + '@nx-tools/core@7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(tslib@2.8.1)': dependencies: '@actions/io': 1.1.3 - '@nx/devkit': 22.6.5(nx@22.6.5) + '@nx/devkit': 22.6.5(nx@22.7.0) csv-parse: 5.6.0 std-env: 3.10.0 tinyexec: 1.0.2 tinyrainbow: 3.0.3 tslib: 2.8.1 - '@nx-tools/nx-container@7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(@nx/js@22.6.5(@babel/traverse@7.29.0)(nx@22.6.5))(dotenv@17.4.2)(nx@22.6.5)(tslib@2.8.1)': + '@nx-tools/nx-container@7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(@nx/js@22.6.5(@babel/traverse@7.29.0)(nx@22.7.0))(dotenv@17.4.2)(nx@22.7.0)(tslib@2.8.1)': dependencies: - '@nx-tools/container-metadata': 7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(tslib@2.8.1) - '@nx-tools/core': 7.2.1(@nx/devkit@22.6.5(nx@22.6.5))(tslib@2.8.1) - '@nx/devkit': 22.6.5(nx@22.6.5) - '@nx/js': 22.6.5(@babel/traverse@7.29.0)(nx@22.6.5) + '@nx-tools/container-metadata': 7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(tslib@2.8.1) + '@nx-tools/core': 7.2.1(@nx/devkit@22.6.5(nx@22.7.0))(tslib@2.8.1) + '@nx/devkit': 22.6.5(nx@22.7.0) + '@nx/js': 22.6.5(@babel/traverse@7.29.0)(nx@22.7.0) csv-parse: 5.6.0 dotenv: 17.4.2 handlebars: 4.7.9 - nx: 22.6.5 + nx: 22.7.0 semver: 7.7.4 tmp: 0.2.5 tslib: 2.8.1 @@ -15899,7 +16018,18 @@ snapshots: tslib: 2.8.1 yargs-parser: 21.1.1 - '@nx/js@22.6.5(@babel/traverse@7.29.0)(nx@22.6.5)': + '@nx/devkit@22.6.5(nx@22.7.0)': + dependencies: + '@zkochan/js-yaml': 0.0.7 + ejs: 5.0.1 + enquirer: 2.3.6 + minimatch: 10.2.4 + nx: 22.7.0 + semver: 7.7.4 + tslib: 2.8.1 + yargs-parser: 21.1.1 + + '@nx/js@22.6.5(@babel/traverse@7.29.0)(nx@22.7.0)': dependencies: '@babel/core': 7.29.0 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) @@ -15908,7 +16038,7 @@ snapshots: '@babel/preset-env': 7.29.2(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@babel/runtime': 7.29.2 - '@nx/devkit': 22.6.5(nx@22.6.5) + '@nx/devkit': 22.6.5(nx@22.7.0) '@nx/workspace': 22.6.5 '@zkochan/js-yaml': 0.0.7 babel-plugin-const-enum: 1.2.0(@babel/core@7.29.0) @@ -15938,33 +16068,63 @@ snapshots: '@nx/nx-darwin-arm64@22.6.5': optional: true + '@nx/nx-darwin-arm64@22.7.0': + optional: true + '@nx/nx-darwin-x64@22.6.5': optional: true + '@nx/nx-darwin-x64@22.7.0': + optional: true + '@nx/nx-freebsd-x64@22.6.5': optional: true + '@nx/nx-freebsd-x64@22.7.0': + optional: true + '@nx/nx-linux-arm-gnueabihf@22.6.5': optional: true + '@nx/nx-linux-arm-gnueabihf@22.7.0': + optional: true + '@nx/nx-linux-arm64-gnu@22.6.5': optional: true + '@nx/nx-linux-arm64-gnu@22.7.0': + optional: true + '@nx/nx-linux-arm64-musl@22.6.5': optional: true + '@nx/nx-linux-arm64-musl@22.7.0': + optional: true + '@nx/nx-linux-x64-gnu@22.6.5': optional: true + '@nx/nx-linux-x64-gnu@22.7.0': + optional: true + '@nx/nx-linux-x64-musl@22.6.5': optional: true + '@nx/nx-linux-x64-musl@22.7.0': + optional: true + '@nx/nx-win32-arm64-msvc@22.6.5': optional: true + '@nx/nx-win32-arm64-msvc@22.7.0': + optional: true + '@nx/nx-win32-x64-msvc@22.6.5': optional: true + '@nx/nx-win32-x64-msvc@22.7.0': + optional: true + '@nx/workspace@22.6.5': dependencies: '@nx/devkit': 22.6.5(nx@22.6.5) @@ -19375,6 +19535,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.3: {} + balanced-match@4.0.4: {} bare-events@2.8.2: {} @@ -19506,6 +19668,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.2: + dependencies: + balanced-match: 4.0.4 + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -20749,6 +20915,10 @@ snapshots: dependencies: dotenv: 16.6.1 + dotenv-expand@12.0.3: + dependencies: + dotenv: 16.6.1 + dotenv@16.4.7: {} dotenv@16.6.1: {} @@ -21993,6 +22163,10 @@ snapshots: dependencies: hookified: 1.15.1 + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -23938,6 +24112,132 @@ snapshots: transitivePeerDependencies: - debug + nx@22.7.0: + dependencies: + '@emnapi/core': 1.4.5 + '@emnapi/runtime': 1.4.5 + '@emnapi/wasi-threads': 1.0.4 + '@jest/diff-sequences': 30.0.1 + '@napi-rs/wasm-runtime': 0.2.4 + '@tybys/wasm-util': 0.9.0 + '@yarnpkg/lockfile': 1.1.0 + '@zkochan/js-yaml': 0.0.7 + ansi-colors: 4.1.3 + ansi-regex: 5.0.1 + ansi-styles: 4.3.0 + argparse: 2.0.1 + asynckit: 0.4.0 + axios: 1.15.0 + balanced-match: 4.0.3 + base64-js: 1.5.1 + bl: 4.1.0 + brace-expansion: 5.0.2 + buffer: 5.7.1 + call-bind-apply-helpers: 1.0.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.6.1 + cliui: 8.0.1 + clone: 1.0.4 + color-convert: 2.0.1 + color-name: 1.1.4 + combined-stream: 1.0.8 + defaults: 1.0.4 + define-lazy-prop: 2.0.0 + delayed-stream: 1.0.0 + dotenv: 16.4.7 + dotenv-expand: 12.0.3 + dunder-proto: 1.0.1 + ejs: 5.0.1 + emoji-regex: 8.0.0 + end-of-stream: 1.4.5 + enquirer: 2.3.6 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + escalade: 3.2.0 + escape-string-regexp: 1.0.5 + figures: 3.2.0 + flat: 5.0.2 + follow-redirects: 1.16.0 + form-data: 4.0.5 + fs-constants: 1.0.0 + function-bind: 1.1.2 + get-caller-file: 2.0.5 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + has-flag: 4.0.0 + has-symbols: 1.1.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + ieee754: 1.2.1 + ignore: 7.0.5 + inherits: 2.0.4 + is-docker: 2.2.1 + is-fullwidth-code-point: 3.0.0 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + is-wsl: 2.2.0 + json5: 2.2.3 + jsonc-parser: 3.2.0 + lines-and-columns: 2.0.3 + log-symbols: 4.1.0 + math-intrinsics: 1.1.0 + mime-db: 1.52.0 + mime-types: 2.1.35 + mimic-fn: 2.1.0 + minimatch: 10.2.4 + minimist: 1.2.8 + npm-run-path: 4.0.1 + once: 1.4.0 + onetime: 5.1.2 + open: 8.4.2 + ora: 5.3.0 + path-key: 3.1.1 + picocolors: 1.1.1 + proxy-from-env: 2.1.0 + readable-stream: 3.6.2 + require-directory: 2.1.1 + resolve.exports: 2.0.3 + restore-cursor: 3.1.0 + safe-buffer: 5.2.1 + semver: 7.7.4 + signal-exit: 3.0.7 + smol-toml: 1.6.1 + string-width: 4.2.3 + string_decoder: 1.3.0 + strip-ansi: 6.0.1 + strip-bom: 3.0.0 + supports-color: 7.2.0 + tar-stream: 2.2.0 + tmp: 0.2.4 + tree-kill: 1.2.2 + tsconfig-paths: 4.2.0 + tslib: 2.8.1 + util-deprecate: 1.0.2 + wcwidth: 1.0.1 + wrap-ansi: 7.0.0 + wrappy: 1.0.2 + y18n: 5.0.8 + yaml: 2.8.0 + yargs: 17.7.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@nx/nx-darwin-arm64': 22.7.0 + '@nx/nx-darwin-x64': 22.7.0 + '@nx/nx-freebsd-x64': 22.7.0 + '@nx/nx-linux-arm-gnueabihf': 22.7.0 + '@nx/nx-linux-arm64-gnu': 22.7.0 + '@nx/nx-linux-arm64-musl': 22.7.0 + '@nx/nx-linux-x64-gnu': 22.7.0 + '@nx/nx-linux-x64-musl': 22.7.0 + '@nx/nx-win32-arm64-msvc': 22.7.0 + '@nx/nx-win32-x64-msvc': 22.7.0 + transitivePeerDependencies: + - debug + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -26513,6 +26813,8 @@ snapshots: dependencies: tmp: 0.2.5 + tmp@0.2.4: {} + tmp@0.2.5: {} tmpl@1.0.5: {} @@ -27516,6 +27818,8 @@ snapshots: yaml@1.10.3: {} + yaml@2.8.0: {} + yaml@2.8.3: {} yargs-parser@18.1.3: From 881b15d1deed49b0d4d29677ecba975192e65235 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:27:04 +0000 Subject: [PATCH 04/23] Update testcontainers docker digests (#33312) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/web/playwright/testcontainers/mas.ts | 2 +- apps/web/playwright/testcontainers/synapse.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/playwright/testcontainers/mas.ts b/apps/web/playwright/testcontainers/mas.ts index 85b727189f..46da585e97 100644 --- a/apps/web/playwright/testcontainers/mas.ts +++ b/apps/web/playwright/testcontainers/mas.ts @@ -11,7 +11,7 @@ import { } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js"; const DOCKER_IMAGE = - "ghcr.io/element-hq/matrix-authentication-service:main@sha256:034500c4797287bfcdc4d13304e89ac65ce44a0fa33664836aea6e42c33535fb"; + "ghcr.io/element-hq/matrix-authentication-service:main@sha256:b56910ddd9d6cbbe9e896b6e5bcfcded3367dba779f979166e178878b02e3a07"; /** * MatrixAuthenticationServiceContainer which freezes the docker digest to diff --git a/apps/web/playwright/testcontainers/synapse.ts b/apps/web/playwright/testcontainers/synapse.ts index 5fb1c27bfd..8e8cbd920e 100644 --- a/apps/web/playwright/testcontainers/synapse.ts +++ b/apps/web/playwright/testcontainers/synapse.ts @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js"; const DOCKER_IMAGE = - "ghcr.io/element-hq/synapse:develop@sha256:b2fec2c9460f5b297a3a4ce78037902590240a1978301ed1d4bc97918c451041"; + "ghcr.io/element-hq/synapse:develop@sha256:539f3cbe5e1feba357c3394b6db0c7f90ddd5eafc8bdb282e23cc1111aa54eab"; /** * SynapseContainer which freezes the docker digest to stabilise tests, From 72d316df79609e56c6f8598ec604f195241ad3c0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:34:21 +0000 Subject: [PATCH 05/23] Update dependency @typescript-eslint/eslint-plugin to v8.59.0 (#33315) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 72 +++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 45 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88aac9c171..f47cf7a3f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -136,7 +136,7 @@ importers: version: 3.14.0 eslint-plugin-matrix-org: specifier: ^3.0.0 - version: 3.0.0(756bde0db757d9adac5fccdfadf6b9bb) + version: 3.0.0(e6b158d923c3b8a20a719d9049b4bf8c) husky: specifier: ^9.0.0 version: 9.1.7 @@ -239,7 +239,7 @@ importers: version: 11.1.8 '@typescript-eslint/eslint-plugin': specifier: ^8.0.0 - version: 8.58.2(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) + version: 8.59.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) '@typescript-eslint/parser': specifier: ^8.0.0 version: 8.58.2(eslint@8.57.1)(typescript@6.0.3) @@ -278,7 +278,7 @@ importers: version: 2.32.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1) eslint-plugin-matrix-org: specifier: ^3.0.0 - version: 3.0.0(756bde0db757d9adac5fccdfadf6b9bb) + version: 3.0.0(e6b158d923c3b8a20a719d9049b4bf8c) eslint-plugin-n: specifier: ^17.12.0 version: 17.24.0(eslint@8.57.1)(typescript@6.0.3) @@ -696,7 +696,7 @@ importers: version: 0.7.39 '@typescript-eslint/eslint-plugin': specifier: ^8.19.0 - version: 8.58.2(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) + version: 8.59.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) '@typescript-eslint/parser': specifier: ^8.19.0 version: 8.58.2(eslint@8.57.1)(typescript@6.0.3) @@ -741,13 +741,13 @@ importers: version: 2.32.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1) eslint-plugin-jest: specifier: ^29.0.0 - version: 29.15.2(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(jest@30.3.0(@types/node@18.19.130)(babel-plugin-macros@3.1.0))(typescript@6.0.3) + version: 29.15.2(@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(jest@30.3.0(@types/node@18.19.130)(babel-plugin-macros@3.1.0))(typescript@6.0.3) eslint-plugin-jsx-a11y: specifier: ^6.5.1 version: 6.10.2(eslint@8.57.1) eslint-plugin-matrix-org: specifier: ^3.0.0 - version: 3.0.0(756bde0db757d9adac5fccdfadf6b9bb) + version: 3.0.0(e6b158d923c3b8a20a719d9049b4bf8c) eslint-plugin-react: specifier: ^7.28.0 version: 7.37.5(eslint@8.57.1) @@ -1119,7 +1119,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@typescript-eslint/eslint-plugin': specifier: ^8.53.1 - version: 8.58.2(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) + version: 8.59.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) '@typescript-eslint/parser': specifier: ^8.53.1 version: 8.58.2(eslint@8.57.1)(typescript@6.0.3) @@ -1152,7 +1152,7 @@ importers: version: 6.10.2(eslint@8.57.1) eslint-plugin-matrix-org: specifier: ^3.0.0 - version: 3.0.0(756bde0db757d9adac5fccdfadf6b9bb) + version: 3.0.0(e6b158d923c3b8a20a719d9049b4bf8c) eslint-plugin-react: specifier: ^7.37.5 version: 7.37.5(eslint@8.57.1) @@ -5660,11 +5660,11 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript-eslint/eslint-plugin@8.58.2': - resolution: {integrity: sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==} + '@typescript-eslint/eslint-plugin@8.59.0': + resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.58.2 + '@typescript-eslint/parser': ^8.59.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' @@ -5707,8 +5707,8 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.58.2': - resolution: {integrity: sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==} + '@typescript-eslint/type-utils@8.59.0': + resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -5734,13 +5734,6 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.58.2': - resolution: {integrity: sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 - typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.59.0': resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -18457,14 +18450,14 @@ snapshots: '@types/node': 18.19.130 optional: true - '@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 '@typescript-eslint/parser': 8.58.2(eslint@8.57.1)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/type-utils': 8.58.2(eslint@8.57.1)(typescript@6.0.3) - '@typescript-eslint/utils': 8.58.2(eslint@8.57.1)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.58.2 + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/type-utils': 8.59.0(eslint@8.57.1)(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@8.57.1)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 eslint: 8.57.1 ignore: 7.0.5 natural-compare: 1.4.0 @@ -18521,11 +18514,11 @@ snapshots: dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.58.2(eslint@8.57.1)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.59.0(eslint@8.57.1)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) - '@typescript-eslint/utils': 8.58.2(eslint@8.57.1)(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@8.57.1)(typescript@6.0.3) debug: 4.4.3 eslint: 8.57.1 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -18567,17 +18560,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.58.2(eslint@8.57.1)(typescript@6.0.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.58.2 - '@typescript-eslint/types': 8.58.2 - '@typescript-eslint/typescript-estree': 8.58.2(typescript@6.0.3) - eslint: 8.57.1 - typescript: 6.0.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/utils@8.59.0(eslint@8.57.1)(typescript@6.0.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) @@ -21313,12 +21295,12 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(jest@30.3.0(@types/node@18.19.130)(babel-plugin-macros@3.1.0))(typescript@6.0.3): + eslint-plugin-jest@29.15.2(@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(jest@30.3.0(@types/node@18.19.130)(babel-plugin-macros@3.1.0))(typescript@6.0.3): dependencies: '@typescript-eslint/utils': 8.59.0(eslint@8.57.1)(typescript@6.0.3) eslint: 8.57.1 optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) jest: 30.3.0(@types/node@18.19.130)(babel-plugin-macros@3.1.0) typescript: 6.0.3 transitivePeerDependencies: @@ -21343,20 +21325,20 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-matrix-org@3.0.0(756bde0db757d9adac5fccdfadf6b9bb): + eslint-plugin-matrix-org@3.0.0(e6b158d923c3b8a20a719d9049b4bf8c): dependencies: '@babel/core': 7.29.0 '@babel/eslint-parser': 7.28.6(@babel/core@7.29.0)(eslint@8.57.1) '@babel/eslint-plugin': 7.27.1(@babel/eslint-parser@7.28.6(@babel/core@7.29.0)(eslint@8.57.1))(eslint@8.57.1) '@stylistic/eslint-plugin': 5.10.0(eslint@8.57.1) - '@typescript-eslint/eslint-plugin': 8.58.2(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) + '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3) '@typescript-eslint/parser': 8.58.2(eslint@8.57.1)(typescript@6.0.3) eslint: 8.57.1 eslint-config-google: 0.14.0(eslint@8.57.1) eslint-config-prettier: 10.1.8(eslint@8.57.1) eslint-plugin-deprecate: 0.9.0(eslint@8.57.1) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1) - eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.58.2(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(jest@30.3.0(@types/node@18.19.130)(babel-plugin-macros@3.1.0))(typescript@6.0.3) + eslint-plugin-jest: 29.15.2(@typescript-eslint/eslint-plugin@8.59.0(@typescript-eslint/parser@8.58.2(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(typescript@6.0.3))(eslint@8.57.1)(jest@30.3.0(@types/node@18.19.130)(babel-plugin-macros@3.1.0))(typescript@6.0.3) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 7.1.1(eslint@8.57.1) From 2b943768ea8457a563c85dca05174f05ccc68518 Mon Sep 17 00:00:00 2001 From: Will Hunt <2072976+Half-Shot@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:08:22 +0100 Subject: [PATCH 06/23] Disable URL Preview setting if disabled on the homeserver (#33279) * Disallow URL Previews if disabled by homeserver. * Cleanup nonsense * cleanup * cleanup * fixes * Remove import * Add an error handler * cleanup --- apps/web/src/i18n/strings/en_EN.json | 1 + apps/web/src/settings/Settings.tsx | 8 +- .../controllers/RequiresSettingsController.ts | 59 ++++++++-- apps/web/test/test-utils/client.ts | 1 + apps/web/test/test-utils/test-utils.ts | 1 + .../components/structures/RoomView-test.tsx | 2 + .../user/PreferencesUserSettingsTab-test.tsx | 12 +- .../RequiresSettingsController-test.ts | 106 ++++++++++++++++++ 8 files changed, 181 insertions(+), 9 deletions(-) diff --git a/apps/web/src/i18n/strings/en_EN.json b/apps/web/src/i18n/strings/en_EN.json index 62104e0c61..cbd2ee9b97 100644 --- a/apps/web/src/i18n/strings/en_EN.json +++ b/apps/web/src/i18n/strings/en_EN.json @@ -476,6 +476,7 @@ "description": "Description", "deselect_all": "Deselect all", "device": "Device", + "disabled_by_homeserver": "Disabled by homeserver", "edited": "edited", "email_address": "Email address", "emoji": "Emoji", diff --git a/apps/web/src/settings/Settings.tsx b/apps/web/src/settings/Settings.tsx index 67d0a31e14..376b690afc 100644 --- a/apps/web/src/settings/Settings.tsx +++ b/apps/web/src/settings/Settings.tsx @@ -1126,7 +1126,13 @@ export const SETTINGS: Settings = { supportedLevelsAreOrdered: true, displayName: _td("settings|inline_url_previews_default"), default: true, - controller: new UIFeatureController(UIFeature.URLPreviews), + controller: new RequiresSettingsController([UIFeature.URLPreviews], false, (c) => { + if (c["io.element.msc4452.preview_url"]?.enabled !== false) { + // If the capability is not listed, or explicitly true then do not disable. + return false; + } + return _t("common|disabled_by_homeserver"); + }), }, "urlPreviewsEnabled_e2ee": { // Can only be enabled per-device to ensure neither the homeserver nor client config diff --git a/apps/web/src/settings/controllers/RequiresSettingsController.ts b/apps/web/src/settings/controllers/RequiresSettingsController.ts index a5bbc4b26b..3729ce7fbb 100644 --- a/apps/web/src/settings/controllers/RequiresSettingsController.ts +++ b/apps/web/src/settings/controllers/RequiresSettingsController.ts @@ -5,30 +5,75 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import SettingController from "./SettingController"; +import { logger as rootLogger } from "matrix-js-sdk/src/logger"; + +import type { Capabilities } from "matrix-js-sdk/src/matrix"; import SettingsStore from "../SettingsStore"; import type { BooleanSettingKey } from "../Settings.tsx"; +import MatrixClientBackedController from "./MatrixClientBackedController.ts"; +const logger = rootLogger.getChild("RequiresSettingsController"); /** * Disables a setting & forces it's value if one or more settings are not enabled + * and/or a capability on the client check does not pass. */ -export default class RequiresSettingsController extends SettingController { +export default class RequiresSettingsController extends MatrixClientBackedController { public constructor( public readonly settingNames: BooleanSettingKey[], - private forcedValue = false, + private readonly forcedValue = false, + /** + * Function to check the capabilites of the client. + * If defined this will be called when the MatrixClient is instantiated to check + * the returned capabilites. + * @returns `true` or a string if the feature is disabled by the feature, otherwise false. + */ + private readonly isCapabilityDisabled?: (caps: Capabilities) => boolean | string, ) { super(); } + protected initMatrixClient(): void { + if (this.client && this.isCapabilityDisabled) { + // Ensure we fetch capabilies at least once. + this.client.getCapabilities().catch((ex) => { + logger.warn("Failed to fetch capabilities", ex); + }); + } + } + + /** + * Checks if the `isCapabilityDisabled` function blocks the setting. + * @returns `true` or a string if the feature is disabled by the feature, otherwise false. + */ + private get isBlockedByCapabilites(): boolean | string { + if (!this.isCapabilityDisabled) { + return false; + } + // This works because the cached caps are stored forever, and we have made + // at least one call to get capaibilies. + const cachedCaps = this.client?.getCachedCapabilities(); + if (!cachedCaps) { + // If we do not have any capabilites yet, then assume the setting IS blocked. + return true; + } + return this.isCapabilityDisabled(cachedCaps); + } + + public get settingDisabled(): boolean | string { + if (this.settingNames.some((s) => !SettingsStore.getValue(s))) { + return true; + } + return this.isBlockedByCapabilites; + } + public getValueOverride(): any { if (this.settingDisabled) { // per the docs: we force a disabled state when the feature isn't active return this.forcedValue; } + if (this.isBlockedByCapabilites) { + return this.forcedValue; + } return null; // no override } - - public get settingDisabled(): boolean { - return this.settingNames.some((s) => !SettingsStore.getValue(s)); - } } diff --git a/apps/web/test/test-utils/client.ts b/apps/web/test/test-utils/client.ts index f4d35220c2..7ab88da0b7 100644 --- a/apps/web/test/test-utils/client.ts +++ b/apps/web/test/test-utils/client.ts @@ -132,6 +132,7 @@ export const mockClientMethodsServer = (): Partial Promise.resolve(), isUserIgnored: jest.fn().mockReturnValue(false), getCapabilities: jest.fn().mockResolvedValue({}), + getCachedCapabilities: jest.fn().mockReturnValue({}), supportsThreads: jest.fn().mockReturnValue(false), supportsIntentionalMentions: jest.fn().mockReturnValue(false), getRoomUpgradeHistory: jest.fn().mockReturnValue([]), diff --git a/apps/web/test/unit-tests/components/structures/RoomView-test.tsx b/apps/web/test/unit-tests/components/structures/RoomView-test.tsx index c9092d7aa1..c00a3573e2 100644 --- a/apps/web/test/unit-tests/components/structures/RoomView-test.tsx +++ b/apps/web/test/unit-tests/components/structures/RoomView-test.tsx @@ -71,6 +71,7 @@ import ErrorDialog from "../../../../src/components/views/dialogs/ErrorDialog.ts import * as pinnedEventHooks from "../../../../src/hooks/usePinnedEvents"; import { TimelineRenderingType } from "../../../../src/contexts/RoomContext"; import { ModuleApi } from "../../../../src/modules/Api"; +import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController.ts"; import { type ComposerInsertPayload, ComposerType } from "../../../../src/dispatcher/payloads/ComposerInsertPayload.ts"; // Used by group calls @@ -93,6 +94,7 @@ describe("RoomView", () => { beforeEach(() => { mockPlatformPeg({ reload: () => {} }); cli = mocked(stubClient()); + MatrixClientBackedController.matrixClient = cli; const roomName = (expect.getState().currentTestName ?? "").replace(/[^a-zA-Z0-9]/g, "").toLowerCase(); diff --git a/apps/web/test/unit-tests/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx b/apps/web/test/unit-tests/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx index b0f17fae4c..16dde05448 100644 --- a/apps/web/test/unit-tests/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx +++ b/apps/web/test/unit-tests/components/views/settings/tabs/user/PreferencesUserSettingsTab-test.tsx @@ -12,7 +12,13 @@ import userEvent from "@testing-library/user-event"; import PreferencesUserSettingsTab from "../../../../../../../src/components/views/settings/tabs/user/PreferencesUserSettingsTab"; import { MatrixClientPeg } from "../../../../../../../src/MatrixClientPeg"; -import { mockPlatformPeg, stubClient } from "../../../../../../test-utils"; +import { + getMockClientWithEventEmitter, + mockClientMethodsServer, + mockClientMethodsUser, + mockPlatformPeg, + stubClient, +} from "../../../../../../test-utils"; import SettingsStore from "../../../../../../../src/settings/SettingsStore"; import { SettingLevel } from "../../../../../../../src/settings/SettingLevel"; import MatrixClientBackedController from "../../../../../../../src/settings/controllers/MatrixClientBackedController"; @@ -29,6 +35,10 @@ describe("PreferencesUserSettingsTab", () => { }; it("should render", () => { + MatrixClientBackedController.matrixClient = getMockClientWithEventEmitter({ + ...mockClientMethodsServer(), + ...mockClientMethodsUser(), + }); const { asFragment } = renderTab(); expect(asFragment()).toMatchSnapshot(); }); diff --git a/apps/web/test/unit-tests/settings/controllers/RequiresSettingsController-test.ts b/apps/web/test/unit-tests/settings/controllers/RequiresSettingsController-test.ts index dafd7c8896..36335d9323 100644 --- a/apps/web/test/unit-tests/settings/controllers/RequiresSettingsController-test.ts +++ b/apps/web/test/unit-tests/settings/controllers/RequiresSettingsController-test.ts @@ -5,9 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ +import type { Capabilities } from "matrix-js-sdk/src/matrix"; import RequiresSettingsController from "../../../../src/settings/controllers/RequiresSettingsController"; import { SettingLevel } from "../../../../src/settings/SettingLevel"; import SettingsStore from "../../../../src/settings/SettingsStore"; +import MatrixClientBackedController from "../../../../src/settings/controllers/MatrixClientBackedController"; +import { getMockClientWithEventEmitter, mockClientMethodsServer } from "../../../test-utils"; describe("RequiresSettingsController", () => { afterEach(() => { @@ -30,4 +33,107 @@ describe("RequiresSettingsController", () => { expect(controller.settingDisabled).toEqual(false); expect(controller.getValueOverride()).toEqual(null); }); + + describe("with capabilites", () => { + let client: ReturnType; + beforeEach(() => { + client = getMockClientWithEventEmitter({ + ...mockClientMethodsServer(), + getCachedCapabilities: jest.fn().mockImplementation(() => {}), + getCapabilities: jest.fn().mockRejectedValue({}), + }); + MatrixClientBackedController["_matrixClient"] = client; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("will disable setting if capability check is true", async () => { + const caps = { + "m.change_password": { + enabled: false, + }, + }; + client.getCachedCapabilities.mockImplementation(() => caps); + const controller = new RequiresSettingsController([], false, (c: Capabilities) => { + expect(c).toEqual(caps); + return !c["m.change_password"]?.enabled; + }); + + // Test that we fetch caps + controller["initMatrixClient"](); + expect(client.getCapabilities).toHaveBeenCalled(); + + // Test that we check caps. + expect(controller.settingDisabled).toEqual(true); + expect(controller.getValueOverride()).toEqual(false); + expect(client.getCachedCapabilities).toHaveBeenCalled(); + }); + + it("will not disable setting if capability check is false", async () => { + const caps = { + "m.change_password": { + enabled: true, + }, + }; + client.getCachedCapabilities.mockImplementation(() => caps); + const controller = new RequiresSettingsController([], false, (c: Capabilities) => { + expect(c).toEqual(caps); + return !c["m.change_password"]?.enabled; + }); + + // Test that we fetch caps + controller["initMatrixClient"](); + expect(client.getCapabilities).toHaveBeenCalled(); + + // Test that we check caps. + expect(controller.settingDisabled).toEqual(false); + expect(controller.getValueOverride()).toEqual(null); + expect(client.getCachedCapabilities).toHaveBeenCalled(); + }); + + it("will check dependency settings before checking capabilites", async () => { + const caps = { + "m.change_password": { + enabled: false, + }, + }; + client.getCachedCapabilities.mockImplementation(() => caps); + await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, false); + const controller = new RequiresSettingsController(["useCompactLayout"], false, (c: Capabilities) => false); + + // Test that we fetch caps + controller["initMatrixClient"](); + expect(client.getCapabilities).toHaveBeenCalled(); + + // Test that we check caps. + expect(controller.settingDisabled).toEqual(true); + expect(controller.getValueOverride()).toEqual(false); + expect(client.getCachedCapabilities).not.toHaveBeenCalled(); + }); + + it("will disable setting if capability check is true and dependency settings are true", async () => { + const caps = { + "m.change_password": { + enabled: false, + }, + }; + client.getCachedCapabilities.mockImplementation(() => caps); + await SettingsStore.setValue("useCompactLayout", null, SettingLevel.DEVICE, true); + const controller = new RequiresSettingsController(["useCompactLayout"], false, (c: Capabilities) => { + expect(c).toEqual(caps); + return !c["m.change_password"]?.enabled; + }); + + // Test that we fetch caps + controller["initMatrixClient"](); + expect(client.getCapabilities).toHaveBeenCalled(); + + // Test that we check caps. + expect(controller.settingDisabled).toEqual(true); + expect(controller.getValueOverride()).toEqual(false); + expect(client.getCachedCapabilities).toHaveBeenCalled(); + }); + }); }); From e85dc97450301c6598ffc9848fa947647c2efbe5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:20:25 +0100 Subject: [PATCH 07/23] Update dependency storybook-addon-vis to v4.2.2 (#33313) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f47cf7a3f0..7ffc755483 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1176,7 +1176,7 @@ importers: version: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) storybook-addon-vis: specifier: ^4.0.0 - version: 4.2.1(@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.5))(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(babel-plugin-macros@3.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)(vitest@4.1.5) + version: 4.2.2(@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.5))(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(babel-plugin-macros@3.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)(vitest@4.1.5) typedoc: specifier: ^0.28.16 version: 0.28.19(typescript@6.0.3) @@ -4438,6 +4438,9 @@ packages: resolution: {integrity: sha512-2FK1hF93Fuf1laSdfiEmJvSJPVIDHEUTz68D3Fi9s0IZrrpaEcj6pTFBTbYvsgC5du4ogrtf5re7yMMvrKNgkw==} engines: {node: ^20.9.0 || ^22.11.0 || ^24, pnpm: ^10.0.0} + '@repobuddy/test@1.0.1': + resolution: {integrity: sha512-eNXZ6Cfs18EGQtPS/H68E0GQyQeB+al5sPcMgb70553CE0vs5Y7jDdO8Csgk/CHiuPy/rCKdN33OvvkSiGaG+A==} + '@rolldown/binding-android-arm64@1.0.0-rc.16': resolution: {integrity: sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -12097,8 +12100,8 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - storybook-addon-vis@4.2.1: - resolution: {integrity: sha512-ayHdkg0/NyQ0RqznkD6vpYpjRBMZCWTmRaocTCWMS4mzaTFeCyA6c62U5HO1T9UYoW4ApTsUbDk0dPYHJDhrnA==} + storybook-addon-vis@4.2.2: + resolution: {integrity: sha512-ipe1ws+0PXmCZBJ7JgZEOTpNkpevHRs8a/dwXv+fS4Cd3bu+6xyPgV4F2kvdRhUGsatLu1aJbgI0jdH8/aY+jA==} peerDependencies: '@storybook/addon-vitest': ^10.1.10 '@vitest/browser': ^4 @@ -17145,6 +17148,8 @@ snapshots: '@renovatebot/pep440@4.2.1': {} + '@repobuddy/test@1.0.1': {} + '@rolldown/binding-android-arm64@1.0.0-rc.16': optional: true @@ -26245,8 +26250,9 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook-addon-vis@4.2.1(@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.5))(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(babel-plugin-macros@3.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)(vitest@4.1.5): + storybook-addon-vis@4.2.2(@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.5))(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(babel-plugin-macros@3.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)(vitest@4.1.5): dependencies: + '@repobuddy/test': 1.0.1 '@storybook/addon-vitest': 10.3.5(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.5) '@storybook/icons': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@vitest/browser': 4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) From 4c3cb0754b2a7c150d9a567571892366ea5825e4 Mon Sep 17 00:00:00 2001 From: ElementRobot Date: Wed, 29 Apr 2026 09:22:26 +0200 Subject: [PATCH 08/23] [create-pull-request] automated change (#33325) Co-authored-by: t3chguy <2403652+t3chguy@users.noreply.github.com> --- apps/web/src/i18n/strings/cs.json | 4 -- apps/web/src/i18n/strings/cy.json | 4 -- apps/web/src/i18n/strings/da.json | 2 - apps/web/src/i18n/strings/de_DE.json | 4 -- apps/web/src/i18n/strings/el.json | 1 - apps/web/src/i18n/strings/eo.json | 1 - apps/web/src/i18n/strings/es.json | 1 - apps/web/src/i18n/strings/et.json | 4 -- apps/web/src/i18n/strings/fa.json | 1 - apps/web/src/i18n/strings/fi.json | 1 - apps/web/src/i18n/strings/fr.json | 29 ++++++-- apps/web/src/i18n/strings/hr.json | 4 -- apps/web/src/i18n/strings/hu.json | 4 -- apps/web/src/i18n/strings/hy.json | 1 - apps/web/src/i18n/strings/id.json | 4 -- apps/web/src/i18n/strings/is.json | 1 - apps/web/src/i18n/strings/it.json | 1 - apps/web/src/i18n/strings/ja.json | 1 - apps/web/src/i18n/strings/ka.json | 1 - apps/web/src/i18n/strings/ko.json | 4 -- apps/web/src/i18n/strings/lt.json | 71 ++++++++++++++++--- apps/web/src/i18n/strings/lv.json | 1 - apps/web/src/i18n/strings/mg_MG.json | 1 - apps/web/src/i18n/strings/nb_NO.json | 4 -- apps/web/src/i18n/strings/nl.json | 1 - apps/web/src/i18n/strings/pl.json | 3 - apps/web/src/i18n/strings/pt.json | 1 - apps/web/src/i18n/strings/pt_BR.json | 4 -- apps/web/src/i18n/strings/ru.json | 9 +-- apps/web/src/i18n/strings/sk.json | 4 -- apps/web/src/i18n/strings/sq.json | 1 - apps/web/src/i18n/strings/sv.json | 1 - apps/web/src/i18n/strings/tr.json | 1 - apps/web/src/i18n/strings/uk.json | 4 -- apps/web/src/i18n/strings/vi.json | 1 - apps/web/src/i18n/strings/zh_Hans.json | 34 +++++---- apps/web/src/i18n/strings/zh_Hant.json | 1 - .../src/i18n/strings/fr.json | 2 + .../src/i18n/strings/ru.json | 3 + .../src/i18n/strings/zh_Hans.json | 3 + 40 files changed, 120 insertions(+), 103 deletions(-) diff --git a/apps/web/src/i18n/strings/cs.json b/apps/web/src/i18n/strings/cs.json index b2c02f1358..b2ab1feb4a 100644 --- a/apps/web/src/i18n/strings/cs.json +++ b/apps/web/src/i18n/strings/cs.json @@ -588,7 +588,6 @@ "video": "Video", "video_room": "Video místnost", "view_message": "Zobrazit zprávu", - "voice": "Hlas", "warning": "Upozornění" }, "composer": { @@ -3909,7 +3908,6 @@ "connection_lost": "Došlo ke ztrátě připojení k serveru", "connection_lost_description": "Bez připojení k serveru nelze uskutečňovat hovory.", "consulting": "Konzultace s %(transferTarget)s. Převod na %(transferee)s", - "decline_call": "Odmítnout", "default_device": "Výchozí zařízení", "dial": "Vytočit", "dialpad": "Číselník", @@ -3960,7 +3958,6 @@ "show_sidebar_button": "Zobrazit postranní panel", "silence": "Ztlumit zvonění", "silenced": "Oznámení ztlumena", - "skip_lobby_toggle_option": "Připojte se ihned", "start_screenshare": "Začít sdílet obrazovku", "stop_screenshare": "Ukončit sdílení obrazovky", "too_many_calls": "Přiliš mnoho hovorů", @@ -3982,7 +3979,6 @@ "user_is_presenting": "%(sharerName)s prezentuje", "video_call": "Videohovor", "video_call_incoming": "Příchozí videohovor", - "video_call_started": "Videohovor byl zahájen", "video_call_using": "Videohovor pomocí:", "voice_call": "Hlasový hovor", "voice_call_incoming": "Příchozí hlasový hovor", diff --git a/apps/web/src/i18n/strings/cy.json b/apps/web/src/i18n/strings/cy.json index 9bac8f0faf..7b45070fec 100644 --- a/apps/web/src/i18n/strings/cy.json +++ b/apps/web/src/i18n/strings/cy.json @@ -591,7 +591,6 @@ "video": "Fideo", "video_room": "Ystafell fideo", "view_message": "Gweld neges", - "voice": "Llais", "warning": "Rhybudd" }, "composer": { @@ -3872,7 +3871,6 @@ "connection_lost": "Mae cysylltedd â'r gweinydd wedi'i golli", "connection_lost_description": "Allwch chi ddim osod galwadau heb gysylltiad â'r gweinydd.", "consulting": "Ymgynghori â %(transferTarget)s. Trosglwyddo i %(transferee)s", - "decline_call": "Gwrthod", "default_device": "Dyfais Rhagosodedig", "dial": "Deial", "dialpad": "Pad Deialu", @@ -3923,7 +3921,6 @@ "show_sidebar_button": "Dangos y bar ochr", "silence": "Distewi galwad", "silenced": "Hysbysiadau wedi'u distewi", - "skip_lobby_toggle_option": "Ymunwch ar unwaith", "start_screenshare": "Dechreuwch rannu'ch sgrin", "stop_screenshare": "Stopiwch rannu'ch sgrin", "too_many_calls": "Gormod o Alwadau", @@ -3945,7 +3942,6 @@ "user_is_presenting": "Mae %(sharerName)s yn cyflwyno", "video_call": "Galwad fideo", "video_call_incoming": "Galwad fideo i mewn", - "video_call_started": "Galwad fideo wedi dechrau", "video_call_using": "Galwad fideo gan ddefnyddio:", "voice_call": "Galwad llais", "voice_call_incoming": "Galwad llais i mewn", diff --git a/apps/web/src/i18n/strings/da.json b/apps/web/src/i18n/strings/da.json index 5d439bf984..aad00cb3f7 100644 --- a/apps/web/src/i18n/strings/da.json +++ b/apps/web/src/i18n/strings/da.json @@ -3412,7 +3412,6 @@ "connection_lost": "Forbindelsen til serveren er tabt", "connection_lost_description": "Du kan ikke lave et opkald uden en forbindelse til serveren.", "consulting": "Konfererer med %(transferTarget)s. Overfør til %(transferee)s", - "decline_call": "Afvis", "default_device": "Standardenhed", "dial": "Ring", "dialpad": "Tastatur", @@ -3480,7 +3479,6 @@ "user_busy_description": "Brugeren du ringede til er optaget.", "user_is_presenting": "%(sharerName)s præsenterer", "video_call": "Videoopkald", - "video_call_started": "Videoopkald startet", "video_call_using": "Videoopkald med:", "voice_call": "Stemmeopkald", "you_are_presenting": "Du præsenterer" diff --git a/apps/web/src/i18n/strings/de_DE.json b/apps/web/src/i18n/strings/de_DE.json index 05eb83b9ba..cef095738d 100644 --- a/apps/web/src/i18n/strings/de_DE.json +++ b/apps/web/src/i18n/strings/de_DE.json @@ -588,7 +588,6 @@ "video": "Video", "video_room": "Videochat", "view_message": "Nachricht anzeigen", - "voice": "Sprachanruf", "warning": "Warnung" }, "composer": { @@ -3865,7 +3864,6 @@ "connection_lost": "Verbindung zum Server unterbrochen", "connection_lost_description": "Du kannst ohne Verbindung zum Server keine Anrufe tätigen.", "consulting": "%(transferTarget)s wird angefragt. Übertragung zu %(transferee)s", - "decline_call": "Ablehnen", "default_device": "Standardgerät", "dial": "Wählen", "dialpad": "Telefontastatur", @@ -3916,7 +3914,6 @@ "show_sidebar_button": "Seitenleiste anzeigen", "silence": "Anruf stummschalten", "silenced": "Benachrichtigungen stummgeschaltet", - "skip_lobby_toggle_option": "Sofort beitreten", "start_screenshare": "Bildschirmfreigabe starten", "stop_screenshare": "Bildschirmfreigabe beenden", "too_many_calls": "Zu viele Anrufe", @@ -3938,7 +3935,6 @@ "user_is_presenting": "%(sharerName)s präsentiert", "video_call": "Videoanruf", "video_call_incoming": "Eingehender Videoanruf", - "video_call_started": "Videoanruf hat begonnen", "video_call_using": "Videoanruf mit:", "voice_call": "Sprachanruf", "voice_call_incoming": "Eingehender Anruf", diff --git a/apps/web/src/i18n/strings/el.json b/apps/web/src/i18n/strings/el.json index ac5f9aa888..4844e88a55 100644 --- a/apps/web/src/i18n/strings/el.json +++ b/apps/web/src/i18n/strings/el.json @@ -3164,7 +3164,6 @@ "user_busy_description": "Ο χρήστης που καλέσατε είναι απασχολημένος.", "user_is_presenting": "%(sharerName)s παρουσιάζει", "video_call": "Βιντεοκλήση", - "video_call_started": "Ξεκίνησε η βιντεοκλήση", "voice_call": "Φωνητική κλήση", "you_are_presenting": "Παρουσιάζετε" }, diff --git a/apps/web/src/i18n/strings/eo.json b/apps/web/src/i18n/strings/eo.json index bddde9a546..30c79b8a0b 100644 --- a/apps/web/src/i18n/strings/eo.json +++ b/apps/web/src/i18n/strings/eo.json @@ -2485,7 +2485,6 @@ "user_busy_description": "La uzanto, kiun vi vokis, estas okupata.", "user_is_presenting": "%(sharerName)s prezentas", "video_call": "Vidvoko", - "video_call_started": "Videovoko komenciĝis", "voice_call": "Voĉvoko", "you_are_presenting": "Vi prezentas" }, diff --git a/apps/web/src/i18n/strings/es.json b/apps/web/src/i18n/strings/es.json index 55a0d5c10d..98d5720c42 100644 --- a/apps/web/src/i18n/strings/es.json +++ b/apps/web/src/i18n/strings/es.json @@ -3250,7 +3250,6 @@ "user_busy_description": "La persona a la que has llamado está ocupada.", "user_is_presenting": "%(sharerName)s está presentando", "video_call": "Llamada de vídeo", - "video_call_started": "Videollamada iniciada", "voice_call": "Llamada de voz", "you_are_presenting": "Estás presentando" }, diff --git a/apps/web/src/i18n/strings/et.json b/apps/web/src/i18n/strings/et.json index 3d8f43520c..dd0138bc07 100644 --- a/apps/web/src/i18n/strings/et.json +++ b/apps/web/src/i18n/strings/et.json @@ -588,7 +588,6 @@ "video": "Video", "video_room": "Videotuba", "view_message": "Vaata sõnumit", - "voice": "Hääl", "warning": "Hoiatus" }, "composer": { @@ -3865,7 +3864,6 @@ "connection_lost": "Ühendus sinu serveriga on katkenud", "connection_lost_description": "Kui ühendus sinu serveriga on katkenud, siis sa ei saa helistada.", "consulting": "Suhtlen teise osapoolega %(transferTarget)s. Saadan andmeid kasutajale %(transferee)s", - "decline_call": "Keeldu", "default_device": "Vaikimisi seade", "dial": "Helista", "dialpad": "Numbriklahvistik", @@ -3916,7 +3914,6 @@ "show_sidebar_button": "Näita külgpaani", "silence": "Vaigista kõne", "silenced": "Teavitused on summutatud", - "skip_lobby_toggle_option": "Liitu kohe", "start_screenshare": "Alusta oma seadme ekraani jagamist", "stop_screenshare": "Lõpeta oma seadme ekraani jagamine", "too_many_calls": "Liiga palju kõnesid", @@ -3938,7 +3935,6 @@ "user_is_presenting": "%(sharerName)s esitab", "video_call": "Videokõne", "video_call_incoming": "Saabuv videokõne", - "video_call_started": "Videokõne algas", "video_call_using": "Videokõne, kus on kasutusel:", "voice_call": "Häälkõne", "voice_call_incoming": "Saaduv häälkõne", diff --git a/apps/web/src/i18n/strings/fa.json b/apps/web/src/i18n/strings/fa.json index 355487d80d..a511425eb5 100644 --- a/apps/web/src/i18n/strings/fa.json +++ b/apps/web/src/i18n/strings/fa.json @@ -2187,7 +2187,6 @@ "user_busy": "کاربر مشغول", "user_busy_description": "کاربر موردنظر مشغول است.", "video_call": "تماس تصویری", - "video_call_started": "تماس تصویری شروع شد", "voice_call": "تماس صوتی" }, "web_default_device_name": "%(appName)s: %(browserName)s: روی %(osName)s", diff --git a/apps/web/src/i18n/strings/fi.json b/apps/web/src/i18n/strings/fi.json index a5988d49b9..7abd73cce3 100644 --- a/apps/web/src/i18n/strings/fi.json +++ b/apps/web/src/i18n/strings/fi.json @@ -3364,7 +3364,6 @@ "user_busy_description": "Käyttäjä, jolle soitit, on varattu.", "user_is_presenting": "%(sharerName)s esittää", "video_call": "Videopuhelu", - "video_call_started": "Videopuhelu aloitettu", "video_call_using": "Videopuhelu käyttäen:", "voice_call": "Äänipuhelu", "you_are_presenting": "Esität parhaillaan" diff --git a/apps/web/src/i18n/strings/fr.json b/apps/web/src/i18n/strings/fr.json index ea8aaa193d..cf422de78a 100644 --- a/apps/web/src/i18n/strings/fr.json +++ b/apps/web/src/i18n/strings/fr.json @@ -588,7 +588,6 @@ "video": "Vidéo", "video_room": "Salon vidéo", "view_message": "Afficher le message", - "voice": "Voix", "warning": "Attention" }, "composer": { @@ -1368,6 +1367,14 @@ "impossible_dialog_title": "Les intégrations ne sont pas autorisées" }, "invite": { + "confirm_unknown_users": { + "invite_subtitle": "Vous n'avez actuellement aucune discussion avec ces contacts. Veuillez confirmer leur invitation à rejoindre ce salon avant de continuer.", + "invite_title": "Inviter de nouveaux contacts dans ce salon ?", + "start_chat_subtitle_multiple_users": "Vous n'avez actuellement aucune discussion avec ces personnes. Veuillez confirmer leur invitation avant de continuer.", + "start_chat_subtitle_one_user": "Vous n'avez actuellement aucune discussion avec cette personne. Confirmez son invitation avant de continuer.", + "start_chat_title_multiple_users": "Démarrer une discussion avec ces nouveaux contacts ?", + "start_chat_title_one_user": "Démarrer une discussion avec ce nouveau contact ?" + }, "email_caption": "Inviter par e-mail", "email_limit_one": "Les invitations par e-mail ne peuvent être envoyées qu’une par une", "email_use_default_is": "Utilisez un serveur d’identité pour inviter avec un e-mail. Utilisez le serveur par défaut (%(defaultIdentityServerName)s) ou gérez-le dans les Paramètres.", @@ -3886,6 +3893,16 @@ "call_held": "%(peerName)s a mis l’appel en attente", "call_held_resume": "Vous avez mis l’appel en attente Reprendre", "call_held_switch": "Vous avez mis l’appel en attente Basculer", + "call_members": { + "exhaustive": { + "one": " participant à l'appel", + "other": " participants à l'appel" + }, + "overflow": { + "one": "+ %(overflowCount)s participant à l'appel", + "other": "+ %(overflowCount)s participants à l'appel" + } + }, "call_toast_unknown_room": "Salon inconnu", "camera_disabled": "Votre caméra est éteinte", "camera_enabled": "Votre caméra est toujours allumée", @@ -3895,7 +3912,6 @@ "connection_lost": "La connexion au serveur a été perdue", "connection_lost_description": "Vous ne pouvez pas passer d’appels sans connexion au serveur.", "consulting": "Consultation avec %(transferTarget)s. Transfert à %(transferee)s", - "decline_call": "Refuser", "default_device": "Appareil par défaut", "dial": "Composer", "dialpad": "Pavé numérique", @@ -3909,10 +3925,12 @@ "enable_microphone": "Activer le microphone", "expand": "Revenir à l’appel", "get_call_link": "Partager le lien de l'appel", + "group_call_started": "L'appel de groupe a commencé", "hangup": "Raccrocher", "hide_sidebar_button": "Masquer la barre latérale", "input_devices": "Périphériques d’entrée", "jitsi_call": "Conférence Jitsi", + "join_with_video": "Rejoigner en vidéo", "legacy_call": "Appel vidéo", "maximise": "Remplir l’écran", "maximise_call": "Plein écran", @@ -3946,7 +3964,6 @@ "show_sidebar_button": "Afficher la barre latérale", "silence": "Mettre l’appel en sourdine", "silenced": "Notifications silencieuses", - "skip_lobby_toggle_option": "Rejoignez immédiatement", "start_screenshare": "Commencer à partager mon écran", "stop_screenshare": "Arrêter de partager mon écran", "too_many_calls": "Trop d’appels", @@ -3968,7 +3985,6 @@ "user_is_presenting": "%(sharerName)s est à l’écran", "video_call": "Appel vidéo", "video_call_incoming": "Appel vidéo entrant", - "video_call_started": "Appel vidéo commencé", "video_call_using": "Appel vidéo utilisant :", "voice_call": "Appel audio", "voice_call_incoming": "Appel vocal entrant", @@ -3976,6 +3992,11 @@ "you_are_presenting": "Vous êtes à l’écran" }, "web_default_device_name": "%(appName)s : %(browserName)s pour %(osName)s", + "welcome": { + "tagline_element": "Conçu pour la vitesse et la simplicité.", + "title_element": "Soyez dans votre Element", + "title_generic": "Bienvenue sur %(brand)s" + }, "widget": { "added_by": "Widget ajouté par", "capabilities_dialog": { diff --git a/apps/web/src/i18n/strings/hr.json b/apps/web/src/i18n/strings/hr.json index 0c7c1a3cfd..e568dc3f39 100644 --- a/apps/web/src/i18n/strings/hr.json +++ b/apps/web/src/i18n/strings/hr.json @@ -593,7 +593,6 @@ "video": "Videozapis", "video_room": "Videosoba", "view_message": "Prikaži poruku", - "voice": "Glas", "warning": "Upozorenje" }, "composer": { @@ -3951,7 +3950,6 @@ "connection_lost": "Veza s poslužiteljem je prekinuta", "connection_lost_description": "Ne možete upućivati ​​pozive ako niste povezani s poslužiteljem.", "consulting": "Savjetovanje s %(transferTarget)s. Prijenos na %(transferee)s", - "decline_call": "Odbij", "default_device": "Zadani uređaj", "dial": "Biranje", "dialpad": "Brojčanik", @@ -4003,7 +4001,6 @@ "show_sidebar_button": "Prikaži bočnu traku", "silence": "Utišaj poziv", "silenced": "Obavijesti su utišane", - "skip_lobby_toggle_option": "Pridruži se odmah", "start_screenshare": "Započni dijeljenje zaslona", "stop_screenshare": "Zaustavi dijeljenje zaslona", "too_many_calls": "Previše poziva", @@ -4025,7 +4022,6 @@ "user_is_presenting": "%(sharerName)s dijeli zaslon", "video_call": "Videopoziv", "video_call_incoming": "Dolazni videopoziv", - "video_call_started": "Videopoziv je započeo", "video_call_using": "Videopoziv putem:", "voice_call": "Glasovni poziv", "voice_call_incoming": "Dolazni glasovni poziv", diff --git a/apps/web/src/i18n/strings/hu.json b/apps/web/src/i18n/strings/hu.json index c57bb688e5..e8ed517044 100644 --- a/apps/web/src/i18n/strings/hu.json +++ b/apps/web/src/i18n/strings/hu.json @@ -586,7 +586,6 @@ "video": "Videó", "video_room": "Videószoba", "view_message": "Üzenet megjelenítése", - "voice": "Hang", "warning": "Figyelmeztetés" }, "composer": { @@ -3860,7 +3859,6 @@ "connection_lost": "Megszakadt a kapcsolat a kiszolgálóval", "connection_lost_description": "Nem kezdeményezhet hívást a kiszolgálóval való kapcsolat nélkül.", "consulting": "Egyeztetés vele: %(transferTarget)s. Átadás ide: %(transferee)s", - "decline_call": "Elutasítás", "default_device": "Alapértelmezett eszköz", "dial": "Tárcsázás", "dialpad": "Tárcsázó", @@ -3910,7 +3908,6 @@ "show_sidebar_button": "Oldalsáv megjelenítése", "silence": "Hívás némítása", "silenced": "Értesítések némítva", - "skip_lobby_toggle_option": "Csatlakozás azonnal", "start_screenshare": "Képernyőmegosztás bekapcsolása", "stop_screenshare": "Képernyőmegosztás kikapcsolása", "too_many_calls": "Túl sok hívás", @@ -3932,7 +3929,6 @@ "user_is_presenting": "%(sharerName)s tartja a bemutatót", "video_call": "Videóhívás", "video_call_incoming": "Bejövő videóhívás", - "video_call_started": "A videóhívás elindult", "video_call_using": "Videóhívás:", "voice_call": "Hanghívás", "voice_call_incoming": "Bejövő hanghívás", diff --git a/apps/web/src/i18n/strings/hy.json b/apps/web/src/i18n/strings/hy.json index 69599a6d2e..39a0596f7f 100644 --- a/apps/web/src/i18n/strings/hy.json +++ b/apps/web/src/i18n/strings/hy.json @@ -3839,7 +3839,6 @@ "user_busy_description": "Ձեր զանգահարած օգտատերը զբաղված է։", "user_is_presenting": "%(sharerName)s-ը ներկայացնում է", "video_call": "Տեսազանգ", - "video_call_started": "Տեսազանգը սկսվեց", "video_call_using": "Տեսազանգ՝ օգտագործելով՝", "voice_call": "Ձայնային զանգ", "you_are_presenting": "Դուք ներկայացնում եք" diff --git a/apps/web/src/i18n/strings/id.json b/apps/web/src/i18n/strings/id.json index 6868a9c8cd..72e200fb87 100644 --- a/apps/web/src/i18n/strings/id.json +++ b/apps/web/src/i18n/strings/id.json @@ -588,7 +588,6 @@ "video": "Video", "video_room": "Ruangan video", "view_message": "Tampilkan pesan", - "voice": "Suara", "warning": "Peringatan" }, "composer": { @@ -3852,7 +3851,6 @@ "connection_lost": "Koneksi ke server telah hilang", "connection_lost_description": "Anda tidak dapat membuat panggilan tanpa terhubung ke server.", "consulting": "Mengkonsultasi dengan %(transferTarget)s. Transfer ke %(transferee)s", - "decline_call": "Tolak", "default_device": "Perangkat Bawaan", "dial": "Panggil", "dialpad": "Tombol Penyetel", @@ -3903,7 +3901,6 @@ "show_sidebar_button": "Tampilkan sisi bilah", "silence": "Diamkan panggilan", "silenced": "Notifikasi dibisukan", - "skip_lobby_toggle_option": "Segera bergabung", "start_screenshare": "Mulai membagikan layar Anda", "stop_screenshare": "Berhenti membagikan layar Anda", "too_many_calls": "Terlalu Banyak Panggilan", @@ -3925,7 +3922,6 @@ "user_is_presenting": "%(sharerName)s sedang mempresentasi", "video_call": "Panggilan video", "video_call_incoming": "Panggilan video masuk", - "video_call_started": "Panggilan video dimulai", "video_call_using": "Panggilan video menggunakan:", "voice_call": "Panggilan suara", "voice_call_incoming": "Panggilan suara masuk", diff --git a/apps/web/src/i18n/strings/is.json b/apps/web/src/i18n/strings/is.json index b7fad1bd7a..ecf64657ee 100644 --- a/apps/web/src/i18n/strings/is.json +++ b/apps/web/src/i18n/strings/is.json @@ -2824,7 +2824,6 @@ "user_busy_description": "Notandinn sem þú hringdir í er upptekinn.", "user_is_presenting": "%(sharerName)s er að kynna", "video_call": "Myndsímtal", - "video_call_started": "Myndsímtal er byrjað", "voice_call": "Raddsímtal", "you_are_presenting": "Þú ert að kynna" }, diff --git a/apps/web/src/i18n/strings/it.json b/apps/web/src/i18n/strings/it.json index 07296b29f9..8bfd4c3dfa 100644 --- a/apps/web/src/i18n/strings/it.json +++ b/apps/web/src/i18n/strings/it.json @@ -3412,7 +3412,6 @@ "user_busy_description": "L'utente che hai chiamato è occupato.", "user_is_presenting": "%(sharerName)s sta presentando", "video_call": "Videochiamata", - "video_call_started": "Videochiamata iniziata", "video_call_using": "Videochiamata usando:", "voice_call": "Telefonata", "you_are_presenting": "Stai presentando" diff --git a/apps/web/src/i18n/strings/ja.json b/apps/web/src/i18n/strings/ja.json index 74abafc872..f7f4cb95b5 100644 --- a/apps/web/src/i18n/strings/ja.json +++ b/apps/web/src/i18n/strings/ja.json @@ -3136,7 +3136,6 @@ "user_busy_description": "呼び出したユーザーは通話中です。", "user_is_presenting": "%(sharerName)sが画面を共有しています", "video_call": "ビデオ通話", - "video_call_started": "ビデオ通話を開始しました", "voice_call": "音声通話", "you_are_presenting": "あなたが画面を共有しています" }, diff --git a/apps/web/src/i18n/strings/ka.json b/apps/web/src/i18n/strings/ka.json index 9c14feaf3a..4e949f5cb2 100644 --- a/apps/web/src/i18n/strings/ka.json +++ b/apps/web/src/i18n/strings/ka.json @@ -2669,7 +2669,6 @@ "user_busy_description": "მომხმარებელი, რომელსაც დაურეკე, დაკავებულია.", "user_is_presenting": "%(sharerName)sწარმოადგენს", "video_call": "ვიდეო ზარი", - "video_call_started": "ვიდეო ზარი დაიწყო", "voice_call": "ხმოვანი ზარი", "you_are_presenting": "წარმოგიდგენთ" }, diff --git a/apps/web/src/i18n/strings/ko.json b/apps/web/src/i18n/strings/ko.json index fcbf1776ef..c453a1f0de 100644 --- a/apps/web/src/i18n/strings/ko.json +++ b/apps/web/src/i18n/strings/ko.json @@ -583,7 +583,6 @@ "video": "비디오", "video_room": "비디오 room", "view_message": "메시지 보기", - "voice": "음성", "warning": "경고" }, "composer": { @@ -3785,7 +3784,6 @@ "connection_lost": "서버와의 연결이 끊어졌습니다.", "connection_lost_description": "서버에 연결되지 않으면 전화를 걸 수 없습니다.", "consulting": "%(transferTarget)s와 상담 중입니다. %(transferee)s에게 통화 이전", - "decline_call": "거부", "default_device": "기본 기기", "dial": "전화걸기", "dialpad": "다이얼패드", @@ -3835,7 +3833,6 @@ "show_sidebar_button": "사이드바 표시", "silence": "통화 음소거", "silenced": "알림 음소거됨", - "skip_lobby_toggle_option": "즉시 가입하세요", "start_screenshare": "화면 공유를 시작하세요", "stop_screenshare": "화면 공유 중지", "too_many_calls": "전화가 너무 많아요", @@ -3857,7 +3854,6 @@ "user_is_presenting": "%(sharerName)s님이 화면을 공유중입니다.", "video_call": "영상 통화", "video_call_incoming": "수신 영상 통화", - "video_call_started": "영상통화가 시작되었습니다", "video_call_using": "사용 중인 영상 통화:", "voice_call": "음성 통화", "voice_call_incoming": "수신 음성 통화", diff --git a/apps/web/src/i18n/strings/lt.json b/apps/web/src/i18n/strings/lt.json index 2ff4a8ad86..04a9fad051 100644 --- a/apps/web/src/i18n/strings/lt.json +++ b/apps/web/src/i18n/strings/lt.json @@ -94,7 +94,7 @@ "show_advanced": "Rodyti išplėstinius", "show_all": "Rodyti viską", "sign_in": "Prisijungti", - "sign_out": "Atsijungti", + "sign_out": "Šalinti šį įrenginį", "skip": "Praleisti", "start": "Pradėti", "start_chat": "Pradėti pokalbį", @@ -208,7 +208,7 @@ "set_email": { "description": "Tai jums leis iš naujo nustatyti slaptažodį ir gauti pranešimus.", "verification_pending_description": "Patikrinkite savo el. laišką ir spustelėkite jame esančią nuorodą. Kai tai padarysite, spauskite tęsti.", - "verification_pending_title": "Laukiama Patikrinimo" + "verification_pending_title": "Laukiama patvirtinimo" }, "set_email_prompt": "Ar norite nustatyti el. pašto adresą?", "sign_in_instead_prompt": "Jau turite paskyrą? Prisijunkite čia", @@ -402,8 +402,13 @@ "format_code_block": "Kodo blokas", "format_inline_code": "Kodas", "format_insert_link": "Įterpti nuorodą", + "format_italic": "Kursyvas", "format_italics": "Kursyvas", + "format_link": "Nuoroda", + "format_ordered_list": "Sunumeruotas sąrašas", "format_strikethrough": "Perbrauktas", + "format_underline": "Pabraukimas", + "format_unordered_list": "Suženklintasis sąrašas", "no_perms_notice": "Jūs neturite leidimų rašyti šiame kambaryje", "placeholder": "Siųsti žinutę…", "placeholder_encrypted": "Siųsti šifruotą žinutę…", @@ -424,6 +429,8 @@ "voice_message_button": "Balso žinutė" }, "create_room": { + "action_create_room": "Kurti kambarį", + "action_create_video_room": "Kurti vaizdo kambarį", "encryption_forced": "Jūsų serveris reikalauja, kad šifravimas būtų įjungtas privačiuose kambariuose.", "encryption_label": "Įjungti visapusį šifravimą", "error_title": "Nepavyko sukurti kambario", @@ -431,6 +438,7 @@ "name_validation_required": "Įveskite kambario pavadinimą", "title_private_room": "Sukurti privatų kambarį", "title_public_room": "Sukurti viešą kambarį", + "title_video_room": "Kurti vaizdo pokalbių kambarį", "topic_label": "Tema (nebūtina)", "unfederated": "Blokuoti bet ką, kas nėra iš %(serverName)s, niekada nebeleidžiant prisijungti prie šio kambario.", "unfederated_label_default_off": "Jūs galite tai įjungti, jei kambarys bus naudojamas tik bendradarbiavimui su vidinėmis komandomis jūsų serveryje. Tai negali būti vėliau pakeista.", @@ -461,16 +469,33 @@ "event_content": "Įvykio turinys", "event_sent": "Įvykis išsiųstas!", "event_type": "Įvykio tipas", + "explore_account_data": "Naršyti paskyros duomenis", + "explore_room_account_data": "Naršyti kambario paskyros duomenis", + "explore_room_state": "Naršyti kambario būseną", "failed_to_find_widget": "Įvyko klaida ieškant šio valdiklio.", + "failed_to_send": "Nepavyko išsiųsti įvykio.", + "invalid_json": "Neatrodo kaip tinkamas JSON.", "level": "Lygis", + "no_receipt_found": "Nerasta kvito.", + "notifications_debug": "Pranešimų derinimas", "original_event_source": "Originalus įvykio šaltinis", + "room_id": "Kambario ID: %(roomId)s", + "send_custom_account_data_event": "Siųsti pasirinktinius paskyros duomenis įvykį", + "send_custom_room_account_data_event": "Siųsti pasirinktinius kambario paskyros duomenis įvykį", + "send_custom_timeline_event": "Siųsti pasirinktinės laiko skalės įvykį", + "server_info": "Serverio informacija", "setting_colon": "Nustatymas:", "setting_id": "Nustatymo ID", + "settings_explorer": "Nustatymų naršyklė", "show_hidden_events": "Rodyti paslėptus įvykius laiko juostoje", "state_key": "Būklės raktas", + "thread_root_id": "Gijos šaknies ID: %(threadRootId)s", "title": "Kūrėjo įrankiai", "toolbox": "Įrankinė", + "user_read_up_to": "Naudotojas perskaitė iki: ", + "user_read_up_to_ignore_synthetic": "Naudotojas perskaitė iki (ignoreSynthetic): ", "value": "Reikšmė", + "view_servers_in_room": "Peržiūrėti serverius kambaryje", "widget_screenshots": "Įjungti valdiklių ekrano kopijas palaikomuose valdikliuose" }, "dialog_close_label": "Uždaryti dialogą", @@ -581,7 +606,7 @@ "unverified_session_toast_title": "Naujas prisijungimas. Ar tai jūs?", "unverified_sessions_toast_description": "Peržiūrėkite, ar jūsų paskyra yra saugi", "unverified_sessions_toast_reject": "Vėliau", - "verification_dialog_title_user": "Patikrinimo Užklausa", + "verification_dialog_title_user": "Patvirtinimo prašymas", "verify_emoji": "Patvirtinti naudojant jaustukus", "verify_emoji_prompt": "Patvirtinti palyginant unikalius jaustukus.", "verify_emoji_prompt_qr": "Jei nuskaityti aukščiau esančio kodo negalite, patvirtinkite palygindami unikalius jaustukus.", @@ -591,7 +616,8 @@ "waiting_other_user": "Laukiama kol %(displayName)s patvirtins…" }, "verify_toast_description": "Kiti vartotojai gali nepasitikėti", - "verify_toast_title": "Patvirtinti šį seansą" + "verify_toast_title": "Patvirtinti šį seansą", + "withdraw_verification_action": "Atšaukti patvirtinimą" }, "error": { "admin_contact_short": "Susisiekite su savo serverio administratoriumi.", @@ -798,7 +824,9 @@ }, "keyboard": { "activate_button": "Aktyvuoti pasirinktą mygtuką", + "alt": "Alt", "autocomplete_cancel": "Atšaukti automatinį užbaigimą", + "backspace": "Naikinimo klavišas", "cancel_reply": "Atšaukti atsakymą į žinutę", "category_autocomplete": "Autorašymas", "category_calls": "Skambučiai", @@ -809,29 +837,42 @@ "composer_toggle_bold": "Perjungti paryškinimą", "composer_toggle_italics": "Perjungti kursyvą", "composer_toggle_quote": "Perjungti citatą", + "control": "Vald", "dismiss_read_marker_and_jump_bottom": "Atsisakyti skaitymo žymeklio ir nušokti į apačią", + "end": "Pab", + "enter": "Įvesti", + "escape": "Gr", "home": "Pradžia", "jump_room_search": "Nušokti į kambarių paiešką", "jump_to_read_marker": "Nušokti iki seniausios neperskaitytos žinutės", "number": "[skaičius]", + "page_down": "Puslapis žemyn", + "page_up": "Puslapis aukštyn", "room_list_collapse_section": "Sutraukti kambarių sąrašo skyrių", "room_list_expand_section": "Išplėsti kambarių sąrašo skyrių", "room_list_select_room": "Pasirinkti kambarį iš kambarių sąrašo", "search": "Paieška (turi būti įjungta)", + "shift": "Lyg2", + "space": "Tarpas", "toggle_microphone_mute": "Perjungti mikrofono nutildymą", "toggle_right_panel": "Perjungti dešinį skydelį", "toggle_top_left_menu": "Perjungti viršutinį kairės pusės meniu", "upload_file": "Įkelti failą" }, "labs": { + "ask_to_join": "Įjungti prašymą jungtis", "beta_feedback_leave_button": "Norėdami išeiti iš beta versijos, apsilankykite savo nustatymuose.", "bridge_state": "Rodyti informaciją apie tiltus kambario nustatymuose", "bridge_state_channel": "Kanalas: ", "bridge_state_creator": "Šis tiltas buvo parūpintas .", "bridge_state_manager": "Šis tiltas yra tvarkomas .", "bridge_state_workspace": "Darbo aplinka: ", + "currently_experimental": "Šiuo metu eksperimentinė.", "custom_themes": "Palaikykite pridėdami pasirinktines temas", + "dynamic_room_predecessors": "Dinaminiai kambario pirmtakai", "element_call_video_rooms": "Element skambučio vaizdo kambariai", + "feature_wysiwyg_composer_description": "Naudoti raiškųjį tekstą vietoj ženklinimo žinučių rengyklėje.", + "group_calls": "Nauja grupinio skambučio patirtis", "group_developer": "Kūrėjas", "group_encryption": "Šifravimas", "group_experimental": "Eksperimentinis", @@ -843,13 +884,21 @@ "group_themes": "Temos", "group_voip": "Garsas ir Vaizdas", "group_widgets": "Valdikliai", + "hidebold": "Slėpti pranešimų tašką (rodyti tik skaičių žymes)", "html_topic": "Rodyti kambarių temų HTML atvaizdavimą", "jump_to_date": "Pereiti prie datos (prideda /jumptodate ir perėjimo prie datos antraštes)", + "jump_to_date_msc_support": "Privaloma, kad jūsų serveris palaikytų MSC3030.", "latex_maths": "Atvaizduoti LaTeX matematikas žinutėse", "leave_beta": "Palikti beta versiją", + "location_share_live": "Tiesioginis vietos bendrinimas", + "location_share_live_description": "Laikinas įgyvendinimas. Vietos išlieka kambario istorijoje.", + "mjolnir": "Nauji būdai ignoruoti žmones", "msc3531_hide_messages_pending_moderation": "Leisti moderatoriams slėpti žinutes, laukiančias moderavimo.", "notification_settings": "Nauji pranešimų nustatymai", "report_to_moderators": "Pranešti prižiūrėtojams", + "sliding_sync": "Slankiojo sinchronizavimo režimas", + "sliding_sync_description": "Aktyviai kuriama, negalima išjungti. Šiuo metu nesuderinama su „Element“ skambučiais.", + "under_active_development": "Šiuo metu aktyviai kuriama.", "video_rooms": "Vaizdo kambariai", "video_rooms_a_new_way_to_chat": "Naujas būdas kalbėtis balsu ir vaizdu per %(brand)s.", "video_rooms_always_on_voip_channels": "Vaizdo kambariai - tai visada veikiantys VoIP kanalai, įterpti į %(brand)s kambarį.", @@ -951,6 +1000,7 @@ }, "powered_by_matrix": "Veikia su Matrix", "presence": { + "away": "Pasitraukę", "busy": "Užsiėmęs", "idle": "Neveiklus", "idle_for": "Neveiklus %(duration)s", @@ -1414,8 +1464,10 @@ "big_emoji": "Įjungti didelius jaustukus pokalbiuose", "code_block_expand_default": "Išplėsti kodo blokus pagal nutylėjimą", "code_block_line_numbers": "Rodyti eilučių numerius kodo blokuose", + "disable_historical_profile": "Rodyti dabartinę profilio nuotrauką ir vardą naudotojams žinučių istorijoje", "emoji_autocomplete": "Įjungti Jaustukų pasiūlymus rašant", "enable_markdown": "Įjungti Markdown", + "enable_markdown_description": "Pradėkite žinutes su /plain norint siųsti be ženklinimo.", "general": { "account_management_section": "Paskyros tvarkymas", "account_section": "Paskyra", @@ -1466,7 +1518,7 @@ "remove_email_prompt": "Pašalinti %(email)s?", "remove_msisdn_prompt": "Pašalinti %(phone)s?" }, - "inline_url_previews_default": "Įjungti URL nuorodų peržiūras kaip numatytasias", + "inline_url_previews_default": "Įjungti peržiūras", "insert_trailing_colon_mentions": "Įterpti dvitaškį po naudotojo paminėjimų žinutės pradžioje", "jump_to_bottom_on_send": "Peršokti į laiko juostos apačią, kai siunčiate žinutę", "key_backup": { @@ -1624,10 +1676,12 @@ "verified_sessions_list_description": "Geriausiam saugumui, atsijunkite iš bet kurios sesijos, kurios neatpažįstate arba nebenaudojate.", "verify_session": "Patvirtinti seansą" }, + "show_avatar_changes": "Rodyti profilio nuotraukos pakeitimus", "show_breadcrumbs": "Rodyti neseniai peržiūrėtų kambarių nuorodas virš kambarių sąrašo", "show_chat_effects": "Rodyti pokalbių efektus (animaciją, kai gaunate, pvz., konfeti)", "show_displayname_changes": "Rodyti rodomo vardo pakeitimus", "show_join_leave": "Rodyti prisijungimo/išėjimo žinutes (kvietimai/pašalinimai/blokavimai neturi įtakos)", + "show_nsfw_content": "Rodyti NSD turinį", "show_read_receipts": "Rodyti kitų vartotojų siųstus perskaitymo kvitus", "show_redaction_placeholder": "Rodyti pašalintų žinučių žymeklį", "show_stickers_button": "Rodyti lipdukų mygtuką", @@ -1905,8 +1959,8 @@ "m.room.member": { "accepted_3pid_invite": "%(targetName)s priėmė kvietimą, skirtą %(displayName)s", "accepted_invite": "%(targetName)s priėmė kvietimą", - "ban": "%(senderName)s uždraudė %(targetName)s", - "ban_reason": "%(senderName)s uždraudė %(targetName)s%(targetName)s: %(reason)s", + "ban": "%(senderName)s uždraudė naudotoją", + "ban_reason": "%(senderName)s uždraudė naudotoją: %(reason)s", "change_avatar": "%(senderName)s pakeitė savo profilio nuotrauką", "change_name": "%(oldDisplayName)s pasikeitė savo rodomą vardą į %(displayName)s", "change_name_avatar": "%(oldDisplayName)s pakeitė savo rodomą vardą ir profilio nuotrauką", @@ -1994,7 +2048,8 @@ "pending_moderation": "Žinutė laukia moderavimo", "pending_moderation_reason": "Žinutė laukia moderavimo: %(reason)s", "reactions": { - "add_reaction_prompt": "Pridėti reakciją" + "add_reaction_prompt": "Pridėti reakciją", + "label": "%(reactors)s sureagavo su %(content)s" }, "read_receipt_title": { "one": "Matė %(count)s žmogus", diff --git a/apps/web/src/i18n/strings/lv.json b/apps/web/src/i18n/strings/lv.json index d0c60566f7..f32957557b 100644 --- a/apps/web/src/i18n/strings/lv.json +++ b/apps/web/src/i18n/strings/lv.json @@ -3354,7 +3354,6 @@ "user_busy_description": "Lietotājs, kuram zvanāt, ir aizņemts.", "user_is_presenting": "%(sharerName)s prezentē", "video_call": "Video zvans", - "video_call_started": "Videozvans uzsākts", "voice_call": "Balss zvans", "you_are_presenting": "Jūs prezentējat" }, diff --git a/apps/web/src/i18n/strings/mg_MG.json b/apps/web/src/i18n/strings/mg_MG.json index c290f91b3e..0fd9d0e609 100644 --- a/apps/web/src/i18n/strings/mg_MG.json +++ b/apps/web/src/i18n/strings/mg_MG.json @@ -3394,7 +3394,6 @@ "user_busy_description": "Mbola Sahirana ny mpampiasa niantsoanao.", "user_is_presenting": "%(sharerName)sIzao", "video_call": "Antso an-tsary", - "video_call_started": "Manomboka ny antso an-tsary", "video_call_using": "Antso an-tsary mampiasa:", "voice_call": "Antso an-tariby", "you_are_presenting": "Mamkahalala anareo" diff --git a/apps/web/src/i18n/strings/nb_NO.json b/apps/web/src/i18n/strings/nb_NO.json index 78bee47967..6ca18886c7 100644 --- a/apps/web/src/i18n/strings/nb_NO.json +++ b/apps/web/src/i18n/strings/nb_NO.json @@ -588,7 +588,6 @@ "video": "Video", "video_room": "Videorom", "view_message": "Se melding", - "voice": "Tale", "warning": "Advarsel" }, "composer": { @@ -3859,7 +3858,6 @@ "connection_lost": "Mistet forbindelsen til serveren", "connection_lost_description": "Du kan ikke ringe uten tilkobling til serveren.", "consulting": "Rådføring med %(transferTarget)s. Overfør til %(transferee)s", - "decline_call": "Avslå", "default_device": "Standardenhet", "dial": "Ring", "dialpad": "Nummerpanel", @@ -3910,7 +3908,6 @@ "show_sidebar_button": "Vis sidepanel", "silence": "Demp samtale", "silenced": "Varslinger er dempet", - "skip_lobby_toggle_option": "Bli med umiddelbart", "start_screenshare": "Begynn å dele skjermen din", "stop_screenshare": "Slutt å dele skjermen din", "too_many_calls": "For mange samtaler", @@ -3932,7 +3929,6 @@ "user_is_presenting": "%(sharerName)s presenterer", "video_call": "Videosamtale", "video_call_incoming": "Innkommende videosamtale", - "video_call_started": "Videosamtale startet", "video_call_using": "Videosamtale ved hjelp av:", "voice_call": "Stemmesamtale", "voice_call_incoming": "Innkommende taleanrop", diff --git a/apps/web/src/i18n/strings/nl.json b/apps/web/src/i18n/strings/nl.json index 7d3426c61f..bb655923b4 100644 --- a/apps/web/src/i18n/strings/nl.json +++ b/apps/web/src/i18n/strings/nl.json @@ -2960,7 +2960,6 @@ "user_busy_description": "De persoon die je belde is bezet.", "user_is_presenting": "%(sharerName)s is aan het presenteren", "video_call": "Video-oproep", - "video_call_started": "Videogesprek gestart", "voice_call": "Spraakoproep", "you_are_presenting": "Je bent aan het presenteren" }, diff --git a/apps/web/src/i18n/strings/pl.json b/apps/web/src/i18n/strings/pl.json index 4a5428e5eb..67a6fbb35b 100644 --- a/apps/web/src/i18n/strings/pl.json +++ b/apps/web/src/i18n/strings/pl.json @@ -3829,7 +3829,6 @@ "connection_lost": "Połączenie z serwerem zostało przerwane", "connection_lost_description": "Nie możesz wykonywać rozmów bez połączenia z serwerem.", "consulting": "Konsultowanie z %(transferTarget)s. Transfer do %(transferee)s", - "decline_call": "Odrzuć", "default_device": "Urządzenie domyślne", "dial": "Wybierz numer", "dialpad": "Klawiatura telefoniczna", @@ -3881,7 +3880,6 @@ "show_sidebar_button": "Pokaż pasek boczny", "silence": "Wycisz rozmowę", "silenced": "Wyciszono powiadomienia", - "skip_lobby_toggle_option": "Dołącz teraz", "start_screenshare": "Udostępnij ekran", "stop_screenshare": "Przestań udostępniać ekran", "too_many_calls": "Zbyt wiele połączeń", @@ -3902,7 +3900,6 @@ "user_busy_description": "Użytkownik, do którego zadzwoniłeś jest zajęty.", "user_is_presenting": "%(sharerName)s prezentuje", "video_call": "Rozmowa wideo", - "video_call_started": "Rozpoczęto rozmowę wideo", "video_call_using": "Połączenie wideo przy użyciu:", "voice_call": "Rozmowa głosowa", "you_are_presenting": "Prezentujesz" diff --git a/apps/web/src/i18n/strings/pt.json b/apps/web/src/i18n/strings/pt.json index cbe8504bd2..d9f9f56f7a 100644 --- a/apps/web/src/i18n/strings/pt.json +++ b/apps/web/src/i18n/strings/pt.json @@ -3750,7 +3750,6 @@ "user_busy_description": "O utilizador para o qual tentou ligar está ocupado.", "user_is_presenting": "%(sharerName)s está apresentando", "video_call": "Chamada de vídeo", - "video_call_started": "Chamada de vídeo iniciada", "video_call_using": "Video-chamada usando:", "voice_call": "Chamada de voz", "you_are_presenting": "Estás a apresentar" diff --git a/apps/web/src/i18n/strings/pt_BR.json b/apps/web/src/i18n/strings/pt_BR.json index c89dadaa45..8223facad5 100644 --- a/apps/web/src/i18n/strings/pt_BR.json +++ b/apps/web/src/i18n/strings/pt_BR.json @@ -587,7 +587,6 @@ "video": "Vídeo", "video_room": "Sala de vídeo", "view_message": "Ver mensagem", - "voice": "Voz", "warning": "Atenção" }, "composer": { @@ -3834,7 +3833,6 @@ "connection_lost": "A conectividade com o servidor foi perdida", "connection_lost_description": "Você não pode fazer chamadas sem uma conexão com o servidor.", "consulting": "Consultar com %(transferTarget)s. Tranferir para %(transferee)s", - "decline_call": "Recusar", "default_device": "Aparelho padrão", "dial": "Discar", "dialpad": "Teclado de discagem", @@ -3885,7 +3883,6 @@ "show_sidebar_button": "Exibir a barra lateral", "silence": "Silenciar chamado", "silenced": "Notificações silenciadas", - "skip_lobby_toggle_option": "Junte-se imediatamente", "start_screenshare": "Começar a compartilhar sua tela", "stop_screenshare": "Parar de compartilhar sua tela", "too_many_calls": "Muitas chamadas", @@ -3907,7 +3904,6 @@ "user_is_presenting": "%(sharerName)s está apresentando", "video_call": "Chamada de vídeo", "video_call_incoming": "Chamada de vídeo recebida", - "video_call_started": "Videochamada iniciada", "video_call_using": "Chamada de vídeo usando:", "voice_call": "Chamada de voz", "voice_call_incoming": "Chamada de voz recebida", diff --git a/apps/web/src/i18n/strings/ru.json b/apps/web/src/i18n/strings/ru.json index 1bbf081882..5c1d981525 100644 --- a/apps/web/src/i18n/strings/ru.json +++ b/apps/web/src/i18n/strings/ru.json @@ -589,7 +589,6 @@ "video": "Видео", "video_room": "Видеочат", "view_message": "Посмотреть сообщение", - "voice": "Голос", "warning": "Внимание" }, "composer": { @@ -681,6 +680,11 @@ "unfederated_label_default_on": "Вы можете отключить это, если комната будет использоваться для совместной работы с внешними командами, у которых есть собственный домашний сервер. Это не может быть изменено позже.", "unsupported_version": "Сервер не поддерживает указанную версию чата." }, + "create_section_dialog": { + "create_section": "Новый раздел", + "label": "Название раздела", + "title": "Новый раздел" + }, "create_space": { "add_details_prompt": "Добавьте некоторые подробности, чтобы помочь людям узнать его.", "add_details_prompt_2": "Вы можете изменить их в любое время.", @@ -3900,7 +3904,6 @@ "connection_lost": "Соединение с сервером потеряно", "connection_lost_description": "Вы не можете совершать вызовы без подключения к серверу.", "consulting": "Общение с %(transferTarget)s. Перевод на %(transferee)s", - "decline_call": "Отклонить", "default_device": "Устройство по умолчанию", "dial": "Набор", "dialpad": "Панель набора номера", @@ -3951,7 +3954,6 @@ "show_sidebar_button": "Показать боковую панель", "silence": "Тихий вызов", "silenced": "Оповещения приглушены", - "skip_lobby_toggle_option": "Присоединиться прямо сейчас", "start_screenshare": "Начать делиться экраном", "stop_screenshare": "Перестать делиться экраном", "too_many_calls": "Слишком много звонков", @@ -3973,7 +3975,6 @@ "user_is_presenting": "%(sharerName)s показывает", "video_call": "Видеовызов", "video_call_incoming": "Входящий видеозвонок", - "video_call_started": "Начался видеозвонок", "video_call_using": "Видеозвонок с использованием:", "voice_call": "Голосовой вызов", "voice_call_incoming": "Входящий голосовой вызов", diff --git a/apps/web/src/i18n/strings/sk.json b/apps/web/src/i18n/strings/sk.json index 667307f088..d840d86287 100644 --- a/apps/web/src/i18n/strings/sk.json +++ b/apps/web/src/i18n/strings/sk.json @@ -593,7 +593,6 @@ "video": "Video", "video_room": "Video miestnosť", "view_message": "Zobraziť správu", - "voice": "Hlas", "warning": "Upozornenie" }, "composer": { @@ -3938,7 +3937,6 @@ "connection_lost": "Spojenie so serverom bolo prerušené", "connection_lost_description": "Bez pripojenia k serveru nie je možné uskutočňovať hovory.", "consulting": "Konzultovanie s %(transferTarget)s. Presmerovanie na %(transferee)s", - "decline_call": "Zamietnuť", "default_device": "Predvolené zariadenie", "dial": "Vytočiť číslo", "dialpad": "Číselník", @@ -3990,7 +3988,6 @@ "show_sidebar_button": "Zobraziť bočný panel", "silence": "Stlmiť hovor", "silenced": "Oznámenia stlmené", - "skip_lobby_toggle_option": "Pripojiť sa okamžite", "start_screenshare": "Spustiť zdieľanie vašej obrazovky", "stop_screenshare": "Zastaviť zdieľanie vašej obrazovky", "too_many_calls": "Príliš veľa hovorov", @@ -4012,7 +4009,6 @@ "user_is_presenting": "%(sharerName)s prezentuje", "video_call": "Video hovor", "video_call_incoming": "Prichádzajúci videohovor", - "video_call_started": "Videohovor bol spustený", "video_call_using": "Videohovor pomocou:", "voice_call": "Hlasový hovor", "voice_call_incoming": "Prichádzajúci hlasový hovor", diff --git a/apps/web/src/i18n/strings/sq.json b/apps/web/src/i18n/strings/sq.json index db8ea4f362..66c61e14f0 100644 --- a/apps/web/src/i18n/strings/sq.json +++ b/apps/web/src/i18n/strings/sq.json @@ -3196,7 +3196,6 @@ "user_busy_description": "Përdoruesi që thirrët është i zënë.", "user_is_presenting": "%(sharerName)s përfaqëson", "video_call": "Thirrje video", - "video_call_started": "Nisi thirrje me video", "voice_call": "Thirrje audio", "you_are_presenting": "Përfaqësoni" }, diff --git a/apps/web/src/i18n/strings/sv.json b/apps/web/src/i18n/strings/sv.json index 52aa32e1e6..1dbaf29712 100644 --- a/apps/web/src/i18n/strings/sv.json +++ b/apps/web/src/i18n/strings/sv.json @@ -3836,7 +3836,6 @@ "user_busy_description": "Användaren du ringde är upptagen.", "user_is_presenting": "%(sharerName)s presenterar", "video_call": "Videosamtal", - "video_call_started": "Videosamtal startat", "video_call_using": "Videosamtal med hjälp av:", "voice_call": "Röstsamtal", "you_are_presenting": "Du presenterar" diff --git a/apps/web/src/i18n/strings/tr.json b/apps/web/src/i18n/strings/tr.json index f98c5870f4..8c7f71555c 100644 --- a/apps/web/src/i18n/strings/tr.json +++ b/apps/web/src/i18n/strings/tr.json @@ -3736,7 +3736,6 @@ "user_busy_description": "Aradığınız kullanıcı meşgul.", "user_is_presenting": "%(sharerName)s sunum yapıyor", "video_call": "Görüntülü arama", - "video_call_started": "Görüntülü arama başlatıldı", "video_call_using": "Görüntülü arama kullanılıyor:", "voice_call": "Sesli arama", "you_are_presenting": "Sunum yapıyorsunuz" diff --git a/apps/web/src/i18n/strings/uk.json b/apps/web/src/i18n/strings/uk.json index 5b64446e6d..a1aab9a0fe 100644 --- a/apps/web/src/i18n/strings/uk.json +++ b/apps/web/src/i18n/strings/uk.json @@ -589,7 +589,6 @@ "video": "Відео", "video_room": "Відеокімната", "view_message": "Переглянути повідомлення", - "voice": "Голос", "warning": "Попередження" }, "composer": { @@ -3870,7 +3869,6 @@ "connection_lost": "Втрачено зʼєднання з сервером", "connection_lost_description": "Неможливо здійснювати виклики без з'єднання з сервером.", "consulting": "Консультація з %(transferTarget)s. Переадресація на %(transferee)s", - "decline_call": "Відхилити", "default_device": "Уставний пристрій", "dial": "Виклик", "dialpad": "Номеронабирач", @@ -3922,7 +3920,6 @@ "show_sidebar_button": "Показати бічну панель", "silence": "Тихий виклик", "silenced": "Сповіщення стишено", - "skip_lobby_toggle_option": "Приєднатися негайно", "start_screenshare": "Почати показ екрана", "stop_screenshare": "Вимкнути показ екрана", "too_many_calls": "Забагато викликів", @@ -3944,7 +3941,6 @@ "user_is_presenting": "%(sharerName)s показує", "video_call": "Відеовиклик", "video_call_incoming": "Вхідний відеовиклик", - "video_call_started": "Відеовиклик розпочато", "video_call_using": "Відеодзвінок за допомогою:", "voice_call": "Голосовий виклик", "voice_call_incoming": "Вхідний голосовий виклик", diff --git a/apps/web/src/i18n/strings/vi.json b/apps/web/src/i18n/strings/vi.json index 141845f695..d5f26ace6a 100644 --- a/apps/web/src/i18n/strings/vi.json +++ b/apps/web/src/i18n/strings/vi.json @@ -3137,7 +3137,6 @@ "user_busy_description": "Người dùng bạn vừa gọi hiện đang bận.", "user_is_presenting": "%(sharerName)s đang trình bày", "video_call": "Gọi video", - "video_call_started": "Cuộc gọi truyền hình đã bắt đầu", "voice_call": "Gọi thoại", "you_are_presenting": "Bạn đang trình bày" }, diff --git a/apps/web/src/i18n/strings/zh_Hans.json b/apps/web/src/i18n/strings/zh_Hans.json index 9c471ec469..ec46018f0c 100644 --- a/apps/web/src/i18n/strings/zh_Hans.json +++ b/apps/web/src/i18n/strings/zh_Hans.json @@ -219,7 +219,7 @@ "incorrect_password": "密码不正确", "log_in_new_account": "登录到你的新账户。", "logout_dialog": { - "description": "你确定要注销吗?", + "description": "你确定要移除此设备?", "megolm_export": "手动导出密钥", "setup_key_backup_title": "你将失去加密消息的访问权", "setup_secure_backup_description_1": "加密消息采用端到端加密技术确保安全。只有你与收件人拥有读取这些消息的密钥。", @@ -588,7 +588,6 @@ "video": "视频", "video_room": "视频房间", "view_message": "查看消息", - "voice": "语音", "warning": "警告" }, "composer": { @@ -684,8 +683,10 @@ "create_section_dialog": { "create_section": "创建区域", "description": "区域仅对你可见", + "edit_section": "编辑区域", "label": "区域名称", - "title": "创建区域" + "title": "创建区域", + "title_edition": "编辑区域" }, "create_space": { "add_details_prompt": "添加一些信息以便人们识别。", @@ -1116,7 +1117,7 @@ "waiting_other_device_details": "等待你在另一台设备上验证,%(deviceName)s(%(deviceId)s)…", "waiting_other_user": "正在等待 %(displayName)s 验证…" }, - "verification_requested_toast_title": "已请求验证", + "verification_requested_toast_title": "请求验证", "verified_identity_changed": "%(displayName)s (%(userId)s) 的数字身份已重置。了解更多", "verified_identity_changed_no_displayname": "%(userId)s的数字身份已重置。了解更多", "verify_toast_description": "可能不受其他用户信任", @@ -1433,7 +1434,7 @@ }, "keyboard": { "activate_button": "激活选择的按钮", - "alt": "Alt", + "alt": "", "autocomplete_cancel": "取消自动补全", "autocomplete_force": "强制完成", "autocomplete_navigate_next": "下一个自动补全建议", @@ -1457,11 +1458,11 @@ "composer_toggle_link": "切换链接", "composer_toggle_quote": "切换引用", "composer_undo": "撤消编辑", - "control": "Ctrl", + "control": "", "dismiss_read_marker_and_jump_bottom": "忽略已读标记并跳转到底部", - "end": "结束", + "end": "", "enter": "", - "escape": "Esc", + "escape": "", "go_home_view": "转到主页视图", "home": "", "jump_first_message": "跳转到首个消息", @@ -1493,7 +1494,7 @@ "scroll_up_timeline": "向上滚动时间线", "search": "搜索(要使其生效必须启用相关功能)", "send_sticker": "发送贴纸", - "shift": "Shift", + "shift": "", "space": "空格", "switch_to_space": "使用数字切换空间", "toggle_hidden_events": "切换隐藏事件的可见性", @@ -1843,6 +1844,12 @@ "ongoing": "正在移除…", "reason_label": "理由(可选)" }, + "remove_section_dialog": { + "confirmation": "你确定要移除此区域?", + "description": "此区域中的聊天仍将显示在聊天列表。", + "remove_section": "移除区域", + "title": "移除此区域?" + }, "report_content": { "description": "举报此消息会将其唯一的“事件 ID”发送给服务器管理员。如果此房间中的消息已加密,则服务器管理员将无法查看消息文本、任何文件或图像。", "disagree": "不同意", @@ -2452,7 +2459,7 @@ "description_3": "浏览器扩展阻止了该请求。", "description_4": "此服务器已离线。", "description_5": "服务器已拒绝你的请求。", - "description_6": "你所在的区域在连接 Internet 时遇到困难。", + "description_6": "你所在的地区在连接 Internet 时遇到困难。", "description_7": "尝试联系服务器时发生连接错误。", "description_8": "服务器的配置未能说明问题原因(CORS)。", "empty_timeline": "你已阅读所有消息", @@ -2573,7 +2580,7 @@ "dialog_title": "设置:加密", "key_storage": { "allow_key_storage": "允许密钥存储", - "description": "这将允许你在任意新设备上查看聊天历史,这对备份聊天和数字身份是必需的。", + "description": "这将允许你在任意新设备上查看聊天历史,这对备份聊天和数字身份是必需的。了解更多", "title": "密钥存储" }, "recovery": { @@ -3741,7 +3748,7 @@ "other": "%(names)s 与其他 %(count)s 个人正在输入…" }, "one_user": "%(displayName)s 正在输入…", - "two_users": "%(names)s 与其他 %(lastPerson)s 个人正在输入…" + "two_users": "%(names)s 与 %(lastPerson)s 正在输入…" }, "undecryptable_tooltip": "此消息无法解密" }, @@ -3894,7 +3901,6 @@ "connection_lost": "与服务器的连接已丢失", "connection_lost_description": "在未连接到服务器的情况下,你无法拨打电话。", "consulting": "正在与 %(transferTarget)s 协商。转接到 %(transferee)s", - "decline_call": "拒绝", "default_device": "默认设备", "dial": "拨号", "dialpad": "拨号盘", @@ -3945,7 +3951,6 @@ "show_sidebar_button": "显示边栏", "silence": "静音通话", "silenced": "通知已静音", - "skip_lobby_toggle_option": "立即加入", "start_screenshare": "开始分享屏幕", "stop_screenshare": "停止分享屏幕", "too_many_calls": "呼叫频繁", @@ -3967,7 +3972,6 @@ "user_is_presenting": "%(sharerName)s 正在分享", "video_call": "视频通话", "video_call_incoming": "视频通话来电", - "video_call_started": "已开始视频通话", "video_call_using": "视频通话时使用:", "voice_call": "语音通话", "voice_call_incoming": "语音通话来电", diff --git a/apps/web/src/i18n/strings/zh_Hant.json b/apps/web/src/i18n/strings/zh_Hant.json index a606287572..70a4fa882a 100644 --- a/apps/web/src/i18n/strings/zh_Hant.json +++ b/apps/web/src/i18n/strings/zh_Hant.json @@ -3399,7 +3399,6 @@ "user_busy_description": "您想要通話的使用者目前忙碌中。", "user_is_presenting": "%(sharerName)s 正在投影", "video_call": "視訊通話", - "video_call_started": "視訊通話已開始", "voice_call": "語音通話", "you_are_presenting": "您正在投影" }, diff --git a/packages/shared-components/src/i18n/strings/fr.json b/packages/shared-components/src/i18n/strings/fr.json index 2fe4d11c69..45ef4ac2ba 100644 --- a/packages/shared-components/src/i18n/strings/fr.json +++ b/packages/shared-components/src/i18n/strings/fr.json @@ -99,6 +99,7 @@ "voice_call": "Ouvrir le salon %(roomName)s contenant un appel vocal." }, "appearance": "Apparence", + "chat_moved": "La conversation a été déplacée", "collapse_filters": "Réduire la liste des filtres", "empty": { "no_chats": "Pas encore de discussions", @@ -224,6 +225,7 @@ "message_timestamp_sent_at": "Envoyé à : %(dateTime)s", "url_preview": { "close": "Fermer l’aperçu", + "open_link": "Ouvrir le lien", "show_n_more": { "one": "Montrer %(count)s autre preview", "other": "Montrer %(count)s autres previews" diff --git a/packages/shared-components/src/i18n/strings/ru.json b/packages/shared-components/src/i18n/strings/ru.json index cc269cfaa3..fa4fb4a7e8 100644 --- a/packages/shared-components/src/i18n/strings/ru.json +++ b/packages/shared-components/src/i18n/strings/ru.json @@ -5,6 +5,7 @@ "action": { "back": "Назад", "click": "Нажмите", + "close": "Закрыть", "collapse": "Свернуть", "delete": "Удалить", "dismiss": "Закрыть", @@ -16,6 +17,7 @@ "invite": "Пригласить", "new_conversation": "Новый диалог", "new_room": "Новая комната", + "new_section": "Новый раздел", "new_video_room": "Новая видеокомната", "open_menu": "Открыть меню", "pause": "Пауза", @@ -34,6 +36,7 @@ "common": { "attachment": "Вложение", "encryption_enabled": "Шифрование включено", + "loading": "Загрузка…", "preferences": "Настройки", "state_encryption_enabled": "Экспериментальное шифрование включено" }, diff --git a/packages/shared-components/src/i18n/strings/zh_Hans.json b/packages/shared-components/src/i18n/strings/zh_Hans.json index 0a4469c2e7..bea7c6a4dd 100644 --- a/packages/shared-components/src/i18n/strings/zh_Hans.json +++ b/packages/shared-components/src/i18n/strings/zh_Hans.json @@ -129,6 +129,9 @@ "room_options": "房间选项", "section_created": "区域已创建", "section_header": { + "edit_section": "编辑区域", + "more_options": "更多选项", + "remove_section": "移除区域", "toggle": "切换区域 %(section)s", "toggle_unread": "切换到区域 %(section)s 中的未读房间" }, From 4bee8450101b2ac7082d82aeac657a213eb12cd8 Mon Sep 17 00:00:00 2001 From: Will Hunt <2072976+Half-Shot@users.noreply.github.com> Date: Wed, 29 Apr 2026 09:14:22 +0100 Subject: [PATCH 09/23] Show user status in timeline (#32991) * Use other branch * All the changes that got lost * Fix merge * Ensure emoji can only be one character long * Fixup labs feature * Remove redundant check * Update snapshot * update snapshot * add snapshot * unpin * fix pnpm lock * undo pn[m lockfile changes altogether as we shouldn't actually need any afaik * update snpahot for changed IDs * Snapshot update * Snapshot update * There is now another section * more snapshots * more snapshot * More snapshots * oh come on snapshots * actual snapshot update * Fix sonar issues * just update the thing manually * [screams internally] * Update snapshot * test for useUserStatus * Make useUserStatus actually truncate * Split out slash command to its own file & add test * Remove irrelevant comment * doc * Comment on non-obvious error message --------- Co-authored-by: David Baker --- apps/web/src/MatrixClientPeg.ts | 3 + .../views/messages/SenderProfile.tsx | 6 + apps/web/src/hooks/useUserStatus.ts | 98 +++++++++++ apps/web/src/i18n/strings/en_EN.json | 13 ++ apps/web/src/settings/Settings.tsx | 26 +++ .../ServerSupportUnstableFeatureController.ts | 18 +- apps/web/src/slash-commands/SlashCommands.tsx | 3 + apps/web/src/slash-commands/status.ts | 51 ++++++ .../DisambiguatedProfileViewModel.ts | 19 ++- apps/web/test/slash-commands/status-test.ts | 62 +++++++ .../__snapshots__/ReplyChain-test.tsx.snap | 4 +- .../MemberTileView-test.tsx.snap | 6 +- .../LayoutSwitcher-test.tsx.snap | 12 +- .../tabs/user/LabsUserSettingsTab-test.tsx | 2 +- .../AppearanceUserSettingsTab-test.tsx.snap | 12 +- .../unit-tests/hooks/useUserStatus-test.tsx | 155 ++++++++++++++++++ .../__snapshots__/HTMLExport-test.ts.snap | 2 +- docs/labs.md | 5 + .../full-example-auto.png | Bin 19267 -> 19920 bytes .../with-user-status-auto.png | Bin 0 -> 4732 bytes .../DisambiguatedProfile.module.css | 4 + .../DisambiguatedProfile.stories.tsx | 19 +++ .../DisambiguatedProfileView.tsx | 20 ++- .../DisambiguatedProfile.test.tsx.snap | 5 + 24 files changed, 523 insertions(+), 22 deletions(-) create mode 100644 apps/web/src/hooks/useUserStatus.ts create mode 100644 apps/web/src/slash-commands/status.ts create mode 100644 apps/web/test/slash-commands/status-test.ts create mode 100644 apps/web/test/unit-tests/hooks/useUserStatus-test.tsx create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.stories.tsx/with-user-status-auto.png diff --git a/apps/web/src/MatrixClientPeg.ts b/apps/web/src/MatrixClientPeg.ts index 8148509ef1..5f8125be67 100644 --- a/apps/web/src/MatrixClientPeg.ts +++ b/apps/web/src/MatrixClientPeg.ts @@ -297,6 +297,9 @@ class MatrixClientPegClass implements IMatrixClientPeg { opts.lazyLoadMembers = true; opts.clientWellKnownPollPeriod = 2 * 60 * 60; // 2 hours opts.threadSupport = true; + if (SettingsStore.getValue("feature_user_status")) { + opts.unstableMSC4429SyncUserProfileFields = ["org.matrix.msc4426.status"]; + } if (SettingsStore.getValue("feature_sliding_sync")) { throw new UserFriendlyError("sliding_sync_legacy_no_longer_supported"); diff --git a/apps/web/src/components/views/messages/SenderProfile.tsx b/apps/web/src/components/views/messages/SenderProfile.tsx index f3465d24f8..70ab205cc1 100644 --- a/apps/web/src/components/views/messages/SenderProfile.tsx +++ b/apps/web/src/components/views/messages/SenderProfile.tsx @@ -13,6 +13,7 @@ import { useCreateAutoDisposedViewModel, DisambiguatedProfileView } from "@eleme import { DisambiguatedProfileViewModel } from "../../../viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel"; import { useRoomMemberProfile } from "../../../hooks/room/useRoomMemberProfile"; +import { useUserStatus } from "../../../hooks/useUserStatus"; interface IProps { mxEvent: MatrixEvent; @@ -27,6 +28,7 @@ export default function SenderProfile({ mxEvent, onClick, withTooltip }: IProps) userId: sender, member: mxEvent.sender, }); + const userStatus = useUserStatus(sender); const disambiguatedProfileVM = useCreateAutoDisposedViewModel( () => @@ -37,9 +39,13 @@ export default function SenderProfile({ mxEvent, onClick, withTooltip }: IProps) colored: true, emphasizeDisplayName: true, withTooltip, + userStatus, }), ); + useEffect(() => { + disambiguatedProfileVM.setUserStatus(userStatus); + }, [disambiguatedProfileVM, userStatus]); useEffect(() => { disambiguatedProfileVM.setMember(sender ?? "", member); }, [disambiguatedProfileVM, member, sender]); diff --git a/apps/web/src/hooks/useUserStatus.ts b/apps/web/src/hooks/useUserStatus.ts new file mode 100644 index 0000000000..e73679c9b4 --- /dev/null +++ b/apps/web/src/hooks/useUserStatus.ts @@ -0,0 +1,98 @@ +/** +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. +*/ + +import { useEffect, useState } from "react"; +import { ClientEvent, MatrixError } from "matrix-js-sdk/src/matrix"; +import { logger as rootLogger } from "matrix-js-sdk/src/logger"; + +import { useMatrixClientContext } from "../contexts/MatrixClientContext"; +import { useTypedEventEmitter } from "./useEventEmitter"; +import { useFeatureEnabled } from "./useSettings"; + +const logger = rootLogger.getChild("useUserStatus"); + +export interface UserStatus { + emoji: string; + text: string; +} + +const MAX_STATUS_TEXT_BYTES = 256; + +export function userStatusTextWithinMaxLength(text: string): boolean { + const textEncoder = new TextEncoder(); + return textEncoder.encode(text).length <= MAX_STATUS_TEXT_BYTES; +} + +/** + * Hook to get the MSC4426 user status for a given user ID. Returns undefined if the feature is disabled, + * the user does not have a status, or if there was an error fetching the status. + * + * @param userId The ID of the user whose status is being fetched. + * @returns The user's status, or undefined if not available. + */ +export function useUserStatus(userId: string | undefined): UserStatus | undefined { + const isEnabled = useFeatureEnabled("feature_user_status"); + const matrixClient = useMatrixClientContext(); + const [rawUserStatus, setRawUserStatus] = useState(); + + useTypedEventEmitter(matrixClient, ClientEvent.UserProfileUpdate, (syncedUserId, syncProfile) => { + if (syncedUserId !== userId) { + return; + } + if (syncProfile["org.matrix.msc4426.status"]) { + setRawUserStatus(syncProfile["org.matrix.msc4426.status"]); + } + }); + useEffect(() => { + (async () => { + if (!isEnabled) { + return; + } + if (!userId) { + setRawUserStatus(undefined); + return; + } + if ((await matrixClient.doesServerSupportExtendedProfiles()) === false) { + setRawUserStatus(undefined); + return; + } + try { + const result = await matrixClient.getExtendedProfileProperty(userId, "org.matrix.msc4426.status"); + setRawUserStatus(result); + } catch (ex) { + if (ex instanceof MatrixError && ex.errcode === "M_NOT_FOUND") { + setRawUserStatus(undefined); + } else { + logger.warn(`Failed to get userStatus for ${userId}`, ex); + } + } + })(); + }, [isEnabled, userId, matrixClient]); + if (!isEnabled) { + return; + } + + if (typeof rawUserStatus !== "object" || rawUserStatus === null) { + logger.warn(`value of "org.matrix.msc4426.status" was not an object for ${userId}`); + return; + } + if ("emoji" in rawUserStatus === false || typeof rawUserStatus.emoji !== "string" || !rawUserStatus.emoji) { + logger.warn(`"emoji" property was not a valid string for ${userId}`); + return; + } + if ("text" in rawUserStatus === false || typeof rawUserStatus.text !== "string" || !rawUserStatus.text) { + logger.warn(`"text" property was not a valid string for ${userId}`); + return; + } + + return { + emoji: rawUserStatus.emoji, + text: userStatusTextWithinMaxLength(rawUserStatus.text) + ? rawUserStatus.text + : `${rawUserStatus.text.slice(0, MAX_STATUS_TEXT_BYTES)}…`, + }; +} diff --git a/apps/web/src/i18n/strings/en_EN.json b/apps/web/src/i18n/strings/en_EN.json index cbd2ee9b97..c838f38bf8 100644 --- a/apps/web/src/i18n/strings/en_EN.json +++ b/apps/web/src/i18n/strings/en_EN.json @@ -1541,6 +1541,11 @@ "experimental_section": "Early previews", "extended_profiles_msc_support": "Requires your server to support MSC4133", "feature_disable_call_per_sender_encryption": "Disable per-sender encryption for Element Call", + "feature_user_status": { + "description": "Enables being able to see and set a current status.", + "display_name": "User status", + "required_msc_support": "Requires MSC4429 (Profile Updates for Legacy Sync)" + }, "feature_wysiwyg_composer_description": "Use rich text instead of Markdown in the message composer.", "group_calls": "New group call experience", "group_developer": "Developer", @@ -3148,6 +3153,14 @@ "server_error_detail": "Server unavailable, overloaded, or something else went wrong.", "shrug": "Prepends ¯\\_(ツ)_/¯ to a plain-text message", "spoiler": "Sends the given message as a spoiler", + "status": { + "description": "Set your current status", + "no_args": "No arguments provided. You should supply an emoij and an optional text component.", + "no_emoji": "You did not provide an emoji", + "no_text": "You did not provide any status text", + "too_long_emoji": "The first argument must be an emoji", + "too_long_text": "The text you provided was too long." + }, "tableflip": "Prepends (╯°□°)╯︵ ┻━┻ to a plain-text message", "topic": "Gets or sets the room topic", "topic_none": "This room has no topic.", diff --git a/apps/web/src/settings/Settings.tsx b/apps/web/src/settings/Settings.tsx index 376b690afc..0cd889bb3c 100644 --- a/apps/web/src/settings/Settings.tsx +++ b/apps/web/src/settings/Settings.tsx @@ -1,4 +1,5 @@ /* +Copyright 2026 Element Creations Ltd. Copyright 2024, 2025 New Vector Ltd. Copyright 2018-2024 The Matrix.org Foundation C.I.C. Copyright 2017 Travis Ralston @@ -228,6 +229,7 @@ export interface Settings { "feature_ask_to_join": IFeature; "feature_notifications": IFeature; "feature_msc4362_encrypted_state_events": IFeature; + "feature_user_status": IFeature; // These are in the feature namespace but aren't actually features "feature_hidebold": IBaseSetting; @@ -789,6 +791,30 @@ export const SETTINGS: Settings = { shouldWarn: true, default: false, }, + "feature_user_status": { + isFeature: true, + labsGroup: LabGroup.Profile, + displayName: _td("labs|feature_user_status|display_name"), + description: _td("labs|feature_user_status|description"), + supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG_PRIORITISED, + supportedLevelsAreOrdered: true, + controller: new ServerSupportUnstableFeatureController( + "feature_user_status", + defaultWatchManager, + [["org.matrix.msc4429"], ["org.matrix.msc4429.stable"]], + undefined, + _td("labs|feature_user_status|required_msc_support"), + false, + // We have to assume it's available during early startup because of a race: + // The feature is used to enable extra sync filters during MatrixClient setup + // and we can't check for serverside support until the client has finished setting up. + // Once the client has setup, (so by the time the user actually opens the labs menu) we can + // enforce proper checks. + true, + true, + ), + default: false, + }, "useCompactLayout": { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, displayName: _td("settings|preferences|compact_modern"), diff --git a/apps/web/src/settings/controllers/ServerSupportUnstableFeatureController.ts b/apps/web/src/settings/controllers/ServerSupportUnstableFeatureController.ts index 1b212da638..c5c3fc3911 100644 --- a/apps/web/src/settings/controllers/ServerSupportUnstableFeatureController.ts +++ b/apps/web/src/settings/controllers/ServerSupportUnstableFeatureController.ts @@ -1,4 +1,5 @@ /* +Copyright 2026 Element Creations Ltd. Copyright 2024 New Vector Ltd. Copyright 2023 The Matrix.org Foundation C.I.C. @@ -12,6 +13,7 @@ import { type WatchManager } from "../WatchManager"; import SettingsStore from "../SettingsStore"; import { type SettingKey } from "../Settings.tsx"; import { _t } from "../../languageHandler.tsx"; +import PlatformPeg from "../../PlatformPeg.ts"; /** * Disables a given setting if the server unstable feature it requires is not supported @@ -28,6 +30,9 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient * @param unstableFeatureGroups - If any one of the feature groups is satisfied, * then the setting is considered enabled. A feature group is satisfied if all of * the features in the group are supported (all features in a group are required). + * @param defaultEnabled - If we haven't been able to check for support yet, should + * this feature be enabled or disabled (default). + * @param forceReload - Should the client force reload. */ public constructor( private readonly settingName: SettingKey, @@ -36,12 +41,23 @@ export default class ServerSupportUnstableFeatureController extends MatrixClient private readonly stableVersion?: string, private readonly disabledMessage?: TranslationKey, private readonly forcedValue: any = false, + private readonly defaultEnabled = false, + private readonly forceReload = false, ) { super(); } + public onChange(): void { + if (this.forceReload) { + PlatformPeg.get()?.reload(); + } + } + public get disabled(): boolean { - return !this.enabled; + if (this.enabled !== undefined) { + return !this.enabled; + } + return !this.defaultEnabled; } public set disabled(newDisabledValue: boolean) { diff --git a/apps/web/src/slash-commands/SlashCommands.tsx b/apps/web/src/slash-commands/SlashCommands.tsx index f3e1d1ed85..d34949a4bc 100644 --- a/apps/web/src/slash-commands/SlashCommands.tsx +++ b/apps/web/src/slash-commands/SlashCommands.tsx @@ -1,4 +1,5 @@ /* +Copyright 2026 Element Creations Ltd. Copyright 2024 New Vector Ltd. Copyright 2020 The Matrix.org Foundation C.I.C. Copyright 2019 Michael Telatynski <7t3chguy@gmail.com> @@ -62,6 +63,7 @@ import { goto, join } from "./join"; import { manuallyVerifyDevice } from "../components/views/dialogs/ManualDeviceKeyVerificationDialog"; import upgraderoom from "./upgraderoom/upgraderoom"; import { emoticon } from "./emoticon"; +import { statusCommand } from "./status"; export { CommandCategories, Command }; @@ -819,6 +821,7 @@ export const Commands = [ }, renderingTypes: [TimelineRenderingType.Room], }), + statusCommand, // Command definitions for autocompletion ONLY: // /me is special because its not handled by SlashCommands.js and is instead done inside the Composer classes diff --git a/apps/web/src/slash-commands/status.ts b/apps/web/src/slash-commands/status.ts new file mode 100644 index 0000000000..7a1bf36d94 --- /dev/null +++ b/apps/web/src/slash-commands/status.ts @@ -0,0 +1,51 @@ +/* +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. +*/ + +import { _td } from "@element-hq/web-shared-components"; + +import { Command, CommandCategories, splitAtFirstSpace } from "./SlashCommands"; +import SettingsStore from "../settings/SettingsStore"; +import { reject, success } from "./utils"; +import { UserFriendlyError } from "../languageHandler"; +import { userStatusTextWithinMaxLength } from "../hooks/useUserStatus"; +import { TimelineRenderingType } from "../contexts/RoomContext"; + +export const statusCommand = new Command({ + command: "status", + args: " ", + description: _td("slash_command|status|description"), + isEnabled: () => SettingsStore.getValue("feature_user_status"), + runFn: function (cli, _roomId, _threadId, args) { + if (!args) { + return reject(new UserFriendlyError("slash_command|status|no_args")); + } + const [emojiText, text] = splitAtFirstSpace(args); + if (!emojiText) { + return reject(new UserFriendlyError("slash_command|status|no_emoji")); + } + if (!text) { + return reject(new UserFriendlyError("slash_command|status|no_text")); + } + const [emoji, additionalSegment] = [...new Intl.Segmenter().segment(emojiText)]; + if (additionalSegment) { + // This is "too long" in that it's more than one grapheme, so the error we give is + // that it's "not an emoji". + return reject(new UserFriendlyError("slash_command|status|too_long_emoji")); + } + if (!userStatusTextWithinMaxLength(text)) { + return reject(new UserFriendlyError("slash_command|status|too_long_text")); + } + return success( + cli.setExtendedProfileProperty("org.matrix.msc4426.status", { + emoji: emoji.segment, + text, + }), + ); + }, + category: CommandCategories.actions, + renderingTypes: [TimelineRenderingType.Room], +}); diff --git a/apps/web/src/viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel.ts b/apps/web/src/viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel.ts index c3b97087b3..858c8110c7 100644 --- a/apps/web/src/viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel.ts +++ b/apps/web/src/viewmodels/room/timeline/event-tile/DisambiguatedProfileViewModel.ts @@ -15,6 +15,7 @@ import { type MouseEvent } from "react"; import { _t } from "../../../../languageHandler"; import { getUserNameColorClass } from "../../../../utils/FormattingUtils"; import UserIdentifier from "../../../../customisations/UserIdentifier"; +import type { UserStatus } from "../../../../hooks/useUserStatus"; /** * Information about a member for disambiguation purposes. @@ -46,6 +47,10 @@ export interface DisambiguatedProfileViewModelProps { * The member information for disambiguation. */ member?: MemberInfo | null; + /** + * The user's present status. + */ + userStatus?: UserStatus; /** * The fallback name to use if the member's display name is not available. */ @@ -62,6 +67,7 @@ export interface DisambiguatedProfileViewModelProps { * Whether to show a tooltip with additional information. */ withTooltip?: boolean; + /** * Optional click handler for the profile. */ @@ -79,7 +85,7 @@ export class DisambiguatedProfileViewModel private static readonly computeSnapshot = ( props: DisambiguatedProfileViewModelProps, ): DisambiguatedProfileViewSnapshot => { - const { member, fallbackName, colored, emphasizeDisplayName, withTooltip } = props; + const { member, fallbackName, colored, emphasizeDisplayName, withTooltip, userStatus } = props; // Compute display name const displayName = member?.rawDisplayName || fallbackName; @@ -122,11 +128,15 @@ export class DisambiguatedProfileViewModel displayIdentifier, title, emphasizeDisplayName, + userStatus, }; }; public constructor(props: DisambiguatedProfileViewModelProps) { super(props, DisambiguatedProfileViewModel.computeSnapshot(props)); + this.snapshot.merge({ + userStatus: props.userStatus, + }); } public setMember(fallbackName: string, member?: MemberInfo | null): void { @@ -136,6 +146,13 @@ export class DisambiguatedProfileViewModel this.snapshot.set(DisambiguatedProfileViewModel.computeSnapshot(this.props)); } + public setUserStatus(userStatus?: UserStatus): void { + this.props.userStatus = userStatus; + this.snapshot.merge({ + userStatus, + }); + } + public onClick = (evt: MouseEvent): void => { this.props.onClick?.(evt); }; diff --git a/apps/web/test/slash-commands/status-test.ts b/apps/web/test/slash-commands/status-test.ts new file mode 100644 index 0000000000..2f81af17c6 --- /dev/null +++ b/apps/web/test/slash-commands/status-test.ts @@ -0,0 +1,62 @@ +/* +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. +*/ + +import { stubClient } from "../test-utils"; +import { statusCommand } from "../../src/slash-commands/status"; +import { UserFriendlyError } from "../../src/languageHandler"; + +describe("/status", () => { + const roomId = "!room:example.com"; + + let client: ReturnType; + + beforeEach(() => { + client = stubClient(); + client.setExtendedProfileProperty = jest.fn().mockResolvedValue(undefined); + }); + + function run(args?: string) { + return statusCommand.run(client, roomId, null, args); + } + + it("should reject if no args provided", () => { + const result = run(undefined); + expect(result.error).toBeInstanceOf(UserFriendlyError); + expect((result.error as UserFriendlyError).message).toBe( + "No arguments provided. You should supply an emoij and an optional text component.", + ); + }); + + it("should reject if no text is provided after the emoji", () => { + const result = run("🎉"); + expect(result.error).toBeInstanceOf(UserFriendlyError); + expect((result.error as UserFriendlyError).message).toBe("You did not provide any status text"); + }); + + it("should reject if the emoji field has more than one grapheme segment", () => { + const result = run("ab hello"); + expect(result.error).toBeInstanceOf(UserFriendlyError); + expect((result.error as UserFriendlyError).message).toBe("The first argument must be an emoji"); + }); + + it("should reject if the status text exceeds the maximum byte length", () => { + const longText = "a".repeat(257); + const result = run(`🎉 ${longText}`); + expect(result.error).toBeInstanceOf(UserFriendlyError); + expect((result.error as UserFriendlyError).message).toBe("The text you provided was too long."); + }); + + it("should set the extended profile property on success", async () => { + const result = run("🎉 Having a great day"); + expect(result.error).toBeUndefined(); + await result.promise; + expect(client.setExtendedProfileProperty).toHaveBeenCalledWith("org.matrix.msc4426.status", { + emoji: "🎉", + text: "Having a great day", + }); + }); +}); diff --git a/apps/web/test/unit-tests/components/views/elements/__snapshots__/ReplyChain-test.tsx.snap b/apps/web/test/unit-tests/components/views/elements/__snapshots__/ReplyChain-test.tsx.snap index 4b072e701c..3201fd4289 100644 --- a/apps/web/test/unit-tests/components/views/elements/__snapshots__/ReplyChain-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/elements/__snapshots__/ReplyChain-test.tsx.snap @@ -31,12 +31,12 @@ exports[`ReplyChain should call setQuoteExpanded if chain is longer than 2 lines u
@userId:matrix.org diff --git a/apps/web/test/unit-tests/components/views/rooms/memberlist/__snapshots__/MemberTileView-test.tsx.snap b/apps/web/test/unit-tests/components/views/rooms/memberlist/__snapshots__/MemberTileView-test.tsx.snap index 9177eaecbc..ba2c0fceb4 100644 --- a/apps/web/test/unit-tests/components/views/rooms/memberlist/__snapshots__/MemberTileView-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/rooms/memberlist/__snapshots__/MemberTileView-test.tsx.snap @@ -35,7 +35,7 @@ exports[`MemberTileView RoomMemberTileView should display an verified E2EIcon wh class="mx_MemberTileView_name" >
should render 1`] = ` tabindex="-1" >
Alice @@ -165,12 +165,12 @@ exports[` should render 1`] = ` tabindex="-1" >
Alice @@ -262,12 +262,12 @@ exports[` should render 1`] = ` class="mx_MessageTimestamp" />
Alice diff --git a/apps/web/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx b/apps/web/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx index cb6048deb3..14dad87f00 100644 --- a/apps/web/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx +++ b/apps/web/test/unit-tests/components/views/settings/tabs/user/LabsUserSettingsTab-test.tsx @@ -48,6 +48,6 @@ describe("", () => { // non-beta labs section expect(screen.getByText("Early previews")).toBeInTheDocument(); const labsSections = container.getElementsByClassName("mx_SettingsSubsection"); - expect(labsSections).toHaveLength(8); + expect(labsSections).toHaveLength(9); }); }); diff --git a/apps/web/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AppearanceUserSettingsTab-test.tsx.snap b/apps/web/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AppearanceUserSettingsTab-test.tsx.snap index 8521fdcc3c..7c0e5a48a9 100644 --- a/apps/web/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AppearanceUserSettingsTab-test.tsx.snap +++ b/apps/web/test/unit-tests/components/views/settings/tabs/user/__snapshots__/AppearanceUserSettingsTab-test.tsx.snap @@ -214,12 +214,12 @@ exports[`AppearanceUserSettingsTab should render 1`] = ` tabindex="-1" >
@userId:matrix.org @@ -308,12 +308,12 @@ exports[`AppearanceUserSettingsTab should render 1`] = ` tabindex="-1" >
@userId:matrix.org @@ -405,12 +405,12 @@ exports[`AppearanceUserSettingsTab should render 1`] = ` class="mx_MessageTimestamp" />
@userId:matrix.org diff --git a/apps/web/test/unit-tests/hooks/useUserStatus-test.tsx b/apps/web/test/unit-tests/hooks/useUserStatus-test.tsx new file mode 100644 index 0000000000..5375223909 --- /dev/null +++ b/apps/web/test/unit-tests/hooks/useUserStatus-test.tsx @@ -0,0 +1,155 @@ +/* +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. +*/ + +import React from "react"; +import { renderHook, waitFor } from "jest-matrix-react"; +import { ClientEvent } from "matrix-js-sdk/src/matrix"; + +import { useUserStatus, userStatusTextWithinMaxLength } from "../../../src/hooks/useUserStatus"; +import { getMockClientWithEventEmitter, mockClientMethodsUser, mockClientMethodsServer } from "../../test-utils"; +import { MatrixClientContextProvider } from "../../../src/components/structures/MatrixClientContextProvider"; +import SettingsStore from "../../../src/settings/SettingsStore"; + +const userId = "@alice:example.com"; + +const client = getMockClientWithEventEmitter({ + ...mockClientMethodsUser(), + ...mockClientMethodsServer(), + getCrypto: jest.fn().mockReturnValue(null), + doesServerSupportExtendedProfiles: jest.fn().mockResolvedValue(true), + getExtendedProfileProperty: jest.fn().mockResolvedValue(undefined), +}); + +function render(uid: string | undefined = userId) { + return renderHook(() => useUserStatus(uid), { + wrapper: ({ children }) => ( + {children} + ), + }); +} + +describe("userStatusTextWithinMaxLength", () => { + it("returns true for short text", () => { + expect(userStatusTextWithinMaxLength("on a horse")).toBe(true); + }); + + it("returns false for text exceeding 256 bytes", () => { + expect(userStatusTextWithinMaxLength("a".repeat(257))).toBe(false); + }); + + it("returns true for text exactly 256 bytes", () => { + expect(userStatusTextWithinMaxLength("a".repeat(256))).toBe(true); + }); +}); + +describe("useUserStatus", () => { + beforeEach(() => { + jest.spyOn(SettingsStore, "getValue").mockImplementation((name): any => { + if (name === "feature_user_status") return true; + }); + client.doesServerSupportExtendedProfiles.mockResolvedValue(true); + client.getExtendedProfileProperty.mockResolvedValue(undefined); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it("returns undefined when feature is disabled", async () => { + jest.spyOn(SettingsStore, "getValue").mockReturnValue(false); + const { result } = render(); + expect(result.current).toBeUndefined(); + }); + + it("returns undefined when userId is undefined", async () => { + const { result } = render(undefined); + expect(result.current).toBeUndefined(); + }); + + it("returns undefined when server does not support extended profiles", async () => { + client.doesServerSupportExtendedProfiles.mockResolvedValue(false); + const { result } = render(); + expect(result.current).toBeUndefined(); + }); + + it("returns undefined when status property is not set", async () => { + client.getExtendedProfileProperty.mockResolvedValue(undefined); + const { result } = render(); + await waitFor(() => + expect(client.getExtendedProfileProperty).toHaveBeenCalledWith(userId, "org.matrix.msc4426.status"), + ); + expect(result.current).toBeUndefined(); + }); + + it("returns undefined when status is not an object", async () => { + client.getExtendedProfileProperty.mockResolvedValue("not an object"); + const { result } = render(); + await waitFor(() => expect(client.getExtendedProfileProperty).toHaveBeenCalled()); + expect(result.current).toBeUndefined(); + }); + + it("returns undefined when emoji is missing", async () => { + client.getExtendedProfileProperty.mockResolvedValue({ text: "on a horse" }); + const { result } = render(); + await waitFor(() => expect(client.getExtendedProfileProperty).toHaveBeenCalled()); + expect(result.current).toBeUndefined(); + }); + + it("returns undefined when text is missing", async () => { + client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎" }); + const { result } = render(); + await waitFor(() => expect(client.getExtendedProfileProperty).toHaveBeenCalled()); + expect(result.current).toBeUndefined(); + }); + + it("returns the user status when valid", async () => { + client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎", text: "on a horse" }); + const { result } = render(); + await waitFor(() => expect(result.current).toEqual({ emoji: "🐎", text: "on a horse" })); + }); + + it("truncates text that exceeds 256 bytes", async () => { + const longText = "a".repeat(257); + client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎", text: longText }); + const { result } = render(); + await waitFor(() => expect(result.current).toEqual({ emoji: "🐎", text: `${"a".repeat(256)}…` })); + }); + + it("returns undefined when M_NOT_FOUND error is thrown", async () => { + const error = new Error(); + client.getExtendedProfileProperty.mockRejectedValue(error); + const { result } = render(); + await waitFor(() => expect(client.getExtendedProfileProperty).toHaveBeenCalled()); + expect(result.current).toBeUndefined(); + }); + + it("updates status when UserProfileUpdate event is emitted", async () => { + client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎", text: "on a horse" }); + const { result } = render(); + await waitFor(() => expect(result.current).toEqual({ emoji: "🐎", text: "on a horse" })); + + // Simulate a profile update event + client.emit(ClientEvent.UserProfileUpdate, userId, { + "org.matrix.msc4426.status": { emoji: "😵", text: "off a horse" }, + }); + + await waitFor(() => expect(result.current).toEqual({ emoji: "😵", text: "off a horse" })); + }); + + it("ignores UserProfileUpdate events for different users", async () => { + client.getExtendedProfileProperty.mockResolvedValue({ emoji: "🐎", text: "on a horse" }); + const { result } = render(); + await waitFor(() => expect(result.current).toEqual({ emoji: "🐎", text: "on a horse" })); + + client.emit(ClientEvent.UserProfileUpdate, "@bob:example.com", { + "org.matrix.msc4426.status": { emoji: "🤷", text: "unrelated status" }, + }); + + // Should still have original status + expect(result.current).toEqual({ emoji: "🐎", text: "on a horse" }); + }); +}); diff --git a/apps/web/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap b/apps/web/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap index e2c37efaee..6d3fb83b66 100644 --- a/apps/web/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap +++ b/apps/web/test/unit-tests/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap @@ -57,7 +57,7 @@ exports[`HTMLExport should export 1`] = `

-
  • @user49:example.com
    00:00
    Message #49
  • @user48:example.com
    00:00
    Message #48
  • @user47:example.com
    00:00
    Message #47
  • @user46:example.com
    00:00
    Message #46
  • @user45:example.com
    00:00
    Message #45
  • @user44:example.com
    00:00
    Message #44
  • @user43:example.com
    00:00
    Message #43
  • @user42:example.com
    00:00
    Message #42
  • @user41:example.com
    00:00
    Message #41
  • @user40:example.com
    00:00
    Message #40
  • @user39:example.com
    00:00
    Message #39
  • @user38:example.com
    00:00
    Message #38
  • @user37:example.com
    00:00
    Message #37
  • @user36:example.com
    00:00
    Message #36
  • @user35:example.com
    00:00
    Message #35
  • @user34:example.com
    00:00
    Message #34
  • @user33:example.com
    00:00
    Message #33
  • @user32:example.com
    00:00
    Message #32
  • @user31:example.com
    00:00
    Message #31
  • @user30:example.com
    00:00
    Message #30
  • @user29:example.com
    00:00
    Message #29
  • @user28:example.com
    00:00
    Message #28
  • @user27:example.com
    00:00
    Message #27
  • @user26:example.com
    00:00
    Message #26
  • @user25:example.com
    00:00
    Message #25
  • @user24:example.com
    00:00
    Message #24
  • @user23:example.com
    00:00
    Message #23
  • @user22:example.com
    00:00
    Message #22
  • @user21:example.com
    00:00
    Message #21
  • @user20:example.com
    00:00
    Message #20
  • @user19:example.com
    00:00
    Message #19
  • @user18:example.com
    00:00
    Message #18
  • @user17:example.com
    00:00
    Message #17
  • @user16:example.com
    00:00
    Message #16
  • @user15:example.com
    00:00
    Message #15
  • @user14:example.com
    00:00
    Message #14
  • @user13:example.com
    00:00
    Message #13
  • @user12:example.com
    00:00
    Message #12
  • @user11:example.com
    00:00
    Message #11
  • @user10:example.com
    00:00
    Message #10
  • @user9:example.com
    00:00
    Message #9
  • @user8:example.com
    00:00
    Message #8
  • @user7:example.com
    00:00
    Message #7
  • @user6:example.com
    00:00
    Message #6
  • @user5:example.com
    00:00
    Message #5
  • @user4:example.com
    00:00
    Message #4
  • @user3:example.com
    00:00
    Message #3
  • @user2:example.com
    00:00
    Message #2
  • @user1:example.com
    00:00
    Message #1
  • @user0:example.com
    00:00
    Message #0
  • +
  • @user49:example.com
    00:00
    Message #49
  • @user48:example.com
    00:00
    Message #48
  • @user47:example.com
    00:00
    Message #47
  • @user46:example.com
    00:00
    Message #46
  • @user45:example.com
    00:00
    Message #45
  • @user44:example.com
    00:00
    Message #44
  • @user43:example.com
    00:00
    Message #43
  • @user42:example.com
    00:00
    Message #42
  • @user41:example.com
    00:00
    Message #41
  • @user40:example.com
    00:00
    Message #40
  • @user39:example.com
    00:00
    Message #39
  • @user38:example.com
    00:00
    Message #38
  • @user37:example.com
    00:00
    Message #37
  • @user36:example.com
    00:00
    Message #36
  • @user35:example.com
    00:00
    Message #35
  • @user34:example.com
    00:00
    Message #34
  • @user33:example.com
    00:00
    Message #33
  • @user32:example.com
    00:00
    Message #32
  • @user31:example.com
    00:00
    Message #31
  • @user30:example.com
    00:00
    Message #30
  • @user29:example.com
    00:00
    Message #29
  • @user28:example.com
    00:00
    Message #28
  • @user27:example.com
    00:00
    Message #27
  • @user26:example.com
    00:00
    Message #26
  • @user25:example.com
    00:00
    Message #25
  • @user24:example.com
    00:00
    Message #24
  • @user23:example.com
    00:00
    Message #23
  • @user22:example.com
    00:00
    Message #22
  • @user21:example.com
    00:00
    Message #21
  • @user20:example.com
    00:00
    Message #20
  • @user19:example.com
    00:00
    Message #19
  • @user18:example.com
    00:00
    Message #18
  • @user17:example.com
    00:00
    Message #17
  • @user16:example.com
    00:00
    Message #16
  • @user15:example.com
    00:00
    Message #15
  • @user14:example.com
    00:00
    Message #14
  • @user13:example.com
    00:00
    Message #13
  • @user12:example.com
    00:00
    Message #12
  • @user11:example.com
    00:00
    Message #11
  • @user10:example.com
    00:00
    Message #10
  • @user9:example.com
    00:00
    Message #9
  • @user8:example.com
    00:00
    Message #8
  • @user7:example.com
    00:00
    Message #7
  • @user6:example.com
    00:00
    Message #6
  • @user5:example.com
    00:00
    Message #5
  • @user4:example.com
    00:00
    Message #4
  • @user3:example.com
    00:00
    Message #3
  • @user2:example.com
    00:00
    Message #2
  • @user1:example.com
    00:00
    Message #1
  • @user0:example.com
    00:00
    Message #0
  • diff --git a/docs/labs.md b/docs/labs.md index 7919c4737f..e9786cddfe 100644 --- a/docs/labs.md +++ b/docs/labs.md @@ -131,5 +131,10 @@ joining a room. Replaces the legacy notification settings with a new one to manage push rules. +## User status (`feature_user_status`) + +Enables setting a status message in your profile and to be able to view other's statuses. +Requires [MSC4429](https://github.com/matrix-org/matrix-spec-proposals/pull/4429) and [MSC4426](https://github.com/matrix-org/matrix-spec-proposals/pull/4426). + **Warning** This feature has options which are not backwards compatible, disabling it may have unintended consequences. diff --git a/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.stories.tsx/full-example-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.stories.tsx/full-example-auto.png index fcc6af53b3e9520283506581b36dd5fc05cc890a..f016ac3067d498642c048d5584cd2a20cce91e0a 100644 GIT binary patch literal 19920 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 diff --git a/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.stories.tsx/with-user-status-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.stories.tsx/with-user-status-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..ad7c25d3171893d5ec2185c8523a12ec0514c3db GIT binary patch literal 4732 zcmeHL>razs6n_8*Mnz1RLlBCaFZ{L-D-+$1rCnwK2&$;~0@0^nJ z5qRf)9{T_Q&SBr4`w0MtJ^<{;_u5+}?N7W2phd_uaz|xqWyyH9*VF{__$esuGwT9(;CX1V}%^7BF_?`gWVx7M6wHz!ha z^^yiW23>gFqQED+SPaSrp>V1)lV#YiBinm`qQjZ#Os2)MW4N3xTp}?BBkSFfXQ6J+ zFt)T*SIkC+6L;X8^(uUYV{!4IaAJd_P1Ebhcr2`pasVKPOJMw}P(dgetLc7+a$MBN zD&O#ig5(LFrcH@PDpAYJ9`8auSBBKd&d@d%gHf6cF5DSO8Jku2CMPFPUBcGgukFqn ztawKj*9RG&cT9!XI5xV?up#xksnY$iL~dqI&WqhIavnnt{OrM70&}Z?%_dMpPRvAx z@lF|2`7sgg)-E8}!uk1zuLYfoIyz>1Qq4lfnxVYqz^ngkboGsvv~-n^lzA4WBQ&>m zNR#w1n8sKu<1?jAnxO23m0bgmYNx>G&Qndoogk*^tNu6An1IeA7z<_Dh1|y zM7UsigoM_#L|wpIOzql59}-QcR4R`?C2qN~ELcT_`8A7VnUFnwjW~xktvkNF*}b-Lclpc4wb4Z>I)$94y|=$SC2! zN~!NFlRt^R7EYuZqM|LMnchb8vc~vYifY-dtfYj5gaWCa${gHjpQFl${S6uHs=zf) z<3v0&Ev@r3kM?|8J^}}PX&%q|<(gd5U!vo+ZHRmTXuIOKrL+b~qU&=Ln}fL|8TC|f zaN$xrAB$p4&MXOkw1C~4yIW_`9^wS*I))@#o$xqp?KCWAqnN~+z319q|+fSVI6#cq# zquAh!V$`%G z5C;%)><>F|?9jLs;r6Biuy;=Y0DG)GZd0=b#8x4;Ua*0K4J~bR!8Ryul)%Q3Y^1T; z{cM<(8lTv`1=y&vZFl(pEy?U+y$+zL#Ls{C)Z(~XFb+WaL34(zW3t040b!vL=i0xD Gzws}ut&wN| literal 0 HcmV?d00001 diff --git a/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.module.css b/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.module.css index 5733e1c71c..39b7c5227e 100644 --- a/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.module.css +++ b/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.module.css @@ -24,4 +24,8 @@ font-size: var(--cpd-font-size-body-sm); margin-inline-start: 5px; } + + .userStatus { + margin-inline-start: 5px; + } } diff --git a/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.stories.tsx b/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.stories.tsx index 3766c7b19d..02cd5913bc 100644 --- a/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.stories.tsx +++ b/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfile.stories.tsx @@ -40,6 +40,10 @@ const meta = { displayIdentifier: { control: "text" }, title: { control: "text" }, emphasizeDisplayName: { control: "boolean" }, + userStatus: { + status: { control: "string" }, + emoji: { control: "string" }, + }, }, args: { displayName: "Alice", @@ -82,6 +86,17 @@ export const WithTooltip: Story = { }, }; +export const WithUserStatus: Story = { + args: { + displayName: "Eve", + title: "Eve (@eve:matrix.org)", + userStatus: { + emoji: "🏝️", + text: "On holiday", + }, + }, +}; + export const FullExample: Story = { args: { displayName: "Eve", @@ -89,5 +104,9 @@ export const FullExample: Story = { colorClass: "mx_Username_color5", title: "Eve (@eve:matrix.org)", emphasizeDisplayName: true, + userStatus: { + emoji: "🏝️", + text: "On holiday", + }, }, }; diff --git a/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfileView.tsx b/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfileView.tsx index fa1102b81d..f7a3a2b62a 100644 --- a/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfileView.tsx +++ b/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/DisambiguatedProfileView.tsx @@ -7,6 +7,7 @@ import React, { type JSX, type KeyboardEventHandler, type MouseEventHandler } from "react"; import classNames from "classnames"; +import { Text, Tooltip } from "@vector-im/compound-web"; import { type ViewModel, useViewModel } from "../../../../../core/viewmodel"; import styles from "./DisambiguatedProfile.module.css"; @@ -38,6 +39,14 @@ export interface DisambiguatedProfileViewSnapshot { * Whether to emphasize the display name with additional styling. */ emphasizeDisplayName?: boolean; + + /** + * User status message + */ + userStatus?: { + emoji: string; + text: string; + }; } /** @@ -80,7 +89,9 @@ interface DisambiguatedProfileViewProps { * ``` */ export function DisambiguatedProfileView({ vm, className }: Readonly): JSX.Element { - const { displayName, colorClass, displayIdentifier, title, emphasizeDisplayName } = useViewModel(vm); + const { displayName, colorClass, displayIdentifier, title, emphasizeDisplayName, userStatus } = useViewModel(vm); + + const userStatusEmoji = userStatus && [...new Intl.Segmenter().segment(userStatus.emoji)][0]?.segment; const displayNameClasses = classNames(colorClass, { [styles.disambiguatedProfile_displayName]: emphasizeDisplayName, @@ -115,6 +126,13 @@ export function DisambiguatedProfileView({ vm, className }: Readonly )} + {userStatus && ( + + + {userStatusEmoji} + + + )}
    ); } diff --git a/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/__snapshots__/DisambiguatedProfile.test.tsx.snap b/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/__snapshots__/DisambiguatedProfile.test.tsx.snap index 3ef52cea4d..9d4c1fc3d6 100644 --- a/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/__snapshots__/DisambiguatedProfile.test.tsx.snap +++ b/packages/shared-components/src/room/timeline/event-tile/EventTileView/DisambiguatedProfile/__snapshots__/DisambiguatedProfile.test.tsx.snap @@ -36,6 +36,11 @@ exports[`DisambiguatedProfileView > renders the full example 1`] = ` > @eve:matrix.org
    + + 🏝️ +
    `; From 5ff302539e48df30afda02339332b08223564d7a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 29 Apr 2026 09:46:09 +0100 Subject: [PATCH 10/23] Harden settings types (#33311) * Harden settings types * Fix types * Update apps/web/src/emojipicker/recent.ts Co-authored-by: Will Hunt <2072976+Half-Shot@users.noreply.github.com> * Fix suggestion --------- Co-authored-by: Will Hunt <2072976+Half-Shot@users.noreply.github.com> --- apps/web/src/@types/media_preview.ts | 4 +- apps/web/src/LegacyCallHandler.tsx | 2 +- apps/web/src/Notifier.ts | 17 ++++-- apps/web/src/PosthogAnalytics.ts | 6 +- .../eventindex/ManageEventIndexDialog.tsx | 2 +- .../src/components/structures/MatrixChat.tsx | 4 +- .../views/settings/LayoutSwitcher.tsx | 4 +- .../tabs/user/PreferencesUserSettingsTab.tsx | 6 +- .../web/src/device-listener/DeviceListener.ts | 2 +- .../payloads/SettingUpdatedPayload.ts | 22 +++++-- apps/web/src/emojipicker/recent.ts | 4 +- apps/web/src/mjolnir/Mjolnir.ts | 15 ++--- apps/web/src/settings/Settings.tsx | 11 +--- apps/web/src/settings/SettingsStore.ts | 60 ++++++++++--------- .../MediaPreviewConfigController.ts | 4 +- .../right-panel/RightPanelStoreIPanelState.ts | 4 +- .../web/src/stores/room-list/RoomListStore.ts | 2 +- .../message-body/EventContentBodyViewModel.ts | 4 +- .../message-body/RedactedBodyViewModel.ts | 2 +- .../message-body/UrlPreviewGroupViewModel.ts | 2 +- .../room/timeline/DateSeparatorViewModel.tsx | 4 +- .../components/structures/MatrixChat-test.tsx | 2 +- .../views/dialogs/UserSettingsDialog-test.tsx | 2 +- .../stores/ReleaseAnnouncementStore-test.tsx | 2 +- .../stores/room-list/RoomListStore-test.ts | 12 ++-- .../room-list/RoomListItemViewModel-test.tsx | 2 +- 26 files changed, 104 insertions(+), 97 deletions(-) diff --git a/apps/web/src/@types/media_preview.ts b/apps/web/src/@types/media_preview.ts index d340e64caf..ffce5944ee 100644 --- a/apps/web/src/@types/media_preview.ts +++ b/apps/web/src/@types/media_preview.ts @@ -25,9 +25,9 @@ export interface MediaPreviewConfig extends Record { /** * Media preview setting for thumbnails of media in rooms. */ - media_previews: MediaPreviewValue; + media_previews?: MediaPreviewValue; /** * Media preview settings for avatars of rooms we have been invited to. */ - invite_avatars: MediaPreviewValue.On | MediaPreviewValue.Off; + invite_avatars?: MediaPreviewValue.On | MediaPreviewValue.Off; } diff --git a/apps/web/src/LegacyCallHandler.tsx b/apps/web/src/LegacyCallHandler.tsx index 81fb0093d0..3ccf500b26 100644 --- a/apps/web/src/LegacyCallHandler.tsx +++ b/apps/web/src/LegacyCallHandler.tsx @@ -725,7 +725,7 @@ export default class LegacyCallHandler extends TypedEventEmitter { - SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow); + SettingsStore.setValue("fallbackICEServerAllowed", null, SettingLevel.DEVICE, allow ?? null); }); } diff --git a/apps/web/src/Notifier.ts b/apps/web/src/Notifier.ts index b0a2ce5b42..b7e2144132 100644 --- a/apps/web/src/Notifier.ts +++ b/apps/web/src/Notifier.ts @@ -142,6 +142,16 @@ interface EmittedEvents { [NotifierEvent.NotificationHiddenChange]: (hidden: boolean) => void; } +/** + * Type representing a notification sound setting + */ +export type NotificationSound = { + url: string; + name?: string; + type?: string; + size?: number; +}; + class NotifierClass extends TypedEventEmitter { private notifsByRoom: Record = {}; @@ -223,12 +233,7 @@ class NotifierClass extends TypedEventEmitter): void => { this.setState({ crawlerSleepTime: parseInt(e.target.value, 10) }); - SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.value); + SettingsStore.setValue("crawlerSleepTime", null, SettingLevel.DEVICE, e.target.valueAsNumber); }; public render(): React.ReactNode { diff --git a/apps/web/src/components/structures/MatrixChat.tsx b/apps/web/src/components/structures/MatrixChat.tsx index af4fe18a9f..0dbc2af2e4 100644 --- a/apps/web/src/components/structures/MatrixChat.tsx +++ b/apps/web/src/components/structures/MatrixChat.tsx @@ -1794,11 +1794,11 @@ export default class MatrixChat extends React.PureComponent { SettingsStore.watchSetting( "blacklistUnverifiedDevices", null, - (_settingName, _roomId, atLevel, blacklistEnabled: boolean) => { + (_settingName, _roomId, atLevel, blacklistEnabled) => { if (atLevel != SettingLevel.DEVICE) { return; } - crypto.globalBlacklistUnverifiedDevices = blacklistEnabled; + crypto.globalBlacklistUnverifiedDevices = !!blacklistEnabled; }, ); } diff --git a/apps/web/src/components/views/settings/LayoutSwitcher.tsx b/apps/web/src/components/views/settings/LayoutSwitcher.tsx index 9df3968859..5997c95707 100644 --- a/apps/web/src/components/views/settings/LayoutSwitcher.tsx +++ b/apps/web/src/components/views/settings/LayoutSwitcher.tsx @@ -38,8 +38,8 @@ function LayoutSelector(): JSX.Element { { - // We don't have any file in the form, we can cast it as string safely - const newLayout = new FormData(evt.currentTarget).get("layout") as string | null; + // We don't have any file in the form, we can cast it as Layout safely + const newLayout = new FormData(evt.currentTarget).get("layout") as Layout; await SettingsStore.setValue("layout", null, SettingLevel.DEVICE, newLayout); }} > diff --git a/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 51ccb7bd3e..84febf8e5d 100644 --- a/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/apps/web/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -211,17 +211,17 @@ export default class PreferencesUserSettingsTab extends React.Component): void => { this.setState({ autocompleteDelay: e.target.value }); - SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.value); + SettingsStore.setValue("autocompleteDelay", null, SettingLevel.DEVICE, e.target.valueAsNumber); }; private onReadMarkerInViewThresholdMs = (e: React.ChangeEvent): void => { this.setState({ readMarkerInViewThresholdMs: e.target.value }); - SettingsStore.setValue("readMarkerInViewThresholdMs", null, SettingLevel.DEVICE, e.target.value); + SettingsStore.setValue("readMarkerInViewThresholdMs", null, SettingLevel.DEVICE, e.target.valueAsNumber); }; private onReadMarkerOutOfViewThresholdMs = (e: React.ChangeEvent): void => { this.setState({ readMarkerOutOfViewThresholdMs: e.target.value }); - SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.value); + SettingsStore.setValue("readMarkerOutOfViewThresholdMs", null, SettingLevel.DEVICE, e.target.valueAsNumber); }; private renderGroup(settingIds: BooleanSettingKey[], level = SettingLevel.ACCOUNT): JSX.Element { diff --git a/apps/web/src/device-listener/DeviceListener.ts b/apps/web/src/device-listener/DeviceListener.ts index 4d62bf0ab5..fea00c0380 100644 --- a/apps/web/src/device-listener/DeviceListener.ts +++ b/apps/web/src/device-listener/DeviceListener.ts @@ -340,7 +340,7 @@ export class DeviceListener { }); } - private onRecordClientInformationSettingChange: CallbackFn = ( + private onRecordClientInformationSettingChange: CallbackFn<"deviceClientInformationOptIn"> = ( _originalSettingName, _roomId, _level, diff --git a/apps/web/src/dispatcher/payloads/SettingUpdatedPayload.ts b/apps/web/src/dispatcher/payloads/SettingUpdatedPayload.ts index ed84c2b18e..51844faa16 100644 --- a/apps/web/src/dispatcher/payloads/SettingUpdatedPayload.ts +++ b/apps/web/src/dispatcher/payloads/SettingUpdatedPayload.ts @@ -9,14 +9,26 @@ Please see LICENSE files in the repository root for full details. import { type ActionPayload } from "../payloads"; import { type Action } from "../actions"; import { type SettingLevel } from "../../settings/SettingLevel"; -import { type SettingValueType } from "../../settings/Settings"; +import { type SettingKey, type Settings } from "../../settings/Settings"; -export interface SettingUpdatedPayload extends ActionPayload { +export interface SettingUpdatedPayload extends ActionPayload { action: Action.SettingUpdated; - settingName: string; + settingName: S; roomId: string | null; level: SettingLevel; - newValueAtLevel: SettingLevel; - newValue: SettingValueType; + newValueAtLevel: Settings[S]["default"]; + newValue: Settings[S]["default"]; +} + +/** + * Type guard to check if a payload is a SettingUpdatedPayload for a specific setting. + * @param payload the payload to assert + * @param settingName the setting name to check for + */ +export function isSettingUpdatedPayload( + payload: SettingUpdatedPayload, + settingName: S, +): payload is SettingUpdatedPayload { + return payload.settingName === settingName; } diff --git a/apps/web/src/emojipicker/recent.ts b/apps/web/src/emojipicker/recent.ts index 05c1321716..42b3c4b276 100644 --- a/apps/web/src/emojipicker/recent.ts +++ b/apps/web/src/emojipicker/recent.ts @@ -29,7 +29,7 @@ const STORAGE_LIMIT = 100; function migrate(): void { const data: ILegacyFormat = JSON.parse(window.localStorage.mx_reaction_count || "{}"); const sorted = Object.entries(data).sort(([, [count1, date1]], [, [count2, date2]]) => date2 - date1); - const newFormat = sorted.map(([emoji, [count, date]]) => [emoji, count]); + const newFormat = sorted.map(([emoji, [count, date]]) => [emoji, count]); SettingsStore.setValue(SETTING_NAME, null, SettingLevel.ACCOUNT, newFormat.slice(0, STORAGE_LIMIT)); } @@ -41,7 +41,7 @@ export function add(emoji: string): void { const recents = getRecentEmoji(); const i = recents.findIndex(([e]) => e === emoji); - let newEntry; + let newEntry: RecentEmojiData[number]; if (i >= 0) { // first remove the existing tuple so that we can increment it and push it to the front [newEntry] = recents.splice(i, 1); diff --git a/apps/web/src/mjolnir/Mjolnir.ts b/apps/web/src/mjolnir/Mjolnir.ts index be4acf5965..63059b3c04 100644 --- a/apps/web/src/mjolnir/Mjolnir.ts +++ b/apps/web/src/mjolnir/Mjolnir.ts @@ -11,7 +11,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { MatrixClientPeg } from "../MatrixClientPeg"; import { ALL_RULE_TYPES, BanList } from "./BanList"; -import SettingsStore from "../settings/SettingsStore"; +import SettingsStore, { type CallbackFn } from "../settings/SettingsStore"; import { _t } from "../languageHandler"; import dis from "../dispatcher/dispatcher"; import { SettingLevel } from "../settings/SettingLevel"; @@ -38,7 +38,7 @@ export class Mjolnir { } public start(): void { - this.mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this.onListsChanged.bind(this)); + this.mjolnirWatchRef = SettingsStore.watchSetting("mjolnirRooms", null, this.onListsChanged); this.dispatcherRef = dis.register(this.onAction); dis.dispatch>({ @@ -130,15 +130,10 @@ export class Mjolnir { this.updateLists(this._roomIds); }; - private onListsChanged( - settingName: string, - roomId: string | null, - atLevel: SettingLevel, - newValue: string[], - ): void { + private onListsChanged: CallbackFn<"mjolnirRooms"> = (settingName, roomId, atLevel, newValue): void => { // We know that ban lists are only recorded at one level so we don't need to re-eval them - this.updateLists(newValue); - } + this.updateLists(newValue ?? []); + }; private updateLists(listRoomIds: string[]): void { if (!MatrixClientPeg.get()) return; diff --git a/apps/web/src/settings/Settings.tsx b/apps/web/src/settings/Settings.tsx index 0cd889bb3c..ba6b84e9c1 100644 --- a/apps/web/src/settings/Settings.tsx +++ b/apps/web/src/settings/Settings.tsx @@ -54,6 +54,7 @@ import { type ComputedInviteConfig } from "../@types/invite-rules.ts"; import BlockInvitesConfigController from "./controllers/BlockInvitesConfigController.ts"; import RequiresSettingsController from "./controllers/RequiresSettingsController.ts"; import { type OrderedCustomSections, type CustomSectionsData } from "../stores/room-list-v3/section.ts"; +import { type NotificationSound } from "../Notifier.ts"; export const defaultWatchManager = new WatchManager(); @@ -311,15 +312,7 @@ export interface Settings { "urlPreviewsEnabled_e2ee": IBaseSetting; "notificationsEnabled": IBaseSetting; "deviceNotificationsEnabled": IBaseSetting; - "notificationSound": IBaseSetting< - | { - name: string; - type: string; - size: number; - url: string; - } - | false - >; + "notificationSound": IBaseSetting; "notificationBodyEnabled": IBaseSetting; "audioNotificationsEnabled": IBaseSetting; "enableWidgetScreenshots": IBaseSetting; diff --git a/apps/web/src/settings/SettingsStore.ts b/apps/web/src/settings/SettingsStore.ts index c2f0eb98c8..10100a1af6 100644 --- a/apps/web/src/settings/SettingsStore.ts +++ b/apps/web/src/settings/SettingsStore.ts @@ -79,7 +79,7 @@ export const LEVEL_ORDER = [ SettingLevel.DEFAULT, ]; -function getLevelOrder(setting: ISetting): SettingLevel[] { +function getLevelOrder(setting: Settings[keyof Settings]): SettingLevel[] { // Settings which support only a single setting level are inherently ordered if (setting.supportedLevelsAreOrdered || setting.supportedLevels.length === 1) { // return a copy to prevent callers from modifying the array @@ -88,12 +88,12 @@ function getLevelOrder(setting: ISetting): SettingLevel[] { return LEVEL_ORDER; } -export type CallbackFn = ( - settingName: SettingKey, +export type CallbackFn = ( + settingName: S, roomId: string | null, atLevel: SettingLevel, - newValAtLevel: any, - newVal: any, + newValAtLevel: Settings[S]["default"] | null, + newVal: Settings[S]["default"] | null, ) => void; type HandlerMap = Partial<{ @@ -167,7 +167,11 @@ export default class SettingsStore { * if the change in value is worthwhile enough to react upon. * @returns {string} A reference to the watcher that was employed. */ - public static watchSetting(settingName: SettingKey, roomId: string | null, callbackFn: CallbackFn): string { + public static watchSetting( + settingName: S, + roomId: string | null, + callbackFn: CallbackFn, + ): string { const setting = SETTINGS[settingName]; if (!setting) throw new Error(`${settingName} is not a setting`); @@ -175,7 +179,11 @@ export default class SettingsStore { const watcherId = `${new Date().getTime()}_${SettingsStore.watcherCount++}_${finalSettingName}_${roomId}`; - const localizedCallback = (changedInRoomId: string | null, atLevel: SettingLevel, newValAtLevel: any): void => { + const localizedCallback = ( + changedInRoomId: string | null, + atLevel: SettingLevel, + newValAtLevel: Settings[S]["default"], + ): void => { if (!SettingsStore.doesSettingSupportLevel(settingName, atLevel)) { logger.warn( `Setting handler notified for an update of an invalid setting level: ` + @@ -220,7 +228,7 @@ export default class SettingsStore { * @param {string} settingName The setting name to monitor. * @param {String} roomId The room ID to monitor for changes in. Use null for all rooms. */ - public static monitorSetting(settingName: SettingKey, roomId: string | null): void { + public static monitorSetting(settingName: S, roomId: string | null): void { roomId = roomId || null; // the thing wants null specifically to work, so appease it. if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map()); @@ -228,7 +236,7 @@ export default class SettingsStore { const registerWatcher = (): void => { this.monitors.get(settingName)!.set( roomId, - SettingsStore.watchSetting( + SettingsStore.watchSetting( settingName, roomId, (settingName, inRoomId, level, newValueAtLevel, newValue) => { @@ -449,11 +457,10 @@ export default class SettingsStore { /** * Gets the default value of a setting. - * @param {string} settingName The name of the setting to read the value of. - * @param {String} roomId The room ID to read the setting value in, may be null. - * @return {*} The default value + * @param settingName The name of the setting to read the value of. + * @return The default value */ - public static getDefaultValue(settingName: SettingKey): any { + public static getDefaultValue(settingName: S): Settings[S]["default"] { // Verify that the setting is actually a setting if (!SETTINGS[settingName]) { throw new Error("Setting '" + settingName + "' does not appear to be a setting."); @@ -462,13 +469,13 @@ export default class SettingsStore { return SETTINGS[settingName].default; } - private static getFinalValue( - setting: ISetting, + private static getFinalValue( + setting: Settings[S], level: SettingLevel, roomId: string | null, - calculatedValue: any, + calculatedValue: Settings[S]["default"], calculatedAtLevel: SettingLevel | null, - ): any { + ): Settings[S]["default"] { let resultingValue = calculatedValue; if (setting.controller) { @@ -480,25 +487,22 @@ export default class SettingsStore { return resultingValue; } - /* eslint-disable valid-jsdoc */ //https://github.com/eslint/eslint/issues/7307 /** * Sets the value for a setting. The room ID is optional if the setting is not being * set for a particular room, otherwise it should be supplied. The value may be null * to indicate that the level should no longer have an override. - * @param {string} settingName The name of the setting to change. - * @param {String} roomId The room ID to change the value in, may be null. - * @param {SettingLevel} level The level + * @param settingName The name of the setting to change. + * @param roomId The room ID to change the value in, may be null. + * @param level The level * to change the value at. - * @param {*} value The new value of the setting, may be null. - * @return {Promise} Resolves when the setting has been changed. + * @param value The new value of the setting, may be null. + * @return Resolves when the setting has been changed. */ - - /* eslint-enable valid-jsdoc */ - public static async setValue( - settingName: SettingKey, + public static async setValue( + settingName: S, roomId: string | null, level: SettingLevel, - value: any, + value: Settings[S]["default"] | null, ): Promise { // Verify that the setting is actually a setting const setting = SETTINGS[settingName]; diff --git a/apps/web/src/settings/controllers/MediaPreviewConfigController.ts b/apps/web/src/settings/controllers/MediaPreviewConfigController.ts index d349ef33fa..beda3d1cf5 100644 --- a/apps/web/src/settings/controllers/MediaPreviewConfigController.ts +++ b/apps/web/src/settings/controllers/MediaPreviewConfigController.ts @@ -38,8 +38,8 @@ export default class MediaPreviewConfigController extends MatrixClientBackedCont const validMediaPreviews = Object.values(MediaPreviewValue); const validInviteAvatars = [MediaPreviewValue.Off, MediaPreviewValue.On]; return { - invite_avatars: validInviteAvatars.includes(inviteAvatars) ? inviteAvatars : undefined, - media_previews: validMediaPreviews.includes(mediaPreviews) ? mediaPreviews : undefined, + invite_avatars: validInviteAvatars.includes(inviteAvatars!) ? inviteAvatars : undefined, + media_previews: validMediaPreviews.includes(mediaPreviews!) ? mediaPreviews : undefined, }; } diff --git a/apps/web/src/stores/right-panel/RightPanelStoreIPanelState.ts b/apps/web/src/stores/right-panel/RightPanelStoreIPanelState.ts index 8b145c25ec..cd690ca147 100644 --- a/apps/web/src/stores/right-panel/RightPanelStoreIPanelState.ts +++ b/apps/web/src/stores/right-panel/RightPanelStoreIPanelState.ts @@ -60,8 +60,8 @@ export type IRightPanelForRoomStored = { history: Array; }; -export function convertToStorePanel(cacheRoom?: IRightPanelForRoom): IRightPanelForRoomStored | undefined { - if (!cacheRoom) return undefined; +export function convertToStorePanel(cacheRoom?: IRightPanelForRoom): IRightPanelForRoomStored | null { + if (!cacheRoom) return null; const storeHistory = [...cacheRoom.history].map((panelState) => convertCardToStore(panelState)); return { isOpen: cacheRoom.isOpen, history: storeHistory }; } diff --git a/apps/web/src/stores/room-list/RoomListStore.ts b/apps/web/src/stores/room-list/RoomListStore.ts index d35328bdad..fc2343d441 100644 --- a/apps/web/src/stores/room-list/RoomListStore.ts +++ b/apps/web/src/stores/room-list/RoomListStore.ts @@ -69,7 +69,7 @@ export class RoomListStoreClass extends AsyncStoreWithClient implem "feature_dynamic_room_predecessors", null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.msc3946ProcessDynamicPredecessor = newVal; + this.msc3946ProcessDynamicPredecessor = !!newVal; this.regenerateAllLists({ trigger: true }); }, ); diff --git a/apps/web/src/viewmodels/message-body/EventContentBodyViewModel.ts b/apps/web/src/viewmodels/message-body/EventContentBodyViewModel.ts index e1f765403f..cfa49544b9 100644 --- a/apps/web/src/viewmodels/message-body/EventContentBodyViewModel.ts +++ b/apps/web/src/viewmodels/message-body/EventContentBodyViewModel.ts @@ -231,7 +231,7 @@ export class EventContentBodyViewModel "TextualBody.enableBigEmoji", null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.setEnableBigEmoji(newVal); + this.setEnableBigEmoji(!!newVal); }, ); this.disposables.track(() => SettingsStore.unwatchSetting(enableBigEmojiWatcherRef)); @@ -240,7 +240,7 @@ export class EventContentBodyViewModel "Pill.shouldShowPillAvatar", null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.setShouldShowPillAvatar(newVal); + this.setShouldShowPillAvatar(!!newVal); }, ); this.disposables.track(() => SettingsStore.unwatchSetting(shouldShowPillAvatarWatcherRef)); diff --git a/apps/web/src/viewmodels/message-body/RedactedBodyViewModel.ts b/apps/web/src/viewmodels/message-body/RedactedBodyViewModel.ts index 4086c1db14..490f7be43f 100644 --- a/apps/web/src/viewmodels/message-body/RedactedBodyViewModel.ts +++ b/apps/web/src/viewmodels/message-body/RedactedBodyViewModel.ts @@ -75,7 +75,7 @@ export class RedactedBodyViewModel (_settingName, _roomId, _level, _newValAtLevel, newVal) => { if (this.showTwelveHour === newVal) return; - this.showTwelveHour = newVal; + this.showTwelveHour = !!newVal; this.updateTooltip(); }, ); diff --git a/apps/web/src/viewmodels/message-body/UrlPreviewGroupViewModel.ts b/apps/web/src/viewmodels/message-body/UrlPreviewGroupViewModel.ts index 55109ac876..b722def238 100644 --- a/apps/web/src/viewmodels/message-body/UrlPreviewGroupViewModel.ts +++ b/apps/web/src/viewmodels/message-body/UrlPreviewGroupViewModel.ts @@ -296,7 +296,7 @@ export class UrlPreviewGroupViewModel null, (_setting, _roomid, _level, compactLayout) => { this.snapshot.merge({ - compactLayout, + compactLayout: !!compactLayout, }); }, ); diff --git a/apps/web/src/viewmodels/room/timeline/DateSeparatorViewModel.tsx b/apps/web/src/viewmodels/room/timeline/DateSeparatorViewModel.tsx index 2f3f4bf1ba..131c365530 100644 --- a/apps/web/src/viewmodels/room/timeline/DateSeparatorViewModel.tsx +++ b/apps/web/src/viewmodels/room/timeline/DateSeparatorViewModel.tsx @@ -79,7 +79,7 @@ export class DateSeparatorViewModel "feature_jump_to_date", null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.jumpToDateEnabled = newVal; + this.jumpToDateEnabled = !!newVal; this.updateSnapshot(); }, ); @@ -89,7 +89,7 @@ export class DateSeparatorViewModel UIFeature.TimelineEnableRelativeDates, null, (_settingName, _roomId, _level, _newValAtLevel, newVal) => { - this.relativeDatesEnabled = newVal; + this.relativeDatesEnabled = !!newVal; this.updateSnapshot(); }, ); diff --git a/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx b/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx index 04ce9de90f..bf53fe8910 100644 --- a/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx +++ b/apps/web/test/unit-tests/components/structures/MatrixChat-test.tsx @@ -557,7 +557,7 @@ describe("", () => { }); it("should not persist device language when not available", async () => { - await SettingsStore.setValue("language", null, SettingLevel.DEVICE, undefined); + await SettingsStore.setValue("language", null, SettingLevel.DEVICE, null); const languageBefore = SettingsStore.getValueAt(SettingLevel.DEVICE, "language", null, true, true); jest.spyOn(Lifecycle, "attemptDelegatedAuthLogin"); diff --git a/apps/web/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx b/apps/web/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx index 6582bd4d3a..cd04abfde7 100644 --- a/apps/web/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx +++ b/apps/web/test/unit-tests/components/views/dialogs/UserSettingsDialog-test.tsx @@ -225,7 +225,7 @@ describe("", () => { }); it("watches settings", async () => { - const watchSettingCallbacks: Record = {}; + const watchSettingCallbacks: Record> = {}; mockSettingsStore.watchSetting.mockImplementation((settingName, roomId, callback) => { watchSettingCallbacks[settingName] = callback; diff --git a/apps/web/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx b/apps/web/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx index 44d40f91d8..dca3429f1f 100644 --- a/apps/web/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx +++ b/apps/web/test/unit-tests/stores/ReleaseAnnouncementStore-test.tsx @@ -28,7 +28,7 @@ describe("ReleaseAnnouncementStore", () => { settings = { releaseAnnouncementData: {}, }; - const watchCallbacks: Array = []; + const watchCallbacks: Array> = []; mocked(SettingsStore.getValue).mockImplementation((setting: string) => { return settings[setting]; diff --git a/apps/web/test/unit-tests/stores/room-list/RoomListStore-test.ts b/apps/web/test/unit-tests/stores/room-list/RoomListStore-test.ts index ec771a1d82..268162909b 100644 --- a/apps/web/test/unit-tests/stores/room-list/RoomListStore-test.ts +++ b/apps/web/test/unit-tests/stores/room-list/RoomListStore-test.ts @@ -203,13 +203,11 @@ describe("RoomListStore", () => { let featureFlagValue = false; jest.spyOn(SettingsStore, "getValue").mockImplementation(() => featureFlagValue); - let watchCallback: CallbackFn | undefined; - jest.spyOn(SettingsStore, "watchSetting").mockImplementation( - (_settingName: string, _roomId: string | null, callbackFn: CallbackFn) => { - watchCallback = callbackFn; - return "dyn_pred_ref"; - }, - ); + let watchCallback: CallbackFn | undefined; + jest.spyOn(SettingsStore, "watchSetting").mockImplementation((_settingName, _roomId, callbackFn) => { + watchCallback = callbackFn; + return "dyn_pred_ref"; + }); jest.spyOn(SettingsStore, "unwatchSetting"); const { store } = createStore(); diff --git a/apps/web/test/viewmodels/room-list/RoomListItemViewModel-test.tsx b/apps/web/test/viewmodels/room-list/RoomListItemViewModel-test.tsx index 10c7d66855..b545f316f9 100644 --- a/apps/web/test/viewmodels/room-list/RoomListItemViewModel-test.tsx +++ b/apps/web/test/viewmodels/room-list/RoomListItemViewModel-test.tsx @@ -664,7 +664,7 @@ describe("RoomListItemViewModel", () => { }); it("should update sections when OrderedCustomSections setting changes", () => { - let watchCallback: CallbackFn = () => {}; + let watchCallback: CallbackFn<"RoomList.OrderedCustomSections"> = () => {}; jest.spyOn(SettingsStore, "watchSetting").mockImplementation((setting, _room, callback) => { if (setting === "RoomList.OrderedCustomSections") watchCallback = callback; return "watcher-id"; From 6aee85aef58ceab19625be6894da42e9728c359a Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 29 Apr 2026 15:05:38 +0100 Subject: [PATCH 11/23] Fix OIDC login callback handling on Element Desktop (#33332) * Fix OIDC login callback handling on Element Desktop * Add unit tests * Iterate * Fix lcov reporter * Fix coverage paths * Fix coverage upload * Ensure `.test.ts` files don't get included in the desktop package * Fix coverage artifact name * Delint * Tidy coverage name * Improve coverage --- .github/workflows/tests.yml | 36 +++++--- apps/desktop/.eslintrc.cjs | 7 ++ apps/desktop/electron-builder.ts | 3 +- apps/desktop/package.json | 11 ++- apps/desktop/src/protocol.test.ts | 87 +++++++++++++++++ apps/desktop/src/protocol.ts | 25 ++++- apps/desktop/tsconfig.json | 3 +- apps/desktop/tsconfig.node.json | 15 +++ apps/desktop/vitest.config.ts | 64 +++++++++++++ pnpm-lock.yaml | 149 +++++++++++++++++++++++++++++- sonar-project.properties | 27 ++++-- 11 files changed, 395 insertions(+), 32 deletions(-) create mode 100644 apps/desktop/src/protocol.test.ts create mode 100644 apps/desktop/tsconfig.node.json create mode 100644 apps/desktop/vitest.config.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8a237b6950..f365f39531 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -93,7 +93,7 @@ jobs: if: env.ENABLE_COVERAGE == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: coverage-${{ matrix.runner }} + name: coverage-jest-${{ matrix.runner }} path: | apps/web/coverage !apps/web/coverage/lcov-report @@ -124,9 +124,10 @@ jobs: name: Vitest strategy: matrix: - package: - - shared-components - - module-api + path: + - apps/desktop + - packages/shared-components + - packages/module-api runs-on: ubuntu-24.04 steps: - name: Checkout code @@ -149,30 +150,39 @@ jobs: uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | - packages/${{ matrix.package }}/node_modules/.cache - packages/${{ matrix.package }}/node_modules/.vite/vitest - key: ${{ hashFiles('pnpm-lock.yaml') }} + ${{ matrix.path }}/node_modules/.cache + ${{ matrix.path }}/node_modules/.vite/vitest + key: ${{ matrix.path }}-${{ hashFiles('pnpm-lock.yaml') }} - name: Setup playwright uses: ./.github/actions/setup-playwright - if: matrix.package == 'shared-components' + if: matrix.path == 'packages/shared-components' with: write-cache: ${{ github.event_name != 'merge_group' }} - name: Run tests - working-directory: "packages/${{ matrix.package }}" + working-directory: ${{ matrix.path }} run: pnpm test:unit --coverage=$ENABLE_COVERAGE # Dump the disk usage on failure, because this job seems to fail with disk fills sometimes - name: df - run: df + run: df -h && df -i if: ${{ failure() }} + - name: Calculate artifact name + if: env.ENABLE_COVERAGE == 'true' + id: artifact + run: | + NAME=$(basename "$MATRIX_PATH") + echo "name=$NAME" >> $GITHUB_OUTPUT + env: + MATRIX_PATH: ${{ matrix.path }} + - name: Upload Artifact if: env.ENABLE_COVERAGE == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: coverage-${{ matrix.package }} + name: coverage-${{ steps.artifact.outputs.name }} path: | - packages/${{ matrix.package }}/coverage - !packages/${{ matrix.package }}/coverage/lcov-report + ${{ matrix.path }}/coverage + !${{ matrix.path }}/coverage/lcov-report diff --git a/apps/desktop/.eslintrc.cjs b/apps/desktop/.eslintrc.cjs index d8f162c2f0..7a1d06729c 100644 --- a/apps/desktop/.eslintrc.cjs +++ b/apps/desktop/.eslintrc.cjs @@ -86,5 +86,12 @@ module.exports = { "@typescript-eslint/no-non-null-assertion": "off", }, }, + { + files: ["src/**/*.test.ts", "electron-builder.ts", "vitest.config.ts"], + extends: ["plugin:matrix-org/typescript"], + parserOptions: { + project: ["tsconfig.node.json"], + }, + }, ], }; diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index 967a129c1c..9bcd771a73 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -52,6 +52,7 @@ interface Variant extends Metadata { } type Writable = NonNullable< + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type T extends Function ? T : T extends object ? { -readonly [K in keyof T]: Writable } : T >; @@ -74,7 +75,7 @@ if (process.env.VARIANT_PATH) { } for (const key in variant) { - console.log(`${key}: ${variant[key]}`); + console.log(`${key}: ${variant[key as keyof Variant]}`); } interface Configuration extends BaseConfiguration { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index fdc5fb0a54..f4f065c6ce 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -32,8 +32,9 @@ "lint": "pnpm lint:types && pnpm lint:js", "lint:js": "eslint --max-warnings 0 src hak playwright scripts", "lint:js-fix": "eslint --fix --max-warnings 0 src hak playwright scripts && prettier --log-level=warn --write .", - "lint:types": "pnpm lint:types:src && pnpm lint:types:test && pnpm lint:types:scripts && pnpm lint:types:hak", + "lint:types": "pnpm lint:types:src && pnpm lint:types:node && pnpm lint:types:test && pnpm lint:types:scripts && pnpm lint:types:hak", "lint:types:src": "tsc --noEmit", + "lint:types:node": "tsc --noEmit -p tsconfig.node.json", "lint:types:test": "tsc --noEmit -p playwright/tsconfig.json", "lint:types:scripts": "tsc --noEmit -p scripts/tsconfig.json", "lint:types:hak": "tsc --noEmit -p hak/tsconfig.json", @@ -49,6 +50,7 @@ "docker:install": "scripts/in-docker.sh pnpm install", "clean": "rimraf webapp.asar dist packages deploys lib", "hak": "node scripts/hak/index.ts", + "test:unit": "vitest", "test:playwright": "nx test:playwright --", "test:playwright:open": "nx test:playwright -- --ui", "test:playwright:screenshots": "nx test:playwright:screenshots --", @@ -79,6 +81,7 @@ "@types/pacote": "^11.1.1", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", + "@vitest/coverage-v8": "^4.1.5", "app-builder-lib": "26.9.0", "chokidar": "^5.0.0", "detect-libc": "^2.0.0", @@ -95,12 +98,16 @@ "eslint-plugin-unicorn": "^56.0.0", "glob": "^13.0.0", "matrix-web-i18n": "catalog:", + "memfs": "^4.57.2", "mkdirp": "^3.0.0", "pacote": "^21.0.0", "prettier": "^3.0.0", "rimraf": "^6.0.0", "tar": "^7.5.8", - "typescript": "6.0.3" + "typescript": "6.0.3", + "vite": "^8.0.9", + "vitest": "^4.1.5", + "vitest-sonar-reporter": "^3.0.0" }, "hakDependencies": { "matrix-seshat": "4.2.0" diff --git a/apps/desktop/src/protocol.test.ts b/apps/desktop/src/protocol.test.ts new file mode 100644 index 0000000000..396a59f065 --- /dev/null +++ b/apps/desktop/src/protocol.test.ts @@ -0,0 +1,87 @@ +/* +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. +*/ + +import { expect, describe, it, beforeEach, vi } from "vitest"; +import { fs as memfs, vol } from "memfs"; + +import ProtocolHandler from "./protocol.js"; + +const TEST_PROTOCOL = "test.proto"; +const TEST_SESSION_ID = "test_session_id"; +const USER_DATA_DIR = "/Users/name/Library/Application Support/Element"; + +vi.mock("node:fs", () => ({ default: memfs })); +vi.mock("electron", () => ({ + app: { + getPath: vi.fn().mockReturnValue("/Users/name/Library/Application Support/Element"), + on: vi.fn(), + }, + ipcMain: { + handle: vi.fn(), + }, +})); + +beforeEach(() => { + // Reset the state of the in-memory fs + vol.reset(); +}); + +describe("ProtocolHandler", () => { + describe("getProfileFromDeeplink", () => { + const handler = new ProtocolHandler(TEST_PROTOCOL); + + beforeEach(() => { + vol.fromJSON( + { + "./sso-sessions.json": JSON.stringify({ [TEST_SESSION_ID]: USER_DATA_DIR }), + }, + USER_DATA_DIR, + ); + }); + + it("should handle legacy SSO URIs", () => { + expect( + handler.getProfileFromDeeplink([ + "Element.app", + `element://vector/webapp/?element-desktop-ssoid=${TEST_SESSION_ID}`, + ]), + ).toBe(USER_DATA_DIR); + }); + + it("should handle OIDC URIs with response_mode=query", () => { + expect( + handler.getProfileFromDeeplink([ + "Element.app", + `${TEST_PROTOCOL}:/vector/webapp/?no_universal_links=true&code=DEADBEEF&state=foobar:element-desktop-ssoid:${TEST_SESSION_ID}`, + ]), + ).toBe(USER_DATA_DIR); + }); + + it("should handle OIDC URIs with response_mode=fragment", () => { + expect( + handler.getProfileFromDeeplink([ + "Element.app", + `${TEST_PROTOCOL}:/vector/webapp/?no_universal_links=true#code=DEADBEEF&state=foobar:element-desktop-ssoid:${TEST_SESSION_ID}`, + ]), + ).toBe(USER_DATA_DIR); + }); + + it("should handle malformed OIDC URIs gracefully", () => { + expect( + handler.getProfileFromDeeplink([ + "Element.app", + `${TEST_PROTOCOL}:/vector/webapp/?no_universal_links=true#code=DEADBEEF:element-desktop-ssoid:${TEST_SESSION_ID}`, + ]), + ).toBeUndefined(); + }); + + it("should handle unrelated URIs gracefully", () => { + expect(handler.getProfileFromDeeplink(["Element.app", `${TEST_PROTOCOL}:/vector/webapp/`])).toBeUndefined(); + expect(handler.getProfileFromDeeplink(["Element.app", `test.unrelated:/vector/webapp/`])).toBeUndefined(); + }); + }); +}); diff --git a/apps/desktop/src/protocol.ts b/apps/desktop/src/protocol.ts index f6812b49e6..ad2558fc2a 100644 --- a/apps/desktop/src/protocol.ts +++ b/apps/desktop/src/protocol.ts @@ -97,7 +97,8 @@ export default class ProtocolHandler { const s = fs.readFileSync(storePath, { encoding: "utf8" }); const o = JSON.parse(s); return typeof o === "object" ? o : {}; - } catch { + } catch (e) { + console.warn("Unable to read protocol store, starting with empty store: ", e); return {}; } } @@ -130,10 +131,26 @@ export default class ProtocolHandler { let sessionId = parsedUrl.searchParams.get(SEARCH_PARAM); if (!sessionId) { // In OIDC, we must shuttle the value in the `state` param rather than `element-desktop-ssoid` - // We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ` - sessionId = parsedUrl.searchParams.get("state")!.split(`:${SEARCH_PARAM}:`)[1]; + // We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ`. + // The OIDC flow may have used response_mode=fragment or query, so we need to handle both cases. + let searchParams = parsedUrl.searchParams; + if (parsedUrl.hash.includes("=")) { + const [params] = parsedUrl.hash.substring(1).split("?", 2); + searchParams = new URLSearchParams(params); + } + + const state = searchParams.get("state"); + if (state) { + sessionId = state.split(`:${SEARCH_PARAM}:`)[1]; + } } - console.log("Forwarding to profile: ", store[sessionId]); + + if (!sessionId) { + console.warn("Unable to read session ID in deeplink url:", deeplinkUrl); + return undefined; + } + + console.log("Forwarding to profile:", store[sessionId]); return store[sessionId]; } } diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index e0490357fc..5993a344e6 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -14,5 +14,6 @@ "lib": ["es2022", "es2024.promise"], "strict": true }, - "include": ["./src/**/*.ts", "./src/**/*.cts"] + "include": ["./src/**/*.ts", "./src/**/*.cts"], + "exclude": ["./src/**/*.test.ts"] } diff --git a/apps/desktop/tsconfig.node.json b/apps/desktop/tsconfig.node.json new file mode 100644 index 0000000000..c9de5ead8d --- /dev/null +++ b/apps/desktop/tsconfig.node.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "module": "nodenext", + "moduleResolution": "NodeNext", + "target": "es2022", + "sourceMap": false, + "typeRoots": [], + "types": [], + "skipLibCheck": true, + "noEmit": true, + "strict": true + }, + "include": ["./electron-builder.ts", "./vitest.config.ts", "./src/**/*.d.ts", "./src/**/*.test.ts"] +} diff --git a/apps/desktop/vitest.config.ts b/apps/desktop/vitest.config.ts new file mode 100644 index 0000000000..1d9e9f179c --- /dev/null +++ b/apps/desktop/vitest.config.ts @@ -0,0 +1,64 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { defineConfig } from "vitest/config"; +import { type UserConfig } from "vite"; +import { type Reporter } from "vitest/reporters"; +import { env } from "node:process"; + +const reporters: NonNullable["reporters"] = [["default"]]; + +const slowTestReporter: Reporter = { + onTestRunEnd(testModules, unhandledErrors, reason) { + const tests = testModules + .flatMap((m) => Array.from(m.children.allTests())) + .filter((test) => test.diagnostic()?.slow); + tests.sort((x, y) => x.diagnostic()!.duration! - y.diagnostic()!.duration!); + tests.reverse(); + + if (tests.length > 0) { + console.warn("Slowest 10 tests:"); + } + for (const t of tests.slice(0, 10)) { + console.warn(`${t.module.moduleId} > ${t.fullName}: ${t.diagnostic()?.duration.toFixed(0)}ms`); + } + }, +}; + +// if we're running under GHA, enable the GHA & Sonar reporters +if (env["GITHUB_ACTIONS"] !== undefined) { + reporters.push(["github-actions", { silent: false }]); + reporters.push([ + "vitest-sonar-reporter", + { + outputFile: "coverage/sonar-report.xml", + onWritePath: (path): string => `apps/desktop/${path}`, + }, + ]); + + // if we're running against the develop branch, also enable the slow test reporter + if (env["GITHUB_REF"] == "refs/heads/develop") { + reporters.push(slowTestReporter); + } +} + +export default defineConfig({ + test: { + coverage: { + provider: "v8", + include: ["src/**/*"], + // The coverage report currently chokes on this file as it doesn't process it as TypeScript + exclude: ["src/preload.cts"], + reporter: [["lcov", { projectRoot: "../../" }]], + }, + environment: "node", + reporters, + globals: true, + pool: "threads", + include: ["src/**/*.test.ts"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ffc755483..0e54938527 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -243,6 +243,9 @@ importers: '@typescript-eslint/parser': specifier: ^8.0.0 version: 8.58.2(eslint@8.57.1)(typescript@6.0.3) + '@vitest/coverage-v8': + specifier: ^4.1.5 + version: 4.1.5(@vitest/browser@4.1.5)(vitest@4.1.5) app-builder-lib: specifier: 26.9.0 version: 26.9.0(patch_hash=2dfb3fcdfe573cca6c248cecf63ddea5c8fa0276859695fba6c9664d0ff285d6)(dmg-builder@26.9.0)(electron-builder-squirrel-windows@26.9.0) @@ -291,6 +294,9 @@ importers: matrix-web-i18n: specifier: 'catalog:' version: 3.6.0 + memfs: + specifier: ^4.57.2 + version: 4.57.2(tslib@2.8.1) mkdirp: specifier: ^3.0.0 version: 3.0.1 @@ -309,6 +315,15 @@ importers: typescript: specifier: 6.0.3 version: 6.0.3 + vite: + specifier: ^8.0.9 + version: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitest: + specifier: ^4.1.5 + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest-sonar-reporter: + specifier: ^3.0.0 + version: 3.0.0(vitest@4.1.5) apps/web: dependencies: @@ -446,7 +461,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/60993dd10ada731e37db81873fede2a0afa93a6b + version: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/69985ee350a33ba75f1ad11f96468344f0c92a8d matrix-widget-api: specifier: ^1.17.0 version: 1.17.0 @@ -2987,48 +3002,96 @@ packages: peerDependencies: tslib: '2' + '@jsonjoy.com/fs-core@4.57.2': + resolution: {integrity: sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-fsa@4.56.10': resolution: {integrity: sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-fsa@4.57.2': + resolution: {integrity: sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-node-builtins@4.56.10': resolution: {integrity: sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-node-builtins@4.57.2': + resolution: {integrity: sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-node-to-fsa@4.56.10': resolution: {integrity: sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-node-to-fsa@4.57.2': + resolution: {integrity: sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-node-utils@4.56.10': resolution: {integrity: sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-node-utils@4.57.2': + resolution: {integrity: sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-node@4.56.10': resolution: {integrity: sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-node@4.57.2': + resolution: {integrity: sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-print@4.56.10': resolution: {integrity: sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-print@4.57.2': + resolution: {integrity: sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-snapshot@4.56.10': resolution: {integrity: sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-snapshot@4.57.2': + resolution: {integrity: sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/json-pack@1.21.0': resolution: {integrity: sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==} engines: {node: '>=10.0'} @@ -9934,8 +9997,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/60993dd10ada731e37db81873fede2a0afa93a6b: - resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/60993dd10ada731e37db81873fede2a0afa93a6b} + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/69985ee350a33ba75f1ad11f96468344f0c92a8d: + resolution: {tarball: https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/69985ee350a33ba75f1ad11f96468344f0c92a8d} version: 41.4.0 engines: {node: '>=22.0.0'} @@ -9980,6 +10043,11 @@ packages: peerDependencies: tslib: '2' + memfs@4.57.2: + resolution: {integrity: sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==} + peerDependencies: + tslib: '2' + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} @@ -15586,6 +15654,13 @@ snapshots: thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-core@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-fsa@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) @@ -15594,10 +15669,22 @@ snapshots: thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-fsa@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-node-builtins@4.56.10(tslib@2.8.1)': dependencies: tslib: 2.8.1 + '@jsonjoy.com/fs-node-builtins@4.57.2(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + '@jsonjoy.com/fs-node-to-fsa@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) @@ -15605,11 +15692,23 @@ snapshots: '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-node-to-fsa@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-fsa': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-node-utils@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-node-utils@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-node@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) @@ -15621,12 +15720,29 @@ snapshots: thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-node@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.57.2(tslib@2.8.1) + glob-to-regex.js: 1.2.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-print@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-print@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-snapshot@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) @@ -15635,6 +15751,14 @@ snapshots: '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-snapshot@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/json-pack': 17.65.0(tslib@2.8.1) + '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/json-pack@1.21.0(tslib@2.8.1)': dependencies: '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) @@ -23553,7 +23677,7 @@ snapshots: matrix-events-sdk@0.0.1: {} - matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/60993dd10ada731e37db81873fede2a0afa93a6b: + matrix-js-sdk@https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/69985ee350a33ba75f1ad11f96468344f0c92a8d: dependencies: '@babel/runtime': 7.29.2 '@matrix-org/matrix-sdk-crypto-wasm': 18.2.0 @@ -23633,6 +23757,23 @@ snapshots: tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 + memfs@4.57.2(tslib@2.8.1): + dependencies: + '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-to-fsa': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) + glob-to-regex.js: 1.2.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + memoize-one@5.2.1: {} memoize-one@6.0.0: {} diff --git a/sonar-project.properties b/sonar-project.properties index d5e990eb83..7a25cf4571 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,8 +5,14 @@ sonar.organization=element-hq #sonar.sourceEncoding=UTF-8 sonar.sources=. -sonar.tests=apps/web/test,apps/web/playwright,apps/desktop/playwright,packages -sonar.test.inclusions=apps/web/test/*,apps/web/playwright/*,apps/desktop/playwright/*,packages/*/src/**/*.test.*,packages/*/src/test/**/* +sonar.tests=apps/web/test,apps/web/playwright,apps/desktop,packages +sonar.test.inclusions=\ + apps/web/test/*,\ + apps/web/playwright/*,\ + apps/desktop/playwright/*,\ + apps/desktop/src/**/*.test.ts,\ + packages/*/src/**/*.test.*,\ + packages/*/src/test/**/* sonar.exclusions=\ apps/web/__mocks__,\ docs,\ @@ -40,16 +46,23 @@ sonar.coverage.exclusions=\ apps/web/playwright/**/*,\ apps/web/res/**/*,\ apps/web/scripts/**/*,\ - apps/desktop/scripts/**/*,\ - apps/desktop/playwright/**/*,\ - apps/desktop/hak/**/*,\ - scripts/**/*,\ + apps/web/__mocks__/**/*,\ + apps/web/I18nWebpackPlugin.ts,\ + apps/web/recorder-worklet-loader.cjs,\ apps/web/src/components/views/dialogs/devtools/**/*,\ apps/web/src/utils/SessionLock.ts,\ apps/web/src/**/*.d.ts,\ apps/web/src/vector/mobile_guide/**/*,\ + apps/desktop/electron-builder.ts,\ + apps/desktop/scripts/**/*,\ + apps/desktop/playwright/**/*,\ + apps/desktop/hak/**/*,\ packages/shared-components/src/test/**/*,\ packages/shared-components/src/**/*.stories.tsx,\ + packages/shared-components/scripts/**/*,\ packages/playwright-common/**/*,\ - **/*.config.ts + scripts/**/*,\ + docs/**/*,\ + **/*.config.*,\ + knip.ts sonar.testExecutionReportPaths=apps/web/coverage/jest-sonar-report.xml From aeefdfd751841954442c177422527808e63802ab Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 29 Apr 2026 16:53:00 +0100 Subject: [PATCH 12/23] Remove duplicated UI in appearance settings (#33336) * Remove duplicated UI This setting is available on the same tab in a higher up section already * Update screenshot --- .../appearance-tab-linux.png | Bin 64555 -> 60430 bytes .../tabs/user/AppearanceUserSettingsTab.tsx | 2 -- 2 files changed, 2 deletions(-) diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/appearance-user-settings-tab.spec.ts/appearance-tab-linux.png index 15da4c7c025eb2088d4963a0646bdc61ff82de67..afce0e5b04ce01d1dce24ffb3b5a335743de1793 100644 GIT binary patch literal 60430 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 - - Date: Thu, 30 Apr 2026 08:43:59 +0100 Subject: [PATCH 13/23] Refactor QR link flow to use new SDK methods (#33309) * Refactor QR link flow to use new SDK methods Requires https://github.com/matrix-org/matrix-js-sdk/pull/5283 For element-hq/wat-internal#188 Split out from https://github.com/element-hq/element-web/pull/33184 * Link to js-sdk branch * Update tests * Simplify * Revert js-sdk linking * Use js-sdk isSignInWithQRAvailable API to simplify code --- .../src/components/views/auth/LoginWithQR.tsx | 44 ++++++------- .../settings/devices/LoginWithQRSection.tsx | 37 ++++------- .../settings/tabs/user/SessionManagerTab.tsx | 15 +---- .../settings/devices/LoginWithQR-test.tsx | 64 +++++++++++++------ 4 files changed, 74 insertions(+), 86 deletions(-) diff --git a/apps/web/src/components/views/auth/LoginWithQR.tsx b/apps/web/src/components/views/auth/LoginWithQR.tsx index 4e1906b25e..808e42e66b 100644 --- a/apps/web/src/components/views/auth/LoginWithQR.tsx +++ b/apps/web/src/components/views/auth/LoginWithQR.tsx @@ -9,9 +9,8 @@ Please see LICENSE files in the repository root for full details. import React from "react"; import { ClientRendezvousFailureReason, + linkNewDeviceByGeneratingQR, MSC4108FailureReason, - MSC4108RendezvousSession, - MSC4108SecureChannel, MSC4108SignInWithQR, RendezvousError, type RendezvousFailureReason, @@ -55,6 +54,7 @@ export type FailureReason = RendezvousFailureReason | LoginWithQRFailureReason; */ export default class LoginWithQR extends React.Component { private finished = false; + private abortController?: AbortController; public constructor(props: IProps) { super(props); @@ -69,35 +69,31 @@ export default class LoginWithQR extends React.Component { } public componentDidMount(): void { - this.updateMode(this.props.mode).then(() => {}); + void this.updateMode(this.props.mode); } public componentDidUpdate(prevProps: Readonly): void { if (prevProps.mode !== this.props.mode) { - this.updateMode(this.props.mode).then(() => {}); + void this.updateMode(this.props.mode); } } private async updateMode(mode: Mode, showLoading = true): Promise { - if (this.state.rendezvous) { - const rendezvous = this.state.rendezvous; - rendezvous.onFailure = undefined; - this.setState({ rendezvous: undefined }); - } + this.abortController?.abort(); + this.abortController = new AbortController(); + this.setState({ rendezvous: undefined }); if (showLoading) { this.setState({ phase: Phase.Loading }); } + if (mode === Mode.Show) { - await this.generateAndShowCode(); + await this.generateAndShowCode(this.abortController); } } public componentWillUnmount(): void { - if (this.state.rendezvous && !this.finished) { - // eslint-disable-next-line react/no-direct-mutation-state - this.state.rendezvous.onFailure = undefined; - // calling cancel will call close() as well to clean up the resources - this.state.rendezvous.cancel(MSC4108FailureReason.UserCancelled); + if (!this.finished) { + this.abortController?.abort(); } } @@ -106,24 +102,18 @@ export default class LoginWithQR extends React.Component { this.props.onFinished(success); } - private generateAndShowCode = async (): Promise => { + private generateAndShowCode = async (abortController: AbortController): Promise => { let rendezvous: MSC4108SignInWithQR; try { - const transport = new MSC4108RendezvousSession({ - onFailure: this.onFailure, - client: this.props.client, - }); - await transport.send(""); - const channel = new MSC4108SecureChannel(transport, undefined, this.onFailure); - rendezvous = new MSC4108SignInWithQR(channel, false, this.props.client, this.onFailure); - - await rendezvous.generateCode(); + rendezvous = await linkNewDeviceByGeneratingQR(this.props.client, this.onFailure, abortController.signal); + if (abortController.signal.aborted) return; this.setState({ phase: Phase.ShowingQR, rendezvous, failureReason: undefined, }); } catch (e) { + if (abortController.signal.aborted) return; logger.error("Error whilst generating QR code", e); this.setState({ phase: Phase.Error, failureReason: ClientRendezvousFailureReason.HomeserverLacksSupport }); return; @@ -142,8 +132,9 @@ export default class LoginWithQR extends React.Component { // we ask the user to confirm that the channel is secure } catch (e: RendezvousError | unknown) { + if (abortController.signal.aborted) return; logger.error("Error whilst approving login", e); - await rendezvous?.cancel( + await rendezvous.cancel( e instanceof RendezvousError ? (e.code as MSC4108FailureReason) : ClientRendezvousFailureReason.Unknown, ); } @@ -210,6 +201,7 @@ export default class LoginWithQR extends React.Component { }; public reset(): void { + this.abortController?.abort(); this.setState({ rendezvous: undefined, verificationUri: undefined, diff --git a/apps/web/src/components/views/settings/devices/LoginWithQRSection.tsx b/apps/web/src/components/views/settings/devices/LoginWithQRSection.tsx index 523633c884..f7f73b90ca 100644 --- a/apps/web/src/components/views/settings/devices/LoginWithQRSection.tsx +++ b/apps/web/src/components/views/settings/devices/LoginWithQRSection.tsx @@ -7,48 +7,35 @@ Please see LICENSE files in the repository root for full details. */ import React from "react"; -import { - type IServerVersions, - type OidcClientConfig, - type MatrixClient, - DEVICE_CODE_SCOPE, -} from "matrix-js-sdk/src/matrix"; +import { type MatrixClient } from "matrix-js-sdk/src/matrix"; import QrCodeIcon from "@vector-im/compound-design-tokens/assets/web/icons/qr-code"; import { Text } from "@vector-im/compound-web"; +import { isSignInWithQRAvailable } from "matrix-js-sdk/src/rendezvous"; import { _t } from "../../../../languageHandler"; import AccessibleButton from "../../elements/AccessibleButton"; import { SettingsSubsection } from "../shared/SettingsSubsection"; import { useMatrixClientContext } from "../../../../contexts/MatrixClientContext"; +import { useAsyncMemo } from "../../../../hooks/useAsyncMemo"; interface IProps { onShowQr: () => void; - versions?: IServerVersions; - oidcClientConfig?: OidcClientConfig; isCrossSigningReady?: boolean; } -export function shouldShowQr( - cli: MatrixClient, - isCrossSigningReady: boolean, - oidcClientConfig?: OidcClientConfig, - versions?: IServerVersions, -): boolean { - const msc4108Supported = !!versions?.unstable_features?.["org.matrix.msc4108"]; +export async function shouldShowQrForLinkNewDevice(cli: MatrixClient, isCrossSigningReady: boolean): Promise { + const doesServerHaveSupport = await isSignInWithQRAvailable(cli); - const deviceAuthorizationGrantSupported = oidcClientConfig?.grant_types_supported.includes(DEVICE_CODE_SCOPE); - - return ( - !!deviceAuthorizationGrantSupported && - msc4108Supported && - !!cli.getCrypto()?.exportSecretsBundle && - isCrossSigningReady - ); + return doesServerHaveSupport && !!cli.getCrypto()?.exportSecretsBundle && isCrossSigningReady; } -const LoginWithQRSection: React.FC = ({ onShowQr, versions, oidcClientConfig, isCrossSigningReady }) => { +const LoginWithQRSection: React.FC = ({ onShowQr, isCrossSigningReady }) => { const cli = useMatrixClientContext(); - const offerShowQr = shouldShowQr(cli, !!isCrossSigningReady, oidcClientConfig, versions); + const offerShowQr = useAsyncMemo( + () => shouldShowQrForLinkNewDevice(cli, !!isCrossSigningReady), + [cli, isCrossSigningReady], + false, + ); return ( diff --git a/apps/web/src/components/views/settings/tabs/user/SessionManagerTab.tsx b/apps/web/src/components/views/settings/tabs/user/SessionManagerTab.tsx index 8cf234ca12..01aba5ccdb 100644 --- a/apps/web/src/components/views/settings/tabs/user/SessionManagerTab.tsx +++ b/apps/web/src/components/views/settings/tabs/user/SessionManagerTab.tsx @@ -162,14 +162,6 @@ const SessionManagerTab: React.FC<{ const disableMultipleSignout = !!accountManagement?.endpoint; const userId = matrixClient?.getUserId(); const currentUserMember = (userId && matrixClient?.getUser(userId)) || undefined; - const clientVersions = useAsyncMemo(() => matrixClient.getVersions(), [matrixClient]); - const oidcClientConfig = useAsyncMemo(async () => { - try { - return await matrixClient?.getAuthMetadata(); - } catch (e) { - logger.error("Failed to discover OIDC metadata", e); - } - }, [matrixClient]); const isCrossSigningReady = useAsyncMemo( async () => matrixClient.getCrypto()?.isCrossSigningReady() ?? false, [matrixClient], @@ -279,12 +271,7 @@ const SessionManagerTab: React.FC<{ return ( - + ( }); function makeClient() { - return mocked({ + const cli = mocked({ getUser: jest.fn(), isGuest: jest.fn().mockReturnValue(false), isUserIgnored: jest.fn(), @@ -49,7 +49,16 @@ function makeClient() { }, getClientWellKnown: jest.fn().mockReturnValue({}), getCrypto: jest.fn().mockReturnValue({}), + getDomain: jest.fn(), } as unknown as MatrixClient); + + cli.http = new MatrixHttpApi(cli, { + baseUrl: "https://server/", + prefix: "prefix", + onlyData: true, + }) as any; + + return cli; } function unresolvedPromise(): Promise { @@ -62,13 +71,12 @@ describe("", () => { legacy: true, mode: Mode.Show, onFinished: jest.fn(), - }; + } as const; beforeEach(() => { mockedFlow.mockReset(); jest.resetAllMocks(); client = makeClient(); - jest.useFakeTimers(); }); afterEach(() => { @@ -79,14 +87,20 @@ describe("", () => { }); describe("MSC4108", () => { - const getComponent = (props: { client: MatrixClient; onFinished?: () => void }) => ( - - ); + const getComponent = (props: { + client: MatrixClient; + onFinished?: () => void; + ref?: RefObject; + }) => ; test("render QR then back", async () => { const onFinished = jest.fn(); jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockReturnValue(unresolvedPromise()); - render(getComponent({ client, onFinished })); + jest.spyOn(MSC4108SignInWithQR.prototype, "generateCode"); + jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols"); + jest.spyOn(MSC4108SignInWithQR.prototype, "cancel"); + const ref = createRef(); + render(getComponent({ client, onFinished, ref })); await waitFor(() => expect(mockedFlow).toHaveBeenLastCalledWith({ @@ -95,7 +109,7 @@ describe("", () => { }), ); - const rendezvous = mocked(MSC4108SignInWithQR).mock.instances[0]; + const rendezvous = ref.current!.state.rendezvous!; expect(rendezvous.generateCode).toHaveBeenCalled(); expect(rendezvous.negotiateProtocols).toHaveBeenCalled(); @@ -109,7 +123,8 @@ describe("", () => { test("should open a new channel if expires before qr scan", async () => { const onFinished = jest.fn(); jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockReturnValue(unresolvedPromise()); - render(getComponent({ client, onFinished })); + const ref = createRef(); + render(getComponent({ client, onFinished, ref })); await waitFor(() => expect(mockedFlow).toHaveBeenLastCalledWith({ @@ -118,15 +133,15 @@ describe("", () => { }), ); - const rendezvous = mocked(MSC4108SignInWithQR).mock.instances[0]; + const rendezvous = ref.current!.state.rendezvous!; expect(rendezvous.generateCode).toHaveBeenCalled(); expect(rendezvous.negotiateProtocols).toHaveBeenCalled(); // Expire the channel - const onFailure = mocked(MSC4108SignInWithQR).mock.calls[0][3]; - onFailure!(ClientRendezvousFailureReason.Expired); + rendezvous.onFailure!(ClientRendezvousFailureReason.Expired); await jest.runAllTimersAsync(); - await waitFor(() => expect(mocked(MSC4108SignInWithQR).mock.instances).toHaveLength(2)); + await waitFor(() => expect(ref.current!.state.rendezvous).toBeDefined()); + expect(ref.current!.state.rendezvous).not.toBe(rendezvous); }); test("failed to connect", async () => { @@ -168,9 +183,11 @@ describe("", () => { }); test("reciprocates login", async () => { + const ref = createRef(); jest.spyOn(global.window, "open"); - render(getComponent({ client })); + render(getComponent({ client, ref })); + jest.spyOn(MSC4108SignInWithQR.prototype, "shareSecrets").mockResolvedValue({}); jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({}); jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({ verificationUri: "mock-verification-uri", @@ -193,10 +210,14 @@ describe("", () => { }), ); expect(global.window.open).toHaveBeenCalledWith("mock-verification-uri", "_blank"); + + const rendezvous = ref.current!.state.rendezvous!; + expect(rendezvous.shareSecrets).toHaveBeenCalled(); }); test("handles errors during protocol negotiation", async () => { - render(getComponent({ client })); + const ref = createRef(); + render(getComponent({ client, ref })); jest.spyOn(MSC4108SignInWithQR.prototype, "cancel").mockResolvedValue(); const err = new RendezvousError("Unknown Failure", MSC4108FailureReason.UnsupportedProtocol); // @ts-ignore work-around for lazy mocks @@ -211,7 +232,7 @@ describe("", () => { ); await waitFor(() => { - const rendezvous = mocked(MSC4108SignInWithQR).mock.instances[0]; + const rendezvous = ref.current!.state.rendezvous!; expect(rendezvous.cancel).toHaveBeenCalledWith(MSC4108FailureReason.UnsupportedProtocol); }); }); @@ -244,7 +265,8 @@ describe("", () => { }); test("handles user cancelling during reciprocation", async () => { - render(getComponent({ client })); + const ref = createRef(); + render(getComponent({ client, ref })); jest.spyOn(MSC4108SignInWithQR.prototype, "negotiateProtocols").mockResolvedValue({}); jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({}); jest.spyOn(MSC4108SignInWithQR.prototype, "deviceAuthorizationGrant").mockResolvedValue({}); @@ -259,7 +281,7 @@ describe("", () => { const onClick = mockedFlow.mock.calls[0][0].onClick; await onClick(Click.Cancel); - const rendezvous = mocked(MSC4108SignInWithQR).mock.instances[0]; + const rendezvous = ref.current!.state.rendezvous!; expect(rendezvous.cancel).toHaveBeenCalledWith(MSC4108FailureReason.UserCancelled); }); }); From 30484ef126fbaf5e2395063dbe6a8c4ec79278d1 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 30 Apr 2026 08:44:29 +0100 Subject: [PATCH 14/23] DRY vite configs using a shared config (#33334) * Fix OIDC login callback handling on Element Desktop * Add unit tests * Iterate * Fix lcov reporter * DRY vite configs using a shared config * Iterate * Revert change to electron-builder.ts * Iterate --- apps/desktop/package.json | 8 +- apps/desktop/vitest.config.ts | 69 +-- packages/module-api/package.json | 9 +- packages/module-api/vite.config.ts | 76 ++- packages/shared-components/package.json | 11 +- packages/shared-components/project.json | 8 + packages/shared-components/vitest.config.ts | 220 ++++----- packages/vite-common/package.json | 19 + packages/vite-common/tsconfig.json | 9 + packages/vite-common/vite.config.ts | 61 +++ pnpm-lock.yaml | 482 ++++++++------------ pnpm-workspace.yaml | 5 + 12 files changed, 434 insertions(+), 543 deletions(-) create mode 100644 packages/vite-common/package.json create mode 100644 packages/vite-common/tsconfig.json create mode 100644 packages/vite-common/vite.config.ts diff --git a/apps/desktop/package.json b/apps/desktop/package.json index f4f065c6ce..9e32dd19a8 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -72,6 +72,7 @@ "@babel/preset-typescript": "^7.18.6", "@electron/asar": "4.2.0", "@electron/fuses": "^2.1.1", + "@element-hq/vite-common": "workspace:*", "@playwright/test": "catalog:", "@stylistic/eslint-plugin": "^5.0.0", "@types/auto-launch": "^5.0.1", @@ -81,7 +82,7 @@ "@types/pacote": "^11.1.1", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", - "@vitest/coverage-v8": "^4.1.5", + "@vitest/coverage-v8": "catalog:", "app-builder-lib": "26.9.0", "chokidar": "^5.0.0", "detect-libc": "^2.0.0", @@ -105,9 +106,8 @@ "rimraf": "^6.0.0", "tar": "^7.5.8", "typescript": "6.0.3", - "vite": "^8.0.9", - "vitest": "^4.1.5", - "vitest-sonar-reporter": "^3.0.0" + "vitest": "catalog:", + "vitest-sonar-reporter": "catalog:" }, "hakDependencies": { "matrix-seshat": "4.2.0" diff --git a/apps/desktop/vitest.config.ts b/apps/desktop/vitest.config.ts index 1d9e9f179c..e9572047a0 100644 --- a/apps/desktop/vitest.config.ts +++ b/apps/desktop/vitest.config.ts @@ -5,60 +5,19 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -import { defineConfig } from "vitest/config"; -import { type UserConfig } from "vite"; -import { type Reporter } from "vitest/reporters"; -import { env } from "node:process"; +import { defineConfig, mergeConfig } from "vitest/config"; +import baseConfig from "@element-hq/vite-common/vite.config.js"; -const reporters: NonNullable["reporters"] = [["default"]]; - -const slowTestReporter: Reporter = { - onTestRunEnd(testModules, unhandledErrors, reason) { - const tests = testModules - .flatMap((m) => Array.from(m.children.allTests())) - .filter((test) => test.diagnostic()?.slow); - tests.sort((x, y) => x.diagnostic()!.duration! - y.diagnostic()!.duration!); - tests.reverse(); - - if (tests.length > 0) { - console.warn("Slowest 10 tests:"); - } - for (const t of tests.slice(0, 10)) { - console.warn(`${t.module.moduleId} > ${t.fullName}: ${t.diagnostic()?.duration.toFixed(0)}ms`); - } - }, -}; - -// if we're running under GHA, enable the GHA & Sonar reporters -if (env["GITHUB_ACTIONS"] !== undefined) { - reporters.push(["github-actions", { silent: false }]); - reporters.push([ - "vitest-sonar-reporter", - { - outputFile: "coverage/sonar-report.xml", - onWritePath: (path): string => `apps/desktop/${path}`, +export default mergeConfig( + baseConfig, + defineConfig({ + test: { + coverage: { + // The coverage report currently chokes on this file as it doesn't process it as TypeScript + exclude: ["src/preload.cts"], + }, + include: ["src/**/*.test.ts"], }, - ]); - - // if we're running against the develop branch, also enable the slow test reporter - if (env["GITHUB_REF"] == "refs/heads/develop") { - reporters.push(slowTestReporter); - } -} - -export default defineConfig({ - test: { - coverage: { - provider: "v8", - include: ["src/**/*"], - // The coverage report currently chokes on this file as it doesn't process it as TypeScript - exclude: ["src/preload.cts"], - reporter: [["lcov", { projectRoot: "../../" }]], - }, - environment: "node", - reporters, - globals: true, - pool: "threads", - include: ["src/**/*.test.ts"], - }, -}); + }), + true, +); diff --git a/packages/module-api/package.json b/packages/module-api/package.json index c366f61942..c02b754185 100644 --- a/packages/module-api/package.json +++ b/packages/module-api/package.json @@ -30,21 +30,22 @@ "test:unit": "vitest" }, "devDependencies": { + "@element-hq/vite-common": "workspace:*", "@matrix-org/react-sdk-module-api": "^2.5.0", "@microsoft/api-extractor": "^7.49.1", "@types/node": "^22.10.7", "@types/react": "^19", "@types/react-dom": "^19.0.4", "@types/semver": "^7.5.8", - "@vitest/coverage-v8": "^4.0.0", + "@vitest/coverage-v8": "catalog:", "matrix-widget-api": "^1.17.0", "rollup-plugin-external-globals": "^0.13.0", "semver": "^7.6.3", "typescript": "^6.0.0", "unplugin-dts": "1.0.0-beta.6", - "vite": "^8.0.0", - "vitest": "^4.0.0", - "vitest-sonar-reporter": "^3.0.0" + "vite": "catalog:", + "vitest": "catalog:", + "vitest-sonar-reporter": "catalog:" }, "peerDependencies": { "@matrix-org/react-sdk-module-api": "*", diff --git a/packages/module-api/vite.config.ts b/packages/module-api/vite.config.ts index 43dd25d974..5aa3d1bd19 100644 --- a/packages/module-api/vite.config.ts +++ b/packages/module-api/vite.config.ts @@ -7,57 +7,43 @@ Please see LICENSE files in the repository root for full details. import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { defineConfig } from "vite"; +import { defineConfig, mergeConfig } from "vitest/config"; import dts from "unplugin-dts/vite"; import externalGlobals from "rollup-plugin-external-globals"; +import baseConfig from "@element-hq/vite-common/vite.config"; import packageJson from "./package.json" with { type: "json" }; const __dirname = dirname(fileURLToPath(import.meta.url)); -export default defineConfig({ - build: { - lib: { - entry: resolve(__dirname, "src/index.ts"), - name: "element-web-plugin-engine", - fileName: "element-web-plugin-engine", +export default mergeConfig( + baseConfig, + defineConfig({ + build: { + lib: { + entry: resolve(__dirname, "src/index.ts"), + name: "element-web-plugin-engine", + fileName: "element-web-plugin-engine", + }, + outDir: "lib", + target: "esnext", + sourcemap: true, }, - outDir: "lib", - target: "esnext", - sourcemap: true, - }, - plugins: [ - dts(), - externalGlobals({ - // Reuse React from the host app - react: "window.React", - }), - ], - define: { - // We cannot use `process.env.npm_package_version` as when building element-web with module-api set to `workspace` - // this would contain the version of element-web rather than that of the module-api. - __VERSION__: JSON.stringify(packageJson.version), - // Use production mode for the build as it is tested against production builds of Element Web, - // this is required for React JSX versions to be compatible. - process: { env: { NODE_ENV: "production" } }, - }, - test: { - coverage: { - provider: "v8", - include: ["src/**/*"], - reporter: [["lcov", { projectRoot: "../../" }]], - }, - reporters: [ - ["default", { summary: false }], - [ - "vitest-sonar-reporter", - { - outputFile: "coverage/sonar-report.xml", - onWritePath(path: string): string { - return `packages/element-web-module-api/${path}`; - }, - }, - ], + plugins: [ + dts(), + externalGlobals({ + // Reuse React from the host app + react: "window.React", + }), ], - }, -}); + define: { + // We cannot use `process.env.npm_package_version` as when building element-web with module-api set to `workspace` + // this would contain the version of element-web rather than that of the module-api. + __VERSION__: JSON.stringify(packageJson.version), + // Use production mode for the build as it is tested against production builds of Element Web, + // this is required for React JSX versions to be compatible. + process: { env: { NODE_ENV: "production" } }, + }, + }), + true, +); diff --git a/packages/shared-components/package.json b/packages/shared-components/package.json index f3cd793f4a..9d0f114103 100644 --- a/packages/shared-components/package.json +++ b/packages/shared-components/package.json @@ -48,7 +48,7 @@ "build:doc": "nx typedoc", "lint": "pnpm lint:types && pnpm lint:js", "lint:js": "eslint --max-warnings 0 src", - "lint:types": "tsc --noEmit && tsc --noEmit -p tsconfig.node.json" + "lint:types": "nx lint:types" }, "dependencies": { "@element-hq/element-web-module-api": "workspace:*", @@ -71,6 +71,7 @@ }, "devDependencies": { "@element-hq/element-web-playwright-common": "workspace:*", + "@element-hq/vite-common": "workspace:*", "@fetch-mock/vitest": "^0.2.18", "@fontsource/inter": "catalog:", "@matrix-org/react-sdk-module-api": "^2.5.0", @@ -94,7 +95,7 @@ "@typescript-eslint/parser": "^8.53.1", "@vector-im/compound-web": "catalog:", "@vitest/browser-playwright": "^4.0.17", - "@vitest/coverage-v8": "^4.0.17", + "@vitest/coverage-v8": "catalog:", "eslint": "8", "eslint-config-google": "^0.14.0", "eslint-config-prettier": "^10.1.8", @@ -115,10 +116,10 @@ "typedoc-plugin-missing-exports": "^4.1.2", "typescript": "catalog:", "unplugin-dts": "1.0.0-beta.6", - "vite": "^8.0.0", + "vite": "catalog:", "vite-plugin-node-polyfills": "^0.26.0", - "vitest": "^4.0.18", - "vitest-sonar-reporter": "^3.0.0" + "vitest": "catalog:", + "vitest-sonar-reporter": "catalog:" }, "engines": { "node": ">=20.0.0" diff --git a/packages/shared-components/project.json b/packages/shared-components/project.json index 1751c4c6c4..590ee73520 100644 --- a/packages/shared-components/project.json +++ b/packages/shared-components/project.json @@ -54,6 +54,14 @@ "cwd": "packages/shared-components" }, "dependsOn": ["typedoc", "^build:playwright"] + }, + "lint:types": { + "executor": "nx:run-commands", + "options": { + "commands": ["tsc --noEmit", "tsc --noEmit -p tsconfig.node.json"], + "cwd": "packages/shared-components" + }, + "dependsOn": ["^build"] } } } diff --git a/packages/shared-components/vitest.config.ts b/packages/shared-components/vitest.config.ts index 1fa6c1b3a7..07cbe8c8e9 100644 --- a/packages/shared-components/vitest.config.ts +++ b/packages/shared-components/vitest.config.ts @@ -5,58 +5,18 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial Please see LICENSE files in the repository root for full details. */ -import { defineConfig, ViteUserConfig } from "vitest/config"; +import { defineConfig, mergeConfig } from "vitest/config"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { storybookTest } from "@storybook/addon-vitest/vitest-plugin"; import { storybookVis } from "storybook-addon-vis/vitest-plugin"; import { playwright, PlaywrightProviderOptions } from "@vitest/browser-playwright"; import { nodePolyfills } from "vite-plugin-node-polyfills"; -import { Reporter } from "vitest/reporters"; -import { env } from "process"; + +import baseConfig from "@element-hq/vite-common/vite.config"; const dirname = typeof __dirname !== "undefined" ? __dirname : path.dirname(fileURLToPath(import.meta.url)); -const reporters: NonNullable["reporters"] = [["default"]]; -const slowTestReporter: Reporter = { - onTestRunEnd(testModules, unhandledErrors, reason) { - const tests = testModules - .flatMap((m) => Array.from(m.children.allTests())) - .filter((test) => test.diagnostic()?.slow); - tests.sort((x, y) => x.diagnostic()?.duration! - y.diagnostic()?.duration!); - tests.reverse(); - if (tests.length > 0) { - console.warn("Slowest 10 tests:"); - } - for (const t of tests.slice(0, 10)) { - console.warn(`${t.module.moduleId} > ${t.fullName}: ${t.diagnostic()?.duration.toFixed(0)}ms`); - } - }, -}; - -// if we're running under GHA, enable the GHA & Sonar reporters -if (env["GITHUB_ACTIONS"] !== undefined) { - reporters.push([ - "github-actions", - { - silent: false, - }, - ]); - - reporters.push([ - "vitest-sonar-reporter", - { - outputFile: "coverage/sonar-report.xml", - onWritePath: (path) => `packages/shared-components/${path}`, - }, - ]); - - // if we're running against the develop branch, also enable the slow test reporter - if (env["GITHUB_REF"] == "refs/heads/develop") { - reporters.push(slowTestReporter); - } -} - const commonContextOptions: PlaywrightProviderOptions["contextOptions"] = { reducedMotion: "reduce", // Force consistent font rendering @@ -70,95 +30,93 @@ const commonLaunchOptions = { args: ["--font-render-hinting=none", "--disable-font-subpixel-positioning", "--disable-lcd-text"], }; -export default defineConfig({ - test: { - coverage: { - provider: "v8", - include: ["src/**/*.{ts,tsx}"], - exclude: ["src/**/*.stories.tsx"], - reporter: [["lcov", { projectRoot: "../../" }]], - }, - reporters, - globals: false, - pool: "threads", - projects: [ - { - extends: true, - plugins: [ - // The plugin will run tests for the stories defined in your Storybook config - // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest - storybookTest({ - configDir: path.join(dirname, ".storybook"), - storybookScript: "storybook --ci", - tags: { - exclude: ["skip-test"], +export default mergeConfig( + baseConfig, + defineConfig({ + test: { + coverage: { + exclude: ["src/**/*.stories.tsx"], + }, + projects: [ + { + extends: true, + plugins: [ + // The plugin will run tests for the stories defined in your Storybook config + // See options at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon#storybooktest + storybookTest({ + configDir: path.join(dirname, ".storybook"), + storybookScript: "storybook --ci", + tags: { + exclude: ["skip-test"], + }, + }), + storybookVis({ + // 3px of difference allowed before marking as failed + failureThreshold: 3, + // When running in CI=1 mode, set the platform to `linux` as that is the platform where the browser-in-docker is running + snapshotRootDir: ({ ci, platform }) => `__vis__/${ci ? "linux" : platform}`, + }), + ], + test: { + name: "storybook", + browser: { + enabled: true, + headless: true, + provider: playwright({ + contextOptions: commonContextOptions, + launchOptions: commonLaunchOptions, + connectOptions: process.env.PW_TEST_CONNECT_WS_ENDPOINT + ? { + wsEndpoint: process.env.PW_TEST_CONNECT_WS_ENDPOINT, + exposeNetwork: "", + } + : undefined, + }), + instances: [{ browser: "chromium" }], }, - }), - storybookVis({ - // 3px of difference allowed before marking as failed - failureThreshold: 3, - // When running in CI=1 mode, set the platform to `linux` as that is the platform where the browser-in-docker is running - snapshotRootDir: ({ ci, platform }) => `__vis__/${ci ? "linux" : platform}`, - }), - ], - test: { - name: "storybook", - browser: { - enabled: true, - headless: true, - provider: playwright({ - contextOptions: commonContextOptions, - launchOptions: commonLaunchOptions, - connectOptions: process.env.PW_TEST_CONNECT_WS_ENDPOINT - ? { - wsEndpoint: process.env.PW_TEST_CONNECT_WS_ENDPOINT, - exposeNetwork: "", - } - : undefined, - }), - instances: [{ browser: "chromium" }], - }, - setupFiles: [".storybook/vitest.setup.ts"], - }, - }, - { - extends: true, - // as any is workaround for https://github.com/davidmyersdev/vite-plugin-node-polyfills/issues/150 - plugins: [nodePolyfills({ include: ["util"], globals: { global: false } }) as any], - test: { - name: "unit", - browser: { - enabled: true, - headless: true, - provider: playwright({ - // These tests don't actually take screenshots (at least at time of writing) - // but let's pass these options everywhere for consistency - contextOptions: commonContextOptions, - launchOptions: commonLaunchOptions, - }), - instances: [{ browser: "chromium" }], - }, - setupFiles: ["src/test/setupTests.ts"], - }, - css: { - modules: { - // Stabilise snapshots while keeping names distinct across CSS modules. - generateScopedName: "[name]_[local]", + setupFiles: [".storybook/vitest.setup.ts"], }, }, - }, - ], - }, - optimizeDeps: { - include: [ - "vite-plugin-node-polyfills/shims/buffer", - "vite-plugin-node-polyfills/shims/process", - "@vector-im/compound-design-tokens/assets/web/icons", - ], - }, - resolve: { - alias: { - "@test-utils": path.resolve(__dirname, "./src/test/utils/index.tsx"), + { + extends: true, + // as any is workaround for https://github.com/davidmyersdev/vite-plugin-node-polyfills/issues/150 + plugins: [nodePolyfills({ include: ["util"], globals: { global: false } }) as any], + test: { + name: "unit", + browser: { + enabled: true, + headless: true, + provider: playwright({ + // These tests don't actually take screenshots (at least at time of writing) + // but let's pass these options everywhere for consistency + contextOptions: commonContextOptions, + launchOptions: commonLaunchOptions, + }), + instances: [{ browser: "chromium" }], + }, + setupFiles: ["src/test/setupTests.ts"], + }, + css: { + modules: { + // Stabilise snapshots while keeping names distinct across CSS modules. + generateScopedName: "[name]_[local]", + }, + }, + }, + ], }, - }, -}); + optimizeDeps: { + include: [ + "vite-plugin-node-polyfills/shims/buffer", + "vite-plugin-node-polyfills/shims/process", + "@vector-im/compound-design-tokens/assets/web/icons", + ], + }, + resolve: { + alias: { + "@test-utils": path.resolve(__dirname, "./src/test/utils/index.tsx"), + }, + }, + }), + true, +); diff --git a/packages/vite-common/package.json b/packages/vite-common/package.json new file mode 100644 index 0000000000..4a335ac0e7 --- /dev/null +++ b/packages/vite-common/package.json @@ -0,0 +1,19 @@ +{ + "name": "@element-hq/vite-common", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "lint:types": "tsc --noEmit" + }, + "dependencies": { + "vitest": "catalog:" + }, + "devDependencies": { + "typescript": "catalog:" + }, + "peerDependencies": { + "@vitest/coverage-v8": "catalog:", + "vitest-sonar-reporter": "catalog:" + } +} diff --git a/packages/vite-common/tsconfig.json b/packages/vite-common/tsconfig.json new file mode 100644 index 0000000000..e540b512a3 --- /dev/null +++ b/packages/vite-common/tsconfig.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "esnext", + "moduleResolution": "bundler", + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/vite-common/vite.config.ts b/packages/vite-common/vite.config.ts new file mode 100644 index 0000000000..f455b2d191 --- /dev/null +++ b/packages/vite-common/vite.config.ts @@ -0,0 +1,61 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { defineConfig, type ViteUserConfig } from "vitest/config"; +import { type Reporter } from "vitest/reporters"; +import { env } from "node:process"; + +const reporters: NonNullable["reporters"] = [["default"]]; + +const slowTestReporter: Reporter = { + onTestRunEnd(testModules, unhandledErrors, reason) { + const tests = testModules + .flatMap((m) => Array.from(m.children.allTests())) + .filter((test) => test.diagnostic()?.slow); + tests.sort((x, y) => x.diagnostic()!.duration! - y.diagnostic()!.duration!); + tests.reverse(); + + if (tests.length > 0) { + console.warn("Slowest 10 tests:"); + } + for (const t of tests.slice(0, 10)) { + console.warn(`${t.module.moduleId} > ${t.fullName}: ${t.diagnostic()?.duration.toFixed(0)}ms`); + } + }, +}; + +// if we're running under GHA, enable the GHA & Sonar reporters +if (env["GITHUB_ACTIONS"] !== undefined) { + reporters.push(["github-actions", { silent: false }]); + reporters.push([ + "vitest-sonar-reporter", + { + outputFile: "coverage/sonar-report.xml", + onWritePath: (path): string => `${process.cwd()}/${path}`, + }, + ]); + + // if we're running against the develop branch, also enable the slow test reporter + if (env["GITHUB_REF"] == "refs/heads/develop") { + reporters.push(slowTestReporter); + } +} + +export default defineConfig({ + test: { + coverage: { + provider: "v8", + include: ["src/**/*.{ts,tsx}"], + reporter: [["lcov", { projectRoot: "../../" }]], + }, + environment: "node", + reporters, + pool: "threads", + globals: false, + include: ["src/**/*.test.ts"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e54938527..92878b13f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ catalogs: '@vector-im/compound-web': specifier: 9.2.1 version: 9.2.1 + '@vitest/coverage-v8': + specifier: 4.1.5 + version: 4.1.5 matrix-web-i18n: specifier: 3.6.0 version: 3.6.0 @@ -33,6 +36,15 @@ catalogs: typescript: specifier: 6.0.3 version: 6.0.3 + vite: + specifier: 8.0.10 + version: 8.0.10 + vitest: + specifier: 4.1.5 + version: 4.1.5 + vitest-sonar-reporter: + specifier: 3.0.0 + version: 3.0.0 overrides: pretty-format@30>react-is: 19.2.5 @@ -216,6 +228,9 @@ importers: '@electron/fuses': specifier: ^2.1.1 version: 2.1.1 + '@element-hq/vite-common': + specifier: workspace:* + version: link:../../packages/vite-common '@playwright/test': specifier: 'catalog:' version: 1.59.1 @@ -244,7 +259,7 @@ importers: specifier: ^8.0.0 version: 8.58.2(eslint@8.57.1)(typescript@6.0.3) '@vitest/coverage-v8': - specifier: ^4.1.5 + specifier: 'catalog:' version: 4.1.5(@vitest/browser@4.1.5)(vitest@4.1.5) app-builder-lib: specifier: 26.9.0 @@ -315,14 +330,11 @@ importers: typescript: specifier: 6.0.3 version: 6.0.3 - vite: - specifier: ^8.0.9 - version: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vitest: - specifier: ^4.1.5 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 'catalog:' + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest-sonar-reporter: - specifier: ^3.0.0 + specifier: 'catalog:' version: 3.0.0(vitest@4.1.5) apps/web: @@ -926,6 +938,9 @@ importers: specifier: ^19 version: 19.2.5 devDependencies: + '@element-hq/vite-common': + specifier: workspace:* + version: link:../vite-common '@matrix-org/react-sdk-module-api': specifier: ^2.5.0 version: 2.5.0(patch_hash=016146c9cc96e6363609d2b2ac0896ccef567882eb1d73b75a77b8a30929de96)(react@19.2.5) @@ -945,7 +960,7 @@ importers: specifier: ^7.5.8 version: 7.7.1 '@vitest/coverage-v8': - specifier: ^4.0.0 + specifier: 'catalog:' version: 4.1.5(@vitest/browser@4.1.5)(vitest@4.1.5) matrix-widget-api: specifier: ^1.17.0 @@ -961,15 +976,15 @@ importers: version: 6.0.3 unplugin-dts: specifier: 1.0.0-beta.6 - version: 1.0.0-beta.6(patch_hash=c71b9e0f229ad1d76152030e8eceff26ee0b23c18619c205aa55fee274f89073)(@microsoft/api-extractor@7.58.5(@types/node@18.19.130))(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@6.0.3)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) + version: 1.0.0-beta.6(patch_hash=c71b9e0f229ad1d76152030e8eceff26ee0b23c18619c205aa55fee274f89073)(@microsoft/api-extractor@7.58.5(@types/node@18.19.130))(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@6.0.3)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) vite: - specifier: ^8.0.0 - version: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + specifier: 'catalog:' + version: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vitest: - specifier: ^4.0.0 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 'catalog:' + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest-sonar-reporter: - specifier: ^3.0.0 + specifier: 'catalog:' version: 3.0.0(vitest@4.1.5) packages/playwright-common: @@ -1075,6 +1090,9 @@ importers: '@element-hq/element-web-playwright-common': specifier: workspace:* version: link:../playwright-common + '@element-hq/vite-common': + specifier: workspace:* + version: link:../vite-common '@fetch-mock/vitest': specifier: ^0.2.18 version: 0.2.18(vitest@4.1.5) @@ -1095,10 +1113,10 @@ importers: version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/addon-designs': specifier: ^11.0.1 - version: 11.1.3(@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + version: 11.1.3(@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/addon-docs': specifier: ^10.0.7 - version: 10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) + version: 10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) '@storybook/addon-vitest': specifier: ^10.1.11 version: 10.3.5(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.5) @@ -1107,7 +1125,7 @@ importers: version: 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@storybook/react-vite': specifier: ^10.0.7 - version: 10.3.5(esbuild@0.27.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) + version: 10.3.5(esbuild@0.27.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) '@stylistic/eslint-plugin': specifier: ^5.7.0 version: 5.10.0(eslint@8.57.1) @@ -1143,9 +1161,9 @@ importers: version: 9.2.1(@fontsource/inconsolata@5.2.8)(@fontsource/inter@5.2.8)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vector-im/compound-design-tokens@10.1.0(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@vitest/browser-playwright': specifier: ^4.0.17 - version: 4.1.5(playwright@1.59.1)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + version: 4.1.5(playwright@1.59.1)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) '@vitest/coverage-v8': - specifier: ^4.0.17 + specifier: 'catalog:' version: 4.1.5(@vitest/browser@4.1.5)(vitest@4.1.5) eslint: specifier: '8' @@ -1206,20 +1224,36 @@ importers: version: 6.0.3 unplugin-dts: specifier: 1.0.0-beta.6 - version: 1.0.0-beta.6(patch_hash=c71b9e0f229ad1d76152030e8eceff26ee0b23c18619c205aa55fee274f89073)(@microsoft/api-extractor@7.58.5(@types/node@18.19.130))(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@6.0.3)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) + version: 1.0.0-beta.6(patch_hash=c71b9e0f229ad1d76152030e8eceff26ee0b23c18619c205aa55fee274f89073)(@microsoft/api-extractor@7.58.5(@types/node@18.19.130))(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@6.0.3)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) vite: - specifier: ^8.0.0 - version: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + specifier: 'catalog:' + version: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-node-polyfills: specifier: ^0.26.0 - version: 0.26.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 0.26.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest: - specifier: ^4.0.18 - version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + specifier: 'catalog:' + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest-sonar-reporter: - specifier: ^3.0.0 + specifier: 'catalog:' version: 3.0.0(vitest@4.1.5) + packages/vite-common: + dependencies: + '@vitest/coverage-v8': + specifier: 'catalog:' + version: 4.1.5(@vitest/browser@4.1.5)(vitest@4.1.5) + vitest: + specifier: 'catalog:' + version: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest-sonar-reporter: + specifier: 'catalog:' + version: 3.0.0(vitest@4.1.5) + devDependencies: + typescript: + specifier: 'catalog:' + version: 6.0.3 + packages: 7zip-bin@5.2.0: @@ -2497,18 +2531,12 @@ packages: '@emnapi/core@1.4.5': resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==} - '@emnapi/core@1.9.2': - resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/runtime@1.4.5': resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} - '@emnapi/runtime@1.9.2': - resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} - '@emnapi/wasi-threads@1.0.4': resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} @@ -2996,96 +3024,48 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/fs-core@4.56.10': - resolution: {integrity: sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@jsonjoy.com/fs-core@4.57.2': resolution: {integrity: sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-fsa@4.56.10': - resolution: {integrity: sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@jsonjoy.com/fs-fsa@4.57.2': resolution: {integrity: sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-builtins@4.56.10': - resolution: {integrity: sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@jsonjoy.com/fs-node-builtins@4.57.2': resolution: {integrity: sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-to-fsa@4.56.10': - resolution: {integrity: sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@jsonjoy.com/fs-node-to-fsa@4.57.2': resolution: {integrity: sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node-utils@4.56.10': - resolution: {integrity: sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@jsonjoy.com/fs-node-utils@4.57.2': resolution: {integrity: sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-node@4.56.10': - resolution: {integrity: sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@jsonjoy.com/fs-node@4.57.2': resolution: {integrity: sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-print@4.56.10': - resolution: {integrity: sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@jsonjoy.com/fs-print@4.57.2': resolution: {integrity: sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' - '@jsonjoy.com/fs-snapshot@4.56.10': - resolution: {integrity: sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@jsonjoy.com/fs-snapshot@4.57.2': resolution: {integrity: sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==} engines: {node: '>=10.0'} @@ -3905,8 +3885,8 @@ packages: '@oxc-project/types@0.121.0': resolution: {integrity: sha512-CGtOARQb9tyv7ECgdAlFxi0Fv7lmzvmlm2rpD/RdijOO9rfk/JvB1CjT8EnoD+tjna/IYgKKw3IV7objRb+aYw==} - '@oxc-project/types@0.126.0': - resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} @@ -4504,103 +4484,103 @@ packages: '@repobuddy/test@1.0.1': resolution: {integrity: sha512-eNXZ6Cfs18EGQtPS/H68E0GQyQeB+al5sPcMgb70553CE0vs5Y7jDdO8Csgk/CHiuPy/rCKdN33OvvkSiGaG+A==} - '@rolldown/binding-android-arm64@1.0.0-rc.16': - resolution: {integrity: sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==} + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.16': - resolution: {integrity: sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.16': - resolution: {integrity: sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==} + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.16': - resolution: {integrity: sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16': - resolution: {integrity: sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': - resolution: {integrity: sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': - resolution: {integrity: sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': - resolution: {integrity: sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': - resolution: {integrity: sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.16': - resolution: {integrity: sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16': - resolution: {integrity: sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.16': - resolution: {integrity: sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rolldown/pluginutils@1.0.0-rc.16': - resolution: {integrity: sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==} + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} '@rollup/plugin-inject@5.0.5': resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} @@ -10038,11 +10018,6 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - memfs@4.56.10: - resolution: {integrity: sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==} - peerDependencies: - tslib: '2' - memfs@4.57.2: resolution: {integrity: sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==} peerDependencies: @@ -11786,8 +11761,8 @@ packages: robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - rolldown@1.0.0-rc.16: - resolution: {integrity: sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==} + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -13041,8 +13016,8 @@ packages: terser: optional: true - vite@8.0.9: - resolution: {integrity: sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==} + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -15107,12 +15082,6 @@ snapshots: '@emnapi/wasi-threads': 1.0.4 tslib: 2.8.1 - '@emnapi/core@1.9.2': - dependencies: - '@emnapi/wasi-threads': 1.2.1 - tslib: 2.8.1 - optional: true - '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 @@ -15121,11 +15090,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@emnapi/runtime@1.9.2': - dependencies: - tslib: 2.8.1 - optional: true - '@emnapi/wasi-threads@1.0.4': dependencies: tslib: 2.8.1 @@ -15254,7 +15218,7 @@ snapshots: '@fetch-mock/vitest@0.2.18(vitest@4.1.5)': dependencies: fetch-mock: 12.6.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@figspec/components@2.1.0': {} @@ -15589,11 +15553,11 @@ snapshots: '@types/yargs': 17.0.35 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@6.0.3)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@6.0.3)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@6.0.3) - vite: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) optionalDependencies: typescript: 6.0.3 @@ -15647,13 +15611,6 @@ snapshots: dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-core@4.56.10(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) - tslib: 2.8.1 - '@jsonjoy.com/fs-core@4.57.2(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) @@ -15661,14 +15618,6 @@ snapshots: thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-fsa@4.56.10(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) - tslib: 2.8.1 - '@jsonjoy.com/fs-fsa@4.57.2(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) @@ -15677,21 +15626,10 @@ snapshots: thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-builtins@4.56.10(tslib@2.8.1)': - dependencies: - tslib: 2.8.1 - '@jsonjoy.com/fs-node-builtins@4.57.2(tslib@2.8.1)': dependencies: tslib: 2.8.1 - '@jsonjoy.com/fs-node-to-fsa@4.56.10(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - tslib: 2.8.1 - '@jsonjoy.com/fs-node-to-fsa@4.57.2(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-fsa': 4.57.2(tslib@2.8.1) @@ -15699,27 +15637,11 @@ snapshots: '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node-utils@4.56.10(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - tslib: 2.8.1 - '@jsonjoy.com/fs-node-utils@4.57.2(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-node@4.56.10(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.56.10(tslib@2.8.1) - glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) - tslib: 2.8.1 - '@jsonjoy.com/fs-node@4.57.2(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) @@ -15731,26 +15653,12 @@ snapshots: thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-print@4.56.10(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - tree-dump: 1.1.0(tslib@2.8.1) - tslib: 2.8.1 - '@jsonjoy.com/fs-print@4.57.2(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-snapshot@4.56.10(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/json-pack': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) - tslib: 2.8.1 - '@jsonjoy.com/fs-snapshot@4.57.2(tslib@2.8.1)': dependencies: '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) @@ -15983,13 +15891,6 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': - dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@tybys/wasm-util': 0.10.1 - optional: true - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 @@ -16711,7 +16612,7 @@ snapshots: '@oxc-project/types@0.121.0': {} - '@oxc-project/types@0.126.0': {} + '@oxc-project/types@0.127.0': {} '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -17274,56 +17175,56 @@ snapshots: '@repobuddy/test@1.0.1': {} - '@rolldown/binding-android-arm64@1.0.0-rc.16': + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.16': + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.16': + '@rolldown/binding-darwin-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.16': + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.16': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': dependencies: - '@emnapi/core': 1.9.2 - '@emnapi/runtime': 1.9.2 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.16': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': optional: true - '@rolldown/pluginutils@1.0.0-rc.16': {} + '@rolldown/pluginutils@1.0.0-rc.17': {} '@rollup/plugin-inject@5.0.5(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))': dependencies: @@ -17765,21 +17666,21 @@ snapshots: axe-core: 4.11.3 storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@storybook/addon-designs@11.1.3(@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': + '@storybook/addon-designs@11.1.3(@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': dependencies: '@figspec/react': 2.0.1(@types/react@19.2.14)(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) optionalDependencies: - '@storybook/addon-docs': 10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) + '@storybook/addon-docs': 10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) react: 19.2.5 react-dom: 19.2.5(react@19.2.5) transitivePeerDependencies: - '@types/react' - '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4))': + '@storybook/addon-docs@10.3.5(@types/react@19.2.14)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.5) - '@storybook/csf-plugin': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) + '@storybook/csf-plugin': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) '@storybook/icons': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) react: 19.2.5 @@ -17799,33 +17700,33 @@ snapshots: '@storybook/icons': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) optionalDependencies: - '@vitest/browser': 4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) - '@vitest/browser-playwright': 4.1.5(playwright@1.59.1)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + '@vitest/browser': 4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + '@vitest/browser-playwright': 4.1.5(playwright@1.59.1)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) '@vitest/runner': 4.1.5 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - react - react-dom - '@storybook/builder-vite@10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4))': + '@storybook/builder-vite@10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4))': dependencies: - '@storybook/csf-plugin': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) + '@storybook/csf-plugin': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) ts-dedent: 2.2.0 - vite: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4))': + '@storybook/csf-plugin@10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4))': dependencies: storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.4 rollup: 4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8) - vite: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) webpack: 5.106.2(esbuild@0.27.4) '@storybook/global@5.0.0': {} @@ -17841,11 +17742,11 @@ snapshots: react-dom: 19.2.5(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@storybook/react-vite@10.3.5(esbuild@0.27.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4))': + '@storybook/react-vite@10.3.5(esbuild@0.27.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@rollup/pluginutils': 5.3.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) - '@storybook/builder-vite': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) + '@storybook/builder-vite': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)) '@storybook/react': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@6.0.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -17855,7 +17756,7 @@ snapshots: resolve: 1.22.12 storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tsconfig-paths: 4.2.0 - vite: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - esbuild - rollup @@ -18811,29 +18712,29 @@ snapshots: vite: 5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1) vue: 3.5.31(typescript@6.0.3) - '@vitest/browser-playwright@4.1.5(playwright@1.59.1)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5)': + '@vitest/browser-playwright@4.1.5(playwright@1.59.1)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5)': dependencies: - '@vitest/browser': 4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) - '@vitest/mocker': 4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/browser': 4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) playwright: 1.59.1 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5)': + '@vitest/browser@4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/utils': 4.1.5 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) ws: 8.20.0 transitivePeerDependencies: - bufferutil @@ -18853,9 +18754,9 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: - '@vitest/browser': 4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + '@vitest/browser': 4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) '@vitest/expect@3.2.4': dependencies: @@ -18874,13 +18775,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.5 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -23740,23 +23641,6 @@ snapshots: media-typer@1.1.0: {} - memfs@4.56.10(tslib@2.8.1): - dependencies: - '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-to-fsa': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.56.10(tslib@2.8.1) - '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) - '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) - glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) - tree-dump: 1.1.0(tslib@2.8.1) - tslib: 2.8.1 - memfs@4.57.2(tslib@2.8.1): dependencies: '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) @@ -25862,26 +25746,26 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.0.0-rc.16: + rolldown@1.0.0-rc.17: dependencies: - '@oxc-project/types': 0.126.0 - '@rolldown/pluginutils': 1.0.0-rc.16 + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.16 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.16 - '@rolldown/binding-darwin-x64': 1.0.0-rc.16 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.16 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.16 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.16 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.16 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.16 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.16 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.16 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.16 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.16 + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 rollup-plugin-external-globals@0.13.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)): dependencies: @@ -26396,14 +26280,14 @@ snapshots: '@repobuddy/test': 1.0.1 '@storybook/addon-vitest': 10.3.5(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.5) '@storybook/icons': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@vitest/browser': 4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + '@vitest/browser': 4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) glob: 13.0.6 is-ci: 4.1.0 memoize: 11.0.0 pathe: 2.0.3 storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) type-plus: 8.0.0-beta.8(typescript@6.0.3) - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest-plugin-vis: 5.1.0(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(babel-plugin-macros@3.1.0)(typescript@6.0.3)(vitest@4.1.5) transitivePeerDependencies: - '@vitest/browser-playwright' @@ -27214,7 +27098,7 @@ snapshots: unpipe@1.0.0: {} - unplugin-dts@1.0.0-beta.6(patch_hash=c71b9e0f229ad1d76152030e8eceff26ee0b23c18619c205aa55fee274f89073)(@microsoft/api-extractor@7.58.5(@types/node@18.19.130))(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@6.0.3)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)): + unplugin-dts@1.0.0-beta.6(patch_hash=c71b9e0f229ad1d76152030e8eceff26ee0b23c18619c205aa55fee274f89073)(@microsoft/api-extractor@7.58.5(@types/node@18.19.130))(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@6.0.3)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.106.2(esbuild@0.27.4)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) '@volar/typescript': 2.4.28 @@ -27229,7 +27113,7 @@ snapshots: '@microsoft/api-extractor': 7.58.5(@types/node@18.19.130) esbuild: 0.27.4 rollup: 4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8) - vite: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) webpack: 5.106.2(esbuild@0.27.4) transitivePeerDependencies: - supports-color @@ -27376,11 +27260,11 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plugin-node-polyfills@0.26.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-node-polyfills@0.26.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) node-stdlib-browser: 1.3.1 - vite: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - rollup @@ -27396,12 +27280,12 @@ snapshots: sugarss: 5.0.1(postcss@8.5.10) terser: 5.46.1 - vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 postcss: 8.5.10 - rolldown: 1.0.0-rc.16 + rolldown: 1.0.0-rc.17 tinyglobby: 0.2.16 optionalDependencies: '@types/node': 18.19.130 @@ -27471,7 +27355,7 @@ snapshots: vitest-plugin-vis@5.1.0(@vitest/browser-playwright@4.1.5)(@vitest/browser@4.1.5)(babel-plugin-macros@3.1.0)(typescript@6.0.3)(vitest@4.1.5): dependencies: - '@vitest/browser': 4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + '@vitest/browser': 4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) dedent: 1.7.2(babel-plugin-macros@3.1.0) glob: 13.0.6 is-ci: 4.1.0 @@ -27482,21 +27366,21 @@ snapshots: rimraf: 6.1.3 ssim.js: 3.5.0 type-plus: 8.0.0-beta.8(typescript@6.0.3) - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: - '@vitest/browser-playwright': 4.1.5(playwright@1.59.1)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + '@vitest/browser-playwright': 4.1.5(playwright@1.59.1)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) transitivePeerDependencies: - babel-plugin-macros - typescript vitest-sonar-reporter@3.0.0(vitest@4.1.5): dependencies: - vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.5(@opentelemetry/api@1.9.1)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.5)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.5 '@vitest/runner': 4.1.5 '@vitest/snapshot': 4.1.5 @@ -27513,12 +27397,12 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 '@types/node': 18.19.130 - '@vitest/browser-playwright': 4.1.5(playwright@1.59.1)(vite@8.0.9(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) + '@vitest/browser-playwright': 4.1.5(playwright@1.59.1)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) '@vitest/coverage-v8': 4.1.5(@vitest/browser@4.1.5)(vitest@4.1.5) jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f) transitivePeerDependencies: @@ -27641,7 +27525,7 @@ snapshots: webpack-dev-middleware@7.4.5(tslib@2.8.1)(webpack@5.106.2): dependencies: colorette: 2.0.20 - memfs: 4.56.10(tslib@2.8.1) + memfs: 4.57.2(tslib@2.8.1) mime-types: 3.0.2 on-finished: 2.4.1 range-parser: 1.2.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e25bc040e1..c2e8a9c23a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,6 +22,11 @@ catalog: matrix-web-i18n: 3.6.0 # fonts "@fontsource/inter": 5.2.8 + # vite + vite: 8.0.10 + vitest: 4.1.5 + vitest-sonar-reporter: 3.0.0 + "@vitest/coverage-v8": 4.1.5 packageExtensions: fdir: dependencies: From 208e8ca1a134f48a0863814fde135ba05bc2d85c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 30 Apr 2026 08:51:08 +0100 Subject: [PATCH 15/23] [Backport] Fix OIDC login callback handling on Element Desktop (#33337) --- .github/workflows/tests.yml | 36 +- apps/desktop/.eslintrc.cjs | 7 + apps/desktop/electron-builder.ts | 3 +- apps/desktop/package.json | 11 +- apps/desktop/src/protocol.test.ts | 87 ++++ apps/desktop/src/protocol.ts | 25 +- apps/desktop/tsconfig.json | 3 +- apps/desktop/tsconfig.node.json | 15 + apps/desktop/vitest.config.ts | 64 +++ pnpm-lock.yaml | 785 ++++++++++++++++++++++++++---- sonar-project.properties | 27 +- 11 files changed, 931 insertions(+), 132 deletions(-) create mode 100644 apps/desktop/src/protocol.test.ts create mode 100644 apps/desktop/tsconfig.node.json create mode 100644 apps/desktop/vitest.config.ts diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8a237b6950..f365f39531 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -93,7 +93,7 @@ jobs: if: env.ENABLE_COVERAGE == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: coverage-${{ matrix.runner }} + name: coverage-jest-${{ matrix.runner }} path: | apps/web/coverage !apps/web/coverage/lcov-report @@ -124,9 +124,10 @@ jobs: name: Vitest strategy: matrix: - package: - - shared-components - - module-api + path: + - apps/desktop + - packages/shared-components + - packages/module-api runs-on: ubuntu-24.04 steps: - name: Checkout code @@ -149,30 +150,39 @@ jobs: uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5 with: path: | - packages/${{ matrix.package }}/node_modules/.cache - packages/${{ matrix.package }}/node_modules/.vite/vitest - key: ${{ hashFiles('pnpm-lock.yaml') }} + ${{ matrix.path }}/node_modules/.cache + ${{ matrix.path }}/node_modules/.vite/vitest + key: ${{ matrix.path }}-${{ hashFiles('pnpm-lock.yaml') }} - name: Setup playwright uses: ./.github/actions/setup-playwright - if: matrix.package == 'shared-components' + if: matrix.path == 'packages/shared-components' with: write-cache: ${{ github.event_name != 'merge_group' }} - name: Run tests - working-directory: "packages/${{ matrix.package }}" + working-directory: ${{ matrix.path }} run: pnpm test:unit --coverage=$ENABLE_COVERAGE # Dump the disk usage on failure, because this job seems to fail with disk fills sometimes - name: df - run: df + run: df -h && df -i if: ${{ failure() }} + - name: Calculate artifact name + if: env.ENABLE_COVERAGE == 'true' + id: artifact + run: | + NAME=$(basename "$MATRIX_PATH") + echo "name=$NAME" >> $GITHUB_OUTPUT + env: + MATRIX_PATH: ${{ matrix.path }} + - name: Upload Artifact if: env.ENABLE_COVERAGE == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: - name: coverage-${{ matrix.package }} + name: coverage-${{ steps.artifact.outputs.name }} path: | - packages/${{ matrix.package }}/coverage - !packages/${{ matrix.package }}/coverage/lcov-report + ${{ matrix.path }}/coverage + !${{ matrix.path }}/coverage/lcov-report diff --git a/apps/desktop/.eslintrc.cjs b/apps/desktop/.eslintrc.cjs index d8f162c2f0..7a1d06729c 100644 --- a/apps/desktop/.eslintrc.cjs +++ b/apps/desktop/.eslintrc.cjs @@ -86,5 +86,12 @@ module.exports = { "@typescript-eslint/no-non-null-assertion": "off", }, }, + { + files: ["src/**/*.test.ts", "electron-builder.ts", "vitest.config.ts"], + extends: ["plugin:matrix-org/typescript"], + parserOptions: { + project: ["tsconfig.node.json"], + }, + }, ], }; diff --git a/apps/desktop/electron-builder.ts b/apps/desktop/electron-builder.ts index 967a129c1c..9bcd771a73 100644 --- a/apps/desktop/electron-builder.ts +++ b/apps/desktop/electron-builder.ts @@ -52,6 +52,7 @@ interface Variant extends Metadata { } type Writable = NonNullable< + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type T extends Function ? T : T extends object ? { -readonly [K in keyof T]: Writable } : T >; @@ -74,7 +75,7 @@ if (process.env.VARIANT_PATH) { } for (const key in variant) { - console.log(`${key}: ${variant[key]}`); + console.log(`${key}: ${variant[key as keyof Variant]}`); } interface Configuration extends BaseConfiguration { diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 7a9e9ca615..6717fda312 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -32,8 +32,9 @@ "lint": "pnpm lint:types && pnpm lint:js", "lint:js": "eslint --max-warnings 0 src hak playwright scripts", "lint:js-fix": "eslint --fix --max-warnings 0 src hak playwright scripts && prettier --log-level=warn --write .", - "lint:types": "pnpm lint:types:src && pnpm lint:types:test && pnpm lint:types:scripts && pnpm lint:types:hak", + "lint:types": "pnpm lint:types:src && pnpm lint:types:node && pnpm lint:types:test && pnpm lint:types:scripts && pnpm lint:types:hak", "lint:types:src": "tsc --noEmit", + "lint:types:node": "tsc --noEmit -p tsconfig.node.json", "lint:types:test": "tsc --noEmit -p playwright/tsconfig.json", "lint:types:scripts": "tsc --noEmit -p scripts/tsconfig.json", "lint:types:hak": "tsc --noEmit -p hak/tsconfig.json", @@ -49,6 +50,7 @@ "docker:install": "scripts/in-docker.sh pnpm install", "clean": "rimraf webapp.asar dist packages deploys lib", "hak": "node scripts/hak/index.ts", + "test:unit": "vitest", "test:playwright": "nx test:playwright --", "test:playwright:open": "nx test:playwright -- --ui", "test:playwright:screenshots": "nx test:playwright:screenshots --", @@ -79,6 +81,7 @@ "@types/pacote": "^11.1.1", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", + "@vitest/coverage-v8": "^4.1.5", "app-builder-lib": "26.8.2", "chokidar": "^5.0.0", "detect-libc": "^2.0.0", @@ -95,12 +98,16 @@ "eslint-plugin-unicorn": "^56.0.0", "glob": "^13.0.0", "matrix-web-i18n": "catalog:", + "memfs": "^4.57.2", "mkdirp": "^3.0.0", "pacote": "^21.0.0", "prettier": "^3.0.0", "rimraf": "^6.0.0", "tar": "^7.5.8", - "typescript": "5.9.3" + "typescript": "5.9.3", + "vite": "^8.0.9", + "vitest": "^4.1.5", + "vitest-sonar-reporter": "^3.0.0" }, "hakDependencies": { "matrix-seshat": "4.2.0" diff --git a/apps/desktop/src/protocol.test.ts b/apps/desktop/src/protocol.test.ts new file mode 100644 index 0000000000..396a59f065 --- /dev/null +++ b/apps/desktop/src/protocol.test.ts @@ -0,0 +1,87 @@ +/* +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. +*/ + +import { expect, describe, it, beforeEach, vi } from "vitest"; +import { fs as memfs, vol } from "memfs"; + +import ProtocolHandler from "./protocol.js"; + +const TEST_PROTOCOL = "test.proto"; +const TEST_SESSION_ID = "test_session_id"; +const USER_DATA_DIR = "/Users/name/Library/Application Support/Element"; + +vi.mock("node:fs", () => ({ default: memfs })); +vi.mock("electron", () => ({ + app: { + getPath: vi.fn().mockReturnValue("/Users/name/Library/Application Support/Element"), + on: vi.fn(), + }, + ipcMain: { + handle: vi.fn(), + }, +})); + +beforeEach(() => { + // Reset the state of the in-memory fs + vol.reset(); +}); + +describe("ProtocolHandler", () => { + describe("getProfileFromDeeplink", () => { + const handler = new ProtocolHandler(TEST_PROTOCOL); + + beforeEach(() => { + vol.fromJSON( + { + "./sso-sessions.json": JSON.stringify({ [TEST_SESSION_ID]: USER_DATA_DIR }), + }, + USER_DATA_DIR, + ); + }); + + it("should handle legacy SSO URIs", () => { + expect( + handler.getProfileFromDeeplink([ + "Element.app", + `element://vector/webapp/?element-desktop-ssoid=${TEST_SESSION_ID}`, + ]), + ).toBe(USER_DATA_DIR); + }); + + it("should handle OIDC URIs with response_mode=query", () => { + expect( + handler.getProfileFromDeeplink([ + "Element.app", + `${TEST_PROTOCOL}:/vector/webapp/?no_universal_links=true&code=DEADBEEF&state=foobar:element-desktop-ssoid:${TEST_SESSION_ID}`, + ]), + ).toBe(USER_DATA_DIR); + }); + + it("should handle OIDC URIs with response_mode=fragment", () => { + expect( + handler.getProfileFromDeeplink([ + "Element.app", + `${TEST_PROTOCOL}:/vector/webapp/?no_universal_links=true#code=DEADBEEF&state=foobar:element-desktop-ssoid:${TEST_SESSION_ID}`, + ]), + ).toBe(USER_DATA_DIR); + }); + + it("should handle malformed OIDC URIs gracefully", () => { + expect( + handler.getProfileFromDeeplink([ + "Element.app", + `${TEST_PROTOCOL}:/vector/webapp/?no_universal_links=true#code=DEADBEEF:element-desktop-ssoid:${TEST_SESSION_ID}`, + ]), + ).toBeUndefined(); + }); + + it("should handle unrelated URIs gracefully", () => { + expect(handler.getProfileFromDeeplink(["Element.app", `${TEST_PROTOCOL}:/vector/webapp/`])).toBeUndefined(); + expect(handler.getProfileFromDeeplink(["Element.app", `test.unrelated:/vector/webapp/`])).toBeUndefined(); + }); + }); +}); diff --git a/apps/desktop/src/protocol.ts b/apps/desktop/src/protocol.ts index f6812b49e6..ad2558fc2a 100644 --- a/apps/desktop/src/protocol.ts +++ b/apps/desktop/src/protocol.ts @@ -97,7 +97,8 @@ export default class ProtocolHandler { const s = fs.readFileSync(storePath, { encoding: "utf8" }); const o = JSON.parse(s); return typeof o === "object" ? o : {}; - } catch { + } catch (e) { + console.warn("Unable to read protocol store, starting with empty store: ", e); return {}; } } @@ -130,10 +131,26 @@ export default class ProtocolHandler { let sessionId = parsedUrl.searchParams.get(SEARCH_PARAM); if (!sessionId) { // In OIDC, we must shuttle the value in the `state` param rather than `element-desktop-ssoid` - // We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ` - sessionId = parsedUrl.searchParams.get("state")!.split(`:${SEARCH_PARAM}:`)[1]; + // We encode it as a suffix like `:element-desktop-ssoid:XXYYZZ`. + // The OIDC flow may have used response_mode=fragment or query, so we need to handle both cases. + let searchParams = parsedUrl.searchParams; + if (parsedUrl.hash.includes("=")) { + const [params] = parsedUrl.hash.substring(1).split("?", 2); + searchParams = new URLSearchParams(params); + } + + const state = searchParams.get("state"); + if (state) { + sessionId = state.split(`:${SEARCH_PARAM}:`)[1]; + } } - console.log("Forwarding to profile: ", store[sessionId]); + + if (!sessionId) { + console.warn("Unable to read session ID in deeplink url:", deeplinkUrl); + return undefined; + } + + console.log("Forwarding to profile:", store[sessionId]); return store[sessionId]; } } diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index e0490357fc..5993a344e6 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -14,5 +14,6 @@ "lib": ["es2022", "es2024.promise"], "strict": true }, - "include": ["./src/**/*.ts", "./src/**/*.cts"] + "include": ["./src/**/*.ts", "./src/**/*.cts"], + "exclude": ["./src/**/*.test.ts"] } diff --git a/apps/desktop/tsconfig.node.json b/apps/desktop/tsconfig.node.json new file mode 100644 index 0000000000..c9de5ead8d --- /dev/null +++ b/apps/desktop/tsconfig.node.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "resolveJsonModule": true, + "module": "nodenext", + "moduleResolution": "NodeNext", + "target": "es2022", + "sourceMap": false, + "typeRoots": [], + "types": [], + "skipLibCheck": true, + "noEmit": true, + "strict": true + }, + "include": ["./electron-builder.ts", "./vitest.config.ts", "./src/**/*.d.ts", "./src/**/*.test.ts"] +} diff --git a/apps/desktop/vitest.config.ts b/apps/desktop/vitest.config.ts new file mode 100644 index 0000000000..1d9e9f179c --- /dev/null +++ b/apps/desktop/vitest.config.ts @@ -0,0 +1,64 @@ +/* +Copyright 2026 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { defineConfig } from "vitest/config"; +import { type UserConfig } from "vite"; +import { type Reporter } from "vitest/reporters"; +import { env } from "node:process"; + +const reporters: NonNullable["reporters"] = [["default"]]; + +const slowTestReporter: Reporter = { + onTestRunEnd(testModules, unhandledErrors, reason) { + const tests = testModules + .flatMap((m) => Array.from(m.children.allTests())) + .filter((test) => test.diagnostic()?.slow); + tests.sort((x, y) => x.diagnostic()!.duration! - y.diagnostic()!.duration!); + tests.reverse(); + + if (tests.length > 0) { + console.warn("Slowest 10 tests:"); + } + for (const t of tests.slice(0, 10)) { + console.warn(`${t.module.moduleId} > ${t.fullName}: ${t.diagnostic()?.duration.toFixed(0)}ms`); + } + }, +}; + +// if we're running under GHA, enable the GHA & Sonar reporters +if (env["GITHUB_ACTIONS"] !== undefined) { + reporters.push(["github-actions", { silent: false }]); + reporters.push([ + "vitest-sonar-reporter", + { + outputFile: "coverage/sonar-report.xml", + onWritePath: (path): string => `apps/desktop/${path}`, + }, + ]); + + // if we're running against the develop branch, also enable the slow test reporter + if (env["GITHUB_REF"] == "refs/heads/develop") { + reporters.push(slowTestReporter); + } +} + +export default defineConfig({ + test: { + coverage: { + provider: "v8", + include: ["src/**/*"], + // The coverage report currently chokes on this file as it doesn't process it as TypeScript + exclude: ["src/preload.cts"], + reporter: [["lcov", { projectRoot: "../../" }]], + }, + environment: "node", + reporters, + globals: true, + pool: "threads", + include: ["src/**/*.test.ts"], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 363acc8b59..864109f1cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -165,10 +165,10 @@ importers: version: 5.9.3 vitepress: specifier: ^1.6.4 - version: 1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(typescript@5.9.3) + version: 1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.12)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(typescript@5.9.3) vitepress-plugin-mermaid: specifier: ^2.0.17 - version: 2.0.17(mermaid@11.14.0)(vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(typescript@5.9.3)) + version: 2.0.17(mermaid@11.14.0)(vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.12)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(typescript@5.9.3)) yaml: specifier: ^2.3.3 version: 2.8.3 @@ -242,6 +242,9 @@ importers: '@typescript-eslint/parser': specifier: ^8.0.0 version: 8.58.0(eslint@8.57.1)(typescript@5.9.3) + '@vitest/coverage-v8': + specifier: ^4.1.5 + version: 4.1.5(vitest@4.1.5) app-builder-lib: specifier: 26.8.2 version: 26.8.2(patch_hash=2dfb3fcdfe573cca6c248cecf63ddea5c8fa0276859695fba6c9664d0ff285d6)(dmg-builder@26.8.2)(electron-builder-squirrel-windows@26.8.2) @@ -290,6 +293,9 @@ importers: matrix-web-i18n: specifier: 'catalog:' version: 3.6.0 + memfs: + specifier: ^4.57.2 + version: 4.57.2(tslib@2.8.1) mkdirp: specifier: ^3.0.0 version: 3.0.1 @@ -308,6 +314,15 @@ importers: typescript: specifier: 5.9.3 version: 5.9.3 + vite: + specifier: ^8.0.9 + version: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vitest: + specifier: ^4.1.5 + version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest-sonar-reporter: + specifier: ^3.0.0 + version: 3.0.0(vitest@4.1.5) apps/web: dependencies: @@ -933,7 +948,7 @@ importers: version: 7.7.1 '@vitest/coverage-v8': specifier: ^4.0.0 - version: 4.1.2(@vitest/browser@4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(vitest@4.1.2) + version: 4.1.2(@vitest/browser@4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(vitest@4.1.2) matrix-widget-api: specifier: ^1.17.0 version: 1.17.0 @@ -948,13 +963,13 @@ importers: version: 5.9.3 vite: specifier: ^7.3.2 - version: 7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-dts: specifier: ^4.5.0 - version: 4.5.4(@types/node@18.19.130)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@5.9.3)(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.5.4(@types/node@18.19.130)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@5.9.3)(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: ^4.0.0 - version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest-sonar-reporter: specifier: ^3.0.0 version: 3.0.0(vitest@4.1.2) @@ -1079,19 +1094,19 @@ importers: version: 10.3.5(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/addon-designs': specifier: ^11.0.1 - version: 11.1.3(@storybook/addon-docs@10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)))(@types/react@19.2.10)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) + version: 11.1.3(@storybook/addon-docs@10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)))(@types/react@19.2.10)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) '@storybook/addon-docs': specifier: ^10.0.7 - version: 10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) + version: 10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) '@storybook/addon-vitest': specifier: ^10.1.11 - version: 10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2) + version: 10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2) '@storybook/icons': specifier: ^2.0.0 version: 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@storybook/react-vite': specifier: ^10.0.7 - version: 10.3.5(esbuild@0.27.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) + version: 10.3.5(esbuild@0.27.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) '@stylistic/eslint-plugin': specifier: ^5.7.0 version: 5.9.0(eslint@8.57.1) @@ -1127,10 +1142,10 @@ importers: version: 9.2.0(@fontsource/inconsolata@5.2.8)(@fontsource/inter@5.2.8)(@types/react-dom@19.2.3(@types/react@19.2.10))(@types/react@19.2.10)(@vector-im/compound-design-tokens@10.1.0(@types/react@19.2.10)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@vitest/browser-playwright': specifier: ^4.0.17 - version: 4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + version: 4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) '@vitest/coverage-v8': specifier: ^4.0.17 - version: 4.1.2(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(vitest@4.1.2) + version: 4.1.2(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(vitest@4.1.2) eslint: specifier: '8' version: 8.57.1 @@ -1175,7 +1190,7 @@ importers: version: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) storybook-addon-vis: specifier: ^3.1.2 - version: 3.1.5(@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2))(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(babel-plugin-macros@3.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vitest@4.1.2) + version: 3.1.5(@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2))(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(babel-plugin-macros@3.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vitest@4.1.2) typedoc: specifier: ^0.28.16 version: 0.28.19(typescript@5.9.3) @@ -1190,16 +1205,16 @@ importers: version: 5.9.3 vite: specifier: ^8.0.0 - version: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-dts: specifier: ^4.5.4 - version: 4.5.4(@types/node@18.19.130)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.5.4(@types/node@18.19.130)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vite-plugin-node-polyfills: specifier: ^0.26.0 - version: 0.26.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 0.26.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: ^4.0.18 - version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) vitest-sonar-reporter: specifier: ^3.0.0 version: 3.0.0(vitest@4.1.2) @@ -2973,48 +2988,96 @@ packages: peerDependencies: tslib: '2' + '@jsonjoy.com/fs-core@4.57.2': + resolution: {integrity: sha512-SVjwklkpIV5wrynpYtuYnfYH1QF4/nDuLBX7VXdb+3miglcAgBVZb/5y0cOsehRV/9Vb+3UqhkMq3/NR3ztdkQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-fsa@4.56.10': resolution: {integrity: sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-fsa@4.57.2': + resolution: {integrity: sha512-fhO8+iR2I+OCw668ISDJdn1aArc9zx033sWejIyzQ8RBeXa9bDSaUeA3ix0poYOfrj1KdOzytmYNv2/uLDfV6g==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-node-builtins@4.56.10': resolution: {integrity: sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-node-builtins@4.57.2': + resolution: {integrity: sha512-xhiegylRmhw43Ki2HO1ZBL7DQ5ja/qpRsL29VtQ2xuUHiuDGbgf2uD4p9Qd8hJI5P6RCtGYD50IXHXVq/Ocjcg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-node-to-fsa@4.56.10': resolution: {integrity: sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-node-to-fsa@4.57.2': + resolution: {integrity: sha512-18LmWTSONhoAPW+IWRuf8w/+zRolPFGPeGwMxlAhhfY11EKzX+5XHDBPAw67dBF5dxDErHJbl40U+3IXSDRXSQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-node-utils@4.56.10': resolution: {integrity: sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-node-utils@4.57.2': + resolution: {integrity: sha512-rsPSJgekz43IlNbLyAM/Ab+ouYLWGp5DDBfYBNNEqDaSpsbXfthBn29Q4muFA9L0F+Z3mKo+CWlgSCXrf+mOyQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-node@4.56.10': resolution: {integrity: sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-node@4.57.2': + resolution: {integrity: sha512-nX2AdL6cOFwLdju9G4/nbRnYevmCJbh7N7hvR3gGm97Cs60uEjyd0rpR+YBS7cTg175zzl22pGKXR5USaQMvKg==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-print@4.56.10': resolution: {integrity: sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-print@4.57.2': + resolution: {integrity: sha512-wK9NSow48i4DbDl9F1CQE5TqnyZOJ04elU3WFG5aJ76p+YxO/ulyBBQvKsessPxdo381Bc2pcEoyPujMOhcRqQ==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/fs-snapshot@4.56.10': resolution: {integrity: sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==} engines: {node: '>=10.0'} peerDependencies: tslib: '2' + '@jsonjoy.com/fs-snapshot@4.57.2': + resolution: {integrity: sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==} + engines: {node: '>=10.0'} + peerDependencies: + tslib: '2' + '@jsonjoy.com/json-pack@1.21.0': resolution: {integrity: sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==} engines: {node: '>=10.0'} @@ -3173,6 +3236,12 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} @@ -3776,6 +3845,9 @@ packages: '@oxc-project/types@0.122.0': resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} cpu: [arm] @@ -4380,30 +4452,60 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': resolution: {integrity: sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.12': resolution: {integrity: sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': resolution: {integrity: sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': resolution: {integrity: sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4411,6 +4513,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': resolution: {integrity: sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4418,6 +4527,13 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4425,6 +4541,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': resolution: {integrity: sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4432,6 +4555,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': resolution: {integrity: sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4439,6 +4569,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': resolution: {integrity: sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4446,32 +4583,65 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': resolution: {integrity: sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12': resolution: {integrity: sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==} engines: {node: '>=14.0.0'} cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': resolution: {integrity: sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': resolution: {integrity: sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/pluginutils@1.0.0-rc.12': resolution: {integrity: sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==} + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + '@rollup/plugin-inject@5.0.5': resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} engines: {node: '>=14.0.0'} @@ -5851,12 +6021,24 @@ packages: '@vitest/browser': optional: true + '@vitest/coverage-v8@4.1.5': + resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} + peerDependencies: + '@vitest/browser': 4.1.5 + vitest: 4.1.5 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} '@vitest/expect@4.1.2': resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==} + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + '@vitest/mocker@4.1.2': resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==} peerDependencies: @@ -5868,30 +6050,56 @@ packages: vite: optional: true + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} '@vitest/pretty-format@4.1.2': resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==} + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + '@vitest/runner@4.1.2': resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==} + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + '@vitest/snapshot@4.1.2': resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==} + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} '@vitest/spy@4.1.2': resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==} + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} '@vitest/utils@4.1.2': resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -9900,6 +10108,11 @@ packages: peerDependencies: tslib: '2' + memfs@4.57.2: + resolution: {integrity: sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==} + peerDependencies: + tslib: '2' + memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} @@ -11063,6 +11276,10 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.5.12: + resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.8: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} @@ -11633,6 +11850,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-external-globals@0.13.0: resolution: {integrity: sha512-wBS3hmoF0OtEnA0lWsmTC6Nhnkk2zjZbfhaX2gLo8VnfNGFdGhiYKwMpIPQPrYbAw+mAYUYmoHYktAl1eZHgVw==} peerDependencies: @@ -12907,6 +13129,49 @@ packages: yaml: optional: true + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': 18.19.130 + '@vitejs/devtools': ^0.1.0 + esbuild: 0.27.4 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vite@8.0.5: resolution: {integrity: sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -13022,6 +13287,47 @@ packages: jsdom: optional: true + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': 18.19.130 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} @@ -15086,7 +15392,7 @@ snapshots: '@fetch-mock/vitest@0.2.18(vitest@4.1.2)': dependencies: fetch-mock: 12.6.0 - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@figspec/components@2.1.0': {} @@ -15428,11 +15734,11 @@ snapshots: '@types/yargs': 17.0.35 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: glob: 13.0.6 react-docgen-typescript: 2.4.0(typescript@5.9.3) - vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) optionalDependencies: typescript: 5.9.3 @@ -15493,6 +15799,13 @@ snapshots: thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-core@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-fsa@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) @@ -15501,10 +15814,22 @@ snapshots: thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-fsa@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-node-builtins@4.56.10(tslib@2.8.1)': dependencies: tslib: 2.8.1 + '@jsonjoy.com/fs-node-builtins@4.57.2(tslib@2.8.1)': + dependencies: + tslib: 2.8.1 + '@jsonjoy.com/fs-node-to-fsa@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-fsa': 4.56.10(tslib@2.8.1) @@ -15512,11 +15837,23 @@ snapshots: '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-node-to-fsa@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-fsa': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-node-utils@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-node-builtins': 4.56.10(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-node-utils@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-node@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-core': 4.56.10(tslib@2.8.1) @@ -15528,12 +15865,29 @@ snapshots: thingies: 2.5.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-node@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.57.2(tslib@2.8.1) + glob-to-regex.js: 1.2.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-print@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/fs-node-utils': 4.56.10(tslib@2.8.1) tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-print@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/fs-snapshot@4.56.10(tslib@2.8.1)': dependencies: '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) @@ -15542,6 +15896,14 @@ snapshots: '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) tslib: 2.8.1 + '@jsonjoy.com/fs-snapshot@4.57.2(tslib@2.8.1)': + dependencies: + '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/json-pack': 17.65.0(tslib@2.8.1) + '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) + tslib: 2.8.1 + '@jsonjoy.com/json-pack@1.21.0(tslib@2.8.1)': dependencies: '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) @@ -15767,6 +16129,13 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.1 + optional: true + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 @@ -16477,6 +16846,8 @@ snapshots: '@oxc-project/types@0.122.0': {} + '@oxc-project/types@0.127.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -17045,39 +17416,75 @@ snapshots: '@rolldown/binding-android-arm64@1.0.0-rc.12': optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.17': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.12': optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.12': optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.12': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.12': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.12': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.12(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) @@ -17086,14 +17493,29 @@ snapshots: - '@emnapi/runtime' optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.12': optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.12': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + optional: true + '@rolldown/pluginutils@1.0.0-rc.12': {} + '@rolldown/pluginutils@1.0.0-rc.17': {} + '@rollup/plugin-inject@5.0.5(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))': dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) @@ -17533,21 +17955,21 @@ snapshots: axe-core: 4.11.3 storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@storybook/addon-designs@11.1.3(@storybook/addon-docs@10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)))(@types/react@19.2.10)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': + '@storybook/addon-designs@11.1.3(@storybook/addon-docs@10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)))(@types/react@19.2.10)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))': dependencies: '@figspec/react': 2.0.1(@types/react@19.2.10)(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) optionalDependencies: - '@storybook/addon-docs': 10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) + '@storybook/addon-docs': 10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) react: 19.2.5 react-dom: 19.2.5(react@19.2.5) transitivePeerDependencies: - '@types/react' - '@storybook/addon-docs@10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4))': + '@storybook/addon-docs@10.3.5(@types/react@19.2.10)(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.2.10)(react@19.2.5) - '@storybook/csf-plugin': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) + '@storybook/csf-plugin': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) '@storybook/icons': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@storybook/react-dom-shim': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)) react: 19.2.5 @@ -17561,39 +17983,39 @@ snapshots: - vite - webpack - '@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2)': + '@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2)': dependencies: '@storybook/global': 5.0.0 '@storybook/icons': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) optionalDependencies: - '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) - '@vitest/browser-playwright': 4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) - '@vitest/runner': 4.1.2 - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/browser-playwright': 4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/runner': 4.1.5 + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - react - react-dom - '@storybook/builder-vite@10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4))': + '@storybook/builder-vite@10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4))': dependencies: - '@storybook/csf-plugin': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) + '@storybook/csf-plugin': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) ts-dedent: 2.2.0 - vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - esbuild - rollup - webpack - '@storybook/csf-plugin@10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4))': + '@storybook/csf-plugin@10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4))': dependencies: storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) unplugin: 2.3.11 optionalDependencies: esbuild: 0.27.4 rollup: 4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8) - vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) webpack: 5.105.4(esbuild@0.27.4) '@storybook/global@5.0.0': {} @@ -17609,11 +18031,11 @@ snapshots: react-dom: 19.2.5(react@19.2.5) storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@storybook/react-vite@10.3.5(esbuild@0.27.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4))': + '@storybook/react-vite@10.3.5(esbuild@0.27.4)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@rollup/pluginutils': 5.3.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) - '@storybook/builder-vite': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) + '@storybook/builder-vite': 10.3.5(esbuild@0.27.4)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(webpack@5.105.4(esbuild@0.27.4)) '@storybook/react': 10.3.5(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3) empathic: 2.0.0 magic-string: 0.30.21 @@ -17623,7 +18045,7 @@ snapshots: resolve: 1.22.12 storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) tsconfig-paths: 4.2.0 - vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - esbuild - rollup @@ -18578,18 +19000,18 @@ snapshots: dependencies: react: 19.2.5 - '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1))(vue@3.5.31(typescript@5.9.3))': + '@vitejs/plugin-vue@5.2.4(vite@5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1))(vue@3.5.31(typescript@5.9.3))': dependencies: - vite: 5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1) + vite: 5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1) vue: 3.5.31(typescript@5.9.3) - '@vitest/browser-playwright@4.1.2(playwright@1.59.1)(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2)': + '@vitest/browser-playwright@4.1.2(playwright@1.59.1)(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2)': dependencies: - '@vitest/browser': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) - '@vitest/mocker': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/browser': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/mocker': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) playwright: 1.59.1 tinyrainbow: 3.1.0 - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - bufferutil - msw @@ -18597,29 +19019,29 @@ snapshots: - vite optional: true - '@vitest/browser-playwright@4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2)': + '@vitest/browser-playwright@4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2)': dependencies: - '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) - '@vitest/mocker': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/mocker': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) playwright: 1.59.1 tinyrainbow: 3.1.0 - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2)': + '@vitest/browser@4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/utils': 4.1.2 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) ws: 8.20.0 transitivePeerDependencies: - bufferutil @@ -18628,16 +19050,16 @@ snapshots: - vite optional: true - '@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2)': + '@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/utils': 4.1.2 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) ws: 8.20.0 transitivePeerDependencies: - bufferutil @@ -18645,7 +19067,7 @@ snapshots: - utf-8-validate - vite - '@vitest/coverage-v8@4.1.2(@vitest/browser@4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(vitest@4.1.2)': + '@vitest/coverage-v8@4.1.2(@vitest/browser@4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(vitest@4.1.2)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.2 @@ -18657,11 +19079,11 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: - '@vitest/browser': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/browser': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) - '@vitest/coverage-v8@4.1.2(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(vitest@4.1.2)': + '@vitest/coverage-v8@4.1.2(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(vitest@4.1.2)': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.2 @@ -18673,9 +19095,23 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: - '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + + '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.5 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.0.0 + tinyrainbow: 3.1.0 + vitest: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/expect@3.2.4': dependencies: @@ -18694,21 +19130,38 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/expect@4.1.5': dependencies: - '@vitest/spy': 4.1.2 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + chai: 6.2.2 + tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + + '@vitest/mocker@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 4.1.2 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + + '@vitest/mocker@4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))': + dependencies: + '@vitest/spy': 4.1.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) '@vitest/pretty-format@3.2.4': dependencies: @@ -18718,11 +19171,20 @@ snapshots: dependencies: tinyrainbow: 3.1.0 + '@vitest/pretty-format@4.1.5': + dependencies: + tinyrainbow: 3.1.0 + '@vitest/runner@4.1.2': dependencies: '@vitest/utils': 4.1.2 pathe: 2.0.3 + '@vitest/runner@4.1.5': + dependencies: + '@vitest/utils': 4.1.5 + pathe: 2.0.3 + '@vitest/snapshot@4.1.2': dependencies: '@vitest/pretty-format': 4.1.2 @@ -18730,12 +19192,21 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/snapshot@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 '@vitest/spy@4.1.2': {} + '@vitest/spy@4.1.5': {} + '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -18748,6 +19219,12 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@vitest/utils@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + '@volar/language-core@2.4.28': dependencies: '@volar/source-map': 2.4.28 @@ -23581,6 +24058,23 @@ snapshots: tree-dump: 1.1.0(tslib@2.8.1) tslib: 2.8.1 + memfs@4.57.2(tslib@2.8.1): + dependencies: + '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-fsa': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-to-fsa': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-print': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/fs-snapshot': 4.57.2(tslib@2.8.1) + '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) + '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) + glob-to-regex.js: 1.2.0(tslib@2.8.1) + thingies: 2.5.0(tslib@2.8.1) + tree-dump: 1.1.0(tslib@2.8.1) + tslib: 2.8.1 + memoize-one@5.2.1: {} memoize-one@6.0.0: {} @@ -24934,6 +25428,12 @@ snapshots: postcss-value-parser@4.2.0: {} + postcss@8.5.12: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.8: dependencies: nanoid: 3.3.11 @@ -25569,6 +26069,27 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' + rolldown@1.0.0-rc.17: + dependencies: + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + rollup-plugin-external-globals@0.13.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)): dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) @@ -26081,19 +26602,19 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 - storybook-addon-vis@3.1.5(@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2))(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(babel-plugin-macros@3.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vitest@4.1.2): + storybook-addon-vis@3.1.5(@storybook/addon-vitest@10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2))(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(babel-plugin-macros@3.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(typescript@5.9.3)(vitest@4.1.2): dependencies: - '@storybook/addon-vitest': 10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.2)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2) + '@storybook/addon-vitest': 10.3.5(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(@vitest/runner@4.1.5)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(storybook@10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vitest@4.1.2) '@storybook/icons': 2.0.1(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) glob: 13.0.6 is-ci: 4.1.0 memoize: 11.0.0 pathe: 2.0.3 storybook: 10.3.5(@testing-library/dom@10.4.1)(prettier@3.8.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) type-plus: 8.0.0-beta.8(typescript@5.9.3) - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - vitest-plugin-vis: 4.2.2(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(babel-plugin-macros@3.1.0)(typescript@5.9.3)(vitest@4.1.2) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest-plugin-vis: 4.2.2(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(babel-plugin-macros@3.1.0)(typescript@5.9.3)(vitest@4.1.2) transitivePeerDependencies: - '@vitest/browser-playwright' - '@vitest/browser-webdriverio' @@ -26355,6 +26876,11 @@ snapshots: stylis@4.3.6: {} + sugarss@5.0.1(postcss@8.5.12): + dependencies: + postcss: 8.5.12 + optional: true + sugarss@5.0.1(postcss@8.5.8): dependencies: postcss: 8.5.8 @@ -27045,7 +27571,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plugin-dts@4.5.4(@types/node@18.19.130)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@5.9.3)(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-dts@4.5.4(@types/node@18.19.130)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@5.9.3)(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@microsoft/api-extractor': 7.56.0(@types/node@18.19.130) '@rollup/pluginutils': 5.3.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) @@ -27058,13 +27584,13 @@ snapshots: magic-string: 0.30.21 typescript: 5.9.3 optionalDependencies: - vite: 7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-dts@4.5.4(@types/node@18.19.130)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-dts@4.5.4(@types/node@18.19.130)(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(typescript@5.9.3)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@microsoft/api-extractor': 7.56.0(@types/node@18.19.130) '@rollup/pluginutils': 5.3.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) @@ -27077,21 +27603,21 @@ snapshots: magic-string: 0.30.21 typescript: 5.9.3 optionalDependencies: - vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-node-polyfills@0.26.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-node-polyfills@0.26.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) node-stdlib-browser: 1.3.1 - vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - rollup - vite@5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1): + vite@5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1): dependencies: esbuild: 0.27.4 postcss: 8.5.8 @@ -27100,10 +27626,10 @@ snapshots: '@types/node': 18.19.130 fsevents: 2.3.3 lightningcss: 1.32.0 - sugarss: 5.0.1(postcss@8.5.8) + sugarss: 5.0.1(postcss@8.5.12) terser: 5.46.1 - vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: esbuild: 0.27.4 fdir: 6.5.0(picomatch@4.0.4) @@ -27116,12 +27642,29 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 lightningcss: 1.32.0 - sugarss: 5.0.1(postcss@8.5.8) + sugarss: 5.0.1(postcss@8.5.12) terser: 5.46.1 tsx: 4.21.0 yaml: 2.8.3 - vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.12 + rolldown: 1.0.0-rc.17 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 18.19.130 + esbuild: 0.27.4 + fsevents: 2.3.3 + jiti: 2.6.1 + sugarss: 5.0.1(postcss@8.5.12) + terser: 5.46.1 + tsx: 4.21.0 + yaml: 2.8.3 + + vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -27133,7 +27676,7 @@ snapshots: esbuild: 0.27.4 fsevents: 2.3.3 jiti: 2.6.1 - sugarss: 5.0.1(postcss@8.5.8) + sugarss: 5.0.1(postcss@8.5.12) terser: 5.46.1 tsx: 4.21.0 yaml: 2.8.3 @@ -27141,14 +27684,14 @@ snapshots: - '@emnapi/core' - '@emnapi/runtime' - vitepress-plugin-mermaid@2.0.17(mermaid@11.14.0)(vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(typescript@5.9.3)): + vitepress-plugin-mermaid@2.0.17(mermaid@11.14.0)(vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.12)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(typescript@5.9.3)): dependencies: mermaid: 11.14.0 - vitepress: 1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(typescript@5.9.3) + vitepress: 1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.12)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(typescript@5.9.3) optionalDependencies: '@mermaid-js/mermaid-mindmap': 9.3.0 - vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.8)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(typescript@5.9.3): + vitepress@1.6.4(@algolia/client-search@5.50.0)(@types/node@18.19.130)(@types/react@19.2.10)(axios@1.15.0)(jwt-decode@4.0.0)(lightningcss@1.32.0)(postcss@8.5.12)(qrcode@1.5.4)(search-insights@2.17.3)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(typescript@5.9.3): dependencies: '@docsearch/css': 3.8.2 '@docsearch/js': 3.8.2(@algolia/client-search@5.50.0)(@types/react@19.2.10)(search-insights@2.17.3) @@ -27157,7 +27700,7 @@ snapshots: '@shikijs/transformers': 2.5.0 '@shikijs/types': 2.5.0 '@types/markdown-it': 14.1.2 - '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1))(vue@3.5.31(typescript@5.9.3)) + '@vitejs/plugin-vue': 5.2.4(vite@5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1))(vue@3.5.31(typescript@5.9.3)) '@vue/devtools-api': 7.7.9 '@vue/shared': 3.5.31 '@vueuse/core': 12.8.2(typescript@5.9.3) @@ -27166,10 +27709,10 @@ snapshots: mark.js: 8.11.1 minisearch: 7.2.0 shiki: 2.5.0 - vite: 5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1) + vite: 5.4.21(@types/node@18.19.130)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1) vue: 3.5.31(typescript@5.9.3) optionalDependencies: - postcss: 8.5.8 + postcss: 8.5.12 transitivePeerDependencies: - '@algolia/client-search' - '@types/node' @@ -27197,9 +27740,9 @@ snapshots: - typescript - universal-cookie - vitest-plugin-vis@4.2.2(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(babel-plugin-macros@3.1.0)(typescript@5.9.3)(vitest@4.1.2): + vitest-plugin-vis@4.2.2(@vitest/browser-playwright@4.1.2)(@vitest/browser@4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2))(babel-plugin-macros@3.1.0)(typescript@5.9.3)(vitest@4.1.2): dependencies: - '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/browser': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) dedent: 1.7.2(babel-plugin-macros@3.1.0) glob: 13.0.6 is-ci: 4.1.0 @@ -27210,21 +27753,25 @@ snapshots: rimraf: 6.1.3 ssim.js: 3.5.0 type-plus: 8.0.0-beta.8(typescript@5.9.3) - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) optionalDependencies: - '@vitest/browser-playwright': 4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/browser-playwright': 4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) transitivePeerDependencies: - babel-plugin-macros - typescript vitest-sonar-reporter@3.0.0(vitest@4.1.2): dependencies: - vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - vitest@4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest-sonar-reporter@3.0.0(vitest@4.1.5): + dependencies: + vitest: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + + vitest@4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.2 - '@vitest/mocker': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.2(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.2 '@vitest/runner': 4.1.2 '@vitest/snapshot': 4.1.2 @@ -27241,20 +27788,20 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 18.19.130 - '@vitest/browser-playwright': 4.1.2(playwright@1.59.1)(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/browser-playwright': 4.1.2(playwright@1.59.1)(vite@7.3.2(@types/node@18.19.130)(jiti@2.6.1)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f) transitivePeerDependencies: - msw - vitest@4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.2(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/browser-playwright@4.1.2)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: '@vitest/expect': 4.1.2 - '@vitest/mocker': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 4.1.2(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/pretty-format': 4.1.2 '@vitest/runner': 4.1.2 '@vitest/snapshot': 4.1.2 @@ -27271,12 +27818,42 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 18.19.130 - '@vitest/browser-playwright': 4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.8))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + '@vitest/browser-playwright': 4.1.2(playwright@1.59.1)(vite@8.0.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.2) + jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f) + transitivePeerDependencies: + - msw + + vitest@4.1.5(@opentelemetry/api@1.9.0)(@types/node@18.19.130)(@vitest/coverage-v8@4.1.5)(jsdom@26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f))(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.0.0 + tinybench: 2.9.0 + tinyexec: 1.0.4 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.12))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@types/node': 18.19.130 + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) jsdom: 26.1.0(patch_hash=040623e87b1c8b676c2a705513c0276c0704dd1b23fc3a1bb77cde8128b64b5f) transitivePeerDependencies: - msw diff --git a/sonar-project.properties b/sonar-project.properties index 7f08ea24b2..77f937153a 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -5,8 +5,14 @@ sonar.organization=element-hq #sonar.sourceEncoding=UTF-8 sonar.sources=. -sonar.tests=apps/web/test,apps/web/playwright,apps/desktop/playwright,packages -sonar.test.inclusions=apps/web/test/*,apps/web/playwright/*,apps/desktop/playwright/*,packages/*/src/**/*.test.*,packages/*/src/test/**/* +sonar.tests=apps/web/test,apps/web/playwright,apps/desktop,packages +sonar.test.inclusions=\ + apps/web/test/*,\ + apps/web/playwright/*,\ + apps/desktop/playwright/*,\ + apps/desktop/src/**/*.test.ts,\ + packages/*/src/**/*.test.*,\ + packages/*/src/test/**/* sonar.exclusions=\ apps/web/__mocks__,\ docs,\ @@ -32,16 +38,23 @@ sonar.coverage.exclusions=\ apps/web/playwright/**/*,\ apps/web/res/**/*,\ apps/web/scripts/**/*,\ - apps/desktop/scripts/**/*,\ - apps/desktop/playwright/**/*,\ - apps/desktop/hak/**/*,\ - scripts/**/*,\ + apps/web/__mocks__/**/*,\ + apps/web/I18nWebpackPlugin.ts,\ + apps/web/recorder-worklet-loader.cjs,\ apps/web/src/components/views/dialogs/devtools/**/*,\ apps/web/src/utils/SessionLock.ts,\ apps/web/src/**/*.d.ts,\ apps/web/src/vector/mobile_guide/**/*,\ + apps/desktop/electron-builder.ts,\ + apps/desktop/scripts/**/*,\ + apps/desktop/playwright/**/*,\ + apps/desktop/hak/**/*,\ packages/shared-components/src/test/**/*,\ packages/shared-components/src/**/*.stories.tsx,\ + packages/shared-components/scripts/**/*,\ packages/playwright-common/**/*,\ - **/*.config.ts + scripts/**/*,\ + docs/**/*,\ + **/*.config.*,\ + knip.ts sonar.testExecutionReportPaths=apps/web/coverage/jest-sonar-report.xml From 0f9b5f4ab919551396e59342fbb8c13494876fcc Mon Sep 17 00:00:00 2001 From: Clemens <68013019+ClFeSc@users.noreply.github.com> Date: Thu, 30 Apr 2026 10:29:36 +0200 Subject: [PATCH 16/23] fix: `pnpm lint` does not work (#33327) Since sometime after v1.12.15, the first subcommand of `pnpm lint` (`pnpm -r lint:types`) adds files to the tree that do not conform to the prettier requirements tested in the second step of `pnpm lint` (`pnpm lint:prettier`). This change adds the affected paths to the `.prettierignore` file to make the lint pass. --- .prettierignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.prettierignore b/.prettierignore index e0a9e4fc57..0a8f1a2baf 100644 --- a/.prettierignore +++ b/.prettierignore @@ -62,4 +62,10 @@ CHANGELOG.md /packages/shared-components/typedoc/ /packages/shared-components/storybook-static/ +# These files are generated by running `pnpm -r lint:types` and do not adhere to prettier's requirements. +# All of them are .gitignored within their parent directory. +/packages/playwright-common/lib/ +/packages/module-api/lib/ +/packages/module-api/temp/ + /.nx/ From febecb8e568f3b7a30ebc836ff4413ce4ce8178b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:45:21 +0000 Subject: [PATCH 17/23] Update testcontainers docker digests (#33340) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/web/playwright/testcontainers/mas.ts | 2 +- apps/web/playwright/testcontainers/synapse.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/playwright/testcontainers/mas.ts b/apps/web/playwright/testcontainers/mas.ts index 46da585e97..311922acf2 100644 --- a/apps/web/playwright/testcontainers/mas.ts +++ b/apps/web/playwright/testcontainers/mas.ts @@ -11,7 +11,7 @@ import { } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js"; const DOCKER_IMAGE = - "ghcr.io/element-hq/matrix-authentication-service:main@sha256:b56910ddd9d6cbbe9e896b6e5bcfcded3367dba779f979166e178878b02e3a07"; + "ghcr.io/element-hq/matrix-authentication-service:main@sha256:c765fb602f78e77eccaa8e020e56c39eef99eccbabc9cb0df2c5705f60ca899e"; /** * MatrixAuthenticationServiceContainer which freezes the docker digest to diff --git a/apps/web/playwright/testcontainers/synapse.ts b/apps/web/playwright/testcontainers/synapse.ts index 8e8cbd920e..fbd78fc22c 100644 --- a/apps/web/playwright/testcontainers/synapse.ts +++ b/apps/web/playwright/testcontainers/synapse.ts @@ -8,7 +8,7 @@ Please see LICENSE files in the repository root for full details. import { SynapseContainer as BaseSynapseContainer } from "@element-hq/element-web-playwright-common/lib/testcontainers/index.js"; const DOCKER_IMAGE = - "ghcr.io/element-hq/synapse:develop@sha256:539f3cbe5e1feba357c3394b6db0c7f90ddd5eafc8bdb282e23cc1111aa54eab"; + "ghcr.io/element-hq/synapse:develop@sha256:53b1c81dc161be1d999344ca727a520972b912fc24681dfafe25fca4b766b2a5"; /** * SynapseContainer which freezes the docker digest to stabilise tests, From cc00c4c5a8353c35a06d3a8dd86ce4925261acc9 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Thu, 30 Apr 2026 09:55:38 +0000 Subject: [PATCH 18/23] v1.12.17 --- CHANGELOG.md | 7 +++++++ apps/desktop/package.json | 2 +- apps/web/package.json | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de6bb19956..8c53af59eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +Changes in [1.12.17](https://github.com/element-hq/element-web/releases/tag/v1.12.17) (2026-04-30) +================================================================================================== +## 🐛 Bug Fixes + +* [Backport] Fix OIDC login callback handling on Element Desktop ([#33337](https://github.com/element-hq/element-web/pull/33337)). Contributed by @t3chguy. + + Changes in [1.12.16](https://github.com/element-hq/element-web/releases/tag/v1.12.16) (2026-04-28) ================================================================================================== ## 🦖 Deprecations diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 6717fda312..b9c2c021c1 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -3,7 +3,7 @@ "productName": "Element", "main": "lib/electron-main.js", "exports": "./lib/electron-main.js", - "version": "1.12.16", + "version": "1.12.17", "description": "Element: the future of secure communication", "author": { "name": "Element", diff --git a/apps/web/package.json b/apps/web/package.json index a87a5e7fa8..357dc9e0ac 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "element-web", - "version": "1.12.16", + "version": "1.12.17", "description": "Element: the future of secure communication", "author": "New Vector Ltd.", "repository": { From f2d355bb2caf0df5097636d90d6397b52cb5747b Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 30 Apr 2026 11:03:39 +0100 Subject: [PATCH 19/23] Fix lockfile --- pnpm-lock.yaml | 170 ------------------------------------------------- 1 file changed, 170 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f7dc260363..92878b13f0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3072,12 +3072,6 @@ packages: peerDependencies: tslib: '2' - '@jsonjoy.com/fs-snapshot@4.57.2': - resolution: {integrity: sha512-GdduDZuoP5V/QCgJkx9+BZ6SC0EZ/smXAdTS7PfMqgMTGXLlt/bH/FqMYaqB9JmLf05sJPtO0XRbAwwkEEPbVw==} - engines: {node: '>=10.0'} - peerDependencies: - tslib: '2' - '@jsonjoy.com/json-pack@1.21.0': resolution: {integrity: sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==} engines: {node: '>=10.0'} @@ -3236,12 +3230,6 @@ packages: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} - peerDependencies: - '@emnapi/core': ^1.7.1 - '@emnapi/runtime': ^1.7.1 - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} @@ -3900,9 +3888,6 @@ packages: '@oxc-project/types@0.127.0': resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} - '@oxc-project/types@0.127.0': - resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} - '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} cpu: [arm] @@ -4597,9 +4582,6 @@ packages: '@rolldown/pluginutils@1.0.0-rc.17': resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} - '@rolldown/pluginutils@1.0.0-rc.17': - resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} - '@rollup/plugin-inject@5.0.5': resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==} engines: {node: '>=14.0.0'} @@ -5979,15 +5961,6 @@ packages: '@vitest/browser': optional: true - '@vitest/coverage-v8@4.1.5': - resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} - peerDependencies: - '@vitest/browser': 4.1.5 - vitest: 4.1.5 - peerDependenciesMeta: - '@vitest/browser': - optional: true - '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -6005,17 +5978,6 @@ packages: vite: optional: true - '@vitest/mocker@4.1.5': - resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} - peerDependencies: - msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - '@vitest/pretty-format@3.2.4': resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} @@ -6028,27 +5990,18 @@ packages: '@vitest/snapshot@4.1.5': resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} - '@vitest/snapshot@4.1.5': - resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} - '@vitest/spy@3.2.4': resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} '@vitest/spy@4.1.5': resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} - '@vitest/spy@4.1.5': - resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} - '@vitest/utils@3.2.4': resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} '@vitest/utils@4.1.5': resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} - '@vitest/utils@4.1.5': - resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} - '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} @@ -10070,11 +10023,6 @@ packages: peerDependencies: tslib: '2' - memfs@4.57.2: - resolution: {integrity: sha512-2nWzSsJzrukurSDna4Z0WywuScK4Id3tSKejgu74u8KCdW4uNrseKRSIDg75C6Yw5ZRqBe0F0EtMNlTbUq8bAQ==} - peerDependencies: - tslib: '2' - memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} @@ -11818,11 +11766,6 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rolldown@1.0.0-rc.17: - resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - rollup-plugin-external-globals@0.13.0: resolution: {integrity: sha512-wBS3hmoF0OtEnA0lWsmTC6Nhnkk2zjZbfhaX2gLo8VnfNGFdGhiYKwMpIPQPrYbAw+mAYUYmoHYktAl1eZHgVw==} peerDependencies: @@ -13194,47 +13137,6 @@ packages: jsdom: optional: true - vitest@4.1.5: - resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} - engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@opentelemetry/api': ^1.9.0 - '@types/node': 18.19.130 - '@vitest/browser-playwright': 4.1.5 - '@vitest/browser-preview': 4.1.5 - '@vitest/browser-webdriverio': 4.1.5 - '@vitest/coverage-istanbul': 4.1.5 - '@vitest/coverage-v8': 4.1.5 - '@vitest/ui': 4.1.5 - happy-dom: '*' - jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@opentelemetry/api': - optional: true - '@types/node': - optional: true - '@vitest/browser-playwright': - optional: true - '@vitest/browser-preview': - optional: true - '@vitest/browser-webdriverio': - optional: true - '@vitest/coverage-istanbul': - optional: true - '@vitest/coverage-v8': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} @@ -15765,14 +15667,6 @@ snapshots: '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) tslib: 2.8.1 - '@jsonjoy.com/fs-snapshot@4.57.2(tslib@2.8.1)': - dependencies: - '@jsonjoy.com/buffers': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) - '@jsonjoy.com/json-pack': 17.65.0(tslib@2.8.1) - '@jsonjoy.com/util': 17.65.0(tslib@2.8.1) - tslib: 2.8.1 - '@jsonjoy.com/json-pack@1.21.0(tslib@2.8.1)': dependencies: '@jsonjoy.com/base64': 1.1.2(tslib@2.8.1) @@ -15997,13 +15891,6 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': - dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.1 - optional: true - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 @@ -16727,8 +16614,6 @@ snapshots: '@oxc-project/types@0.127.0': {} - '@oxc-project/types@0.127.0': {} - '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -17341,8 +17226,6 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.17': {} - '@rolldown/pluginutils@1.0.0-rc.17': {} - '@rollup/plugin-inject@5.0.5(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8))': dependencies: '@rollup/pluginutils': 5.3.0(rollup@4.60.1(patch_hash=603340e49399c6044e41a3998891667387d5ec1acbd38d4e5862f2ba3ef58de8)) @@ -18920,21 +18803,12 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/snapshot@4.1.5': - dependencies: - '@vitest/pretty-format': 4.1.5 - '@vitest/utils': 4.1.5 - magic-string: 0.30.21 - pathe: 2.0.3 - '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.4 '@vitest/spy@4.1.5': {} - '@vitest/spy@4.1.5': {} - '@vitest/utils@3.2.4': dependencies: '@vitest/pretty-format': 3.2.4 @@ -18947,12 +18821,6 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@vitest/utils@4.1.5': - dependencies: - '@vitest/pretty-format': 4.1.5 - convert-source-map: 2.0.0 - tinyrainbow: 3.1.0 - '@volar/language-core@2.4.28': dependencies: '@volar/source-map': 2.4.28 @@ -23773,23 +23641,6 @@ snapshots: media-typer@1.1.0: {} - memfs@4.57.2(tslib@2.8.1): - dependencies: - '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) - '@jsonjoy.com/fs-fsa': 4.57.2(tslib@2.8.1) - '@jsonjoy.com/fs-node': 4.57.2(tslib@2.8.1) - '@jsonjoy.com/fs-node-builtins': 4.57.2(tslib@2.8.1) - '@jsonjoy.com/fs-node-to-fsa': 4.57.2(tslib@2.8.1) - '@jsonjoy.com/fs-node-utils': 4.57.2(tslib@2.8.1) - '@jsonjoy.com/fs-print': 4.57.2(tslib@2.8.1) - '@jsonjoy.com/fs-snapshot': 4.57.2(tslib@2.8.1) - '@jsonjoy.com/json-pack': 1.21.0(tslib@2.8.1) - '@jsonjoy.com/util': 1.9.0(tslib@2.8.1) - glob-to-regex.js: 1.2.0(tslib@2.8.1) - thingies: 2.5.0(tslib@2.8.1) - tree-dump: 1.1.0(tslib@2.8.1) - tslib: 2.8.1 - memfs@4.57.2(tslib@2.8.1): dependencies: '@jsonjoy.com/fs-core': 4.57.2(tslib@2.8.1) @@ -25895,27 +25746,6 @@ snapshots: robust-predicates@3.0.3: {} - rolldown@1.0.0-rc.17: - dependencies: - '@oxc-project/types': 0.127.0 - '@rolldown/pluginutils': 1.0.0-rc.17 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 - '@rolldown/binding-darwin-x64': 1.0.0-rc.17 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 - rolldown@1.0.0-rc.17: dependencies: '@oxc-project/types': 0.127.0 From b0ee6f5323400897dde29b9a715f16f59b91d731 Mon Sep 17 00:00:00 2001 From: rbondesson Date: Thu, 30 Apr 2026 13:54:49 +0200 Subject: [PATCH 20/23] Add CSS cascade layers for Compound, shared components, and app/web styles (#33302) * Layer Compound and shared component CSS * Layer app theme CSS * Remove !important flags from ActionBarView * Remove unnecessary !important statements from shared components * Avoid dead code errors for *.pcss just because layer is specified after @import url * Remove unnecessary !important styling * Override Banner defaults in RoomStatusBarView * Updated snaps * Updated snaps * Fix styling of media body in app/web * Fix styling for Compound anchors * Fix styling issues in app/web * More styling fixes * Fix a problem extracting css for HTMLExport * Revert changes * Fix for theme styling * Add test to improve coverage * Prettier * Fix styling issues * Add data-kind attribute to avoid global styling override * Update screenshot that now is correct * Revert data-kind attribute * Handle LinkPreview styling in .pcss * Fix flaky test: Avoid racing the lazy-loaded ManageEventIndexDialog * Take care of review comments * Updated snaps * Updated snaps again after merge * Remove !important from RoomStatusBar --- .../window-custom-theme-linux.png | Bin 57186 -> 57394 bytes apps/web/res/css/_common.pcss | 7 +- apps/web/res/css/_compound.pcss | 7 +- .../audio_messages/_PlaybackContainer.pcss | 5 +- .../css/views/auth/_CompleteSecurityBody.pcss | 2 +- .../res/css/views/messages/_MFileBody.pcss | 1 - .../res/css/views/messages/_MediaBody.pcss | 6 +- .../res/css/views/messages/_TextualEvent.pcss | 9 ++ .../css/views/settings/_ThemeChoicePanel.pcss | 12 ++- .../views/settings/tabs/_SettingsSection.pcss | 3 +- .../css/views/settings/tabs/_SettingsTab.pcss | 3 +- .../themes/dark-custom/css/dark-custom.pcss | 18 ++-- apps/web/res/themes/dark/css/dark.pcss | 18 ++-- .../themes/legacy-dark/css/legacy-dark.pcss | 16 ++-- .../themes/legacy-light/css/legacy-light.pcss | 14 +-- .../res/themes/light-custom/css/_custom.pcss | 13 +-- .../themes/light-custom/css/light-custom.pcss | 16 ++-- .../css/light-high-contrast.pcss | 18 ++-- apps/web/res/themes/light/css/light.pcss | 16 ++-- .../views/messages/TextualBodyFactory.tsx | 2 +- apps/web/src/theme.ts | 7 +- apps/web/src/utils/exportUtils/exportCSS.ts | 28 ++++-- .../src/utils/exportUtils/exportCustomCSS.css | 5 - .../__snapshots__/theme-test.ts.snap | 2 +- .../__snapshots__/RoomView-test.tsx.snap | 4 +- .../MessageEditHistoryDialog-test.tsx.snap | 16 ++-- .../__snapshots__/MBodyFactory-test.tsx.snap | 6 +- .../__snapshots__/MImageBody-test.tsx.snap | 4 +- .../RoomListSearch-test.tsx.snap | 8 +- .../__snapshots__/HTMLExport-test.ts.snap | 2 +- .../utils/exportUtils/exportCSS-test.ts | 90 ++++++++++++++++++ knip.ts | 9 +- .../shared-components/.storybook/compound.css | 7 +- packages/shared-components/.storybook/main.ts | 23 ++++- .../PlayPauseButton.module.css | 4 +- .../menu/OptionMenuView.module.css | 2 +- .../RoomListSearchView.module.css | 5 +- .../RoomStatusBarView.module.css | 16 ++-- .../DateSeparatorContextMenuView.module.css | 10 +- .../DateSeparatorDatePickerView.module.css | 12 +-- .../UrlPreviewGroupView.tsx | 11 ++- .../ActionBarView/ActionBarView.module.css | 22 ++--- .../AudioPlayerView.module.css | 4 +- .../MFileBodyView/FileBodyView.module.css | 5 +- .../MessageTimestampView.module.css | 2 +- packages/shared-components/vite.config.ts | 27 +++++- 46 files changed, 351 insertions(+), 166 deletions(-) diff --git a/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png b/apps/web/playwright/snapshots/settings/appearance-user-settings-tab/theme-choice-panel.spec.ts/window-custom-theme-linux.png index ad8bd39b6d30174411defdef0d9a7fcf312ab596..dc175017bb40c3e5a09199d02b925458f730e3be 100644 GIT binary patch literal 57394 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 literal 57186 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 `6f{)z4T)T)ZYrjq_Zvw)LF-OnJD;JXlB8Fs-(6 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 TB .mx_MediaBody { + border-radius: var(--MBody-border-radius); } diff --git a/apps/web/res/css/views/messages/_TextualEvent.pcss b/apps/web/res/css/views/messages/_TextualEvent.pcss index bf7061f2ba..3dbde24791 100644 --- a/apps/web/res/css/views/messages/_TextualEvent.pcss +++ b/apps/web/res/css/views/messages/_TextualEvent.pcss @@ -19,3 +19,12 @@ Please see LICENSE files in the repository root for full details. opacity: unset; /* Unset the opacity value specified above on the search results panel */ } } + +.mx_TextualBody_urlPreviews { + /* Let shared-components own preview link colours instead of the app-wide anchor colour. */ + a:where(:not([data-kind])):hover, + a:where(:not([data-kind])):link, + a:where(:not([data-kind])):visited { + color: revert-layer; + } +} diff --git a/apps/web/res/css/views/settings/_ThemeChoicePanel.pcss b/apps/web/res/css/views/settings/_ThemeChoicePanel.pcss index e61671f4f5..d5e9f5c619 100644 --- a/apps/web/res/css/views/settings/_ThemeChoicePanel.pcss +++ b/apps/web/res/css/views/settings/_ThemeChoicePanel.pcss @@ -26,12 +26,22 @@ Please see LICENSE files in the repository root for full details. &.mx_ThemeChoicePanel_themeSelector_disabled { border-color: var(--cpd-color-border-disabled); + + .mx_ThemeChoicePanel_themeSelector_Label { + color: var(--cpd-color-text-disabled); + cursor: not-allowed; + } } .mx_ThemeChoicePanel_themeSelector_Label { - color: var(--cpd-color-text-primary); font: var(--cpd-font-body-md-semibold); } + + &:not(.mx_ThemeChoicePanel_themeSelector_disabled) { + .mx_ThemeChoicePanel_themeSelector_Label { + color: var(--cpd-color-text-primary); + } + } } } diff --git a/apps/web/res/css/views/settings/tabs/_SettingsSection.pcss b/apps/web/res/css/views/settings/tabs/_SettingsSection.pcss index c98732e884..0a70169248 100644 --- a/apps/web/res/css/views/settings/tabs/_SettingsSection.pcss +++ b/apps/web/res/css/views/settings/tabs/_SettingsSection.pcss @@ -12,7 +12,8 @@ Please see LICENSE files in the repository root for full details. color: $primary-content; - a { + /* Compound links carry data-kind and provide their own colour. */ + a:not([data-kind]) { color: $links; } diff --git a/apps/web/res/css/views/settings/tabs/_SettingsTab.pcss b/apps/web/res/css/views/settings/tabs/_SettingsTab.pcss index c92e454e05..9e43079364 100644 --- a/apps/web/res/css/views/settings/tabs/_SettingsTab.pcss +++ b/apps/web/res/css/views/settings/tabs/_SettingsTab.pcss @@ -10,7 +10,8 @@ Please see LICENSE files in the repository root for full details. color: $primary-content; - a { + /* Compound links carry data-kind and provide their own colour. */ + a:not([data-kind]) { color: $links; } diff --git a/apps/web/res/themes/dark-custom/css/dark-custom.pcss b/apps/web/res/themes/dark-custom/css/dark-custom.pcss index 7eaffcee0c..1b1b30f33d 100644 --- a/apps/web/res/themes/dark-custom/css/dark-custom.pcss +++ b/apps/web/res/themes/dark-custom/css/dark-custom.pcss @@ -1,9 +1,11 @@ -@import "../../../../res/css/_font-sizes.pcss"; -@import "../../legacy-light/css/_fonts.pcss"; -@import "../../legacy-light/css/_legacy-light.pcss"; -@import "../../legacy-dark/css/_legacy-dark.pcss"; -@import "../../light-custom/css/_custom.pcss"; -@import "../../../../res/css/_components.pcss"; +@layer compound-tokens, compound-web, shared-components, app-web; + +@import "../../../../res/css/_font-sizes.pcss" layer(app-web); +@import "../../legacy-light/css/_fonts.pcss" layer(app-web); +@import "../../legacy-light/css/_legacy-light.pcss" layer(app-web); +@import "../../legacy-dark/css/_legacy-dark.pcss" layer(app-web); +@import "../../light-custom/css/_custom.pcss" layer(app-web); +@import "../../../../res/css/_components.pcss" layer(app-web); @import "../../../../res/css/_compound.pcss"; -@import url("highlight.js/styles/atom-one-light.min.css"); -@import url("github-markdown-css/github-markdown-dark.css"); +@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web); +@import url("github-markdown-css/github-markdown-dark.css") layer(app-web); diff --git a/apps/web/res/themes/dark/css/dark.pcss b/apps/web/res/themes/dark/css/dark.pcss index 05cc9dfb6a..abb4fc04ee 100644 --- a/apps/web/res/themes/dark/css/dark.pcss +++ b/apps/web/res/themes/dark/css/dark.pcss @@ -1,9 +1,11 @@ -@import "../../../../res/css/_font-sizes.pcss"; -@import "../../light/css/_fonts.pcss"; -@import "../../light/css/_light.pcss"; -@import "_dark.pcss"; -@import "../../light/css/_mods.pcss"; -@import "../../../../res/css/_components.pcss"; +@layer compound-tokens, compound-web, shared-components, app-web; + +@import "../../../../res/css/_font-sizes.pcss" layer(app-web); +@import "../../light/css/_fonts.pcss" layer(app-web); +@import "../../light/css/_light.pcss" layer(app-web); +@import "_dark.pcss" layer(app-web); +@import "../../light/css/_mods.pcss" layer(app-web); +@import "../../../../res/css/_components.pcss" layer(app-web); @import "../../../../res/css/_compound.pcss"; -@import url("highlight.js/styles/atom-one-dark.min.css"); -@import url("github-markdown-css/github-markdown-dark.css"); +@import url("highlight.js/styles/atom-one-dark.min.css") layer(app-web); +@import url("github-markdown-css/github-markdown-dark.css") layer(app-web); diff --git a/apps/web/res/themes/legacy-dark/css/legacy-dark.pcss b/apps/web/res/themes/legacy-dark/css/legacy-dark.pcss index d816c8c24d..99dadd6eab 100644 --- a/apps/web/res/themes/legacy-dark/css/legacy-dark.pcss +++ b/apps/web/res/themes/legacy-dark/css/legacy-dark.pcss @@ -1,8 +1,10 @@ -@import "../../../../res/css/_font-sizes.pcss"; -@import "../../legacy-light/css/_fonts.pcss"; -@import "../../legacy-light/css/_legacy-light.pcss"; -@import "_legacy-dark.pcss"; -@import "../../../../res/css/_components.pcss"; +@layer compound-tokens, compound-web, shared-components, app-web; + +@import "../../../../res/css/_font-sizes.pcss" layer(app-web); +@import "../../legacy-light/css/_fonts.pcss" layer(app-web); +@import "../../legacy-light/css/_legacy-light.pcss" layer(app-web); +@import "_legacy-dark.pcss" layer(app-web); +@import "../../../../res/css/_components.pcss" layer(app-web); @import "../../../../res/css/_compound.pcss"; -@import url("highlight.js/styles/atom-one-dark.min.css"); -@import url("github-markdown-css/github-markdown-dark.css"); +@import url("highlight.js/styles/atom-one-dark.min.css") layer(app-web); +@import url("github-markdown-css/github-markdown-dark.css") layer(app-web); diff --git a/apps/web/res/themes/legacy-light/css/legacy-light.pcss b/apps/web/res/themes/legacy-light/css/legacy-light.pcss index d196b2850c..dcc7b1a82a 100644 --- a/apps/web/res/themes/legacy-light/css/legacy-light.pcss +++ b/apps/web/res/themes/legacy-light/css/legacy-light.pcss @@ -1,7 +1,9 @@ -@import "../../../../res/css/_font-sizes.pcss"; -@import "_fonts.pcss"; -@import "_legacy-light.pcss"; -@import "../../../../res/css/_components.pcss"; +@layer compound-tokens, compound-web, shared-components, app-web; + +@import "../../../../res/css/_font-sizes.pcss" layer(app-web); +@import "_fonts.pcss" layer(app-web); +@import "_legacy-light.pcss" layer(app-web); +@import "../../../../res/css/_components.pcss" layer(app-web); @import "../../../../res/css/_compound.pcss"; -@import url("highlight.js/styles/atom-one-light.min.css"); -@import url("github-markdown-css/github-markdown-light.css"); +@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web); +@import url("github-markdown-css/github-markdown-light.css") layer(app-web); diff --git a/apps/web/res/themes/light-custom/css/_custom.pcss b/apps/web/res/themes/light-custom/css/_custom.pcss index 5f0210ed10..1bf761a7e7 100644 --- a/apps/web/res/themes/light-custom/css/_custom.pcss +++ b/apps/web/res/themes/light-custom/css/_custom.pcss @@ -25,16 +25,17 @@ $panels: var(--panels, var(--cpd-color-gray-600)); $panel-actions: var(--panels-actions, var(--cpd-color-gray-300)); /* --timeline-background-color */ -$button-secondary-bg-color: var(--timeline-background-color); -$lightbox-border-color: var(--timeline-background-color); -$menu-bg-color: var(--timeline-background-color); -$message-action-bar-bg-color: var(--timeline-background-color); -$background: var(--timeline-background-color); +$custom-theme-background: var(--timeline-background-color, var(--cpd-color-bg-canvas-default)); +$button-secondary-bg-color: $custom-theme-background; +$lightbox-border-color: $custom-theme-background; +$menu-bg-color: $custom-theme-background; +$message-action-bar-bg-color: $custom-theme-background; +$background: $custom-theme-background; $togglesw-ball-color: var(--cpd-color-bg-action-primary-rest); $togglesw-off-color: var(--togglesw-off-color); $droptarget-bg-color: var(--timeline-background-color-50pct); /* still needs alpha at .5 */ $authpage-modal-bg-color: var(--timeline-background-color-50pct); /* still needs alpha at .59 */ -$roomheader-bg-color: var(--timeline-background-color); +$roomheader-bg-color: $custom-theme-background; /* --roomlist-highlights-color */ $panel-actions: var(--roomlist-highlights-color); diff --git a/apps/web/res/themes/light-custom/css/light-custom.pcss b/apps/web/res/themes/light-custom/css/light-custom.pcss index d02c0eeef4..ce575e0518 100644 --- a/apps/web/res/themes/light-custom/css/light-custom.pcss +++ b/apps/web/res/themes/light-custom/css/light-custom.pcss @@ -1,8 +1,10 @@ -@import "../../../../res/css/_font-sizes.pcss"; -@import "../../legacy-light/css/_fonts.pcss"; -@import "../../legacy-light/css/_legacy-light.pcss"; -@import "_custom.pcss"; -@import "../../../../res/css/_components.pcss"; +@layer compound-tokens, compound-web, shared-components, app-web; + +@import "../../../../res/css/_font-sizes.pcss" layer(app-web); +@import "../../legacy-light/css/_fonts.pcss" layer(app-web); +@import "../../legacy-light/css/_legacy-light.pcss" layer(app-web); +@import "_custom.pcss" layer(app-web); +@import "../../../../res/css/_components.pcss" layer(app-web); @import "../../../../res/css/_compound.pcss"; -@import url("highlight.js/styles/atom-one-light.min.css"); -@import url("github-markdown-css/github-markdown-light.css"); +@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web); +@import url("github-markdown-css/github-markdown-light.css") layer(app-web); diff --git a/apps/web/res/themes/light-high-contrast/css/light-high-contrast.pcss b/apps/web/res/themes/light-high-contrast/css/light-high-contrast.pcss index c794d07499..b1c35e414e 100644 --- a/apps/web/res/themes/light-high-contrast/css/light-high-contrast.pcss +++ b/apps/web/res/themes/light-high-contrast/css/light-high-contrast.pcss @@ -1,9 +1,11 @@ -@import "../../../../res/css/_font-sizes.pcss"; -@import "../../light/css/_fonts.pcss"; -@import "../../light/css/_light.pcss"; -@import "_light-high-contrast.pcss"; -@import "../../light/css/_mods.pcss"; -@import "../../../../res/css/_components.pcss"; +@layer compound-tokens, compound-web, shared-components, app-web; + +@import "../../../../res/css/_font-sizes.pcss" layer(app-web); +@import "../../light/css/_fonts.pcss" layer(app-web); +@import "../../light/css/_light.pcss" layer(app-web); +@import "_light-high-contrast.pcss" layer(app-web); +@import "../../light/css/_mods.pcss" layer(app-web); +@import "../../../../res/css/_components.pcss" layer(app-web); @import "../../../../res/css/_compound.pcss"; -@import url("highlight.js/styles/atom-one-light.min.css"); -@import url("github-markdown-css/github-markdown-light.css"); +@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web); +@import url("github-markdown-css/github-markdown-light.css") layer(app-web); diff --git a/apps/web/res/themes/light/css/light.pcss b/apps/web/res/themes/light/css/light.pcss index d3abfe669c..10e5e05d78 100644 --- a/apps/web/res/themes/light/css/light.pcss +++ b/apps/web/res/themes/light/css/light.pcss @@ -1,8 +1,10 @@ -@import "../../../../res/css/_font-sizes.pcss"; -@import "_fonts.pcss"; -@import "_light.pcss"; -@import "_mods.pcss"; -@import "../../../../res/css/_components.pcss"; +@layer compound-tokens, compound-web, shared-components, app-web; + +@import "../../../../res/css/_font-sizes.pcss" layer(app-web); +@import "_fonts.pcss" layer(app-web); +@import "_light.pcss" layer(app-web); +@import "_mods.pcss" layer(app-web); +@import "../../../../res/css/_components.pcss" layer(app-web); @import "../../../../res/css/_compound.pcss"; -@import url("highlight.js/styles/atom-one-light.min.css"); -@import url("github-markdown-css/github-markdown-light.css"); +@import url("highlight.js/styles/atom-one-light.min.css") layer(app-web); +@import url("github-markdown-css/github-markdown-light.css") layer(app-web); diff --git a/apps/web/src/components/views/messages/TextualBodyFactory.tsx b/apps/web/src/components/views/messages/TextualBodyFactory.tsx index d947bb982e..b51eebe78b 100644 --- a/apps/web/src/components/views/messages/TextualBodyFactory.tsx +++ b/apps/web/src/components/views/messages/TextualBodyFactory.tsx @@ -210,7 +210,7 @@ export function TextualBodyFactory(props: Readonly): JSX.Element { vm={textualBodyVm} body={} bodyRef={contentRef} - urlPreviews={} + urlPreviews={} className={getTextualBodyClassName(content.msgtype as MsgType | undefined)} /> ); diff --git a/apps/web/src/theme.ts b/apps/web/src/theme.ts index a0a527c28a..4bd3ea322d 100644 --- a/apps/web/src/theme.ts +++ b/apps/web/src/theme.ts @@ -207,9 +207,10 @@ function generateCustomCompoundCSS(theme: CompoundTheme): string { for (const [token, value] of Object.entries(theme)) if (COMPOUND_TOKEN.test(token)) properties.push(`${token}: ${value};`); else logger.warn(`'${token}' is not a valid Compound token`); - // Insert the design token overrides into the 'custom' cascade layer as - // documented at https://compound.element.io/?path=/docs/develop-theming--docs - return `@layer compound.custom { :root, [class*="cpd-theme-"] { ${properties.join(" ")} } }`; + // Insert the design token overrides into the existing Compound tokens + // layer so custom themes win over the imported default tokens by source + // order without creating a lower-priority nested layer. + return `@layer compound-tokens { :root, [class*="cpd-theme-"] { ${properties.join(" ")} } }`; } /** diff --git a/apps/web/src/utils/exportUtils/exportCSS.ts b/apps/web/src/utils/exportUtils/exportCSS.ts index 8c470aaa0d..7e55b0cebe 100644 --- a/apps/web/src/utils/exportUtils/exportCSS.ts +++ b/apps/web/src/utils/exportUtils/exportCSS.ts @@ -6,11 +6,12 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import type { Rule, StyleSheet } from "css-tree"; +import type { CssNode, Rule, StyleSheet } from "css-tree"; import customCSS from "!!raw-loader!./exportCustomCSS.css"; const cssSelectorTextClassesRegex = /\.[\w-]+/g; +const appLayerName = "app-web"; function mutateCssText(css: string): string { // replace used fonts so that we don't have to bundle Inter & Fira Code @@ -41,6 +42,21 @@ function includeRule(rule: Rule, usedClasses: Set): boolean { return true; } +function includeNode(node: CssNode, usedClasses: Set): boolean { + if (node.type === "Atrule") { + if (node.name === "font-face") { + return false; + } + + if (node.block) { + node.block.children = node.block.children.filter((child) => includeNode(child, usedClasses)); + return !node.block.children.isEmpty; + } + } + + return node.type !== "Rule" || includeRule(node, usedClasses); +} + // naively culls unused css rules based on which classes are present in the html, // doesn't cull rules which won't apply due to the full selector not matching but gets rid of a LOT of cruft anyway. // We cannot use document.styleSheets as it does not handle variables in shorthand properties sanely, @@ -69,13 +85,7 @@ const getExportCSS = async (usedClasses: Set): Promise => { }) as StyleSheet; for (const rule of ast.children) { - if (rule.type === "Atrule") { - if (rule.name === "font-face") { - continue; - } - } - - if (rule.type === "Rule" && !includeRule(rule, usedClasses)) { + if (!includeNode(rule, usedClasses)) { continue; } @@ -83,7 +93,7 @@ const getExportCSS = async (usedClasses: Set): Promise => { } } - return css + customCSS; + return `${css}@layer ${appLayerName} {${customCSS}}`; }; export default getExportCSS; diff --git a/apps/web/src/utils/exportUtils/exportCustomCSS.css b/apps/web/src/utils/exportUtils/exportCustomCSS.css index 117eff6b23..fe8f7c7269 100644 --- a/apps/web/src/utils/exportUtils/exportCustomCSS.css +++ b/apps/web/src/utils/exportUtils/exportCustomCSS.css @@ -60,11 +60,6 @@ body { a.mx_reply_anchor { cursor: pointer; - color: #238cf5; -} - -a.mx_reply_anchor:hover { - text-decoration: underline; } @-webkit-keyframes mx_snackbar_fadein { diff --git a/apps/web/test/unit-tests/__snapshots__/theme-test.ts.snap b/apps/web/test/unit-tests/__snapshots__/theme-test.ts.snap index af61bc7151..83b0c90a7c 100644 --- a/apps/web/test/unit-tests/__snapshots__/theme-test.ts.snap +++ b/apps/web/test/unit-tests/__snapshots__/theme-test.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing -exports[`theme setTheme applies a custom Compound theme 1`] = `"@layer compound.custom { :root, [class*="cpd-theme-"] { --cpd-color-icon-accent-tertiary: var(--cpd-color-blue-800); --cpd-color-text-action-accent: var(--cpd-color-blue-900); } }"`; +exports[`theme setTheme applies a custom Compound theme 1`] = `"@layer compound-tokens { :root, [class*="cpd-theme-"] { --cpd-color-icon-accent-tertiary: var(--cpd-color-blue-800); --cpd-color-text-action-accent: var(--cpd-color-blue-900); } }"`; diff --git a/apps/web/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap b/apps/web/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap index 745d84f52b..0a6b8d1acc 100644 --- a/apps/web/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap +++ b/apps/web/test/unit-tests/components/structures/__snapshots__/RoomView-test.tsx.snap @@ -237,7 +237,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`] class="_content_n7ud0_38" >

    Could not start a chat with this user @@ -247,7 +247,7 @@ exports[`RoomView for a local room in state ERROR should match the snapshot 1`] class="_actions_n7ud0_61" >

    -
  • @user48:example.com
    00:00
    Message #48
  • @user47:example.com
    00:00
    Message #47
  • @user46:example.com
    00:00
    Message #46
  • @user45:example.com
    00:00
    Message #45
  • @user44:example.com
    00:00
    Message #44
  • @user43:example.com
    00:00
    Message #43
  • @user42:example.com
    00:00
    Message #42
  • @user41:example.com
    00:00
    Message #41
  • @user40:example.com
    00:00
    Message #40
  • @user39:example.com
    00:00
    Message #39
  • @user38:example.com
    00:00
    Message #38
  • @user37:example.com
    00:00
    Message #37
  • @user36:example.com
    00:00
    Message #36
  • @user35:example.com
    00:00
    Message #35
  • @user34:example.com
    00:00
    Message #34
  • @user33:example.com
    00:00
    Message #33
  • @user32:example.com
    00:00
    Message #32
  • @user31:example.com
    00:00
    Message #31
  • @user30:example.com
    00:00
    Message #30
  • @user29:example.com
    00:00
    Message #29
  • @user28:example.com
    00:00
    Message #28
  • @user27:example.com
    00:00
    Message #27
  • @user26:example.com
    00:00
    Message #26
  • @user25:example.com
    00:00
    Message #25
  • @user24:example.com
    00:00
    Message #24
  • @user23:example.com
    00:00
    Message #23
  • @user22:example.com
    00:00
    Message #22
  • @user21:example.com
    00:00
    Message #21
  • @user20:example.com
    00:00
    Message #20
  • @user19:example.com
    00:00
    Message #19
  • @user18:example.com
    00:00
    Message #18
  • @user17:example.com
    00:00
    Message #17
  • @user16:example.com
    00:00
    Message #16
  • @user15:example.com
    00:00
    Message #15
  • @user14:example.com
    00:00
    Message #14
  • @user13:example.com
    00:00
    Message #13
  • @user12:example.com
    00:00
    Message #12
  • @user11:example.com
    00:00
    Message #11
  • @user10:example.com
    00:00
    Message #10
  • @user9:example.com
    00:00
    Message #9
  • @user8:example.com
    00:00
    Message #8
  • @user7:example.com
    00:00
    Message #7
  • @user6:example.com
    00:00
    Message #6
  • @user5:example.com
    00:00
    Message #5
  • @user4:example.com
    00:00
    Message #4
  • @user3:example.com
    00:00
    Message #3
  • @user2:example.com
    00:00
    Message #2
  • @user1:example.com
    00:00
    Message #1
  • @user0:example.com
    00:00
    Message #0
  • +
  • @user49:example.com
    00:00
    Message #49
  • @user48:example.com
    00:00
    Message #48
  • @user47:example.com
    00:00
    Message #47
  • @user46:example.com
    00:00
    Message #46
  • @user45:example.com
    00:00
    Message #45
  • @user44:example.com
    00:00
    Message #44
  • @user43:example.com
    00:00
    Message #43
  • @user42:example.com
    00:00
    Message #42
  • @user41:example.com
    00:00
    Message #41
  • @user40:example.com
    00:00
    Message #40
  • @user39:example.com
    00:00
    Message #39
  • @user38:example.com
    00:00
    Message #38
  • @user37:example.com
    00:00
    Message #37
  • @user36:example.com
    00:00
    Message #36
  • @user35:example.com
    00:00
    Message #35
  • @user34:example.com
    00:00
    Message #34
  • @user33:example.com
    00:00
    Message #33
  • @user32:example.com
    00:00
    Message #32
  • @user31:example.com
    00:00
    Message #31
  • @user30:example.com
    00:00
    Message #30
  • @user29:example.com
    00:00
    Message #29
  • @user28:example.com
    00:00
    Message #28
  • @user27:example.com
    00:00
    Message #27
  • @user26:example.com
    00:00
    Message #26
  • @user25:example.com
    00:00
    Message #25
  • @user24:example.com
    00:00
    Message #24
  • @user23:example.com
    00:00
    Message #23
  • @user22:example.com
    00:00
    Message #22
  • @user21:example.com
    00:00
    Message #21
  • @user20:example.com
    00:00
    Message #20
  • @user19:example.com
    00:00
    Message #19
  • @user18:example.com
    00:00
    Message #18
  • @user17:example.com
    00:00
    Message #17
  • @user16:example.com
    00:00
    Message #16
  • @user15:example.com
    00:00
    Message #15
  • @user14:example.com
    00:00
    Message #14
  • @user13:example.com
    00:00
    Message #13
  • @user12:example.com
    00:00
    Message #12
  • @user11:example.com
    00:00
    Message #11
  • @user10:example.com
    00:00
    Message #10
  • @user9:example.com
    00:00
    Message #9
  • @user8:example.com
    00:00
    Message #8
  • @user7:example.com
    00:00
    Message #7
  • @user6:example.com
    00:00
    Message #6
  • @user5:example.com
    00:00
    Message #5
  • @user4:example.com
    00:00
    Message #4
  • @user3:example.com
    00:00
    Message #3
  • @user2:example.com
    00:00
    Message #2
  • @user1:example.com
    00:00
    Message #1
  • @user0:example.com
    00:00
    Message #0
  • diff --git a/apps/web/test/unit-tests/utils/exportUtils/exportCSS-test.ts b/apps/web/test/unit-tests/utils/exportUtils/exportCSS-test.ts index 0cf154e9be..dc37a022e8 100644 --- a/apps/web/test/unit-tests/utils/exportUtils/exportCSS-test.ts +++ b/apps/web/test/unit-tests/utils/exportUtils/exportCSS-test.ts @@ -6,13 +6,103 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ +import fetchMock from "@fetch-mock/jest"; + import getExportCSS from "../../../../src/utils/exportUtils/exportCSS"; describe("exportCSS", () => { describe("getExportCSS", () => { + beforeEach(() => { + document.head.replaceChildren(); + }); + it("supports documents missing stylesheets", async () => { const css = await getExportCSS(new Set()); expect(css).not.toContain("color-scheme: light"); }); + + it("fetches export stylesheets and filters unused css", async () => { + document.head.innerHTML = ` + + + + `; + + fetchMock.get( + "end:/bundle.css", + ` + @font-face { font-family: Inter; src: url(inter.woff2); } + body { margin: 0; } + .mx_Used { font-family: Inter; color: #111111; } + .mx_Code { font-family: Fira Code; } + .mx_Unused { color: #123456; } + .mx_Empty {} + .mx_Used, .mx_UnusedComma { color: #abcdef; } + @media screen { + .mx_Used { --cpd-font-family-sans: "Inter"; } + .mx_UnusedNested { color: #654321; } + } + @supports (display: grid) { + .mx_UnusedSupported { color: #fedcba; } + } + `, + ); + fetchMock.get("end:/theme-light.css", ".mx_Theme { color: #222222; }"); + + const css = await getExportCSS(new Set(["mx_Used", "mx_Code", "mx_Theme"])); + + expect(fetchMock).toHaveFetchedTimes(1, "end:/bundle.css"); + expect(fetchMock).toHaveFetchedTimes(1, "end:/theme-light.css"); + expect(fetchMock).not.toHaveFetched("end:/theme-dark.css"); + + expect(css).toContain("margin:0"); + expect(css).toContain("#111"); + expect(css).toContain("#222"); + expect(css).toContain("#abcdef"); + + expect(css).not.toContain("@font-face"); + expect(css).not.toContain("#123456"); + expect(css).not.toContain("#654321"); + expect(css).not.toContain("#fedcba"); + + expect(css).not.toContain("font-family:Inter"); + expect(css).not.toContain("font-family:Fira Code"); + expect(css).toContain("BlinkMacSystemFont"); + expect(css).toContain("Menlo, Consolas"); + }); + + it("keeps export-only css in the app cascade layer after layered font rules", async () => { + document.head.innerHTML = ` + + `; + + fetchMock.get( + "end:/bundle.css", + ` + @layer compound-tokens, compound-web, shared-components, app-web; + @layer compound-web { + .mx_Typography { + font: var(--cpd-font-heading-lg-regular); + } + } + @layer app-web { + body { + font: var(--cpd-font-body-md-regular) !important; + } + } + `, + ); + + const css = await getExportCSS(new Set(["mx_Typography"])); + + expect(css).toContain("@layer compound-web{.mx_Typography{font:var(--cpd-font-heading-lg-regular)}}"); + expect(css).toContain("@layer app-web{body{font:var(--cpd-font-body-md-regular)!important}}"); + + const exportCssLayerIndex = css.indexOf("@layer app-web {"); + expect(exportCssLayerIndex).toBeGreaterThan( + css.indexOf("@layer app-web{body{font:var(--cpd-font-body-md-regular)!important}}"), + ); + expect(css.slice(exportCssLayerIndex)).toBe("@layer app-web {css-file-stub}"); + }); }); }); diff --git a/knip.ts b/knip.ts index 53718f2e15..9d4fab1d49 100644 --- a/knip.ts +++ b/knip.ts @@ -65,13 +65,8 @@ export default { ignoreExportsUsedInFile: true, compilers: { pcss: (text: string) => - [...text.matchAll(/(?<=@)import[^;]+/g)] - .map(([line]) => { - if (line.startsWith("import url(")) { - return line.replace("url(", "").slice(0, -1); - } - return line; - }) + [...text.matchAll(/@import\s+(?:url\()?["']([^"']+)["']\)?[^;]*;/g)] + .map(([, specifier]) => `import "${specifier}";`) .join("\n"), }, nx: { diff --git a/packages/shared-components/.storybook/compound.css b/packages/shared-components/.storybook/compound.css index 42f8393666..dda8ccf13e 100644 --- a/packages/shared-components/.storybook/compound.css +++ b/packages/shared-components/.storybook/compound.css @@ -5,5 +5,8 @@ * Please see LICENSE files in the repository root for full details. */ -@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound); -@import url("@vector-im/compound-web/dist/style.css"); +/* Shared cascade order: Compound tokens, Compound Web, shared components, then app overrides. */ +@layer compound-tokens, compound-web, shared-components, app-web; + +@import url("@vector-im/compound-design-tokens/assets/web/css/compound-design-tokens.css") layer(compound-tokens); +@import url("@vector-im/compound-web/dist/style.css") layer(compound-web); diff --git a/packages/shared-components/.storybook/main.ts b/packages/shared-components/.storybook/main.ts index 72cb55aea5..60fb388507 100644 --- a/packages/shared-components/.storybook/main.ts +++ b/packages/shared-components/.storybook/main.ts @@ -8,11 +8,13 @@ Please see LICENSE files in the repository root for full details. import type { StorybookConfig } from "@storybook/react-vite"; import fs from "node:fs"; import { nodePolyfills } from "vite-plugin-node-polyfills"; -import { mergeConfig } from "vite"; +import { mergeConfig, normalizePath, type Plugin } from "vite"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); +const srcRoot = normalizePath(join(__dirname, "..", "src")); +const sharedComponentsLayer = "shared-components"; // Get a list of available languages so the language selector can display them at runtime const languageFiles = fs.readdirSync(join(__dirname, "..", "src", "i18n", "strings")).map((f) => f.slice(0, -5)); @@ -36,6 +38,24 @@ function getAbsolutePath(value: string): any { return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`))); } +function layerSharedComponentCssModules(): Plugin { + return { + name: "element-web-shared-components-storybook-css-layer", + enforce: "pre", + transform(code, id) { + const cssPath = normalizePath(id.split("?")[0]); + if (!cssPath.startsWith(srcRoot) || !cssPath.endsWith(".module.css")) { + return; + } + + return { + code: `@layer ${sharedComponentsLayer} {\n${code}\n}\n`, + map: null, + }; + }, + }; +} + const config: StorybookConfig = { stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"], addons: [ @@ -61,6 +81,7 @@ const config: StorybookConfig = { async viteFinal(config) { return mergeConfig(config, { plugins: [ + layerSharedComponentCssModules(), // Needed for counterpart to work nodePolyfills({ include: ["util"], globals: { global: false } }), { diff --git a/packages/shared-components/src/audio/PlayPauseButton/PlayPauseButton.module.css b/packages/shared-components/src/audio/PlayPauseButton/PlayPauseButton.module.css index 859f84bba9..74315673f1 100644 --- a/packages/shared-components/src/audio/PlayPauseButton/PlayPauseButton.module.css +++ b/packages/shared-components/src/audio/PlayPauseButton/PlayPauseButton.module.css @@ -6,6 +6,6 @@ */ .button { - border-radius: 32px !important; - background-color: var(--cpd-color-bg-subtle-primary) !important; + border-radius: 32px; + background-color: var(--cpd-color-bg-subtle-primary); } diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.module.css b/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.module.css index 11a81da947..255babced3 100644 --- a/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.module.css +++ b/packages/shared-components/src/room-list/RoomListHeaderView/menu/OptionMenuView.module.css @@ -7,5 +7,5 @@ .title { /* For first title, there is already enough space at the top */ - margin-top: 0 !important; + margin-top: 0; } diff --git a/packages/shared-components/src/room-list/RoomListSearchView/RoomListSearchView.module.css b/packages/shared-components/src/room-list/RoomListSearchView/RoomListSearchView.module.css index 9b7097373c..20b71a90bf 100644 --- a/packages/shared-components/src/room-list/RoomListSearchView/RoomListSearchView.module.css +++ b/packages/shared-components/src/room-list/RoomListSearchView/RoomListSearchView.module.css @@ -16,9 +16,8 @@ .search { /* The search button should take all the remaining space */ flex: 1; - /* !important is needed to override compound button in EW */ - font: var(--cpd-font-body-md-regular) !important; - color: var(--cpd-color-text-secondary) !important; + font: var(--cpd-font-body-md-regular); + color: var(--cpd-color-text-secondary); min-width: 0; svg { diff --git a/packages/shared-components/src/room/RoomStatusBar/RoomStatusBarView.module.css b/packages/shared-components/src/room/RoomStatusBar/RoomStatusBarView.module.css index c3832e2e9f..78300df3f9 100644 --- a/packages/shared-components/src/room/RoomStatusBar/RoomStatusBarView.module.css +++ b/packages/shared-components/src/room/RoomStatusBar/RoomStatusBarView.module.css @@ -7,18 +7,18 @@ .container { color: var(--cpd-color-text-primary); - svg { - /* Ensure button icons are primary too */ - color: var(--cpd-color-text-primary) !important; - } } -.secondaryAction svg { - color: var(--cpd-color-text-secondary) !important; +.container svg { + color: var(--cpd-color-text-primary); } -.primaryAction svg { - color: var(--cpd-color-text-on-solid-primary) !important; +.secondaryAction.secondaryAction[data-kind="secondary"] > svg { + color: var(--cpd-color-text-secondary); +} + +.primaryAction.primaryAction[data-kind="primary"] > svg { + color: var(--cpd-color-text-on-solid-primary); } .title { diff --git a/packages/shared-components/src/room/timeline/DateSeparatorView/DateSeparatorContextMenuView.module.css b/packages/shared-components/src/room/timeline/DateSeparatorView/DateSeparatorContextMenuView.module.css index fff772c4bd..75aa130936 100644 --- a/packages/shared-components/src/room/timeline/DateSeparatorView/DateSeparatorContextMenuView.module.css +++ b/packages/shared-components/src/room/timeline/DateSeparatorView/DateSeparatorContextMenuView.module.css @@ -6,15 +6,15 @@ */ .picker_menu { - max-inline-size: none !important; - padding: 0 !important; - gap: 0 !important; + max-inline-size: none; + padding: 0; + gap: 0; } .picker_menu_item { - padding: var(--cpd-space-3x) var(--cpd-space-5x) !important; + padding: var(--cpd-space-3x) var(--cpd-space-5x); } .picker_separator { - margin-inline: 0 !important; + margin-inline: 0; } diff --git a/packages/shared-components/src/room/timeline/DateSeparatorView/DateSeparatorDatePickerView.module.css b/packages/shared-components/src/room/timeline/DateSeparatorView/DateSeparatorDatePickerView.module.css index e664b1f5eb..f8e9894c38 100644 --- a/packages/shared-components/src/room/timeline/DateSeparatorView/DateSeparatorDatePickerView.module.css +++ b/packages/shared-components/src/room/timeline/DateSeparatorView/DateSeparatorDatePickerView.module.css @@ -6,15 +6,15 @@ */ .picker_menu_item { - padding: var(--cpd-space-3x) var(--cpd-space-5x) !important; + padding: var(--cpd-space-3x) var(--cpd-space-5x); } .picker_form { display: flex; - flex-direction: row !important; - flex-wrap: wrap !important; - padding: 0 !important; - gap: var(--cpd-space-2x) !important; + flex-direction: row; + flex-wrap: wrap; + padding: 0; + gap: var(--cpd-space-2x); color: var(--cpd-color-text-primary); font: var(--cpd-font-body-md-medium); } @@ -28,7 +28,7 @@ .picker_input_date { font: inherit; color-scheme: light; - padding: var(--cpd-space-2x) !important; + padding: var(--cpd-space-2x); :global(.cpd-theme-dark) &, :global(.cpd-theme-dark-hc) & { diff --git a/packages/shared-components/src/room/timeline/event-tile/UrlPreviewGroupView/UrlPreviewGroupView.tsx b/packages/shared-components/src/room/timeline/event-tile/UrlPreviewGroupView/UrlPreviewGroupView.tsx index 79f0a96f93..01c7a07441 100644 --- a/packages/shared-components/src/room/timeline/event-tile/UrlPreviewGroupView/UrlPreviewGroupView.tsx +++ b/packages/shared-components/src/room/timeline/event-tile/UrlPreviewGroupView/UrlPreviewGroupView.tsx @@ -25,7 +25,14 @@ export interface UrlPreviewGroupViewSnapshot { } export interface UrlPreviewGroupViewProps { + /** + * The view model for the component. + */ vm: ViewModel & UrlPreviewGroupViewActions; + /** + * Extra CSS classes to apply to the component. + */ + className?: string; } export interface UrlPreviewGroupViewActions { @@ -42,7 +49,7 @@ export type UrlPreviewGroupViewModel = ViewModel +
    {previews.map((preview) => ( vm.onImageClick(preview)} {...preview} /> diff --git a/packages/shared-components/src/room/timeline/event-tile/actions/ActionBarView/ActionBarView.module.css b/packages/shared-components/src/room/timeline/event-tile/actions/ActionBarView/ActionBarView.module.css index 50cbb3f0d6..6b08fa635e 100644 --- a/packages/shared-components/src/room/timeline/event-tile/actions/ActionBarView/ActionBarView.module.css +++ b/packages/shared-components/src/room/timeline/event-tile/actions/ActionBarView/ActionBarView.module.css @@ -14,31 +14,31 @@ .toolbar_item { align-items: center; justify-content: center; - padding: 0 !important; - min-block-size: 0 !important; + padding: 0; + min-block-size: 0; - margin: 3px !important; - border-radius: 6px !important; + margin: 3px; + border-radius: 6px; &:hover { - background-color: var(--cpd-color-bg-subtle-secondary) !important; + background-color: var(--cpd-color-bg-subtle-secondary); z-index: 1; } } .toolbar_item[data-presentation="icon"] { - block-size: 28px !important; - inline-size: 28px !important; + block-size: 28px; + inline-size: 28px; - color: var(--cpd-color-icon-secondary) !important; + color: var(--cpd-color-icon-secondary); } .toolbar_item[data-presentation="label"] { - padding-inline-start: var(--cpd-space-2x) !important; - padding-inline-end: var(--cpd-space-2x) !important; + padding-inline-start: var(--cpd-space-2x); + padding-inline-end: var(--cpd-space-2x); font: var(--cpd-font-body-md-regular); - text-decoration: none !important; + text-decoration: none; white-space: nowrap; } } diff --git a/packages/shared-components/src/room/timeline/event-tile/body/AudioPlayerView/AudioPlayerView.module.css b/packages/shared-components/src/room/timeline/event-tile/body/AudioPlayerView/AudioPlayerView.module.css index f6bfec3969..1842cc90a1 100644 --- a/packages/shared-components/src/room/timeline/event-tile/body/AudioPlayerView/AudioPlayerView.module.css +++ b/packages/shared-components/src/room/timeline/event-tile/body/AudioPlayerView/AudioPlayerView.module.css @@ -6,8 +6,8 @@ */ .audioPlayer { - padding: var(--cpd-space-4x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x) !important; - border-radius: 8px !important; + padding: var(--cpd-space-4x) var(--cpd-space-3x) var(--cpd-space-3x) var(--cpd-space-3x); + border-radius: 8px; } .mediaInfo { diff --git a/packages/shared-components/src/room/timeline/event-tile/body/MFileBodyView/FileBodyView.module.css b/packages/shared-components/src/room/timeline/event-tile/body/MFileBodyView/FileBodyView.module.css index bf71413b93..52434c133d 100644 --- a/packages/shared-components/src/room/timeline/event-tile/body/MFileBodyView/FileBodyView.module.css +++ b/packages/shared-components/src/room/timeline/event-tile/body/MFileBodyView/FileBodyView.module.css @@ -26,8 +26,8 @@ & button { display: flex; - margin: 0 !important; - padding: 0 !important; + margin: 0; + padding: 0; border: none; width: 100%; min-block-size: unset; @@ -57,7 +57,6 @@ .content [data-type="download"] { align-items: center; - color: var(--cpd-color-text-action-accent); & iframe { margin: 0; diff --git a/packages/shared-components/src/room/timeline/event-tile/timestamp/MessageTimestampView/MessageTimestampView.module.css b/packages/shared-components/src/room/timeline/event-tile/timestamp/MessageTimestampView/MessageTimestampView.module.css index 6ee9d62aa1..e00b9f0678 100644 --- a/packages/shared-components/src/room/timeline/event-tile/timestamp/MessageTimestampView/MessageTimestampView.module.css +++ b/packages/shared-components/src/room/timeline/event-tile/timestamp/MessageTimestampView/MessageTimestampView.module.css @@ -6,7 +6,7 @@ */ .content { - color: var(--cpd-color-text-secondary) !important; /* override anchor color */ + color: var(--cpd-color-text-secondary); /* override anchor color */ font-size: var(--cpd-font-size-body-xs); font-variant-numeric: tabular-nums; display: inline-block; diff --git a/packages/shared-components/vite.config.ts b/packages/shared-components/vite.config.ts index 0a27742816..116689e645 100644 --- a/packages/shared-components/vite.config.ts +++ b/packages/shared-components/vite.config.ts @@ -6,12 +6,36 @@ * */ +import { readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { defineConfig, esmExternalRequirePlugin } from "vite"; +import { defineConfig, esmExternalRequirePlugin, type Plugin } from "vite"; import dts from "unplugin-dts/vite"; const __dirname = dirname(fileURLToPath(import.meta.url)); +const cssLayerOrder = "@layer compound-tokens, compound-web, shared-components, app-web;"; +const sharedComponentsLayer = "shared-components"; + +function layerCssAssets(): Plugin { + return { + name: "element-web-shared-components-css-layer", + writeBundle(_options, bundle): void { + for (const asset of Object.values(bundle)) { + if (asset.type !== "asset" || asset.fileName !== "element-web-shared-components.css") { + continue; + } + + const cssPath = resolve(__dirname, "dist", asset.fileName); + const source = readFileSync(cssPath, "utf-8"); + if (source.startsWith(cssLayerOrder)) { + continue; + } + + writeFileSync(cssPath, `${cssLayerOrder}\n@layer ${sharedComponentsLayer} {\n${source}\n}\n`); + } + }, + }; +} export default defineConfig({ build: { @@ -50,6 +74,7 @@ export default defineConfig({ }, }, plugins: [ + layerCssAssets(), dts({ bundleTypes: true, include: ["src/**/*.{ts,tsx}"], From af20018ea2378e9b8521d3d899c73f4cb2400781 Mon Sep 17 00:00:00 2001 From: Will Hunt <2072976+Half-Shot@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:49:27 +0100 Subject: [PATCH 21/23] Playwright tests for file uploads (#33319) * Refactor tests to use helper method for composer uploads. * Add drag and drop tests * lint * Add commentary * fixup test * More precise selector --- .../e2e/audio-player/audio-player.spec.ts | 41 ++++++------ .../e2e/file-upload/image-upload.spec.ts | 11 +++- .../e2e/right-panel/file-panel.spec.ts | 35 +++++------ .../playwright/e2e/threads/threads.spec.ts | 45 ++++++++++++++ apps/web/playwright/pages/ElementAppPage.ts | 62 +++++++++++++++++++ 5 files changed, 148 insertions(+), 46 deletions(-) diff --git a/apps/web/playwright/e2e/audio-player/audio-player.spec.ts b/apps/web/playwright/e2e/audio-player/audio-player.spec.ts index 7aac1a6fd9..8223996a61 100644 --- a/apps/web/playwright/e2e/audio-player/audio-player.spec.ts +++ b/apps/web/playwright/e2e/audio-player/audio-player.spec.ts @@ -11,7 +11,7 @@ import type { Locator, Page } from "@playwright/test"; import { test, expect, type ExtendedToMatchScreenshotOptions } from "../../element-web-test"; import { SettingLevel } from "../../../src/settings/SettingLevel"; import { Layout } from "../../../src/settings/enums/Layout"; -import { type ElementAppPage } from "../../pages/ElementAppPage"; +import type { ElementAppPage } from "../../pages/ElementAppPage"; import { getSampleFilePath } from "../../sample-files"; // Find and click "Reply" button @@ -29,22 +29,17 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { displayName: "Hanako", }); - const uploadFile = async (page: Page, sampleFile: string) => { + const uploadFile = async (app: ElementAppPage, sampleFile: string) => { // Upload a file from the message composer - await page - .locator(".mx_MessageComposer_actions input[type='file']") - .setInputFiles(getSampleFilePath(sampleFile)); - - // Find and click primary "Upload" button - await page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click(); + await app.composerUploadFiles("room", getSampleFilePath(sampleFile)); // Wait until the file is sent - await expect(page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible(); - await expect(page.locator(".mx_EventTile.mx_EventTile_last").getByRole("status")).toHaveAccessibleName( + await expect(app.page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible(); + await expect(app.page.locator(".mx_EventTile.mx_EventTile_last").getByRole("status")).toHaveAccessibleName( "Your message was sent", ); // wait for the tile to finish loading - await expect(page.getByTestId("audio-player-name").last().filter({ hasText: sampleFile })).toBeVisible(); + await expect(app.page.getByTestId("audio-player-name").last().filter({ hasText: sampleFile })).toBeVisible(); }; const scrollToBottomOfTimeline = async (page: Page) => { @@ -157,7 +152,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { }); test("should be correctly rendered - light theme", { tag: "@screenshot" }, async ({ page, app }) => { - await uploadFile(page, "1sec-long-name-audio-file.ogg"); + await uploadFile(app, "1sec-long-name-audio-file.ogg"); await takeSnapshots(page, app, "Selected EventTile of audio player (light theme)"); }); @@ -165,7 +160,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { "should be correctly rendered - light theme with monospace font", { tag: "@screenshot" }, async ({ page, app }) => { - await uploadFile(page, "1sec-long-name-audio-file.ogg"); + await uploadFile(app, "1sec-long-name-audio-file.ogg"); await takeSnapshots(page, app, "Selected EventTile of audio player (light theme, monospace font)", true); // Enable monospace }, @@ -182,7 +177,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await app.closeDialog(); - await uploadFile(page, "1sec-long-name-audio-file.ogg"); + await uploadFile(app, "1sec-long-name-audio-file.ogg"); await takeSnapshots(page, app, "Selected EventTile of audio player (high contrast)"); }); @@ -191,13 +186,13 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { // Enable dark theme await app.settings.setValue("theme", null, SettingLevel.ACCOUNT, "dark"); - await uploadFile(page, "1sec-long-name-audio-file.ogg"); + await uploadFile(app, "1sec-long-name-audio-file.ogg"); await takeSnapshots(page, app, "Selected EventTile of audio player (dark theme)"); }); test("should play an audio file", async ({ page, app }) => { - await uploadFile(page, "1sec.ogg"); + await uploadFile(app, "1sec.ogg"); // Assert that the audio player is rendered const container = page.locator(".mx_EventTile_last").getByRole("region", { name: "Audio player" }); @@ -219,7 +214,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { }); test("should support downloading an audio file", async ({ page, app }) => { - await uploadFile(page, "1sec.ogg"); + await uploadFile(app, "1sec.ogg"); const downloadPromise = page.waitForEvent("download"); @@ -237,7 +232,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { "should support replying to audio file with another audio file", { tag: "@screenshot" }, async ({ page, app }) => { - await uploadFile(page, "1sec.ogg"); + await uploadFile(app, "1sec.ogg"); // Assert the audio player is rendered await expect(page.getByRole("region", { name: "Audio player" })).toBeVisible(); @@ -247,7 +242,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await clickButtonReply(tile); // Reply to the player with another audio file - await uploadFile(page, "1sec.ogg"); + await uploadFile(app, "1sec.ogg"); // Assert that the audio player is rendered await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible(); @@ -272,7 +267,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { const tile = page.locator(".mx_EventTile_last"); - await uploadFile(page, "upload-first.ogg"); + await uploadFile(app, "upload-first.ogg"); // Assert that the audio player is rendered await expect( @@ -282,7 +277,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await clickButtonReply(tile); // Reply to the player with another audio file - await uploadFile(page, "upload-second.ogg"); + await uploadFile(app, "upload-second.ogg"); // Assert that the audio player is rendered await expect( @@ -292,7 +287,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { await clickButtonReply(tile); // Reply to the player with yet another audio file to create a reply chain - await uploadFile(page, "upload-third.ogg"); + await uploadFile(app, "upload-third.ogg"); // Assert that the audio player is rendered await expect(tile.getByRole("region", { name: "Audio player" })).toBeVisible(); @@ -324,7 +319,7 @@ test.describe("Audio player", { tag: ["@no-firefox", "@no-webkit"] }, () => { ); test("should be rendered, play, and support replying on a thread", async ({ page, app }) => { - await uploadFile(page, "1sec-long-name-audio-file.ogg"); + await uploadFile(app, "1sec-long-name-audio-file.ogg"); // On the main timeline const messageList = page.locator(".mx_RoomView_MessageList"); diff --git a/apps/web/playwright/e2e/file-upload/image-upload.spec.ts b/apps/web/playwright/e2e/file-upload/image-upload.spec.ts index 45ce44df5c..67ca01bd09 100644 --- a/apps/web/playwright/e2e/file-upload/image-upload.spec.ts +++ b/apps/web/playwright/e2e/file-upload/image-upload.spec.ts @@ -27,12 +27,17 @@ test.describe("Image Upload", () => { }); test("should show image preview when uploading an image", { tag: "@screenshot" }, async ({ page, app }) => { - await page - .locator(".mx_MessageComposer_actions input[type='file']") - .setInputFiles(getSampleFilePath("riot.png")); + await app.setComposerInputFiles("room", getSampleFilePath("riot.png")); await expect(page.getByRole("button", { name: "Upload" })).toBeEnabled(); await expect(page.getByRole("button", { name: "Close dialog" })).toBeEnabled(); await expect(page.locator(".mx_Dialog")).toMatchScreenshot("image-upload-preview.png"); }); + + test("should allow upload via drag and drop", { tag: "@screenshot" }, async ({ page, app }) => { + await app.composerDragAndUploadFiles("room", getSampleFilePath("riot.png"), "image/png"); + await app.timeline.scrollToBottom(); + const imgTile = page.locator(".mx_MImageBody").first(); + await expect(imgTile).toBeVisible(); + }); }); diff --git a/apps/web/playwright/e2e/right-panel/file-panel.spec.ts b/apps/web/playwright/e2e/right-panel/file-panel.spec.ts index 2fcb6d43ed..e89c10b20f 100644 --- a/apps/web/playwright/e2e/right-panel/file-panel.spec.ts +++ b/apps/web/playwright/e2e/right-panel/file-panel.spec.ts @@ -5,26 +5,21 @@ Copyright 2023 Suguru Hirahara 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. */ - -import { type Page } from "@playwright/test"; - import { test, expect } from "../../element-web-test"; import { viewRoomSummaryByName } from "./utils"; import { isDendrite } from "../../plugins/homeserver/dendrite"; import { getSampleFilePath } from "../../sample-files"; +import type { ElementAppPage } from "../../pages/ElementAppPage"; const ROOM_NAME = "Test room"; const NAME = "Alice"; -async function uploadFile(page: Page, sampleFile: string) { +async function uploadFile(app: ElementAppPage, sampleFile: string) { // Upload a file from the message composer - await page.locator(".mx_MessageComposer_actions input[type='file']").setInputFiles(getSampleFilePath(sampleFile)); - - await page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click(); - + await app.composerUploadFiles("room", getSampleFilePath(sampleFile)); // Wait until the file is sent - await expect(page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible(); - await expect(page.locator(".mx_EventTile.mx_EventTile_last").getByRole("status")).toHaveAccessibleName( + await expect(app.page.locator(".mx_RoomView_statusArea_expanded")).not.toBeVisible(); + await expect(app.page.locator(".mx_EventTile.mx_EventTile_last").getByRole("status")).toHaveAccessibleName( "Your message was sent", ); } @@ -52,11 +47,11 @@ test.describe("FilePanel", () => { await expect(page.locator(".mx_RightPanel")).toMatchScreenshot("empty.png"); }); - test("should list tiles on the panel", { tag: "@screenshot" }, async ({ page }) => { + test("should list tiles on the panel", { tag: "@screenshot" }, async ({ page, app }) => { // Upload multiple files - await uploadFile(page, "riot.png"); // Image - await uploadFile(page, "1sec.ogg"); // Audio - await uploadFile(page, "matrix-org-client-versions.json"); // JSON + await uploadFile(app, "riot.png"); // Image + await uploadFile(app, "1sec.ogg"); // Audio + await uploadFile(app, "matrix-org-client-versions.json"); // JSON const roomViewBody = page.locator(".mx_RoomView_body"); // Assert that all of the file were uploaded and rendered @@ -136,9 +131,9 @@ test.describe("FilePanel", () => { }); }); - test("should render the audio player and play the audio file on the panel", async ({ page }) => { + test("should render the audio player and play the audio file on the panel", async ({ page, app }) => { // Upload an image file - await uploadFile(page, "1sec.ogg"); + await uploadFile(app, "1sec.ogg"); const audioBody = page.getByTestId("right-panel").getByRole("region", { name: "Audio player" }); @@ -167,11 +162,11 @@ test.describe("FilePanel", () => { await expect(audioBody.getByRole("button", { name: "Play" })).toBeVisible(); }); - test("should render file size in kibibytes on a file tile", async ({ page }) => { + test("should render file size in kibibytes on a file tile", async ({ page, app }) => { const size = "1.12 KB"; // actual file size in kibibytes (1024 bytes) // Upload a file - await uploadFile(page, "matrix-org-client-versions.json"); + await uploadFile(app, "matrix-org-client-versions.json"); const tile = page.locator(".mx_FilePanel .mx_EventTile"); // Assert that the file size is displayed in kibibytes, not kilobytes (1000 bytes) @@ -183,9 +178,9 @@ test.describe("FilePanel", () => { test.describe("download", () => { test.skip(isDendrite, "due to a Dendrite sending Content-Disposition inline"); - test("should download an image via the link on the panel", async ({ page, context }) => { + test("should download an image via the link on the panel", async ({ page, app, context }) => { // Upload an image file - await uploadFile(page, "riot.png"); + await uploadFile(app, "riot.png"); // Detect the image file on the panel const imageBody = page.locator( diff --git a/apps/web/playwright/e2e/threads/threads.spec.ts b/apps/web/playwright/e2e/threads/threads.spec.ts index 16a2ead379..a454031933 100644 --- a/apps/web/playwright/e2e/threads/threads.spec.ts +++ b/apps/web/playwright/e2e/threads/threads.spec.ts @@ -9,6 +9,7 @@ import { SettingLevel } from "../../../src/settings/SettingLevel"; import { Layout } from "../../../src/settings/enums/Layout"; import { test, expect } from "../../element-web-test"; import { isDendrite } from "../../plugins/homeserver/dendrite"; +import { getSampleFilePath } from "../../sample-files"; test.describe("Threads", () => { test.skip(isDendrite, "due to a Dendrite bug https://github.com/element-hq/dendrite/issues/3489"); @@ -360,6 +361,50 @@ test.describe("Threads", () => { await app.getComposer(true).getByRole("button", { name: "Send voice message" }).click(); await expect(page.locator(".mx_ThreadView .mx_MVoiceMessageBody")).toHaveCount(1); }); + test("can send files", async ({ page, app, user }) => { + // Increase right-panel size, so that files fit + await page.evaluate(() => { + window.localStorage.setItem("mx_rhs_size", "600"); + }); + + const roomId = await app.client.createRoom({}); + await page.goto("/#/room/" + roomId); + + // Send message + const locator = page.locator(".mx_RoomView_body"); + await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hello Mr. Bot"); + await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); + // Create thread + const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" }); + await locator2.hover(); + await locator2.getByRole("button", { name: "Reply in thread" }).click(); + + await expect(page.locator(".mx_ThreadView_timelinePanelWrapper")).toHaveCount(1); + await app.composerUploadFiles("thread", getSampleFilePath("riot.png")); + await expect(page.locator(".mx_ThreadView .mx_EventTile_image")).toHaveCount(1); + }); + test("can send files via drag&drop", async ({ page, app, user }) => { + // Increase right-panel size, so that files fit + await page.evaluate(() => { + window.localStorage.setItem("mx_rhs_size", "600"); + }); + + const roomId = await app.client.createRoom({}); + await page.goto("/#/room/" + roomId); + + // Send message + const locator = page.locator(".mx_RoomView_body"); + await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).fill("Hello Mr. Bot"); + await locator.getByRole("textbox", { name: "Send an unencrypted message…" }).press("Enter"); + // Create thread + const locator2 = locator.locator(".mx_EventTile[data-scroll-tokens]").filter({ hasText: "Hello Mr. Bot" }); + await locator2.hover(); + await locator2.getByRole("button", { name: "Reply in thread" }).click(); + + await expect(page.locator(".mx_ThreadView_timelinePanelWrapper")).toHaveCount(1); + await app.composerDragAndUploadFiles("thread", getSampleFilePath("riot.png"), "image/png"); + await expect(page.locator(".mx_ThreadView .mx_EventTile_image")).toHaveCount(1); + }); }); test( diff --git a/apps/web/playwright/pages/ElementAppPage.ts b/apps/web/playwright/pages/ElementAppPage.ts index a0add8c83d..ac9212fd10 100644 --- a/apps/web/playwright/pages/ElementAppPage.ts +++ b/apps/web/playwright/pages/ElementAppPage.ts @@ -7,6 +7,8 @@ Please see LICENSE files in the repository root for full details. */ import { type Locator, type Page, expect } from "@playwright/test"; +import { readFile } from "node:fs/promises"; +import { basename } from "node:path"; import { Settings } from "./settings"; import { Client } from "./client"; @@ -156,6 +158,66 @@ export class ElementAppPage { return this.page.getByRole("menu"); } + /** + * Sets the files on the composers file input, causing it to open the file + * upload dialog. + * @param location Should the main room input or the thread view input be used. + */ + public setComposerInputFiles( + location: "room" | "thread", + ...params: Parameters + ): ReturnType { + const input = this.page + .locator(location === "room" ? ".mx_RoomView_body" : ".mx_RightPanel") + .getByRole("region", { name: "Message composer" }) + .locator("input[type='file']"); + return input.setInputFiles(...params); + } + + /** + * Sets the files on the composers file input, causing it to open the file + * upload dialog, and then automaticlly submits the dialog that pops up which + * causes the file to be uploaded. + * @param location Should the main room input or the thread view input be used. + */ + public async composerUploadFiles( + location: "room" | "thread", + ...params: Parameters + ): Promise { + await this.setComposerInputFiles(location, ...params); + await this.page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click(); + } + + /** + * Drags a "file" into the specified composer and automatically uploads it. + * @param location Should the drop target the main room or the thread. + * @param path The path to the sample file so it can be read. + * @param type The mimetype of the file. + */ + public async composerDragAndUploadFiles(location: "room" | "thread", path: string, type: string): Promise { + // Based on https://github.com/microsoft/playwright/issues/10667#issuecomment-2742123424 + // This read a file, encodes it into base64 and then sends it along to the page to be treated + // as a DataTransfer (the mechanism for drag and dropped files). + const buffer = await readFile(path); + const name = basename(path); + + const dataTransfer = await this.page.evaluateHandle( + async ([buffer, name, type]) => { + const dt = new DataTransfer(); + const file = new File([Uint8Array.fromBase64(buffer)], name, { + type, + }); + dt.items.add(file); + return dt; + }, + [buffer.toString("base64"), name, type], + ); + await this.page.dispatchEvent(location === "room" ? ".mx_RoomView_body" : ".mx_ThreadPanel", "drop", { + dataTransfer, + }); + await this.page.locator(".mx_Dialog").getByRole("button", { name: "Upload" }).click(); + } + /** * Returns the space panel space button based on a name. The space * must be visible in the space panel From a7ab72af111ce2861dcd950f1403cfc4faff43b8 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 30 Apr 2026 14:15:55 +0100 Subject: [PATCH 22/23] Use frozen-lockfile in Dockerfile pnpm install (#33333) --- apps/web/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 2cc7e22fdb..a29297130e 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -15,7 +15,7 @@ WORKDIR /src COPY --parents package.json pnpm-lock.yaml pnpm-workspace.yaml patches scripts **/package.json /src/ RUN corepack enable RUN --mount=type=bind,source=.git,target=/src/.git /src/scripts/docker-link-repos.sh -RUN pnpm install +RUN pnpm install --frozen-lockfile # Build COPY --link --exclude=.git --exclude=apps/web/docker . /src From c2e5aa7adce7f023ffa076f7eba7378f24f60d8b Mon Sep 17 00:00:00 2001 From: Florian Duros Date: Thu, 30 Apr 2026 16:32:43 +0200 Subject: [PATCH 23/23] Room list: add collapse/expand all sections (#33318) * chore: update compound design tokens * feat(sc): add collapse/expand button to room list header * feat: add new events to broadcast section state * feat(vm): add expand/collpase event to room list events * test: add e2e tests * chore: fix company name in copyright * chore: use two differant actions for collapse/expand * Update apps/web/src/viewmodels/room-list/RoomListHeaderViewModel.ts Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> * test: fix existing tests --------- Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- .../room-list-custom-sections.spec.ts | 41 +++++++ apps/web/src/dispatcher/actions.ts | 16 +++ ...ListSectionsCollapseStateChangedPayload.ts | 20 ++++ .../room-list/RoomListHeaderViewModel.ts | 22 ++++ .../viewmodels/room-list/RoomListViewModel.ts | 42 +++++++ .../room-list/RoomListHeaderViewModel-test.ts | 83 ++++++++++++++ .../room-list/RoomListViewModel-test.tsx | 104 ++++++++++++++++++ .../collapse-sections-auto.png | Bin 0 -> 19279 bytes .../src/i18n/strings/en_EN.json | 2 + .../RoomListHeaderView.stories.tsx | 9 ++ .../RoomListHeaderView.test.tsx | 9 +- .../RoomListHeaderView/RoomListHeaderView.tsx | 32 +++++- .../src/room-list/RoomListHeaderView/index.ts | 1 + .../RoomListHeaderView/test-utils.ts | 1 + pnpm-lock.yaml | 22 ++-- pnpm-workspace.yaml | 2 +- 16 files changed, 392 insertions(+), 14 deletions(-) create mode 100644 apps/web/src/dispatcher/payloads/RoomListSectionsCollapseStateChangedPayload.ts create mode 100644 packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx/collapse-sections-auto.png diff --git a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-custom-sections.spec.ts b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-custom-sections.spec.ts index 742b611faf..e40b1a6c0d 100644 --- a/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-custom-sections.spec.ts +++ b/apps/web/playwright/e2e/left-panel/room-list-panel/room-list-custom-sections.spec.ts @@ -258,6 +258,47 @@ test.describe("Room list custom sections", () => { }); }); + test.describe("Collapse and expand all sections", () => { + test("should collapse all sections when 'Collapse all sections' button is clicked", async ({ page, app }) => { + await app.client.createRoom({ name: "my room" }); + await createCustomSection(page, "Work"); + + const roomList = getRoomList(page); + const header = getRoomListHeader(page); + + await expect(getSectionHeader(page, "Chats")).toBeVisible(); + await expect(getSectionHeader(page, "Work")).toBeVisible(); + + const collapseButton = header.getByRole("button", { name: "Collapse all sections" }); + await expect(collapseButton).toBeVisible(); + + await expect(roomList.getByRole("row", { name: "Open room my room" })).toBeVisible(); + + await collapseButton.click(); + + await expect(getSectionHeader(page, "Chats")).toHaveAttribute("aria-expanded", "false"); + await expect(getSectionHeader(page, "Work")).toHaveAttribute("aria-expanded", "false"); + }); + + test("should expand all sections when 'Expand all sections' button is clicked", async ({ page, app }) => { + await app.client.createRoom({ name: "my room" }); + await createCustomSection(page, "Work"); + + const roomList = getRoomList(page); + const header = getRoomListHeader(page); + + await expect(getSectionHeader(page, "Chats")).toBeVisible(); + + await header.getByRole("button", { name: "Collapse all sections" }).click(); + await expect(roomList.getByRole("row", { name: "Open room my room" })).not.toBeVisible(); + + await header.getByRole("button", { name: "Expand all sections" }).click(); + + await expect(getSectionHeader(page, "Chats")).toHaveAttribute("aria-expanded", "true"); + await expect(getSectionHeader(page, "Work")).toHaveAttribute("aria-expanded", "true"); + }); + }); + test.describe("Adding a room to a custom section", () => { test("should add a room to a custom section via the More Options menu", async ({ page, app }) => { await app.client.createRoom({ name: "my room" }); diff --git a/apps/web/src/dispatcher/actions.ts b/apps/web/src/dispatcher/actions.ts index 49a3ce8868..ad19d1c7d8 100644 --- a/apps/web/src/dispatcher/actions.ts +++ b/apps/web/src/dispatcher/actions.ts @@ -403,4 +403,20 @@ export enum Action { * or keyboard event). */ UserActivity = "user_activity", + + /** + * Fired to request collapsing all room list sections. + */ + RoomListCollapseAllSections = "room_list_collapse_all_sections", + + /** + * Fired to request expanding all room list sections. + */ + RoomListExpandAllSections = "room_list_expand_all_sections", + + /** + * Fired to report the collapse state of a given room list section. + * Payload: {@link RoomListSectionsCollapseStateChangedPayload} + */ + RoomListSectionsCollapseStateChanged = "room_list_sections_collapse_state_changed", } diff --git a/apps/web/src/dispatcher/payloads/RoomListSectionsCollapseStateChangedPayload.ts b/apps/web/src/dispatcher/payloads/RoomListSectionsCollapseStateChangedPayload.ts new file mode 100644 index 0000000000..d3dbe21cf4 --- /dev/null +++ b/apps/web/src/dispatcher/payloads/RoomListSectionsCollapseStateChangedPayload.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +import { type CollapseSectionsOption } from "@element-hq/web-shared-components"; + +import { type ActionPayload } from "../payloads"; +import { type Action } from "../actions"; + +export interface RoomListSectionsCollapseStateChangedPayload extends ActionPayload { + action: Action.RoomListSectionsCollapseStateChanged; + /** + * The new collapse state for the room list sections. + * If undefined, the feature is disabled. + */ + collapseSections?: CollapseSectionsOption; +} diff --git a/apps/web/src/viewmodels/room-list/RoomListHeaderViewModel.ts b/apps/web/src/viewmodels/room-list/RoomListHeaderViewModel.ts index 55626cb252..2e48532078 100644 --- a/apps/web/src/viewmodels/room-list/RoomListHeaderViewModel.ts +++ b/apps/web/src/viewmodels/room-list/RoomListHeaderViewModel.ts @@ -26,6 +26,7 @@ import { showSpaceSettings, } from "../../utils/space"; import type { ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; +import type { RoomListSectionsCollapseStateChangedPayload } from "../../dispatcher/payloads/RoomListSectionsCollapseStateChangedPayload"; import SettingsStore from "../../settings/SettingsStore"; import RoomListStoreV3 from "../../stores/room-list-v3/RoomListStoreV3"; import { SortingAlgorithm } from "../../stores/room-list-v3/skip-list/sorters"; @@ -77,6 +78,10 @@ export class RoomListHeaderViewModel if (this.activeSpace) { this.disposables.trackListener(this.activeSpace, RoomEvent.Name, this.onSpaceNameChange); } + + // Listen for section collapse state changes from RoomListViewModel + const dispatcherRef = defaultDispatcher.register(this.onDispatch); + this.disposables.track(() => defaultDispatcher.unregister(dispatcherRef)); } /** @@ -203,6 +208,23 @@ export class RoomListHeaderViewModel public createSection = (): void => { RoomListStoreV3.instance.createSection(); }; + + public collapseOrExpandSections = (): void => { + const action = + this.snapshot.current.collapseSections === "expand" + ? Action.RoomListExpandAllSections + : Action.RoomListCollapseAllSections; + defaultDispatcher.fire(action); + }; + + private readonly onDispatch = (payload: { action: string }): void => { + if (payload.action === Action.RoomListSectionsCollapseStateChanged) { + const { collapseSections } = payload as RoomListSectionsCollapseStateChangedPayload; + this.snapshot.merge({ + collapseSections: collapseSections && (collapseSections === "collapse" ? "expand" : "collapse"), + }); + } + }; } /** * Get the initial snapshot for the RoomListHeaderViewModel. diff --git a/apps/web/src/viewmodels/room-list/RoomListViewModel.ts b/apps/web/src/viewmodels/room-list/RoomListViewModel.ts index e711e340b0..6e28e61ed7 100644 --- a/apps/web/src/viewmodels/room-list/RoomListViewModel.ts +++ b/apps/web/src/viewmodels/room-list/RoomListViewModel.ts @@ -21,6 +21,7 @@ import { Action } from "../../dispatcher/actions"; import dispatcher from "../../dispatcher/dispatcher"; import { type ViewRoomDeltaPayload } from "../../dispatcher/payloads/ViewRoomDeltaPayload"; import { type ViewRoomPayload } from "../../dispatcher/payloads/ViewRoomPayload"; +import { type RoomListSectionsCollapseStateChangedPayload } from "../../dispatcher/payloads/RoomListSectionsCollapseStateChangedPayload"; import SpaceStore from "../../stores/spaces/SpaceStore"; import RoomListStoreV3, { CHATS_TAG, @@ -325,9 +326,24 @@ export class RoomListViewModel // Handle keyboard navigation shortcuts (Alt+ArrowUp/Down) // This was previously handled by useRoomListNavigation hook this.handleViewRoomDelta(payload as ViewRoomDeltaPayload); + } else if (payload.action === Action.RoomListCollapseAllSections) { + this.onCollapseAllSections(false); + } else if (payload.action === Action.RoomListExpandAllSections) { + this.onCollapseAllSections(true); } }; + /** + * Handles the collapse or expansion of all sections in the room list. + * @param expand - Whether to expand or collapse all sections + */ + private onCollapseAllSections(expand: boolean): void { + for (const sectionHeaderVM of this.roomSectionHeaderViewModels.values()) { + sectionHeaderVM.isExpanded = expand; + } + this.updateRoomListData(); + } + /** * Handle keyboard navigation shortcuts (Alt+ArrowUp/Down) to move between rooms. * Supports both regular navigation and unread-only navigation. @@ -581,6 +597,32 @@ export class RoomListViewModel sections: keepIfSame(previousSections, viewSections), isFlatList, }); + + this.notifyCollapseState(isFlatList); + } + + /** + * Notify the dispatcher about the current collapse state of the room list sections. + * @param isFlatList - Whether the room list is currently displayed as a flat list + */ + private notifyCollapseState(isFlatList: boolean): void { + // Hide collapse/expand all button if sections are disabled or if it's a flat list + if (!SettingsStore.getValue("feature_room_list_sections") || isFlatList) { + dispatcher.dispatch({ + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: undefined, + }); + return; + } + + // Determine if all sections are currently collapsed + const allCollapsed = this.snapshot.current.sections.every( + ({ id }) => !(this.roomSectionHeaderViewModels.get(id)?.isExpanded ?? true), + ); + dispatcher.dispatch({ + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: allCollapsed ? "collapse" : "expand", + }); } public createChatRoom = (): void => { diff --git a/apps/web/test/viewmodels/room-list/RoomListHeaderViewModel-test.ts b/apps/web/test/viewmodels/room-list/RoomListHeaderViewModel-test.ts index 53f76d884f..e4afd6e7b8 100644 --- a/apps/web/test/viewmodels/room-list/RoomListHeaderViewModel-test.ts +++ b/apps/web/test/viewmodels/room-list/RoomListHeaderViewModel-test.ts @@ -323,6 +323,89 @@ describe("RoomListHeaderViewModel", () => { expect(createSectionSpy).toHaveBeenCalled(); }); + describe("collapseOrExpandSections", () => { + it("should dispatch RoomListCollapseAllSections when collapseSections is not 'expand'", () => { + const fireSpy = jest.spyOn(defaultDispatcher, "fire"); + vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance }); + + vm.collapseOrExpandSections(); + + expect(fireSpy).toHaveBeenCalledWith(Action.RoomListCollapseAllSections); + }); + + it("should dispatch RoomListExpandAllSections when collapseSections is 'expand'", () => { + const fireSpy = jest.spyOn(defaultDispatcher, "fire"); + vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance }); + + // Drive the VM into the "expand" state by simulating all sections collapsed + defaultDispatcher.dispatch( + { + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: "collapse", + }, + true, + ); + expect(vm.getSnapshot().collapseSections).toBe("expand"); + vm.collapseOrExpandSections(); + + expect(fireSpy).toHaveBeenCalledWith(Action.RoomListExpandAllSections); + }); + }); + + describe("RoomListSectionsCollapseStateChanged handling", () => { + it("should set collapseSections to 'expand' when collapseSections is collapse", () => { + vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance }); + + defaultDispatcher.dispatch( + { + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: "collapse", + }, + true, + ); + + expect(vm.getSnapshot().collapseSections).toBe("expand"); + }); + + it("should set collapseSections to 'collapse' when collapseSections is expand", () => { + vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance }); + + defaultDispatcher.dispatch( + { + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: "expand", + }, + true, + ); + + expect(vm.getSnapshot().collapseSections).toBe("collapse"); + }); + + it("should set collapseSections to undefined when collapseSections is undefined", () => { + vm = new RoomListHeaderViewModel({ matrixClient, spaceStore: SpaceStore.instance }); + + // First drive it into a non-undefined state + defaultDispatcher.dispatch( + { + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: "collapse", + }, + true, + ); + expect(vm.getSnapshot().collapseSections).toBe("expand"); + + defaultDispatcher.dispatch( + { + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: undefined, + }, + true, + ); + + expect(vm.getSnapshot().collapseSections).toBeUndefined(); + }); + }); + it("should toggle message preview from enabled to disabled", () => { jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName: string) => { if (settingName === "RoomList.showMessagePreview") return true; diff --git a/apps/web/test/viewmodels/room-list/RoomListViewModel-test.tsx b/apps/web/test/viewmodels/room-list/RoomListViewModel-test.tsx index c0eacbdf35..c3fc431646 100644 --- a/apps/web/test/viewmodels/room-list/RoomListViewModel-test.tsx +++ b/apps/web/test/viewmodels/room-list/RoomListViewModel-test.tsx @@ -465,6 +465,20 @@ describe("RoomListViewModel", () => { }); }); + describe("notifyCollapseState", () => { + it("should dispatch collapseSections=undefined when feature_room_list_sections is disabled", () => { + viewModel = new RoomListViewModel({ client: matrixClient }); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: undefined, + }); + }); + }); + describe("Keyboard navigation (ViewRoomDelta)", () => { beforeEach(() => { // stubClient sets up MatrixClientPeg which is needed when ViewRoom action is dispatched @@ -971,6 +985,96 @@ describe("RoomListViewModel", () => { expect(favSection!.roomIds).toEqual(["!fav1:server"]); }); + describe("Collapse/expand all sections", () => { + it("should collapse all sections when Action.RoomListCollapseAllSections is dispatched", async () => { + viewModel = new RoomListViewModel({ client: matrixClient }); + + const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite); + const chatsHeader = viewModel.getSectionHeaderViewModel(CHATS_TAG); + expect(favHeader.isExpanded).toBe(true); + + dispatcher.dispatch({ action: Action.RoomListCollapseAllSections }); + await flushPromisesWithFakeTimers(); + + expect(favHeader.isExpanded).toBe(false); + expect(chatsHeader.isExpanded).toBe(false); + + const snapshot = viewModel.getSnapshot(); + expect(snapshot.sections.find((s) => s.id === DefaultTagID.Favourite)!.roomIds).toEqual([]); + expect(snapshot.sections.find((s) => s.id === CHATS_TAG)!.roomIds).toEqual([]); + }); + + it("should expand all sections when Action.RoomListExpandAllSections is dispatched", async () => { + viewModel = new RoomListViewModel({ client: matrixClient }); + + // Collapse first + const favHeader = viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite); + favHeader.onClick(); + expect(favHeader.isExpanded).toBe(false); + + dispatcher.dispatch({ action: Action.RoomListExpandAllSections }); + await flushPromisesWithFakeTimers(); + + expect(favHeader.isExpanded).toBe(true); + const snapshot = viewModel.getSnapshot(); + expect(snapshot.sections.find((s) => s.id === DefaultTagID.Favourite)!.roomIds).toEqual([ + "!fav1:server", + "!fav2:server", + ]); + }); + }); + + describe("notifyCollapseState", () => { + it("should dispatch collapseSections=expand when all sections are expanded (default)", () => { + viewModel = new RoomListViewModel({ client: matrixClient }); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: "expand", + }); + }); + + it("should dispatch collapseSection=collapse when all sections are collapsed", () => { + viewModel = new RoomListViewModel({ client: matrixClient }); + + // Collapse all sections + viewModel.getSectionHeaderViewModel(DefaultTagID.Favourite).isExpanded = false; + viewModel.getSectionHeaderViewModel(CHATS_TAG).isExpanded = false; + viewModel.getSectionHeaderViewModel(DefaultTagID.LowPriority).isExpanded = false; + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: "collapse", + }); + }); + + it("should dispatch collapseSection=undefined when it is a flat list", () => { + jest.spyOn(RoomListStoreV3.instance, "getSortedRoomsInActiveSpace").mockReturnValue({ + spaceId: "home", + sections: [ + { tag: DefaultTagID.Favourite, rooms: [] }, + { tag: CHATS_TAG, rooms: [regularRoom1] }, + { tag: DefaultTagID.LowPriority, rooms: [] }, + ], + }); + viewModel = new RoomListViewModel({ client: matrixClient }); + + const dispatchSpy = jest.spyOn(dispatcher, "dispatch"); + RoomListStoreV3.instance.emit(RoomListStoreV3Event.ListsUpdate); + + expect(dispatchSpy).toHaveBeenCalledWith({ + action: Action.RoomListSectionsCollapseStateChanged, + collapseSections: undefined, + }); + }); + }); + it("should apply sticky room within the correct section", async () => { stubClient(); viewModel = new RoomListViewModel({ client: matrixClient }); diff --git a/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx/collapse-sections-auto.png b/packages/shared-components/__vis__/linux/__baselines__/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx/collapse-sections-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..333171f50e10c00665670a37a1349e78afdfdb95 GIT binary patch literal 19279 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≤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 literal 0 HcmV?d00001 diff --git a/packages/shared-components/src/i18n/strings/en_EN.json b/packages/shared-components/src/i18n/strings/en_EN.json index d747318b37..fe6d4e0865 100644 --- a/packages/shared-components/src/i18n/strings/en_EN.json +++ b/packages/shared-components/src/i18n/strings/en_EN.json @@ -100,6 +100,7 @@ }, "appearance": "Appearance", "chat_moved": "Chat moved", + "collapse_all_sections": "Collapse all sections", "collapse_filters": "Collapse filter list", "empty": { "no_chats": "No chats yet", @@ -118,6 +119,7 @@ "show_activity": "See all activity", "show_chats": "Show all chats" }, + "expand_all_sections": "Expand all sections", "expand_filters": "Expand filter list", "filters": { "favourite": "Favourites", diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx index 83551bc21b..d622503c1d 100644 --- a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx +++ b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.stories.tsx @@ -31,6 +31,7 @@ const RoomListHeaderViewWrapperImpl = ({ sort, toggleMessagePreview, createSection, + collapseOrExpandSections, ...rest }: RoomListHeaderProps): JSX.Element => { const vm = useMockedViewModel(rest, { @@ -44,6 +45,7 @@ const RoomListHeaderViewWrapperImpl = ({ openSpacePreferences, toggleMessagePreview, createSection, + collapseOrExpandSections, }); return ; }; @@ -65,6 +67,7 @@ const meta = { openSpacePreferences: fn(), toggleMessagePreview: fn(), createSection: fn(), + collapseOrExpandSections: fn(), }, parameters: { design: { @@ -109,3 +112,9 @@ export const PlusIcon: Story = { useComposeIcon: false, }, }; + +export const CollapseSections: Story = { + args: { + collapseSections: "collapse", + }, +}; diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.test.tsx b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.test.tsx index 48904171cd..d6ce1e9e3f 100644 --- a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.test.tsx +++ b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.test.tsx @@ -12,7 +12,7 @@ import React from "react"; import * as stories from "./RoomListHeaderView.stories"; -const { Default, NoComposeMenu, NoSpaceMenu } = composeStories(stories); +const { Default, NoComposeMenu, NoSpaceMenu, CollapseSections } = composeStories(stories); describe("RoomListHeaderView", () => { it("renders the default state", () => { @@ -29,4 +29,11 @@ describe("RoomListHeaderView", () => { const { container } = render(); expect(container).toMatchSnapshot(); }); + + it("should bind the collapse all sections action", () => { + const { getByRole } = render(); + const collapseButton = getByRole("button", { name: "Collapse all sections" }); + collapseButton.click(); + expect(CollapseSections.args?.collapseOrExpandSections).toHaveBeenCalled(); + }); }); diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.tsx b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.tsx index 05254899ea..d68fd25b0e 100644 --- a/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.tsx +++ b/packages/shared-components/src/room-list/RoomListHeaderView/RoomListHeaderView.tsx @@ -9,6 +9,7 @@ import React, { type JSX } from "react"; import { IconButton, H1 } from "@vector-im/compound-web"; import ComposeIcon from "@vector-im/compound-design-tokens/assets/web/icons/compose"; import PlusIcon from "@vector-im/compound-design-tokens/assets/web/icons/plus"; +import CollapseAllIcon from "@vector-im/compound-design-tokens/assets/web/icons/collapse-all"; import { type ViewModel, useViewModel } from "../../core/viewmodel"; import { Flex } from "../../core/utils/Flex"; @@ -21,6 +22,11 @@ import styles from "./RoomListHeaderView.module.css"; */ export type SortOption = "recent" | "alphabetical" | "unread-first"; +/** + * The available options for collapsing sections in the room list. + */ +export type CollapseSectionsOption = "collapse" | "expand"; + export interface RoomListHeaderViewSnapshot { /** * The title of the room list @@ -68,6 +74,12 @@ export interface RoomListHeaderViewSnapshot { * Whether to use the compose icon instead of the create icon. */ useComposeIcon: boolean; + /** + * If "collapse", an icon to collapse all sections is shown. + * If "expand", an icon to expand all sections is shown. + * If undefined, no icon are shown. + */ + collapseSections?: CollapseSectionsOption; } export interface RoomListHeaderViewActions { @@ -111,6 +123,10 @@ export interface RoomListHeaderViewActions { * Create a new section in the room list. */ createSection: () => void; + /** + * Collapse or expand all sections in the room list depending on the current state. + */ + collapseOrExpandSections: () => void; } /** @@ -136,7 +152,7 @@ interface RoomListHeaderViewProps { */ export function RoomListHeaderView({ vm }: Readonly): JSX.Element { const { translate: _t } = useI18n(); - const { title, displaySpaceMenu, displayComposeMenu, useComposeIcon } = useViewModel(vm); + const { title, displaySpaceMenu, displayComposeMenu, useComposeIcon, collapseSections } = useViewModel(vm); return ( ): J + {collapseSections && ( + vm.collapseOrExpandSections()} + tooltip={ + collapseSections === "collapse" + ? _t("room_list|collapse_all_sections") + : _t("room_list|expand_all_sections") + } + > + + + )} {/* If we don't display the compose menu, it means that the user can only send DM */} {displayComposeMenu ? ( diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/index.ts b/packages/shared-components/src/room-list/RoomListHeaderView/index.ts index a0b6edee11..9e7bf46c2b 100644 --- a/packages/shared-components/src/room-list/RoomListHeaderView/index.ts +++ b/packages/shared-components/src/room-list/RoomListHeaderView/index.ts @@ -10,5 +10,6 @@ export type { RoomListHeaderViewSnapshot, RoomListHeaderViewActions, SortOption, + CollapseSectionsOption, } from "./RoomListHeaderView"; export { RoomListHeaderView } from "./RoomListHeaderView"; diff --git a/packages/shared-components/src/room-list/RoomListHeaderView/test-utils.ts b/packages/shared-components/src/room-list/RoomListHeaderView/test-utils.ts index 37b53af6f3..70c51c45c8 100644 --- a/packages/shared-components/src/room-list/RoomListHeaderView/test-utils.ts +++ b/packages/shared-components/src/room-list/RoomListHeaderView/test-utils.ts @@ -24,6 +24,7 @@ export class MockedViewModel extends MockViewModel i public openSpacePreferences = vi.fn<() => void>(); public toggleMessagePreview = vi.fn<() => void>(); public createSection = vi.fn<() => void>(); + public collapseOrExpandSections = vi.fn<() => void>(); } export { defaultSnapshot } from "./default-snapshot"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92878b13f0..841534b316 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,8 +13,8 @@ catalogs: specifier: 1.59.1 version: 1.59.1 '@vector-im/compound-design-tokens': - specifier: 10.1.0 - version: 10.1.0 + specifier: 10.1.1 + version: 10.1.1 '@vector-im/compound-web': specifier: 9.2.1 version: 9.2.1 @@ -374,10 +374,10 @@ importers: version: 1.0.2 '@vector-im/compound-design-tokens': specifier: 'catalog:' - version: 10.1.0(@types/react@19.2.14)(react@19.2.5) + version: 10.1.1(@types/react@19.2.14)(react@19.2.5) '@vector-im/compound-web': specifier: 'catalog:' - version: 9.2.1(@fontsource/inconsolata@5.2.8)(@fontsource/inter@5.2.8)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vector-im/compound-design-tokens@10.1.0(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 9.2.1(@fontsource/inconsolata@5.2.8)(@fontsource/inter@5.2.8)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vector-im/compound-design-tokens@10.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@vector-im/matrix-wysiwyg': specifier: 2.40.0 version: 2.40.0(patch_hash=7bdf6150f2905bc2f055a6bcaa7b9d78fa7ffde82e800bcc454ac7b0096bd65e)(react@19.2.5) @@ -1043,7 +1043,7 @@ importers: version: 1.16.0 '@vector-im/compound-design-tokens': specifier: 'catalog:' - version: 10.1.0(@types/react@19.2.14)(react@19.2.5) + version: 10.1.1(@types/react@19.2.14)(react@19.2.5) classnames: specifier: ^2.5.1 version: 2.5.1 @@ -1158,7 +1158,7 @@ importers: version: 8.58.2(eslint@8.57.1)(typescript@6.0.3) '@vector-im/compound-web': specifier: 'catalog:' - version: 9.2.1(@fontsource/inconsolata@5.2.8)(@fontsource/inter@5.2.8)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vector-im/compound-design-tokens@10.1.0(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) + version: 9.2.1(@fontsource/inconsolata@5.2.8)(@fontsource/inter@5.2.8)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vector-im/compound-design-tokens@10.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@vitest/browser-playwright': specifier: ^4.0.17 version: 4.1.5(playwright@1.59.1)(vite@8.0.10(@types/node@18.19.130)(esbuild@0.27.4)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.10))(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@4.1.5) @@ -5904,8 +5904,8 @@ packages: '@upsetjs/venn.js@2.0.0': resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} - '@vector-im/compound-design-tokens@10.1.0': - resolution: {integrity: sha512-o+7DGx+NygpT2NPE1Jo//7NZDuyjzRH06eRchS0ZlkJicKx/impEmShmHU/XiE4P84BIFOo9eZ1Ws+rAym6Tuw==} + '@vector-im/compound-design-tokens@10.1.1': + resolution: {integrity: sha512-f2rdTilbPeOjrX7Mh9iTPcp5VergY7JLLWzKVjwMvpT0wtoFKwn59D1hwX2QInpiG70QTCxEdQFYLxQKvJQ74Q==} peerDependencies: '@types/react': ^19.2.10 react: ^17 || ^18 || ^19.0.0 @@ -18677,12 +18677,12 @@ snapshots: d3-selection: 3.0.0 d3-transition: 3.0.1(d3-selection@3.0.0) - '@vector-im/compound-design-tokens@10.1.0(@types/react@19.2.14)(react@19.2.5)': + '@vector-im/compound-design-tokens@10.1.1(@types/react@19.2.14)(react@19.2.5)': optionalDependencies: '@types/react': 19.2.14 react: 19.2.5 - '@vector-im/compound-web@9.2.1(@fontsource/inconsolata@5.2.8)(@fontsource/inter@5.2.8)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vector-im/compound-design-tokens@10.1.0(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + '@vector-im/compound-web@9.2.1(@fontsource/inconsolata@5.2.8)(@fontsource/inter@5.2.8)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(@vector-im/compound-design-tokens@10.1.1(@types/react@19.2.14)(react@19.2.5))(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: '@floating-ui/react': 0.27.17(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@fontsource/inconsolata': 5.2.8 @@ -18693,7 +18693,7 @@ snapshots: '@radix-ui/react-progress': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@radix-ui/react-separator': 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.5) - '@vector-im/compound-design-tokens': 10.1.0(@types/react@19.2.14)(react@19.2.5) + '@vector-im/compound-design-tokens': 10.1.1(@types/react@19.2.14)(react@19.2.5) classnames: 2.5.1 react: 19.2.5 vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c2e8a9c23a..6da463bce7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,7 +16,7 @@ catalog: "@playwright/test": 1.59.1 "playwright-core": 1.59.1 # Compound - "@vector-im/compound-design-tokens": 10.1.0 + "@vector-im/compound-design-tokens": 10.1.1 "@vector-im/compound-web": 9.2.1 # i18n matrix-web-i18n: 3.6.0