From 3f21023fc160074d96b608757069a8376b76187f Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Tue, 16 Dec 2025 18:11:55 +0100 Subject: [PATCH 01/23] chore: update vitest/testcontainer versions, try to fix container teardown on macos (@fehmer) (#7248) Co-authored-by: Miodec --- .../__tests__/__integration__/global-setup.ts | 9 +- backend/package.json | 6 +- frontend/package.json | 4 +- package.json | 4 +- packages/contracts/package.json | 2 +- packages/funbox/package.json | 2 +- packages/schemas/package.json | 2 +- packages/util/package.json | 2 +- pnpm-lock.yaml | 479 +++++++++--------- 9 files changed, 265 insertions(+), 245 deletions(-) diff --git a/backend/__tests__/__integration__/global-setup.ts b/backend/__tests__/__integration__/global-setup.ts index 2aac2acd5..659b9a6fe 100644 --- a/backend/__tests__/__integration__/global-setup.ts +++ b/backend/__tests__/__integration__/global-setup.ts @@ -31,7 +31,14 @@ export async function setup(): Promise { process.env["REDIS_URI"] = redisUrl; } -export async function teardown(): Promise { +async function stopContainers(): Promise { await startedMongoContainer?.stop(); await startedRedisContainer?.stop(); } + +export async function teardown(): Promise { + await stopContainers(); +} + +process.on("SIGTERM", stopContainers); +process.on("SIGINT", stopContainers); diff --git a/backend/package.json b/backend/package.json index c6f6bf79d..4e03ce68e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -77,17 +77,17 @@ "@types/swagger-stats": "0.95.11", "@types/ua-parser-js": "0.7.36", "@types/uuid": "10.0.0", - "@vitest/coverage-v8": "4.0.8", + "@vitest/coverage-v8": "4.0.15", "concurrently": "8.2.2", "openapi3-ts": "2.0.2", "oxlint": "1.33.0", "oxlint-tsgolint": "0.9.0", "readline-sync": "1.4.10", "supertest": "7.1.4", - "testcontainers": "11.4.0", + "testcontainers": "11.10.0", "tsx": "4.16.2", "typescript": "5.9.3", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "engines": { "node": "24.11.0 || 22.21.0" diff --git a/frontend/package.json b/frontend/package.json index 533fd1ddc..0bd6fc1c3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -78,7 +78,7 @@ "@types/object-hash": "3.0.6", "@types/subset-font": "1.4.3", "@types/throttle-debounce": "5.0.2", - "@vitest/coverage-v8": "4.0.8", + "@vitest/coverage-v8": "4.0.15", "autoprefixer": "10.4.20", "concurrently": "8.2.2", "eslint": "9.39.1", @@ -105,7 +105,7 @@ "vite-plugin-inspect": "11.3.3", "vite-plugin-minify": "2.1.0", "vite-plugin-pwa": "1.1.0", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "browserslist": [ "defaults", diff --git a/package.json b/package.json index 20f4594d8..f4b60cecf 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@commitlint/cli": "17.7.1", "@commitlint/config-conventional": "19.2.2", "@monkeytype/release": "workspace:*", - "@vitest/coverage-v8": "4.0.8", + "@vitest/coverage-v8": "4.0.15", "conventional-changelog": "6.0.0", "husky": "8.0.1", "knip": "2.19.2", @@ -77,7 +77,7 @@ "oxlint-tsgolint": "0.9.0", "prettier": "3.7.1", "turbo": "2.5.6", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "packageManager": "pnpm@9.6.0", "engines": { diff --git a/packages/contracts/package.json b/packages/contracts/package.json index d8ff23c92..4dc852c86 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -33,7 +33,7 @@ "oxlint-tsgolint": "0.9.0", "tsup": "8.4.0", "typescript": "5.9.3", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "peerDependencies": { "@ts-rest/core": "3.52.1", diff --git a/packages/funbox/package.json b/packages/funbox/package.json index 7cac67bf0..815dd4497 100644 --- a/packages/funbox/package.json +++ b/packages/funbox/package.json @@ -29,6 +29,6 @@ "oxlint-tsgolint": "0.9.0", "tsup": "8.4.0", "typescript": "5.9.3", - "vitest": "4.0.8" + "vitest": "4.0.15" } } diff --git a/packages/schemas/package.json b/packages/schemas/package.json index a4cb08fde..684530263 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -30,7 +30,7 @@ "oxlint-tsgolint": "0.9.0", "tsup": "8.4.0", "typescript": "5.9.3", - "vitest": "4.0.8" + "vitest": "4.0.15" }, "peerDependencies": { "zod": "3.23.8" diff --git a/packages/util/package.json b/packages/util/package.json index 4e66ba5c2..3a2c6c8a7 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -24,7 +24,7 @@ "oxlint-tsgolint": "0.9.0", "tsup": "8.4.0", "typescript": "5.9.3", - "vitest": "4.0.8", + "vitest": "4.0.15", "zod": "3.23.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e6f380118..0577166a4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ importers: specifier: workspace:* version: link:packages/release '@vitest/coverage-v8': - specifier: 4.0.8 - version: 4.0.8(vitest@4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + specifier: 4.0.15 + version: 4.0.15(vitest@4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) conventional-changelog: specifier: 6.0.0 version: 6.0.0(conventional-commits-filter@5.0.0) @@ -51,8 +51,8 @@ importers: specifier: 2.5.6 version: 2.5.6 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) backend: dependencies: @@ -223,8 +223,8 @@ importers: specifier: 10.0.0 version: 10.0.0 '@vitest/coverage-v8': - specifier: 4.0.8 - version: 4.0.8(vitest@4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + specifier: 4.0.15 + version: 4.0.15(vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) concurrently: specifier: 8.2.2 version: 8.2.2 @@ -244,8 +244,8 @@ importers: specifier: 7.1.4 version: 7.1.4 testcontainers: - specifier: 11.4.0 - version: 11.4.0 + specifier: 11.10.0 + version: 11.10.0 tsx: specifier: 4.16.2 version: 4.16.2 @@ -253,8 +253,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) frontend: dependencies: @@ -398,8 +398,8 @@ importers: specifier: 5.0.2 version: 5.0.2 '@vitest/coverage-v8': - specifier: 4.0.8 - version: 4.0.8(vitest@4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + specifier: 4.0.15 + version: 4.0.15(vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) autoprefixer: specifier: 10.4.20 version: 10.4.20(postcss@8.4.31) @@ -479,8 +479,8 @@ importers: specifier: 1.1.0 version: 1.1.0(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))(workbox-build@7.1.1)(workbox-window@7.1.0) vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) packages/contracts: dependencies: @@ -516,8 +516,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) packages/funbox: dependencies: @@ -550,8 +550,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) packages/oxlint-config: {} @@ -605,8 +605,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) packages/tsup-config: dependencies: @@ -653,8 +653,8 @@ importers: specifier: 5.9.3 version: 5.9.3 vitest: - specifier: 4.0.8 - version: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + specifier: 4.0.15 + version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) zod: specifier: 3.23.8 version: 3.23.8 @@ -3218,8 +3218,8 @@ packages: '@types/docker-modem@3.0.6': resolution: {integrity: sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==} - '@types/dockerode@3.3.42': - resolution: {integrity: sha512-U1jqHMShibMEWHdxYhj3rCMNCiLx5f35i4e3CEUuW+JSSszc/tVqc6WCAPdhwBymG5R/vgbcceagK0St7Cq6Eg==} + '@types/dockerode@3.3.47': + resolution: {integrity: sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw==} '@types/estree@0.0.39': resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} @@ -3403,20 +3403,20 @@ packages: resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} engines: {node: ^18.18.0 || >=20.0.0} - '@vitest/coverage-v8@4.0.8': - resolution: {integrity: sha512-wQgmtW6FtPNn4lWUXi8ZSYLpOIb92j3QCujxX3sQ81NTfQ/ORnE0HtK7Kqf2+7J9jeveMGyGyc4NWc5qy3rC4A==} + '@vitest/coverage-v8@4.0.15': + resolution: {integrity: sha512-FUJ+1RkpTFW7rQITdgTi93qOCWJobWhBirEPCeXh2SW2wsTlFxy51apDz5gzG+ZEYt/THvWeNmhdAoS9DTwpCw==} peerDependencies: - '@vitest/browser': 4.0.8 - vitest: 4.0.8 + '@vitest/browser': 4.0.15 + vitest: 4.0.15 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.0.8': - resolution: {integrity: sha512-Rv0eabdP/xjAHQGr8cjBm+NnLHNoL268lMDK85w2aAGLFoVKLd8QGnVon5lLtkXQCoYaNL0wg04EGnyKkkKhPA==} + '@vitest/expect@4.0.15': + resolution: {integrity: sha512-Gfyva9/GxPAWXIWjyGDli9O+waHDC0Q0jaLdFP1qPAUUfo1FEXPXUfUkp3eZA0sSq340vPycSyOlYUeM15Ft1w==} - '@vitest/mocker@4.0.8': - resolution: {integrity: sha512-9FRM3MZCedXH3+pIh+ME5Up2NBBHDq0wqwhOKkN4VnvCiKbVxddqH9mSGPZeawjd12pCOGnl+lo/ZGHt0/dQSg==} + '@vitest/mocker@4.0.15': + resolution: {integrity: sha512-CZ28GLfOEIFkvCFngN8Sfx5h+Se0zN+h4B7yOsPVCcgtiO7t5jt9xQh2E1UkFep+eb9fjyMfuC5gBypwb07fvQ==} peerDependencies: msw: ^2.4.9 vite: ^6.0.0 || ^7.0.0-0 @@ -3426,20 +3426,20 @@ packages: vite: optional: true - '@vitest/pretty-format@4.0.8': - resolution: {integrity: sha512-qRrjdRkINi9DaZHAimV+8ia9Gq6LeGz2CgIEmMLz3sBDYV53EsnLZbJMR1q84z1HZCMsf7s0orDgZn7ScXsZKg==} + '@vitest/pretty-format@4.0.15': + resolution: {integrity: sha512-SWdqR8vEv83WtZcrfLNqlqeQXlQLh2iilO1Wk1gv4eiHKjEzvgHb2OVc3mIPyhZE6F+CtfYjNlDJwP5MN6Km7A==} - '@vitest/runner@4.0.8': - resolution: {integrity: sha512-mdY8Sf1gsM8hKJUQfiPT3pn1n8RF4QBcJYFslgWh41JTfrK1cbqY8whpGCFzBl45LN028g0njLCYm0d7XxSaQQ==} + '@vitest/runner@4.0.15': + resolution: {integrity: sha512-+A+yMY8dGixUhHmNdPUxOh0la6uVzun86vAbuMT3hIDxMrAOmn5ILBHm8ajrqHE0t8R9T1dGnde1A5DTnmi3qw==} - '@vitest/snapshot@4.0.8': - resolution: {integrity: sha512-Nar9OTU03KGiubrIOFhcfHg8FYaRaNT+bh5VUlNz8stFhCZPNrJvmZkhsr1jtaYvuefYFwK2Hwrq026u4uPWCw==} + '@vitest/snapshot@4.0.15': + resolution: {integrity: sha512-A7Ob8EdFZJIBjLjeO0DZF4lqR6U7Ydi5/5LIZ0xcI+23lYlsYJAfGn8PrIWTYdZQRNnSRlzhg0zyGu37mVdy5g==} - '@vitest/spy@4.0.8': - resolution: {integrity: sha512-nvGVqUunyCgZH7kmo+Ord4WgZ7lN0sOULYXUOYuHr55dvg9YvMz3izfB189Pgp28w0vWFbEEfNc/c3VTrqrXeA==} + '@vitest/spy@4.0.15': + resolution: {integrity: sha512-+EIjOJmnY6mIfdXtE/bnozKEvTC4Uczg19yeZ2vtCz5Yyb0QQ31QWVQ8hswJ3Ysx/K2EqaNsVanjr//2+P3FHw==} - '@vitest/utils@4.0.8': - resolution: {integrity: sha512-pdk2phO5NDvEFfUTxcTP8RFYjVj/kfLSPIN5ebP2Mu9kcIMeAQTbknqcFEyBcC4z2pJlJI9aS5UQjcYfhmKAow==} + '@vitest/utils@4.0.15': + resolution: {integrity: sha512-HXjPW2w5dxhTD0dLwtYHDnelK3j8sR8cWIaLxr22evTyY6q8pRCjZSmhRWVjBaOVXChQd6AwMzi9pucorXCPZA==} '@vue/compiler-core@3.4.37': resolution: {integrity: sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==} @@ -3756,9 +3756,6 @@ packages: balloon-css@1.2.0: resolution: {integrity: sha512-urXwkHgwp6GsXVF+it01485Z2Cj4pnW02ICnM0TemOlkKmCNnDLmyy+ZZiRXBpwldUXO+aRNr7Hdia4CBvXJ5A==} - bare-events@2.4.2: - resolution: {integrity: sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==} - bare-events@2.6.0: resolution: {integrity: sha512-EKZ5BTXYExaNqi3I3f9RtEsaI/xBSGjE0XZCZilPzFAV/goswFHuPd9jEZlPIZ/iNZJwDSao9qRiScySz7MbQg==} @@ -3792,8 +3789,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.5: - resolution: {integrity: sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA==} + baseline-browser-mapping@2.9.7: + resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==} hasBin: true basic-auth-connect@1.0.0: @@ -3873,8 +3870,13 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + browserslist@4.24.4: + resolution: {integrity: sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -3983,14 +3985,17 @@ packages: caniuse-lite@1.0.30001649: resolution: {integrity: sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==} - caniuse-lite@1.0.30001759: - resolution: {integrity: sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==} + caniuse-lite@1.0.30001715: + resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==} + + caniuse-lite@1.0.30001760: + resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} canvas-confetti@1.5.1: resolution: {integrity: sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==} - chai@6.2.0: - resolution: {integrity: sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA==} + chai@6.2.1: + resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} chalk@4.1.2: @@ -4001,10 +4006,6 @@ packages: resolution: {integrity: sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} @@ -4705,16 +4706,16 @@ packages: disposable-email-domains-js@1.16.0: resolution: {integrity: sha512-5BTVWEfUO1xeJ/jQdt+bIeOFYlgKuN5wGUtiMlkj0rqqpeO8LwiIH+7sKYvob87yMBjDKJWD8eqSSHFVM0g2pQ==} - docker-compose@1.2.0: - resolution: {integrity: sha512-wIU1eHk3Op7dFgELRdmOYlPYS4gP8HhH1ZmZa13QZF59y0fblzFDFmKPhyc05phCy2hze9OEvNZAsoljrs+72w==} + docker-compose@1.3.0: + resolution: {integrity: sha512-7Gevk/5eGD50+eMD+XDnFnOrruFkL0kSd7jEG4cjmqweDSUhB7i0g8is/nBdVpl+Bx338SqIB2GLKm32M+Vs6g==} engines: {node: '>= 6.0.0'} docker-modem@5.0.6: resolution: {integrity: sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==} engines: {node: '>= 8.0'} - dockerode@4.0.7: - resolution: {integrity: sha512-R+rgrSRTRdU5mH14PZTCPZtW/zw3HDWNTS/1ZAQpL/5Upe/ye5K9WQkIysu4wBoiMwKynsz0a8qWuGsHgEvSAA==} + dockerode@4.0.9: + resolution: {integrity: sha512-iND4mcOWhPaCNh54WmK/KoSb35AFqPAUWFMffTQcp52uQt36b5uNwEJTSXntJZBbeGad72Crbi/hvDIv6us/6Q==} engines: {node: '>= 8.0'} dom-serializer@1.4.1: @@ -4791,6 +4792,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + electron-to-chromium@1.5.144: + resolution: {integrity: sha512-eJIaMRKeAzxfBSxtjYnoIAw/tdD6VIH6tHBZepZnAbE3Gyqqs5mGN87DvcldPUbVkIljTK8pY0CMcUljP64lfQ==} + electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} @@ -7023,6 +7027,9 @@ packages: node-releases@2.0.18: resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -7137,6 +7144,9 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -7662,9 +7672,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - queue-tick@1.0.1: - resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -7979,9 +7986,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -8377,9 +8381,6 @@ packages: stream-to-array@2.3.0: resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} - streamx@2.18.0: - resolution: {integrity: sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==} - streamx@2.22.1: resolution: {integrity: sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==} @@ -8553,11 +8554,11 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - tar-fs@2.1.3: - resolution: {integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - tar-fs@3.1.0: - resolution: {integrity: sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==} + tar-fs@3.1.1: + resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==} tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -8598,8 +8599,8 @@ packages: engines: {node: '>=10'} hasBin: true - testcontainers@11.4.0: - resolution: {integrity: sha512-eX5nc/Fi5I0LHqwxw6BuUvWNfdl+M2sKX6fX/47RP89Xs5nU6smd0iD7dpFogxy8/wACjlucLoutJc7b5mtq7w==} + testcontainers@11.10.0: + resolution: {integrity: sha512-8hwK2EnrOZfrHPpDC7CPe03q7H8Vv8j3aXdcmFFyNV8dzpBzgZYmqyDtduJ8YQ5kbzj+A+jUXMQ6zI8B5U3z+g==} text-decoder@1.1.1: resolution: {integrity: sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==} @@ -8644,6 +8645,10 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyglobby@0.2.13: resolution: {integrity: sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==} engines: {node: '>=12.0.0'} @@ -8923,8 +8928,8 @@ packages: resolution: {integrity: sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==} engines: {node: '>=18.17'} - undici@7.12.0: - resolution: {integrity: sha512-GrKEsc3ughskmGA9jevVlIOPMiiAHJ4OFUtaAH+NhfTUSiZ1wMPIQqQvAJUrJspFXJt3EBWgpAeoHEDVT1IBug==} + undici@7.16.0: + resolution: {integrity: sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==} engines: {node: '>=20.18.1'} unescape-js@1.1.4: @@ -9015,6 +9020,12 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + update-browserslist-db@1.2.2: resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} hasBin: true @@ -9206,24 +9217,24 @@ packages: yaml: optional: true - vitest@4.0.8: - resolution: {integrity: sha512-urzu3NCEV0Qa0Y2PwvBtRgmNtxhj5t5ULw7cuKhIHh3OrkKTLlut0lnBOv9qe5OvbkMH2g38G7KPDCTpIytBVg==} + vitest@4.0.15: + resolution: {integrity: sha512-n1RxDp8UJm6N0IbJLQo+yzLZ2sQCDyl1o0LeugbPWf8+8Fttp29GghsQBjYJVmWq3gBFfe9Hs1spR44vovn2wA==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 + '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.0.8 - '@vitest/browser-preview': 4.0.8 - '@vitest/browser-webdriverio': 4.0.8 - '@vitest/ui': 4.0.8 + '@vitest/browser-playwright': 4.0.15 + '@vitest/browser-preview': 4.0.15 + '@vitest/browser-webdriverio': 4.0.15 + '@vitest/ui': 4.0.15 happy-dom: '*' jsdom: '*' peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/debug': + '@opentelemetry/api': optional: true '@types/node': optional: true @@ -9640,7 +9651,7 @@ snapshots: dependencies: '@babel/compat-data': 7.25.2 '@babel/helper-validator-option': 7.24.8 - browserslist: 4.28.1 + browserslist: 4.24.4 lru-cache: 5.1.1 semver: 6.3.1 @@ -9648,7 +9659,7 @@ snapshots: dependencies: '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 + browserslist: 4.28.0 lru-cache: 5.1.1 semver: 6.3.1 @@ -10897,12 +10908,12 @@ snapshots: '@firebase/component@0.6.8': dependencies: '@firebase/util': 1.9.7 - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/component@0.7.0': dependencies: '@firebase/util': 1.13.0 - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/data-connect@0.3.11(@firebase/app@0.14.0)': dependencies: @@ -10949,7 +10960,7 @@ snapshots: '@firebase/logger': 0.4.2 '@firebase/util': 1.9.7 faye-websocket: 0.11.4 - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/database@1.1.0': dependencies: @@ -11038,11 +11049,11 @@ snapshots: '@firebase/logger@0.4.2': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/logger@0.5.0': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/messaging-compat@0.2.23(@firebase/app-compat@0.5.0)(@firebase/app@0.14.0)': dependencies: @@ -11143,7 +11154,7 @@ snapshots: '@firebase/util@1.9.7': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 '@firebase/webchannel-wrapper@1.0.4': {} @@ -12331,7 +12342,7 @@ snapshots: '@types/node': 24.9.1 '@types/ssh2': 1.15.5 - '@types/dockerode@3.3.42': + '@types/dockerode@3.3.47': dependencies: '@types/docker-modem': 3.0.6 '@types/node': 24.9.1 @@ -12544,85 +12555,85 @@ snapshots: '@typescript-eslint/types': 7.18.0 eslint-visitor-keys: 3.4.3 - '@vitest/coverage-v8@4.0.8(vitest@4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.8 + '@vitest/utils': 4.0.15 ast-v8-to-istanbul: 0.3.8 - debug: 4.4.3 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 magicast: 0.5.1 + obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + vitest: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitest/coverage-v8@4.0.8(vitest@4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.8 + '@vitest/utils': 4.0.15 ast-v8-to-istanbul: 0.3.8 - debug: 4.4.3 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 magicast: 0.5.1 + obug: 2.1.1 std-env: 3.10.0 tinyrainbow: 3.0.3 - vitest: 4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + vitest: 4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@vitest/expect@4.0.8': + '@vitest/expect@4.0.15': dependencies: '@standard-schema/spec': 1.0.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.0.8 - '@vitest/utils': 4.0.8 - chai: 6.2.0 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 + chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.8(vite@7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + '@vitest/mocker@4.0.15(vite@7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': dependencies: - '@vitest/spy': 4.0.8 + '@vitest/spy': 4.0.15 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - '@vitest/mocker@4.0.8(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + '@vitest/mocker@4.0.15(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': dependencies: - '@vitest/spy': 4.0.8 + '@vitest/spy': 4.0.15 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - '@vitest/pretty-format@4.0.8': + '@vitest/pretty-format@4.0.15': dependencies: tinyrainbow: 3.0.3 - '@vitest/runner@4.0.8': + '@vitest/runner@4.0.15': dependencies: - '@vitest/utils': 4.0.8 + '@vitest/utils': 4.0.15 pathe: 2.0.3 - '@vitest/snapshot@4.0.8': + '@vitest/snapshot@4.0.15': dependencies: - '@vitest/pretty-format': 4.0.8 + '@vitest/pretty-format': 4.0.15 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.8': {} + '@vitest/spy@4.0.15': {} - '@vitest/utils@4.0.8': + '@vitest/utils@4.0.15': dependencies: - '@vitest/pretty-format': 4.0.8 + '@vitest/pretty-format': 4.0.15 tinyrainbow: 3.0.3 '@vue/compiler-core@3.4.37': @@ -12963,9 +12974,6 @@ snapshots: balloon-css@1.2.0: {} - bare-events@2.4.2: - optional: true - bare-events@2.6.0: optional: true @@ -12993,7 +13001,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.5: {} + baseline-browser-mapping@2.9.7: {} basic-auth-connect@1.0.0: {} @@ -13121,13 +13129,20 @@ snapshots: node-releases: 2.0.18 update-browserslist-db: 1.1.0(browserslist@4.23.3) - browserslist@4.28.1: + browserslist@4.24.4: dependencies: - baseline-browser-mapping: 2.9.5 - caniuse-lite: 1.0.30001759 + caniuse-lite: 1.0.30001715 + electron-to-chromium: 1.5.144 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.24.4) + + browserslist@4.28.0: + dependencies: + baseline-browser-mapping: 2.9.7 + caniuse-lite: 1.0.30001760 electron-to-chromium: 1.5.267 node-releases: 2.0.27 - update-browserslist-db: 1.2.2(browserslist@4.28.1) + update-browserslist-db: 1.2.2(browserslist@4.28.0) bson@6.8.0: {} @@ -13234,7 +13249,7 @@ snapshots: camel-case@4.1.2: dependencies: pascal-case: 3.1.2 - tslib: 2.6.3 + tslib: 2.8.1 camelcase-keys@6.2.2: dependencies: @@ -13250,11 +13265,13 @@ snapshots: caniuse-lite@1.0.30001649: {} - caniuse-lite@1.0.30001759: {} + caniuse-lite@1.0.30001715: {} + + caniuse-lite@1.0.30001760: {} canvas-confetti@1.5.1: {} - chai@6.2.0: {} + chai@6.2.1: {} chalk@4.1.2: dependencies: @@ -13263,8 +13280,6 @@ snapshots: chalk@5.2.0: {} - chalk@5.3.0: {} - chalk@5.6.2: {} char-regex@1.0.2: {} @@ -13497,7 +13512,7 @@ snapshots: chalk: 4.1.2 date-fns: 2.30.0 lodash: 4.17.21 - rxjs: 7.8.1 + rxjs: 7.8.2 shell-quote: 1.8.1 spawn-command: 0.0.2 supports-color: 8.1.1 @@ -13647,7 +13662,7 @@ snapshots: core-js-compat@3.47.0: dependencies: - browserslist: 4.28.1 + browserslist: 4.28.0 core-js@3.37.1: {} @@ -13771,7 +13786,7 @@ snapshots: date-fns@2.30.0: dependencies: - '@babel/runtime': 7.25.0 + '@babel/runtime': 7.28.4 date-fns@3.6.0: {} @@ -13953,7 +13968,7 @@ snapshots: disposable-email-domains-js@1.16.0: {} - docker-compose@1.2.0: + docker-compose@1.3.0: dependencies: yaml: 2.8.1 @@ -13966,14 +13981,14 @@ snapshots: transitivePeerDependencies: - supports-color - dockerode@4.0.7: + dockerode@4.0.9: dependencies: '@balena/dockerignore': 1.0.2 '@grpc/grpc-js': 1.11.1 '@grpc/proto-loader': 0.7.13 docker-modem: 5.0.6 protobufjs: 7.3.2 - tar-fs: 2.1.3 + tar-fs: 2.1.4 uuid: 10.0.0 transitivePeerDependencies: - supports-color @@ -14023,7 +14038,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 dot-prop@5.3.0: dependencies: @@ -14071,6 +14086,8 @@ snapshots: dependencies: jake: 10.9.4 + electron-to-chromium@1.5.144: {} + electron-to-chromium@1.5.267: {} electron-to-chromium@1.5.5: {} @@ -14310,13 +14327,13 @@ snapshots: dependencies: '@mdn/browser-compat-data': 5.7.6 ast-metadata-inferer: 0.8.1 - browserslist: 4.28.1 - caniuse-lite: 1.0.30001759 + browserslist: 4.24.4 + caniuse-lite: 1.0.30001715 eslint: 9.39.1 find-up: 5.0.0 globals: 15.15.0 lodash.memoize: 4.1.2 - semver: 7.7.3 + semver: 7.6.3 eslint-scope@8.4.0: dependencies: @@ -15470,7 +15487,7 @@ snapshots: inquirer: 8.2.6 picocolors: 1.1.1 run-async: 2.4.1 - rxjs: 7.8.1 + rxjs: 7.8.2 inquirer@8.2.6: dependencies: @@ -15484,7 +15501,7 @@ snapshots: mute-stream: 0.0.8 ora: 5.4.1 run-async: 2.4.1 - rxjs: 7.8.1 + rxjs: 7.8.2 string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 @@ -15887,7 +15904,7 @@ snapshots: json-schema-to-ts@2.7.2: dependencies: - '@babel/runtime': 7.28.2 + '@babel/runtime': 7.28.4 '@types/json-schema': 7.0.15 ts-algebra: 1.2.2 @@ -16032,7 +16049,7 @@ snapshots: light-my-request@4.12.0: dependencies: - ajv: 8.17.1 + ajv: 8.12.0 cookie: 0.5.0 process-warning: 1.0.0 set-cookie-parser: 2.6.0 @@ -16174,7 +16191,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 lru-cache@10.4.3: {} @@ -16287,7 +16304,7 @@ snapshots: marked-terminal@7.1.0(marked@13.0.3): dependencies: ansi-escapes: 7.0.0 - chalk: 5.3.0 + chalk: 5.6.2 cli-highlight: 2.1.11 cli-table3: 0.6.5 marked: 13.0.3 @@ -16881,7 +16898,7 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.3 + tslib: 2.8.1 node-addon-api@5.1.0: {} @@ -16937,6 +16954,8 @@ snapshots: node-releases@2.0.18: {} + node-releases@2.0.19: {} + node-releases@2.0.27: {} node-source-walk@7.0.0: @@ -17034,7 +17053,7 @@ snapshots: oas-kit-common: 1.0.8 reftools: 1.1.9 yaml: 1.10.2 - yargs: 17.0.1 + yargs: 17.7.2 oas-schema-walker@1.1.5: {} @@ -17070,6 +17089,8 @@ snapshots: has-symbols: 1.1.0 object-keys: 1.1.1 + obug@2.1.1: {} + ohash@2.0.11: {} on-finished@2.3.0: @@ -17136,7 +17157,7 @@ snapshots: openapi3-ts@3.2.0: dependencies: - yaml: 2.5.0 + yaml: 2.8.1 optionator@0.9.4: dependencies: @@ -17260,7 +17281,7 @@ snapshots: param-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 parent-module@1.0.1: dependencies: @@ -17305,7 +17326,7 @@ snapshots: pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 path-browserify@1.0.1: {} @@ -17629,8 +17650,6 @@ snapshots: queue-microtask@1.2.3: {} - queue-tick@1.0.1: {} - quick-format-unescaped@4.0.4: {} quick-lru@4.0.1: {} @@ -18039,10 +18058,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: - dependencies: - tslib: 2.6.3 - rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -18504,21 +18519,12 @@ snapshots: dependencies: any-promise: 1.3.0 - streamx@2.18.0: - dependencies: - fast-fifo: 1.3.2 - queue-tick: 1.0.1 - text-decoder: 1.1.1 - optionalDependencies: - bare-events: 2.4.2 - streamx@2.22.1: dependencies: fast-fifo: 1.3.2 text-decoder: 1.1.1 optionalDependencies: bare-events: 2.6.0 - optional: true string-argv@0.3.2: {} @@ -18760,20 +18766,20 @@ snapshots: oas-validator: 5.0.8 reftools: 1.1.9 yaml: 1.10.2 - yargs: 17.0.1 + yargs: 17.7.2 transitivePeerDependencies: - encoding tapable@2.2.1: {} - tar-fs@2.1.3: + tar-fs@2.1.4: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 2.2.0 - tar-fs@3.1.0: + tar-fs@3.1.1: dependencies: pump: 3.0.0 tar-stream: 3.1.7 @@ -18795,7 +18801,7 @@ snapshots: dependencies: b4a: 1.6.6 fast-fifo: 1.3.2 - streamx: 2.18.0 + streamx: 2.22.1 tar@6.2.1: dependencies: @@ -18851,23 +18857,23 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - testcontainers@11.4.0: + testcontainers@11.10.0: dependencies: '@balena/dockerignore': 1.0.2 - '@types/dockerode': 3.3.42 + '@types/dockerode': 3.3.47 archiver: 7.0.1 async-lock: 1.4.1 byline: 5.0.0 - debug: 4.4.1 - docker-compose: 1.2.0 - dockerode: 4.0.7 + debug: 4.4.3 + docker-compose: 1.3.0 + dockerode: 4.0.9 get-port: 7.1.0 proper-lockfile: 4.1.2 properties-reader: 2.3.0 ssh-remote-port-forward: 1.0.4 - tar-fs: 3.1.0 - tmp: 0.2.3 - undici: 7.12.0 + tar-fs: 3.1.1 + tmp: 0.2.5 + undici: 7.16.0 transitivePeerDependencies: - bare-buffer - supports-color @@ -18906,6 +18912,8 @@ snapshots: tinyexec@0.3.2: {} + tinyexec@1.0.2: {} + tinyglobby@0.2.13: dependencies: fdir: 6.4.4(picomatch@4.0.3) @@ -19167,7 +19175,7 @@ snapshots: undici@6.21.3: {} - undici@7.12.0: {} + undici@7.16.0: {} unescape-js@1.1.4: dependencies: @@ -19248,9 +19256,15 @@ snapshots: escalade: 3.2.0 picocolors: 1.0.1 - update-browserslist-db@1.2.2(browserslist@4.28.1): + update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: - browserslist: 4.28.1 + browserslist: 4.24.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + update-browserslist-db@1.2.2(browserslist@4.28.0): + dependencies: + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -19423,24 +19437,63 @@ snapshots: tsx: 4.16.2 yaml: 2.8.1 - vitest@4.0.8(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): + vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): dependencies: - '@vitest/expect': 4.0.8 - '@vitest/mocker': 4.0.8(vite@7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.8 - '@vitest/runner': 4.0.8 - '@vitest/snapshot': 4.0.8 - '@vitest/spy': 4.0.8 - '@vitest/utils': 4.0.8 - debug: 4.4.3 + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 es-module-lexer: 1.7.0 expect-type: 1.2.2 magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 std-env: 3.10.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@opentelemetry/api': 1.8.0 + '@types/node': 24.9.1 + happy-dom: 20.0.10 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + vitest@4.0.15(@types/node@20.5.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 vite: 7.1.12(@types/node@20.5.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) @@ -19457,46 +19510,6 @@ snapshots: - sass-embedded - stylus - sugarss - - supports-color - - terser - - tsx - - yaml - - vitest@4.0.8(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): - dependencies: - '@vitest/expect': 4.0.8 - '@vitest/mocker': 4.0.8(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) - '@vitest/pretty-format': 4.0.8 - '@vitest/runner': 4.0.8 - '@vitest/snapshot': 4.0.8 - '@vitest/spy': 4.0.8 - '@vitest/utils': 4.0.8 - debug: 4.4.3 - es-module-lexer: 1.7.0 - expect-type: 1.2.2 - magic-string: 0.30.21 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 24.9.1 - happy-dom: 20.0.10 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - terser - tsx - yaml From 96169a149ee47db36aa11b66eca1f272877feab4 Mon Sep 17 00:00:00 2001 From: Seif Soliman Date: Wed, 17 Dec 2025 20:18:46 +0200 Subject: [PATCH 02/23] fix(caret): align caret correctly in RTL tape mode (@byseif21) (#7259) * In RTL tests, enabling tape mode causes the main caret to be misaligned from the start of the test and remain offset while typing. The tape margin was always calculated from the left side, which is correct for LTR but incorrect for RTL layouts. fix * we should have mirrored the margin from the right side. --- frontend/src/ts/utils/caret.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/frontend/src/ts/utils/caret.ts b/frontend/src/ts/utils/caret.ts index 60c603c48..1b2b658af 100644 --- a/frontend/src/ts/utils/caret.ts +++ b/frontend/src/ts/utils/caret.ts @@ -450,6 +450,9 @@ export class Caret { let left = 0; let top = 0; + const tapeOffset = + wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + // yes, this is all super verbose, but its easier to maintain and understand if (isWordRTL) { let afterLetterCorrection = 0; @@ -475,8 +478,7 @@ export class Caret { left += options.letter.getOffsetLeft(); left += afterLetterCorrection; if (this.isMainCaret && lockedMainCaretInTape) { - left += - wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + left += wordsWrapperCache.getOffsetWidth() - tapeOffset; } else { left += options.word.getOffsetLeft(); left += options.word.getOffsetWidth(); @@ -486,8 +488,7 @@ export class Caret { left += width * -1; } if (this.isMainCaret && lockedMainCaretInTape) { - left += - wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + left += wordsWrapperCache.getOffsetWidth() - tapeOffset; } else { left += options.letter.getOffsetLeft(); left += options.word.getOffsetLeft(); @@ -508,15 +509,13 @@ export class Caret { left += options.letter.getOffsetLeft(); left += afterLetterCorrection; if (this.isMainCaret && lockedMainCaretInTape) { - left += - wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + left += tapeOffset; } else { left += options.word.getOffsetLeft(); } } else if (Config.tapeMode === "letter") { if (this.isMainCaret && lockedMainCaretInTape) { - left += - wordsWrapperCache.getOffsetWidth() * (Config.tapeMargin / 100); + left += tapeOffset; } else { left += options.letter.getOffsetLeft(); left += options.word.getOffsetLeft(); From 97b9085f7e252a080b950aff7979d9c58fa886ce Mon Sep 17 00:00:00 2001 From: Md Moushuf Alam <149342172+MoushufAlam@users.noreply.github.com> Date: Wed, 17 Dec 2025 23:49:43 +0530 Subject: [PATCH 03/23] refactor: replace jquery with dom utils in scroll-to-top button (@MoushufAlam) (#7257) ### Description Replaces jQuery usage with dom utils in the scroll-to-top button. Scope intentionally kept small per contributing guidelines. ### Checks - [x] Adding/modifying Typescript code? - [x] I have used `qs`, `qsa` or `qsr` instead of JQuery selectors. - [x] Check if any open issues are related to this PR; if so, be sure to tag them below. - [x] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [x] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. Related to #7186 --- frontend/src/ts/elements/scroll-to-top.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frontend/src/ts/elements/scroll-to-top.ts b/frontend/src/ts/elements/scroll-to-top.ts index 4eb654f11..2ebd2d48e 100644 --- a/frontend/src/ts/elements/scroll-to-top.ts +++ b/frontend/src/ts/elements/scroll-to-top.ts @@ -1,27 +1,30 @@ import * as ActivePage from "../states/active-page"; import { prefersReducedMotion } from "../utils/misc"; +import { qsr } from "../utils/dom"; let visible = false; +const button = qsr(".scrollToTopButton"); + export function hide(): void { - $(".scrollToTopButton").addClass("invisible"); + button.addClass("invisible"); visible = false; } function show(): void { - $(".scrollToTopButton").removeClass("invisible"); + button.removeClass("invisible"); visible = true; } -$(document).on("click", ".scrollToTopButton", () => { - $(".scrollToTopButton").addClass("invisible"); +button.on("click", () => { + button.addClass("invisible"); window.scrollTo({ top: 0, behavior: prefersReducedMotion() ? "instant" : "smooth", }); }); -$(window).on("scroll", () => { +window.addEventListener("scroll", () => { const page = ActivePage.get(); if (page === "test") return; From 74bafad7d5022badd9b41c64e23191c117ed1cb3 Mon Sep 17 00:00:00 2001 From: 100daysummer <138024460+100daysummer@users.noreply.github.com> Date: Wed, 17 Dec 2025 20:32:49 +0200 Subject: [PATCH 04/23] impr(language): add bulgarian 1k (@100daysummer) (#7232) ### Description This commit expands the two bulgarian wordlists - bulgarian.json and bulgarian_latin.json ### Checks - [ ] Adding/modifying Typescript code? - [ ] I have used `qs`,`qsa` or `qsr` instead of JQuery selectors. - [ ] Adding quotes? - [ ] Make sure to include translations for the quotes in the description (or another comment) so we can verify their content. - [x] Adding a language? - Make sure to follow the [languages documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LANGUAGES.md) - [x] Add language to `packages/schemas/src/languages.ts` - [x] Add language to exactly one group in `frontend/src/ts/constants/languages.ts` - [x] Add language json file to `frontend/static/languages` - [ ] Adding a theme? - Make sure to follow the [themes documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/THEMES.md) - [ ] Add theme to `packages/schemas/src/themes.ts` - [ ] Add theme to `frontend/src/ts/constants/themes.ts` - [ ] Add theme css file to `frontend/static/themes` - [ ] Add some screenshot of the theme, especially with different test settings (colorful, flip colors) to your pull request - [ ] Adding a layout? - [ ] Make sure to follow the [layouts documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LAYOUTS.md) - [ ] Add layout to `packages/schemas/src/layouts.ts` - [ ] Add layout json file to `frontend/static/layouts` - [ ] Adding a font? - Make sure to follow the [themes documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/FONTS.md) - [ ] Add font file to `frontend/static/webfonts` - [ ] Add font to `packages/schemas/src/fonts.ts` - [ ] Add font to `frontend/src/ts/constants/fonts.ts` - [ ] Check if any open issues are related to this PR; if so, be sure to tag them below. - [x] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [x] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. --------- Co-authored-by: Jack --- frontend/src/ts/constants/languages.ts | 7 +- frontend/static/languages/bulgarian.json | 4 + frontend/static/languages/bulgarian_1k.json | 1020 +++++++++++++++++ .../static/languages/bulgarian_latin.json | 4 + .../static/languages/bulgarian_latin_1k.json | 1017 ++++++++++++++++ packages/schemas/src/languages.ts | 2 + 6 files changed, 2053 insertions(+), 1 deletion(-) create mode 100644 frontend/static/languages/bulgarian_1k.json create mode 100644 frontend/static/languages/bulgarian_latin_1k.json diff --git a/frontend/src/ts/constants/languages.ts b/frontend/src/ts/constants/languages.ts index af5eddfd3..fd963dfed 100644 --- a/frontend/src/ts/constants/languages.ts +++ b/frontend/src/ts/constants/languages.ts @@ -188,7 +188,12 @@ export const LanguageGroups: Record = { swahili: ["swahili_1k"], maori: ["maori_1k"], catalan: ["catalan", "catalan_1k"], - bulgarian: ["bulgarian", "bulgarian_latin"], + bulgarian: [ + "bulgarian", + "bulgarian_1k", + "bulgarian_latin", + "bulgarian_latin_1k", + ], bosnian: ["bosnian", "bosnian_4k"], esperanto: [ "esperanto", diff --git a/frontend/static/languages/bulgarian.json b/frontend/static/languages/bulgarian.json index 5776c70e6..0f0ced240 100644 --- a/frontend/static/languages/bulgarian.json +++ b/frontend/static/languages/bulgarian.json @@ -1,5 +1,9 @@ { "name": "bulgarian", + "rightToLeft": false, + "ligatures": false, + "orderedByFrequency": false, + "bcp47": "bg", "noLazyMode": true, "words": [ "а", diff --git a/frontend/static/languages/bulgarian_1k.json b/frontend/static/languages/bulgarian_1k.json new file mode 100644 index 000000000..40235cfea --- /dev/null +++ b/frontend/static/languages/bulgarian_1k.json @@ -0,0 +1,1020 @@ +{ + "name": "bulgarian_1k", + "rightToLeft": false, + "ligatures": false, + "orderedByFrequency": false, + "bcp47": "bg", + "noLazyMode": true, + "words": [ + "а", + "август", + "автобус", + "автовоз", + "автоматичен", + "автомобил", + "автомобилен", + "автосалон", + "агония", + "адрес", + "аз", + "ако", + "активен", + "актьор", + "актьори", + "ами", + "ангел", + "април", + "асанасьор", + "асистент", + "асоциация", + "асфалт", + "атака", + "атаки", + "атакувам", + "атакуван", + "аудио", + "баба", + "бавен", + "балон", + "банка", + "баня", + "барабан", + "баскетбол", + "баща", + "бе", + "бебе", + "без", + "бездомник", + "безсмислен", + "бесило", + "беше", + "би", + "бил", + "билет", + "било", + "бира", + "битка", + "бих", + "благ", + "благодарност", + "благодаря", + "благоденствие", + "благоухание", + "ближен", + "близо", + "блок", + "богат", + "боли", + "болка", + "боровинка", + "боя", + "брат", + "братя", + "буря", + "бутилка", + "бутон", + "бъде", + "бърз", + "бял", + "бях", + "в", + "вагон", + "важен", + "ваза", + "вампир", + "вана", + "вар", + "варовик", + "вас", + "веднага", + "весел", + "вест", + "вече", + "вечер", + "взимам", + "ви", + "видим", + "видя", + "вие", + "виж", + "вилица", + "винаги", + "вино", + "виновен", + "виновна", + "виновното", + "висота", + "вихрушка", + "вихър", + "вкус", + "вкъщи", + "влак", + "власт", + "влезе", + "вода", + "воден", + "воденица", + "война", + "войник", + "волейбол", + "восък", + "враг", + "врата", + "време", + "връзка", + "връх", + "все", + "всеки", + "вселена", + "всички", + "всичко", + "всъщност", + "вход", + "вцепенен", + "вчера", + "във", + "въздух", + "въпрос", + "вятър", + "галерия", + "гащи", + "гел", + "ги", + "гирлянд", + "глагол", + "глад", + "глас", + "гласовит", + "глоба", + "глобален", + "глух", + "го", + "говоря", + "години", + "гол", + "голям", + "голямо", + "гора", + "гост", + "готвач", + "град", + "градина", + "грамота", + "граница", + "грешка", + "грозде", + "група", + "гръб", + "гръмотевица", + "гума", + "гърмя", + "да", + "дажба", + "дай", + "далече", + "дали", + "данък", + "дар", + "дарявам", + "дата", + "дванадесет", + "дванайсет", + "две", + "двор", + "дворец", + "девет", + "дело", + "делфин", + "ден", + "десет", + "дете", + "деца", + "диван", + "династия", + "днес", + "до", + "добавка", + "добавям", + "добър", + "довод", + "дойде", + "доказателство", + "докато", + "дол", + "долен", + "дом", + "домат", + "допир", + "дори", + "доста", + "достатъчно", + "достъп", + "доя", + "дреха", + "друг", + "друго", + "думa", + "дупка", + "душа", + "дъвка", + "дъга", + "дъжд", + "дълбок", + "дървета", + "дърво", + "държава", + "дюнер", + "дядо", + "дял", + "е", + "един", + "единадесет", + "единайсет", + "една", + "едно", + "езеро", + "език", + "ей", + "екип", + "екран", + "ела", + "електронен", + "електроника", + "епизод", + "есен", + "етаж", + "ето", + "жаба", + "жаден", + "жажда", + "жена", + "жив", + "живот", + "животно", + "жълт", + "за", + "завеса", + "завод", + "завъртам", + "задача", + "заедно", + "заек", + "закон", + "зала", + "залеж", + "залез", + "заливам", + "залязвам", + "запад", + "запалвам", + "заплаха", + "зар", + "заради", + "затова", + "зашивам", + "зашит", + "защипвам", + "защитавам", + "защо", + "защото", + "заявка", + "звук", + "звяр", + "здание", + "здраве", + "здравей", + "зеле", + "зелен", + "зима", + "зла", + "злато", + "зло", + "злоба", + "змей", + "змия", + "знае", + "знаете", + "знаеш", + "знам", + "знание", + "значи", + "зная", + "зъб", + "и", + "ивица", + "ивици", + "иглолистно", + "игра", + "играчи", + "играя", + "идея", + "избор", + "изглежда", + "излезе", + "излизам", + "изложба", + "изложение", + "изпратен", + "изпращам", + "изход", + "или", + "им", + "има", + "имам", + "имаме", + "имате", + "имаш", + "име", + "имена", + "инат", + "инструмент", + "иска", + "истина", + "история", + "йод", + "кабел", + "каза", + "казах", + "казвам", + "как", + "каква", + "какво", + "каквото", + "както", + "какъв", + "камера", + "камион", + "капачка", + "карта", + "картон", + "картоф", + "категория", + "катерица", + "като", + "кафе", + "квадрат", + "квартал", + "киви", + "китара", + "кифла", + "кифли", + "кифличка", + "кифлички", + "клавиатура", + "клавиш", + "клас", + "клетка", + "клон", + "ключ", + "книга", + "когато", + "което", + "кожа", + "които", + "кой", + "който", + "кокошка", + "кола", + "колко", + "колона", + "коляно", + "кон", + "конституция", + "континент", + "копая", + "копие", + "копирам", + "кораб", + "коса", + "котка", + "която", + "крава", + "крави", + "крак", + "кралица", + "кран", + "красив", + "крещя", + "кръв", + "кръг", + "крясък", + "култура", + "курс", + "кутия", + "куче", + "къде", + "където", + "към", + "къща", + "лале", + "лампа", + "ламя", + "легло", + "лежа", + "леля", + "лепенка", + "лесен", + "леха", + "леща", + "ли", + "лисица", + "листо", + "литература", + "лица", + "лице", + "лоби", + "логика", + "логичен", + "лодка", + "локално", + "лук", + "луна", + "лъв", + "лъжица", + "любимец", + "лято", + "магазин", + "магистрала", + "майка", + "маймуна", + "макарон", + "малина", + "малко", + "мама", + "мантия", + "мантията", + "маса", + "масаж", + "масивен", + "масло", + "масово", + "математика", + "матрак", + "мафия", + "мач", + "мащаб", + "мащабен", + "мая", + "ме", + "мед", + "медал", + "между", + "мен", + "мента", + "меса", + "месец", + "месо", + "мечта", + "ми", + "минавам", + "миниатюра", + "минута", + "мирис", + "мисля", + "мляко", + "мляскам", + "много", + "мога", + "могила", + "мода", + "може", + "мокра", + "молба", + "молив", + "моля", + "момент", + "момиче", + "момче", + "море", + "морков", + "морфема", + "мост", + "моя", + "му", + "музика", + "музикален", + "муха", + "място", + "мяу", + "на", + "навик", + "навреме", + "навън", + "над", + "надлез", + "надпис", + "наистина", + "накит", + "нали", + "намек", + "намери", + "намерих", + "наметвам", + "наметна", + "намеци", + "намирам", + "наоколо", + "направи", + "направя", + "напред", + "нар", + "наред", + "нарцис", + "нарязан", + "нас", + "насекоми", + "насекомо", + "насип", + "наставник", + "натиск", + "нахален", + "находка", + "начин", + "не", + "небе", + "небостъргач", + "него", + "негови", + "неговите", + "нежен", + "нежна", + "неин", + "нейн", + "нека", + "нектар", + "неща", + "нещо", + "нея", + "неясен", + "ни", + "нива", + "ние", + "никога", + "никой", + "нито", + "нищо", + "но", + "нов", + "новатор", + "новина", + "нож", + "номер", + "нос", + "носорог", + "нощ", + "нужда", + "някой", + "няколко", + "ням", + "няма", + "нямам", + "обичам", + "обработвам", + "обработка", + "обратен", + "обратно", + "обръщам", + "общ", + "общество", + "обяд", + "овощна", + "огън", + "ода", + "океан", + "око", + "опа", + "опит", + "ориз", + "осем", + "остров", + "островен", + "острови", + "от", + "отговор", + "отново", + "отрасъл", + "отрицание", + "отрова", + "очаквам", + "очи", + "още", + "паваж", + "пазар", + "пак", + "палеж", + "паница", + "пари", + "парк", + "пасивен", + "пате", + "патица", + "перон", + "песен", + "пет", + "печка", + "пеш", + "пея", + "пиеса", + "пиле", + "пингвин", + "пирамида", + "писан", + "писател", + "писмен", + "писменост", + "писмо", + "писък", + "писъци", + "питка", + "плаж", + "план", + "планета", + "планетариум", + "платно", + "плача", + "плет", + "плещи", + "плещите", + "плискам", + "плитка", + "плод", + "пляскам", + "по", + "повече", + "повечето", + "поглед", + "под", + "подаден", + "подем", + "позор", + "покривка", + "поле", + "полет", + "политика", + "полицай", + "полиция", + "полюс", + "помогнаха", + "помощ", + "популярен", + "поради", + "портал", + "посев", + "после", + "посока", + "посочвам", + "почасов", + "поща", + "право", + "правя", + "празен", + "празник", + "прасе", + "прах", + "прашен", + "пращам", + "превня", + "пред", + "предвид", + "преди", + "предмет", + "представител", + "предстои", + "предстоя", + "през", + "президент", + "премествам", + "пренебрегвам", + "пренебрежение", + "при", + "прибирам", + "прикритие", + "прилагателно", + "приличам", + "приличаш", + "пример", + "принтер", + "природа", + "пристрастен", + "пришивам", + "приятел", + "проблем", + "провеждам", + "прозорец", + "производител", + "пролет", + "промяна", + "прост", + "просто", + "простор", + "простота", + "професионалист", + "професия", + "професор", + "прошепвам", + "прошепнеш", + "пръст", + "прът", + "пръчка", + "птица", + "птици", + "пускам", + "пчела", + "първи", + "път", + "пътека", + "пътентранспорт", + "работа", + "равно", + "радиатор", + "радост", + "разбирам", + "развод", + "разговор", + "разказ", + "размер", + "разстройство", + "разтвор", + "разходка", + "рай", + "рак", + "рана", + "ранен", + "ранени", + "раница", + "рано", + "ред", + "режа", + "резба", + "река", + "релса", + "репичка", + "република", + "ресторант", + "решение", + "риба", + "род", + "роза", + "розова", + "розово", + "рокля", + "рояк", + "ръка", + "рядко", + "рядкост", + "рядък", + "с", + "са", + "сам", + "сама", + "само", + "светкавица", + "свещ", + "свобода", + "свързвам", + "свят", + "сграда", + "се", + "себе", + "сега", + "седем", + "седя", + "село", + "селски", + "семейство", + "сено", + "си", + "сигурен", + "сила", + "син", + "скалп", + "скачам", + "скелет", + "скеч", + "скоро", + "скорост", + "скоростен", + "скорпион", + "скъпа", + "сладко", + "след", + "слепец", + "случи", + "слънце", + "сляп", + "сме", + "смъквам", + "смърт", + "снеговалеж", + "снежен", + "снежинка", + "сняг", + "сол", + "спане", + "списък", + "спомен", + "спорт", + "сравнение", + "сравни", + "среща", + "става", + "ставам", + "стан", + "стар", + "стара", + "старост", + "стая", + "сте", + "стена", + "стене", + "стига", + "стикер", + "сто", + "стой", + "стол", + "страна", + "страст", + "страх", + "стрелям", + "струна", + "студ", + "супермаркет", + "сутрин", + "съдържание", + "съжалявам", + "съм", + "сън", + "сърце", + "със", + "състезавам", + "състезание", + "също", + "сяра", + "тава", + "таван", + "тази", + "тайна", + "така", + "там", + "танц", + "тате", + "татко", + "твърд", + "те", + "теб", + "тези", + "телевизия", + "телевизор", + "телефон", + "тенджера", + "тенджери", + "тетрадка", + "тефтер", + "техен", + "ти", + "тиган", + "тих", + "тихо", + "тича", + "тичам", + "това", + "тогава", + "този", + "той", + "толкова", + "тор", + "тост", + "точно", + "трактор", + "трамвай", + "три", + "тринадесет", + "тринайсет", + "триъгълник", + "труден", + "трябва", + "тук", + "тя", + "тяло", + "тях", + "уважение", + "уединение", + "украса", + "украсявам", + "ум", + "уплаха", + "уплашен", + "упражнение", + "ура", + "уред", + "усет", + "успех", + "успешен", + "устройство", + "утре", + "ухо", + "ученик", + "ученичка", + "училище", + "учител", + "файл", + "фактура", + "фараон", + "фенер", + "филм", + "финтирам", + "фитнес", + "флаг", + "фланелка", + "флейта", + "фокус", + "форма", + "фрактура", + "футбол", + "хайде", + "харесвам", + "хладилник", + "хляб", + "ход", + "хор", + "хора", + "хората", + "хотел", + "храст", + "хризантема", + "художник", + "цар", + "царевица", + "царица", + "цвете", + "цвят", + "цел", + "целина", + "цена", + "ценен", + "ценностс", + "църква", + "чайка", + "чакай", + "чакайте", + "чар", + "чаршаф", + "час", + "часовник", + "чаша", + "че", + "чепка", + "червена", + "червено", + "червеното", + "чест", + "често", + "четири", + "четиринадесет", + "четиринайсет", + "четка", + "чешма", + "числа", + "числително", + "число", + "чист", + "чистач", + "човек", + "чорапогащник", + "чужбина", + "чук", + "чучур", + "чушка", + "шанс", + "шепот", + "шептя", + "шест", + "широколистно", + "шише", + "шия", + "шкаф", + "шпатула", + "щастие", + "ще", + "щора", + "юг", + "юли", + "юни", + "юрган", + "ютия", + "я", + "ябълка", + "ягода", + "яде", + "ядене", + "яйце", + "яма", + "ями", + "яре", + "ярост", + "ярък", + "ясен", + "ястреб", + "ято" + ] +} diff --git a/frontend/static/languages/bulgarian_latin.json b/frontend/static/languages/bulgarian_latin.json index 5dc11a2c7..abe7937e8 100644 --- a/frontend/static/languages/bulgarian_latin.json +++ b/frontend/static/languages/bulgarian_latin.json @@ -1,5 +1,9 @@ { "name": "bulgarian_latin", + "rightToLeft": false, + "ligatures": false, + "orderedByFrequency": false, + "bcp47": "bg", "noLazyMode": true, "words": [ "a", diff --git a/frontend/static/languages/bulgarian_latin_1k.json b/frontend/static/languages/bulgarian_latin_1k.json new file mode 100644 index 000000000..cedc09b47 --- /dev/null +++ b/frontend/static/languages/bulgarian_latin_1k.json @@ -0,0 +1,1017 @@ +{ + "name": "bulgarian_latin_1k", + "rightToLeft": false, + "ligatures": false, + "orderedByFrequency": false, + "bcp47": "bg", + "noLazyMode": true, + "words": [ + "a", + "adres", + "agoniya", + "ako", + "aktiori", + "aktiven", + "aktor", + "ami", + "angel", + "april", + "asansior", + "asfalt", + "asistent", + "asociaciya", + "ataka", + "ataki", + "atakuvam", + "atakuvan", + "audio", + "avgust", + "avtobus", + "avtomatichen", + "avtomobil", + "avtomobilen", + "avtosalon", + "avtovoz", + "az", + "baba", + "balon", + "banka", + "banya", + "baraban", + "bashta", + "basketbol", + "baven", + "be", + "bebe", + "beshe", + "besilo", + "bez", + "bezdomnik", + "bezsmislen", + "bi", + "bih", + "bil", + "bilet", + "bilo", + "bira", + "bitka", + "blag", + "blagodarnost", + "blagodarq", + "blagodenstvie", + "blagouhanie", + "blizhen", + "blizo", + "blok", + "bogat", + "boli", + "bolka", + "borovinka", + "boya", + "bqh", + "brat", + "bratya", + "bude", + "burya", + "burz", + "butilka", + "buton", + "byal", + "car", + "carevica", + "carica", + "carkva", + "cel", + "celina", + "cena", + "cenen", + "cennosts", + "chakai", + "chakayte", + "char", + "charshaf", + "chas", + "chasha", + "chasovnik", + "chayka", + "che", + "chepka", + "chervena", + "cherveno", + "chervenoto", + "cheshma", + "chest", + "chesto", + "chetiri", + "chetirinadeset", + "chetirinayset", + "chetka", + "chisla", + "chislitelno", + "chislo", + "chist", + "chistach", + "chorapogashtnik", + "chovek", + "chuchur", + "chujbina", + "chuk", + "chushka", + "cvete", + "cvyat", + "da", + "dai", + "dajd", + "dalbok", + "daleche", + "dali", + "danuk", + "dar", + "darveta", + "daryavam", + "darzhava", + "data", + "davka", + "dazhba", + "deca", + "delfin", + "delo", + "den", + "deset", + "dete", + "devet", + "dinastiya", + "divan", + "dnes", + "do", + "dobavka", + "dobavyam", + "dobur", + "doide", + "dokato", + "dokazatelstvo", + "dol", + "dolen", + "dom", + "domat", + "dopir", + "dori", + "dosta", + "dostap", + "dostatuchno", + "dovod", + "doya", + "dreha", + "drug", + "drugo", + "duga", + "duma", + "dupka", + "durvo", + "dusha", + "dvanadeset", + "dvanayset", + "dve", + "dvor", + "dvorec", + "dyado", + "dyal", + "dyuner", + "e", + "edin", + "edinadeset", + "edinayset", + "edna", + "edno", + "ei", + "ekip", + "ekran", + "ela", + "elektronika", + "epizod", + "esen", + "etaj", + "eto", + "ezero", + "ezik", + "faktura", + "faraon", + "fayl", + "fener", + "film", + "fintiram", + "fitnes", + "flag", + "flanelka", + "fleyta", + "fokus", + "forma", + "fraktura", + "futbol", + "galeriya", + "gashti", + "gel", + "gi", + "girlyand", + "glad", + "glagol", + "glas", + "glasovit", + "globa", + "globalen", + "gluh", + "go", + "godini", + "gol", + "golyam", + "golyamo", + "gora", + "gost", + "gotvach", + "govorq", + "grad", + "gradina", + "gramota", + "gramotevica", + "granica", + "greshka", + "grozde", + "grub", + "grupa", + "guma", + "gurmq", + "haide", + "haresvam", + "hladilnik", + "hlyab", + "hod", + "hor", + "hora", + "horata", + "hotel", + "hrast", + "hrizantema", + "hudozhnik", + "i", + "ideya", + "iglolistno", + "igra", + "igrachi", + "igraya", + "ili", + "im", + "ima", + "imam", + "imame", + "imash", + "imate", + "ime", + "imena", + "inat", + "instrument", + "iska", + "istina", + "istoriya", + "ivica", + "ivici", + "izbor", + "izglejda", + "izhod", + "izleze", + "izlizam", + "izlozhba", + "izlozhenie", + "izprashtam", + "izpraten", + "jaba", + "jaden", + "jena", + "jiv", + "jivot", + "kabel", + "kadeto", + "kafe", + "kak", + "kakto", + "kakuv", + "kakva", + "kakvo", + "kakvoto", + "kamera", + "kamion", + "kapachka", + "karta", + "kartof", + "karton", + "kashta", + "kategoriya", + "katerica", + "kato", + "kaza", + "kazah", + "kazvam", + "kifla", + "kifli", + "kiflichka", + "kiflichki", + "kitara", + "kivi", + "klas", + "klaviatura", + "klavish", + "kletka", + "klon", + "klyuch", + "kniga", + "koeto", + "kogato", + "koi", + "koito", + "koja", + "kokoshka", + "kola", + "kolko", + "kolona", + "kolyano", + "kon", + "konstituciya", + "kontinent", + "kopaya", + "kopie", + "kopiram", + "koqto", + "korab", + "kosa", + "kotka", + "krag", + "krak", + "kralica", + "kran", + "krasiv", + "krava", + "kravi", + "kreshtya", + "kruv", + "kryasyk", + "kuche", + "kude", + "kultura", + "kum", + "kurs", + "kutiya", + "kvadrat", + "kvartal", + "lale", + "lampa", + "lamya", + "lav", + "lazhica", + "leglo", + "leha", + "lelya", + "lepenka", + "lesen", + "leshta", + "lezha", + "li", + "lica", + "lice", + "lisica", + "listo", + "literatura", + "lobi", + "lodka", + "logichen", + "logika", + "lokalno", + "luk", + "luna", + "lyato", + "lyubimec", + "mach", + "mafiya", + "magazin", + "magistrala", + "maika", + "makaron", + "malina", + "malko", + "mama", + "mantiya", + "mantiyata", + "masa", + "masazh", + "mashtab", + "mashtaben", + "masiven", + "maslo", + "masovo", + "matematika", + "matrak", + "maya", + "maymuna", + "me", + "mechta", + "med", + "medal", + "mejdu", + "men", + "menta", + "mesa", + "mesec", + "meso", + "mi", + "minavam", + "miniatyura", + "minuta", + "miris", + "mislq", + "mlyako", + "mlyaskam", + "mnogo", + "moda", + "moga", + "mogila", + "moje", + "mokra", + "molba", + "moliv", + "molq", + "momche", + "moment", + "momiche", + "moq", + "more", + "morfema", + "morkov", + "most", + "mqsto", + "mu", + "muha", + "muzika", + "muzikalen", + "myau", + "na", + "nachin", + "nad", + "nadlez", + "nadpis", + "nahalen", + "nahodka", + "naistina", + "nakit", + "nali", + "nameci", + "namek", + "nameri", + "namerih", + "nametna", + "nametvam", + "namiram", + "naokolo", + "napravi", + "napravq", + "napred", + "nar", + "narcis", + "nared", + "naryazan", + "nas", + "nasekomi", + "nasekomo", + "nasip", + "nastavnik", + "natisk", + "navan", + "navik", + "navreme", + "ne", + "nebe", + "nebostargach", + "nego", + "negovi", + "negovite", + "nein", + "neka", + "nektar", + "neq", + "neshta", + "neshto", + "neyasen", + "neyn", + "nezhen", + "nezhna", + "ni", + "nie", + "nikoga", + "nikoi", + "nishto", + "nito", + "niva", + "no", + "nomer", + "nos", + "nosht", + "nosorog", + "nov", + "novator", + "novina", + "nozh", + "nqkoi", + "nqkolko", + "nqma", + "nqmam", + "nujda", + "nyam", + "obicham", + "obrabotka", + "obrabotvam", + "obrashtam", + "obraten", + "obratno", + "obsht", + "obshtestvo", + "obyad", + "ochakvam", + "ochi", + "oda", + "ogun", + "okean", + "oko", + "opa", + "opit", + "oriz", + "osem", + "oshte", + "ostrov", + "ostroven", + "ostrovi", + "ot", + "otgovor", + "otnovo", + "otrasal", + "otricanie", + "otrova", + "ovoshtna", + "pak", + "palezh", + "panica", + "pari", + "park", + "parvi", + "pasiven", + "pate", + "pateka", + "patentransport", + "patica", + "pavazh", + "pazar", + "pchela", + "pechka", + "peq", + "peron", + "pesen", + "pesh", + "pet", + "piesa", + "pile", + "pingvin", + "piramida", + "pisaci", + "pisak", + "pisan", + "pisatel", + "pismen", + "pismenost", + "pismo", + "pitka", + "placha", + "plaj", + "plan", + "planeta", + "planetarium", + "platno", + "pleshti", + "pleshtite", + "plet", + "pliskam", + "plitka", + "plod", + "plyaskam", + "po", + "pochasov", + "pod", + "podaden", + "podem", + "pogled", + "pokrivka", + "pole", + "polet", + "policay", + "policiya", + "politika", + "polyus", + "pomognaha", + "pomosht", + "populyaren", + "poradi", + "portal", + "posev", + "poshta", + "posle", + "posochvam", + "posoka", + "poveche", + "povecheto", + "pozor", + "prachka", + "prah", + "prase", + "prashen", + "prashtam", + "prat", + "pravo", + "pravq", + "prazen", + "praznik", + "pred", + "predi", + "predmet", + "predstavitel", + "predstoi", + "predstoya", + "predvid", + "premestvam", + "prenebregvam", + "prenebrezhenie", + "prevnya", + "prez", + "prezident", + "pri", + "pribiram", + "prikritie", + "prilagatelno", + "prilicham", + "prilichash", + "primer", + "printer", + "priqtel", + "priroda", + "prishivam", + "pristrasten", + "problem", + "profesionalist", + "profesiya", + "profesor", + "proizvoditel", + "prolet", + "promyana", + "proshepnesh", + "proshepvam", + "prost", + "prosto", + "prostor", + "prostota", + "provezhdam", + "prozorec", + "prust", + "ptica", + "ptici", + "puskam", + "put", + "q", + "rabota", + "radiator", + "radost", + "rak", + "rana", + "ranen", + "raneni", + "ranica", + "rano", + "ravno", + "ray", + "razbiram", + "razgovar", + "razhodka", + "razkaz", + "razmer", + "razstroystvo", + "raztvor", + "razvod", + "red", + "reka", + "relsa", + "repichka", + "republika", + "reshenie", + "restorant", + "rezba", + "rezha", + "riba", + "rod", + "roklya", + "royak", + "roza", + "rozova", + "rozovo", + "ruka", + "ryadak", + "ryadko", + "ryadkost", + "s", + "sa", + "sadarzhanie", + "sam", + "sama", + "samo", + "sastezanie", + "sastezavam", + "se", + "sebe", + "sedem", + "sedq", + "sega", + "selo", + "selski", + "semeystvo", + "seno", + "sgrada", + "shans", + "shepot", + "sheptya", + "shest", + "shirokolistno", + "shishe", + "shiya", + "shkaf", + "shpatula", + "shtastie", + "shte", + "shtora", + "si", + "siguren", + "sila", + "sin", + "skacham", + "skalp", + "skech", + "skelet", + "skoro", + "skorost", + "skorosten", + "skorpion", + "skupa", + "sladko", + "sled", + "slepec", + "sluchi", + "slunce", + "slyap", + "smakvam", + "smart", + "sme", + "snegovalezh", + "snezhen", + "snezhinka", + "snyag", + "sol", + "spane", + "spisak", + "spomen", + "sport", + "sravnenie", + "sravni", + "sreshta", + "stan", + "star", + "stara", + "starost", + "stava", + "stavam", + "staya", + "ste", + "stena", + "stene", + "stiga", + "stiker", + "sto", + "stol", + "stoy", + "strah", + "strana", + "strast", + "strelyam", + "struna", + "stud", + "sujelqvam", + "sum", + "sun", + "supermarket", + "surce", + "sus", + "sushto", + "sutrin", + "svarzvam", + "svesht", + "svetkavica", + "svoboda", + "svyat", + "syara", + "taka", + "tam", + "tanc", + "tate", + "tatko", + "tava", + "tavan", + "tayna", + "tazi", + "te", + "teb", + "tefter", + "tehen", + "telefon", + "televiziya", + "televizor", + "tendzhera", + "tendzheri", + "tetradka", + "tezi", + "ti", + "ticha", + "ticham", + "tigan", + "tih", + "tiho", + "tochno", + "togava", + "toi", + "tolkova", + "tor", + "tost", + "tova", + "tozi", + "tq", + "tqh", + "traktor", + "tramvay", + "tri", + "triagalnik", + "trinadeset", + "trinayset", + "trqbva", + "truden", + "tuk", + "tvard", + "tyalo", + "uchenichka", + "uchenik", + "uchilishte", + "uchitel", + "uedinenie", + "uho", + "ukrasa", + "ukrasyavam", + "um", + "uplaha", + "uplashen", + "uprazhnenie", + "ura", + "ured", + "uset", + "uspeh", + "uspeshen", + "ustroystvo", + "utre", + "uvazhenie", + "v", + "vagon", + "vampir", + "vana", + "vapros", + "var", + "varovik", + "vas", + "vaza", + "vazduh", + "vazhen", + "vcepenen", + "vchera", + "veche", + "vecher", + "vednaga", + "vesel", + "vest", + "vi", + "vidim", + "vidq", + "vie", + "vihar", + "vihrushka", + "vij", + "vilica", + "vinagi", + "vino", + "vinoven", + "vinovna", + "vinovnoto", + "visota", + "vkashti", + "vkus", + "vlak", + "vlast", + "vleze", + "voda", + "voden", + "vodenica", + "voleibol", + "vosak", + "voyna", + "vrag", + "vrah", + "vrata", + "vrazka", + "vreme", + "vse", + "vseki", + "vselena", + "vsichki", + "vsichko", + "vsushtnost", + "vuv", + "vyatar", + "vzimam", + "yabalka", + "yade", + "yadene", + "yagoda", + "yama", + "yami", + "yarak", + "yare", + "yarost", + "yasen", + "yastreb", + "yato", + "yayce", + "yod", + "yug", + "yuli", + "yuni", + "yurgan", + "yutiya", + "za", + "zadacha", + "zaedno", + "zaek", + "zakon", + "zala", + "zalez", + "zalezh", + "zalivam", + "zalyazvam", + "zapad", + "zapalvam", + "zaplaha", + "zar", + "zaradi", + "zashit", + "zashivam", + "zashtipvam", + "zashtitavam", + "zashto", + "zashtoto", + "zatova", + "zavartam", + "zavesa", + "zavod", + "zayavka", + "zdanie", + "zdrave", + "zdravei", + "zele", + "zelen", + "zhazhda", + "zhivotno", + "zhult", + "zima", + "zla", + "zlatna", + "zlato", + "zlo", + "zloba", + "zmey", + "zmiya", + "zna,", + "znachi", + "znae", + "znaesh", + "znaete", + "znanie", + "znaya", + "zub", + "zvuk", + "zvyar" + ] +} diff --git a/packages/schemas/src/languages.ts b/packages/schemas/src/languages.ts index d199e0e8a..7e26911a3 100644 --- a/packages/schemas/src/languages.ts +++ b/packages/schemas/src/languages.ts @@ -235,7 +235,9 @@ export const LanguageSchema = z.enum( "lithuanian_1k", "lithuanian_3k", "bulgarian", + "bulgarian_1k", "bulgarian_latin", + "bulgarian_latin_1k", "bangla", "bangla_letters", "bangla_10k", From bd9951931e7d49c3ec1db57efa6e2951eafa090a Mon Sep 17 00:00:00 2001 From: Miodec Date: Wed, 17 Dec 2025 20:27:52 +0100 Subject: [PATCH 05/23] fix: timer resetting to initial value when test finishes closes #7263 --- frontend/src/ts/states/time.ts | 4 ++-- frontend/src/ts/test/test-timer.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/ts/states/time.ts b/frontend/src/ts/states/time.ts index 899bf154b..c18761a1a 100644 --- a/frontend/src/ts/states/time.ts +++ b/frontend/src/ts/states/time.ts @@ -4,8 +4,8 @@ export function get(): number { return time; } -export function set(active: number): void { - time = active; +export function set(number: number): void { + time = number; } export function increment(): void { diff --git a/frontend/src/ts/test/test-timer.ts b/frontend/src/ts/test/test-timer.ts index 17c2a46db..d0921a0f9 100644 --- a/frontend/src/ts/test/test-timer.ts +++ b/frontend/src/ts/test/test-timer.ts @@ -62,7 +62,6 @@ export function enableTimerDebug(): void { export function clear(): void { clearLowFpsMode(); - Time.set(0); newTimer.reset(); if (timer !== null) clearTimeout(timer); } From 0e5dd85db2cc0b34ae2176e3f469323669e83cc0 Mon Sep 17 00:00:00 2001 From: Seif Soliman Date: Wed, 17 Dec 2025 21:41:55 +0200 Subject: [PATCH 06/23] fix(lazy-mode): respect manual toggle after unsupported language (@byseif21) (#7260) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description * lazy mode status “stuck” after switching to a language that does not support it then goes back to the one that supports it. * to reproduce After seeing the “This language does not support lazy mode” warning, switch back to the one that was working with and manually toggling lazy mode. It would not update to the selected option. --- frontend/src/ts/states/arabic-lazy-mode.ts | 16 ----- frontend/src/ts/states/remember-lazy-mode.ts | 30 +++++++++ frontend/src/ts/test/test-logic.ts | 66 ++++++++++---------- 3 files changed, 63 insertions(+), 49 deletions(-) delete mode 100644 frontend/src/ts/states/arabic-lazy-mode.ts create mode 100644 frontend/src/ts/states/remember-lazy-mode.ts diff --git a/frontend/src/ts/states/arabic-lazy-mode.ts b/frontend/src/ts/states/arabic-lazy-mode.ts deleted file mode 100644 index 590fd50e5..000000000 --- a/frontend/src/ts/states/arabic-lazy-mode.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { z } from "zod"; -import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; - -const ls = new LocalStorageWithSchema({ - key: "prefersArabicLazyMode", - schema: z.boolean(), - fallback: true, -}); - -export function get(): boolean { - return ls.get(); -} - -export function set(value: boolean): void { - ls.set(value); -} diff --git a/frontend/src/ts/states/remember-lazy-mode.ts b/frontend/src/ts/states/remember-lazy-mode.ts new file mode 100644 index 000000000..08d04a662 --- /dev/null +++ b/frontend/src/ts/states/remember-lazy-mode.ts @@ -0,0 +1,30 @@ +import { z } from "zod"; +import { LocalStorageWithSchema } from "../utils/local-storage-with-schema"; + +const rememberLazyModeLS = new LocalStorageWithSchema({ + key: "rememberLazyMode", + schema: z.boolean(), + fallback: false, +}); + +const arabicLazyModeLS = new LocalStorageWithSchema({ + key: "prefersArabicLazyMode", + schema: z.boolean(), + fallback: true, +}); + +export function getRemember(): boolean { + return rememberLazyModeLS.get(); +} + +export function setRemember(value: boolean): void { + rememberLazyModeLS.set(value); +} + +export function getArabicPref(): boolean { + return arabicLazyModeLS.get(); +} + +export function setArabicPref(value: boolean): void { + arabicLazyModeLS.set(value); +} diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 76d3a3470..479fa9df3 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -39,7 +39,7 @@ import * as AnalyticsController from "../controllers/analytics-controller"; import { getAuthenticatedUser, isAuthenticated } from "../firebase"; import * as ConnectionState from "../states/connection"; import * as KeymapEvent from "../observables/keymap-event"; -import * as ArabicLazyMode from "../states/arabic-lazy-mode"; +import * as LazyModeState from "../states/remember-lazy-mode"; import Format from "../utils/format"; import { QuoteLength, QuoteLengthConfig } from "@monkeytype/schemas/configs"; import { Mode } from "@monkeytype/schemas/shared"; @@ -342,7 +342,6 @@ export function restart(options = {} as RestartOptions): void { } let lastInitError: Error | null = null; -let rememberLazyMode: boolean; let showedLazyModeNotification: boolean = false; let testReinitCount = 0; @@ -421,39 +420,43 @@ async function init(): Promise { .some((lang) => !lang.noLazyMode); if (Config.lazyMode && !anySupportsLazyMode) { - rememberLazyMode = true; - Notifications.add( - "None of the selected polyglot languages support lazy mode.", - 0, - { - important: true, - }, - ); + LazyModeState.setRemember(true); + if (!showedLazyModeNotification) { + Notifications.add( + "None of the selected polyglot languages support lazy mode.", + 0, + { + important: true, + }, + ); + showedLazyModeNotification = true; + } setConfig("lazyMode", false); - } else if (rememberLazyMode && anySupportsLazyMode) { - setConfig("lazyMode", true, { - nosave: true, - }); + } else if (LazyModeState.getRemember() && anySupportsLazyMode) { + setConfig("lazyMode", true); + LazyModeState.setRemember(false); + showedLazyModeNotification = false; } } else { // normal mode if (Config.lazyMode && !allowLazyMode) { - rememberLazyMode = true; - showedLazyModeNotification = true; - Notifications.add("This language does not support lazy mode.", 0, { - important: true, - }); - + LazyModeState.setRemember(true); + if (!showedLazyModeNotification) { + Notifications.add("This language does not support lazy mode.", 0, { + important: true, + }); + showedLazyModeNotification = true; + } setConfig("lazyMode", false); - } else if (rememberLazyMode && !language.noLazyMode) { - setConfig("lazyMode", true, { - nosave: true, - }); + } else if (LazyModeState.getRemember() && allowLazyMode) { + setConfig("lazyMode", true); + LazyModeState.setRemember(false); + showedLazyModeNotification = false; } } if (!Config.lazyMode && !language.noLazyMode) { - rememberLazyMode = false; + LazyModeState.setRemember(false); } if (Config.mode === "custom") { @@ -1549,7 +1552,10 @@ ConfigEvent.subscribe(({ key, newValue, nosave }) => { if (ActivePage.get() === "test") { if (key === "language") { //automatically enable lazy mode for arabic - if ((newValue as string)?.startsWith("arabic") && ArabicLazyMode.get()) { + if ( + (newValue as string)?.startsWith("arabic") && + LazyModeState.getArabicPref() + ) { setConfig("lazyMode", true, { nosave: true, }); @@ -1582,13 +1588,7 @@ ConfigEvent.subscribe(({ key, newValue, nosave }) => { } if (key === "lazyMode" && !nosave) { if (Config.language.startsWith("arabic")) { - ArabicLazyMode.set(newValue); - } - if (newValue) { - if (!showedLazyModeNotification) { - rememberLazyMode = false; - } - showedLazyModeNotification = false; + LazyModeState.setArabicPref(newValue); } } }); From c222098a05f1155e37a073e71c4f5a066ebdc7fb Mon Sep 17 00:00:00 2001 From: Miodec Date: Wed, 17 Dec 2025 20:49:03 +0100 Subject: [PATCH 07/23] fix: previous commit nuking the timer --- frontend/src/ts/test/test-logic.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 479fa9df3..423266590 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -68,6 +68,7 @@ import { canQuickRestart } from "../utils/quick-restart"; import { animate } from "animejs"; import { setInputElementValue } from "../input/input-element"; import { debounce } from "throttle-debounce"; +import * as Time from "../states/time"; let failReason = ""; @@ -100,6 +101,7 @@ export function startTest(now: number): boolean { Replay.startReplayRecording(); Replay.replayGetWordsList(TestWords.words.list); TestInput.resetKeypressTimings(); + Time.set(0); TestTimer.clear(); for (const fb of getActiveFunboxesWithFunction("start")) { From d6a6dad5528abdabb06f7a8949d57f0510d1635c Mon Sep 17 00:00:00 2001 From: Md Moushuf Alam <149342172+MoushufAlam@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:21:07 +0530 Subject: [PATCH 08/23] refactor: replace jQuery with DOM utils in caps lock warning (@MoushufAlam) (#7265) ### Description Replaces jQuery usage with DOM utils for the caps lock warning. Scope intentionally kept small per contributing guidelines. ### Checks - [x] Adding/modifying Typescript code? - [x] I have used `qs`, `qsa` or `qsr` instead of JQuery selectors. - [x] Check if any open issues are related to this PR; if so, be sure to tag them below. - [x] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [x] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. Related to #7186 --- frontend/src/ts/test/caps-warning.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/frontend/src/ts/test/caps-warning.ts b/frontend/src/ts/test/caps-warning.ts index cb78f72fb..87a7ed847 100644 --- a/frontend/src/ts/test/caps-warning.ts +++ b/frontend/src/ts/test/caps-warning.ts @@ -1,7 +1,8 @@ import Config from "../config"; import * as Misc from "../utils/misc"; +import { qsr } from "../utils/dom"; -const el = document.querySelector("#capsWarning") as HTMLElement; +const el = qsr("#capsWarning"); export let capsState = false; @@ -9,23 +10,23 @@ let visible = false; function show(): void { if (!visible) { - el?.classList.remove("hidden"); + el.removeClass("hidden"); visible = true; } } function hide(): void { if (visible) { - el?.classList.add("hidden"); + el.addClass("hidden"); visible = false; } } -function update(event: JQuery.KeyDownEvent | JQuery.KeyUpEvent): void { - if (event?.originalEvent?.key === "CapsLock" && capsState !== null) { +function update(event: KeyboardEvent): void { + if (event.key === "CapsLock" && capsState !== null) { capsState = !capsState; } else { - const modState = event?.originalEvent?.getModifierState?.("CapsLock"); + const modState = event.getModifierState?.("CapsLock"); if (modState !== undefined) { capsState = modState; } @@ -40,8 +41,8 @@ function update(event: JQuery.KeyDownEvent | JQuery.KeyUpEvent): void { } catch {} } -$(document).on("keyup", update); +document.addEventListener("keyup", update); -$(document).on("keydown", (event) => { +document.addEventListener("keydown", (event) => { if (Misc.isMac()) update(event); }); From 9e93af465f38641a4ac61b7e3552a91e457a48c2 Mon Sep 17 00:00:00 2001 From: Md Moushuf Alam <149342172+MoushufAlam@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:13:47 +0530 Subject: [PATCH 09/23] refactor: replace jQuery with DOM utils for alt and shift key trackers (@MoushufAlam) (#7266) ### Description Replaces jQuery usage with DOM utils for alt and shift key trackers. Scope intentionally kept small per contributing guidelines. ### Checks - [x] Adding/modifying Typescript code? - [x] I have used `qs`, `qsa` or `qsr` instead of JQuery selectors. - [x] Check if any open issues are related to this PR; if so, be sure to tag them below. - [x] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [x] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. Related to #7186 --- frontend/src/ts/test/alt-tracker.ts | 4 ++-- frontend/src/ts/test/shift-tracker.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/ts/test/alt-tracker.ts b/frontend/src/ts/test/alt-tracker.ts index edf80e459..fa1393392 100644 --- a/frontend/src/ts/test/alt-tracker.ts +++ b/frontend/src/ts/test/alt-tracker.ts @@ -1,7 +1,7 @@ export let leftState = false; export let rightState = false; -$(document).on("keydown", (e) => { +document.addEventListener("keydown", (e: KeyboardEvent) => { if (e.code === "AltLeft") { leftState = true; } else if (e.code === "AltRight") { @@ -9,7 +9,7 @@ $(document).on("keydown", (e) => { } }); -$(document).on("keyup", (e) => { +document.addEventListener("keyup", (e: KeyboardEvent) => { if (e.code === "AltLeft") { leftState = false; } else if (e.code === "AltRight") { diff --git a/frontend/src/ts/test/shift-tracker.ts b/frontend/src/ts/test/shift-tracker.ts index d60dad90a..90a7feea2 100644 --- a/frontend/src/ts/test/shift-tracker.ts +++ b/frontend/src/ts/test/shift-tracker.ts @@ -4,7 +4,7 @@ import * as KeyConverter from "../utils/key-converter"; export let leftState = false; export let rightState = false; -$(document).on("keydown", (e) => { +document.addEventListener("keydown", (e: KeyboardEvent) => { if (e.code === "ShiftLeft") { leftState = true; rightState = false; @@ -14,7 +14,7 @@ $(document).on("keydown", (e) => { } }); -$(document).on("keyup", (e) => { +document.addEventListener("keyup", (e: KeyboardEvent) => { if (e.code === "ShiftLeft" || e.code === "ShiftRight") { leftState = false; rightState = false; From 8d1eefc5babadd2a442ec1145e17a858401ed7ab Mon Sep 17 00:00:00 2001 From: Miodec Date: Thu, 18 Dec 2025 16:34:36 +0100 Subject: [PATCH 10/23] fix: account icon alignment when not using an avatar --- frontend/src/styles/core.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frontend/src/styles/core.scss b/frontend/src/styles/core.scss index 360a2e5dd..4a34b6271 100644 --- a/frontend/src/styles/core.scss +++ b/frontend/src/styles/core.scss @@ -375,6 +375,11 @@ key { } } + .userIcon { + display: grid; + border-radius: 0; + } + .loading { font-size: 0.8em; line-height: 0.8em; From 9d709c7c7c23df409d0d5a92af5eb37a1f759087 Mon Sep 17 00:00:00 2001 From: Leonabcd123 <156839416+Leonabcd123@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:22:44 +0200 Subject: [PATCH 11/23] impr(quote search): add exact search quotes (@Leonabcd123) (#7261) ### Description Make text wrapped in `""` required when searching for quotes (meaning only quotes that contain this exact text will appear in the results). This allows case insensitivity (so "hello" will match "Hello"). --- frontend/src/ts/modals/quote-search.ts | 82 ++++++++++++++++++++++--- frontend/src/ts/utils/search-service.ts | 12 +++- 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/frontend/src/ts/modals/quote-search.ts b/frontend/src/ts/modals/quote-search.ts index 14e7c043a..ddfacc1bb 100644 --- a/frontend/src/ts/modals/quote-search.ts +++ b/frontend/src/ts/modals/quote-search.ts @@ -28,6 +28,11 @@ const searchServiceCache: Record> = {}; const pageSize = 100; let currentPageNumber = 1; let usingCustomLength = true; +let quotes: Quote[]; + +async function updateQuotes(): Promise { + ({ quotes } = await QuotesController.getQuotes(Config.language)); +} function getSearchService( language: string, @@ -188,10 +193,61 @@ function buildQuoteSearchResult( `; } +function exactSearch(quotes: Quote[], captured: RegExp[]): [Quote[], string[]] { + const matches: Quote[] = []; + const exactSearchQueryTerms: Set = new Set(); + + for (const quote of quotes) { + const textAndSource = quote.text + quote.source; + const currentMatches = []; + let noMatch = false; + + for (const regex of captured) { + const match = textAndSource.match(regex); + + if (!match) { + noMatch = true; + break; + } + + currentMatches.push(match[0]); + } + + if (!noMatch) { + currentMatches.forEach((match) => exactSearchQueryTerms.add(match)); + matches.push(quote); + } + } + + return [matches, Array.from(exactSearchQueryTerms)]; +} + async function updateResults(searchText: string): Promise { if (!modal.isOpen()) return; - const { quotes } = await QuotesController.getQuotes(Config.language); + if (quotes === undefined) { + ({ quotes } = await QuotesController.getQuotes(Config.language)); + } + + let matches: Quote[] = []; + let matchedQueryTerms: string[] = []; + let exactSearchMatches: Quote[] = []; + let exactSearchMatchedQueryTerms: string[] = []; + + const quotationsRegex = /"(.*?)"/g; + const exactSearchQueries = Array.from(searchText.matchAll(quotationsRegex)); + const removedSearchText = searchText.replaceAll(quotationsRegex, ""); + + if (exactSearchQueries[0]) { + const searchQueriesRaw = exactSearchQueries.map( + (query) => new RegExp(query[1] ?? "", "i"), + ); + + [exactSearchMatches, exactSearchMatchedQueryTerms] = exactSearch( + quotes, + searchQueriesRaw, + ); + } const quoteSearchService = getSearchService( Config.language, @@ -200,8 +256,21 @@ async function updateResults(searchText: string): Promise { return `${quote.text} ${quote.id} ${quote.source}`; }, ); - const { results: matches, matchedQueryTerms } = - quoteSearchService.query(searchText); + + if (exactSearchMatches.length > 0 || removedSearchText === searchText) { + const ids = exactSearchMatches.map((match) => match.id); + + ({ results: matches, matchedQueryTerms } = quoteSearchService.query( + removedSearchText, + ids, + )); + + exactSearchMatches.forEach((match) => { + if (!matches.includes(match)) matches.push(match); + }); + + matchedQueryTerms = [...exactSearchMatchedQueryTerms, ...matchedQueryTerms]; + } const quotesToShow = applyQuoteLengthFilter( applyQuoteFavFilter(searchText === "" ? quotes : matches), @@ -340,12 +409,7 @@ export async function show(showOptions?: ShowOptions): Promise { }); }, afterAnimation: async () => { - const quoteSearchInputValue = $( - "#quoteSearchModal input", - ).val() as string; - currentPageNumber = 1; - - void updateResults(quoteSearchInputValue); + void updateQuotes(); }, }); } diff --git a/frontend/src/ts/utils/search-service.ts b/frontend/src/ts/utils/search-service.ts index 2773f3c29..8a57203fd 100644 --- a/frontend/src/ts/utils/search-service.ts +++ b/frontend/src/ts/utils/search-service.ts @@ -2,7 +2,7 @@ import { stemmer } from "stemmer"; import levenshtein from "damerau-levenshtein"; export type SearchService = { - query: (query: string) => SearchResult; + query: (query: string, ids: number[]) => SearchResult; }; type SearchServiceOptions = { @@ -110,7 +110,7 @@ export const buildSearchService = ( const tokenSet = Object.keys(reverseIndex); - const query = (searchQuery: string): SearchResult => { + const query = (searchQuery: string, ids: number[]): SearchResult => { const searchResult: SearchResult = { results: [], matchedQueryTerms: [], @@ -155,7 +155,13 @@ export const buildSearchService = ( const scoreForToken = score * idf * termFrequency; - results.set(document.id, currentScore + scoreForToken); + const quote = documents[document.id] as InternalDocument; + if ( + ids.length === 0 || + (quote !== null && quote !== undefined && ids.includes(quote.id)) + ) { + results.set(document.id, currentScore + scoreForToken); + } }); normalizedTokenToOriginal[token]?.forEach((originalToken) => { From 484ab1bd5f1f7c598bd615da316567e5ca99174b Mon Sep 17 00:00:00 2001 From: Miodec Date: Thu, 18 Dec 2025 19:20:15 +0100 Subject: [PATCH 12/23] chore: remove jquery from toggleSettingsGroup --- frontend/src/styles/settings.scss | 1 + frontend/src/ts/pages/settings.ts | 23 +++++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/frontend/src/styles/settings.scss b/frontend/src/styles/settings.scss index f37ed4b68..2dcc02cab 100644 --- a/frontend/src/styles/settings.scss +++ b/frontend/src/styles/settings.scss @@ -50,6 +50,7 @@ .settingsGroup { display: grid; gap: 2rem; + overflow: hidden; &.quickNav { justify-content: center; .links { diff --git a/frontend/src/ts/pages/settings.ts b/frontend/src/ts/pages/settings.ts index c6707aaf0..885550626 100644 --- a/frontend/src/ts/pages/settings.ts +++ b/frontend/src/ts/pages/settings.ts @@ -43,7 +43,7 @@ import * as CustomBackgroundPicker from "../elements/settings/custom-background- import * as CustomFontPicker from "../elements/settings/custom-font-picker"; import * as AuthEvent from "../observables/auth-event"; import * as FpsLimitSection from "../elements/settings/fps-limit-section"; -import { qsr } from "../utils/dom"; +import { qs, qsr } from "../utils/dom"; let settingsInitialized = false; @@ -759,13 +759,28 @@ function toggleSettingsGroup(groupName: string): void { //The highlight is repeated/broken when toggling the group handleHighlightSection(undefined); - const groupEl = $(`.pageSettings .settingsGroup.${groupName}`); - groupEl.stop(true, true).slideToggle(250).toggleClass("slideup"); - if (groupEl.hasClass("slideup")) { + const groupEl = qs(`.pageSettings .settingsGroup.${groupName}`); + if (!groupEl?.hasClass("slideup")) { + groupEl?.animate({ + height: 0, + duration: 250, + onComplete: () => { + groupEl?.hide(); + }, + }); + groupEl?.addClass("slideup"); $(`.pageSettings .sectionGroupTitle[group=${groupName}]`).addClass( "rotateIcon", ); } else { + groupEl?.show(); + groupEl?.setStyle({ height: "" }); + const height = groupEl.getOffsetHeight(); + groupEl?.animate({ + height: [0, height], + duration: 250, + }); + groupEl?.removeClass("slideup"); $(`.pageSettings .sectionGroupTitle[group=${groupName}]`).removeClass( "rotateIcon", ); From 8a2a3e4d233c9be3f6611ee982cf18ae501b5509 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Thu, 18 Dec 2025 19:27:24 +0100 Subject: [PATCH 13/23] impr: add copy details to notification history (@fehmer) (#7262) Add details to notifications. If details are available show share icon on hover in the notification history. On click the full content is copied to the clipboard. With this is easier for an user to share the full details of an error on github or discord. image image ```json { "title": "Error", "message": "Failed to save config", "details": { "status": 422, "validationErrors": [ "Unrecognized key(s) in object: 'invalid'" ] } } ``` --------- Co-authored-by: Jack --- frontend/src/styles/popups.scss | 6 +- frontend/src/ts/auth.ts | 9 +- frontend/src/ts/db.ts | 26 ++-- .../account-settings/ape-key-table.ts | 13 +- .../account-settings/blocked-user-table.ts | 5 +- frontend/src/ts/elements/alerts.ts | 119 ++++++++++++++++-- frontend/src/ts/elements/notifications.ts | 23 +++- frontend/src/ts/modals/dev-options.ts | 1 + frontend/src/ts/modals/edit-preset.ts | 9 +- frontend/src/ts/modals/edit-profile.ts | 2 +- frontend/src/ts/modals/edit-result-tags.ts | 5 +- frontend/src/ts/modals/edit-tag.ts | 10 +- frontend/src/ts/modals/quote-approve.ts | 8 +- frontend/src/ts/modals/quote-rate.ts | 10 +- frontend/src/ts/modals/quote-report.ts | 2 +- frontend/src/ts/modals/quote-submit.ts | 2 +- frontend/src/ts/modals/simple-modals.ts | 39 +++--- frontend/src/ts/modals/streak-hour-offset.ts | 5 +- frontend/src/ts/modals/user-report.ts | 2 +- .../src/ts/observables/notification-event.ts | 13 +- frontend/src/ts/pages/account.ts | 2 +- frontend/src/ts/test/test-logic.ts | 2 +- frontend/src/ts/utils/results.ts | 5 +- frontend/src/ts/utils/url-handler.ts | 2 +- packages/contracts/src/util/api.ts | 16 +++ 25 files changed, 227 insertions(+), 109 deletions(-) diff --git a/frontend/src/styles/popups.scss b/frontend/src/styles/popups.scss index 82501c7c2..644d865ac 100644 --- a/frontend/src/styles/popups.scss +++ b/frontend/src/styles/popups.scss @@ -1863,8 +1863,7 @@ body.darkMode { } } .notificationHistory .list .item { - grid-template-areas: "indicator title" "indicator body"; - grid-template-columns: 0.25rem calc(100% - 0.25rem); + grid-template-areas: "indicator title buttons" "indicator body buttons"; .title { font-size: 0.75rem; color: var(--sub-color); @@ -1872,6 +1871,9 @@ body.darkMode { .body { opacity: 1; } + .highlight { + color: var(--main-color) !important; + } } .accountAlerts { .title { diff --git a/frontend/src/ts/auth.ts b/frontend/src/ts/auth.ts index 8e33f5822..da87b66a2 100644 --- a/frontend/src/ts/auth.ts +++ b/frontend/src/ts/auth.ts @@ -45,14 +45,11 @@ async function sendVerificationEmail(): Promise { Loader.show(); qs(".sendVerificationEmail")?.disable(); - const result = await Ape.users.verificationEmail(); + const response = await Ape.users.verificationEmail(); qs(".sendVerificationEmail")?.enable(); - if (result.status !== 200) { + if (response.status !== 200) { Loader.hide(); - Notifications.add( - "Failed to request verification email: " + result.body.message, - -1, - ); + Notifications.add("Failed to request verification email", -1, { response }); } else { Loader.hide(); Notifications.add("Verification email sent", 1); diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts index a8e430152..0089fc074 100644 --- a/frontend/src/ts/db.ts +++ b/frontend/src/ts/db.ts @@ -299,7 +299,7 @@ export async function getUserResults(offset?: number): Promise { const response = await Ape.results.get({ query: { offset } }); if (response.status !== 200) { - Notifications.add("Error getting results: " + response.body.message, -1); + Notifications.add("Error getting results", -1, { response }); return false; } @@ -357,10 +357,7 @@ export async function addCustomTheme( const response = await Ape.users.addCustomTheme({ body: { ...theme } }); if (response.status !== 200) { - Notifications.add( - "Error adding custom theme: " + response.body.message, - -1, - ); + Notifications.add("Error adding custom theme", -1, { response }); return false; } @@ -400,10 +397,7 @@ export async function editCustomTheme( body: { themeId, theme: newTheme }, }); if (response.status !== 200) { - Notifications.add( - "Error editing custom theme: " + response.body.message, - -1, - ); + Notifications.add("Error editing custom theme", -1, { response }); return false; } @@ -427,10 +421,7 @@ export async function deleteCustomTheme(themeId: string): Promise { const response = await Ape.users.deleteCustomTheme({ body: { themeId } }); if (response.status !== 200) { - Notifications.add( - "Error deleting custom theme: " + response.body.message, - -1, - ); + Notifications.add("Error deleting custom theme", -1, { response }); return false; } @@ -923,7 +914,7 @@ export async function saveConfig(config: Partial): Promise { if (isAuthenticated()) { const response = await Ape.configs.save({ body: config }); if (response.status !== 200) { - Notifications.add("Failed to save config: " + response.body.message, -1); + Notifications.add("Failed to save config", -1, { response }); } } } @@ -932,7 +923,7 @@ export async function resetConfig(): Promise { if (isAuthenticated()) { const response = await Ape.configs.delete(); if (response.status !== 200) { - Notifications.add("Failed to reset config: " + response.body.message, -1); + Notifications.add("Failed to reset config", -1, { response }); } } } @@ -1055,10 +1046,7 @@ export async function getTestActivityCalendar( Loader.show(); const response = await Ape.users.getTestActivity(); if (response.status !== 200) { - Notifications.add( - "Error getting test activities: " + response.body.message, - -1, - ); + Notifications.add("Error getting test activities", -1, { response }); Loader.hide(); return undefined; } diff --git a/frontend/src/ts/elements/account-settings/ape-key-table.ts b/frontend/src/ts/elements/account-settings/ape-key-table.ts index 83c27771c..0d845b7c0 100644 --- a/frontend/src/ts/elements/account-settings/ape-key-table.ts +++ b/frontend/src/ts/elements/account-settings/ape-key-table.ts @@ -27,7 +27,8 @@ const editApeKey = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to update key: " + response.body.message, + message: "Failed to update key", + notificationOptions: { response }, }; } return { @@ -53,7 +54,8 @@ const deleteApeKeyModal = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to delete key: " + response.body.message, + message: "Failed to delete key", + notificationOptions: { response }, }; } @@ -128,7 +130,8 @@ const generateApeKey = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to generate key: " + response.body.message, + message: "Failed to generate key", + notificationOptions: { response }, }; } @@ -174,7 +177,7 @@ async function getData(): Promise { void update(); return false; } - Notifications.add("Error getting ape keys: " + response.body.message, -1); + Notifications.add("Error getting ape keys", -1, { response }); return false; } @@ -261,7 +264,7 @@ async function toggleActiveKey(keyId: string): Promise { }); Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to update key: " + response.body.message, -1); + Notifications.add("Failed to update key", -1, { response }); return; } key.enabled = !key.enabled; diff --git a/frontend/src/ts/elements/account-settings/blocked-user-table.ts b/frontend/src/ts/elements/account-settings/blocked-user-table.ts index 57839e54a..9bb28e169 100644 --- a/frontend/src/ts/elements/account-settings/blocked-user-table.ts +++ b/frontend/src/ts/elements/account-settings/blocked-user-table.ts @@ -24,10 +24,7 @@ async function getData(): Promise { if (response.status !== 200) { blockedUsers = []; - Notifications.add( - "Error getting blocked users: " + response.body.message, - -1, - ); + Notifications.add("Error getting blocked users", -1, { response }); return false; } diff --git a/frontend/src/ts/elements/alerts.ts b/frontend/src/ts/elements/alerts.ts index 295c292fd..e94aa4612 100644 --- a/frontend/src/ts/elements/alerts.ts +++ b/frontend/src/ts/elements/alerts.ts @@ -6,7 +6,12 @@ import * as NotificationEvent from "../observables/notification-event"; import * as BadgeController from "../controllers/badge-controller"; import * as Notifications from "../elements/notifications"; import * as ConnectionState from "../states/connection"; -import { escapeHTML } from "../utils/misc"; +import { + applyReducedMotion, + createErrorMessage, + escapeHTML, + promiseAnimate, +} from "../utils/misc"; import AnimatedModal from "../utils/animated-modal"; import { updateXp as accountPageUpdateProfile } from "./profile"; import { MonkeyMail } from "@monkeytype/schemas/users"; @@ -29,10 +34,17 @@ let mailToMarkRead: string[] = []; let mailToDelete: string[] = []; type State = { - notifications: { message: string; level: number; customTitle?: string }[]; + notifications: { + id: string; + title: string; + message: string; + level: number; + details?: string | object; + }[]; psas: { message: string; level: number }[]; }; +let notificationId = 0; const state: State = { notifications: [], psas: [], @@ -289,28 +301,29 @@ function fillNotifications(): void { } else { notificationHistoryListEl.empty(); for (const n of state.notifications) { - const { message, level, customTitle } = n; - let title = "Notice"; + const { message, level, title } = n; + let levelClass = "sub"; if (level === -1) { levelClass = "error"; - title = "Error"; } else if (level === 1) { levelClass = "main"; - title = "Success"; - } - - if (customTitle !== undefined) { - title = customTitle; } notificationHistoryListEl.prependHtml(` -
+
${title}
${escapeHTML(message)}
+
+ ${ + n.details !== undefined + ? `` + : `` + } +
`); } @@ -396,15 +409,89 @@ function updateClaimDeleteAllButton(): void { } } +async function copyNotificationToClipboard(target: HTMLElement): Promise { + const id = (target as HTMLElement | null) + ?.closest(".item") + ?.getAttribute("data-id") + ?.toString(); + + if (id === undefined) { + throw new Error("Notification ID is undefined"); + } + const notification = state.notifications.find((it) => it.id === id); + if (notification === undefined) return; + + const icon = target.querySelector("i") as HTMLElement; + + try { + await navigator.clipboard.writeText( + JSON.stringify( + { + title: notification.title, + message: notification.message, + details: notification.details, + }, + null, + 4, + ), + ); + + const duration = applyReducedMotion(100); + + await promiseAnimate(icon, { + scale: [1, 0.8], + opacity: [1, 0], + duration, + }); + icon.classList.remove("fa-clipboard"); + icon.classList.add("fa-check", "highlight"); + await promiseAnimate(icon, { + scale: [0.8, 1], + opacity: [0, 1], + duration, + }); + + await promiseAnimate(icon, { + scale: [1, 0.8], + opacity: [1, 0], + delay: 3000, + duration, + }); + icon.classList.remove("fa-check", "highlight"); + icon.classList.add("fa-clipboard"); + + await promiseAnimate(icon, { + scale: [0.8, 1], + opacity: [0, 1], + duration, + }); + } catch (e: unknown) { + const msg = createErrorMessage(e, "Could not copy to clipboard"); + Notifications.add(msg, -1); + } +} + qs("header nav .showAlerts")?.on("click", () => { void show(); }); -NotificationEvent.subscribe((message, level, customTitle) => { +NotificationEvent.subscribe((message, level, options) => { + let title = "Notice"; + if (level === -1) { + title = "Error"; + } else if (level === 1) { + title = "Success"; + } + if (options.customTitle !== undefined) { + title = options.customTitle; + } + state.notifications.push({ + id: (notificationId++).toString(), + title, message, level, - customTitle, + details: options.details, }); if (state.notifications.length > 25) { state.notifications.shift(); @@ -495,5 +582,11 @@ const modal = new AnimatedModal({ markReadAlert(id); }); + + alertsPopupEl + .qs(".notificationHistory .list") + ?.onChild("click", ".item .buttons .copyNotification", (e) => { + void copyNotificationToClipboard(e.target as HTMLElement); + }); }, }); diff --git a/frontend/src/ts/elements/notifications.ts b/frontend/src/ts/elements/notifications.ts index c15e0638f..e85593a5d 100644 --- a/frontend/src/ts/elements/notifications.ts +++ b/frontend/src/ts/elements/notifications.ts @@ -5,6 +5,7 @@ import * as NotificationEvent from "../observables/notification-event"; import { convertRemToPixels } from "../utils/numbers"; import { animate } from "animejs"; import { qsr } from "../utils/dom"; +import { CommonResponsesType } from "@monkeytype/contracts/util/api"; const notificationCenter = qsr("#notificationCenter"); const notificationCenterHistory = notificationCenter.qsr(".history"); @@ -107,6 +108,7 @@ class Notification { visibleStickyNotifications++; updateClearAllButton(); } + notificationCenterHistory.prependHtml(`
${icon}
${title}
${this.message}
@@ -270,6 +272,8 @@ export type AddNotificationOptions = { customIcon?: string; closeCallback?: () => void; allowHTML?: boolean; + details?: object | string; + response?: CommonResponsesType; }; export function add( @@ -277,7 +281,24 @@ export function add( level = 0, options: AddNotificationOptions = {}, ): void { - NotificationEvent.dispatch(message, level, options.customTitle); + let details = options.details; + + if (options.response !== undefined) { + details = { + status: options.response.status, + additionalDetails: options.details, + validationErrors: + options.response.status === 422 + ? options.response.body.validationErrors + : undefined, + }; + message = message + ": " + options.response.body.message; + } + + NotificationEvent.dispatch(message, level, { + customTitle: options.customTitle, + details, + }); new Notification( "notification", diff --git a/frontend/src/ts/modals/dev-options.ts b/frontend/src/ts/modals/dev-options.ts index 199f530ab..6c254688a 100644 --- a/frontend/src/ts/modals/dev-options.ts +++ b/frontend/src/ts/modals/dev-options.ts @@ -32,6 +32,7 @@ async function setup(modalEl: HTMLElement): Promise { }); Notifications.add("This is a test", -1, { duration: 0, + details: { test: true, error: "Example error message" }, }); void modal.hide(); }); diff --git a/frontend/src/ts/modals/edit-preset.ts b/frontend/src/ts/modals/edit-preset.ts index 694e9b69f..7f3771994 100644 --- a/frontend/src/ts/modals/edit-preset.ts +++ b/frontend/src/ts/modals/edit-preset.ts @@ -284,7 +284,7 @@ async function apply(): Promise { if (response.status !== 200 || response.body.data === null) { Notifications.add( - "Failed to add preset: " + + "Failed to add preset" + response.body.message.replace(presetName, propPresetName), -1, ); @@ -325,7 +325,7 @@ async function apply(): Promise { }); if (response.status !== 200) { - Notifications.add("Failed to edit preset: " + response.body.message, -1); + Notifications.add("Failed to edit preset", -1, { response }); } else { Notifications.add("Preset updated", 1); @@ -344,10 +344,7 @@ async function apply(): Promise { const response = await Ape.presets.delete({ params: { presetId } }); if (response.status !== 200) { - Notifications.add( - "Failed to remove preset: " + response.body.message, - -1, - ); + Notifications.add("Failed to remove preset", -1, { response }); } else { Notifications.add("Preset removed", 1); snapshotPresets.forEach((preset: SnapshotPreset, index: number) => { diff --git a/frontend/src/ts/modals/edit-profile.ts b/frontend/src/ts/modals/edit-profile.ts index 633c89d39..3eed57085 100644 --- a/frontend/src/ts/modals/edit-profile.ts +++ b/frontend/src/ts/modals/edit-profile.ts @@ -180,7 +180,7 @@ async function updateProfile(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to update profile: " + response.body.message, -1); + Notifications.add("Failed to update profile", -1, { response }); return; } diff --git a/frontend/src/ts/modals/edit-result-tags.ts b/frontend/src/ts/modals/edit-result-tags.ts index 6ce533a43..f3a65df61 100644 --- a/frontend/src/ts/modals/edit-result-tags.ts +++ b/frontend/src/ts/modals/edit-result-tags.ts @@ -121,10 +121,7 @@ async function save(): Promise { state.tags = state.tags.filter((el) => el !== undefined); if (response.status !== 200) { - Notifications.add( - "Failed to update result tags: " + response.body.message, - -1, - ); + Notifications.add("Failed to update result tags", -1, { response }); return; } diff --git a/frontend/src/ts/modals/edit-tag.ts b/frontend/src/ts/modals/edit-tag.ts index 880531a57..5582aa14d 100644 --- a/frontend/src/ts/modals/edit-tag.ts +++ b/frontend/src/ts/modals/edit-tag.ts @@ -37,6 +37,7 @@ const actionModals: Record = { message: "Failed to add tag: " + response.body.message.replace(tagName, propTagName), + notificationOptions: { response }, }; } @@ -83,7 +84,8 @@ const actionModals: Record = { if (response.status !== 200) { return { status: -1, - message: "Failed to edit tag: " + response.body.message, + message: "Failed to edit tag", + notificationOptions: { response }, }; } @@ -113,7 +115,8 @@ const actionModals: Record = { if (response.status !== 200) { return { status: -1, - message: "Failed to remove tag: " + response.body.message, + message: "Failed to remove tag", + notificationOptions: { response }, }; } @@ -143,7 +146,8 @@ const actionModals: Record = { if (response.status !== 200) { return { status: -1, - message: "Failed to clear tag pb: " + response.body.message, + message: "Failed to clear tag pb", + notificationOptions: { response }, }; } diff --git a/frontend/src/ts/modals/quote-approve.ts b/frontend/src/ts/modals/quote-approve.ts index 988c7c5a3..3fd7a79fe 100644 --- a/frontend/src/ts/modals/quote-approve.ts +++ b/frontend/src/ts/modals/quote-approve.ts @@ -98,7 +98,7 @@ async function getQuotes(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to get new quotes: " + response.body.message, -1); + Notifications.add("Failed to get new quotes", -1, { response }); return; } @@ -160,7 +160,7 @@ async function approveQuote(index: number, dbid: string): Promise { if (response.status !== 200) { resetButtons(index); quote.find("textarea, input").prop("disabled", false); - Notifications.add("Failed to approve quote: " + response.body.message, -1); + Notifications.add("Failed to approve quote", -1, { response }); return; } @@ -184,7 +184,7 @@ async function refuseQuote(index: number, dbid: string): Promise { if (response.status !== 200) { resetButtons(index); quote.find("textarea, input").prop("disabled", false); - Notifications.add("Failed to refuse quote: " + response.body.message, -1); + Notifications.add("Failed to refuse quote", -1, { response }); return; } @@ -218,7 +218,7 @@ async function editQuote(index: number, dbid: string): Promise { if (response.status !== 200) { resetButtons(index); quote.find("textarea, input").prop("disabled", false); - Notifications.add("Failed to approve quote: " + response.body.message, -1); + Notifications.add("Failed to approve quote", -1, { response }); return; } diff --git a/frontend/src/ts/modals/quote-rate.ts b/frontend/src/ts/modals/quote-rate.ts index 132eef7bf..e22b97f91 100644 --- a/frontend/src/ts/modals/quote-rate.ts +++ b/frontend/src/ts/modals/quote-rate.ts @@ -60,10 +60,7 @@ export async function getQuoteStats( Loader.hide(); if (response.status !== 200) { - Notifications.add( - "Failed to get quote ratings: " + response.body.message, - -1, - ); + Notifications.add("Failed to get quote ratings", -1, { response }); return; } @@ -156,10 +153,7 @@ async function submit(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add( - "Failed to submit quote rating: " + response.body.message, - -1, - ); + Notifications.add("Failed to submit quote rating", -1, { response }); return; } diff --git a/frontend/src/ts/modals/quote-report.ts b/frontend/src/ts/modals/quote-report.ts index 991ec10bc..f24a423ab 100644 --- a/frontend/src/ts/modals/quote-report.ts +++ b/frontend/src/ts/modals/quote-report.ts @@ -121,7 +121,7 @@ async function submitReport(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to report quote: " + response.body.message, -1); + Notifications.add("Failed to report quote", -1, { response }); return; } diff --git a/frontend/src/ts/modals/quote-submit.ts b/frontend/src/ts/modals/quote-submit.ts index 07d0befc7..2a2f99fb3 100644 --- a/frontend/src/ts/modals/quote-submit.ts +++ b/frontend/src/ts/modals/quote-submit.ts @@ -44,7 +44,7 @@ async function submitQuote(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to submit quote: " + response.body.message, -1); + Notifications.add("Failed to submit quote", -1, { response }); return; } diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts index a15a83135..c22d32068 100644 --- a/frontend/src/ts/modals/simple-modals.ts +++ b/frontend/src/ts/modals/simple-modals.ts @@ -288,7 +288,8 @@ list.updateEmail = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to update email: " + response.body.message, + message: "Failed to update email", + notificationOptions: { response }, }; } @@ -499,13 +500,14 @@ list.updateName = new SimpleModal({ }; } - const updateNameResponse = await Ape.users.updateName({ + const response = await Ape.users.updateName({ body: { name: newName }, }); - if (updateNameResponse.status !== 200) { + if (response.status !== 200) { return { status: -1, - message: "Failed to update name: " + updateNameResponse.body.message, + message: "Failed to update name", + notificationOptions: { response }, }; } @@ -598,7 +600,8 @@ list.updatePassword = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to update password: " + response.body.message, + message: "Failed to update password", + notificationOptions: { response }, }; } @@ -699,8 +702,8 @@ list.addPasswordAuth = new SimpleModal({ return { status: -1, message: - "Password authentication added but updating the database email failed. This shouldn't happen, please contact support. Error: " + - response.body.message, + "Password authentication added but updating the database email failed. This shouldn't happen, please contact support. Error", + notificationOptions: { response }, }; } @@ -735,12 +738,13 @@ list.deleteAccount = new SimpleModal({ } Notifications.add("Deleting all data...", 0); - const usersResponse = await Ape.users.delete(); + const response = await Ape.users.delete(); - if (usersResponse.status !== 200) { + if (response.status !== 200) { return { status: -1, - message: "Failed to delete user data: " + usersResponse.body.message, + message: "Failed to delete user data", + notificationOptions: { response }, }; } @@ -791,7 +795,8 @@ list.resetAccount = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to reset account: " + response.body.message, + message: "Failed to reset account", + notificationOptions: { response }, }; } @@ -837,7 +842,8 @@ list.optOutOfLeaderboards = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to opt out: " + response.body.message, + message: "Failed to opt out", + notificationOptions: { response }, }; } @@ -898,7 +904,8 @@ list.resetPersonalBests = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to reset personal bests: " + response.body.message, + message: "Failed to reset personal bests", + notificationOptions: { response }, }; } @@ -974,7 +981,8 @@ list.revokeAllTokens = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to revoke tokens: " + response.body.message, + message: "Failed to revoke tokens", + notificationOptions: { response }, }; } @@ -1015,7 +1023,8 @@ list.unlinkDiscord = new SimpleModal({ if (response.status !== 200) { return { status: -1, - message: "Failed to unlink Discord: " + response.body.message, + message: "Failed to unlink Discord", + notificationOptions: { response }, }; } diff --git a/frontend/src/ts/modals/streak-hour-offset.ts b/frontend/src/ts/modals/streak-hour-offset.ts index b81cc48e9..f2f9d90aa 100644 --- a/frontend/src/ts/modals/streak-hour-offset.ts +++ b/frontend/src/ts/modals/streak-hour-offset.ts @@ -88,10 +88,7 @@ async function apply(): Promise { }); Loader.hide(); if (response.status !== 200) { - Notifications.add( - "Failed to set streak hour offset: " + response.body.message, - -1, - ); + Notifications.add("Failed to set streak hour offset", -1, { response }); } else { Notifications.add("Streak hour offset set", 1); const snap = getSnapshot() as Snapshot; diff --git a/frontend/src/ts/modals/user-report.ts b/frontend/src/ts/modals/user-report.ts index f54945928..1878a09a7 100644 --- a/frontend/src/ts/modals/user-report.ts +++ b/frontend/src/ts/modals/user-report.ts @@ -128,7 +128,7 @@ async function submitReport(): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to report user: " + response.body.message, -1); + Notifications.add("Failed to report user", -1, { response }); return; } diff --git a/frontend/src/ts/observables/notification-event.ts b/frontend/src/ts/observables/notification-event.ts index cf05dc18b..5def1fd9a 100644 --- a/frontend/src/ts/observables/notification-event.ts +++ b/frontend/src/ts/observables/notification-event.ts @@ -1,7 +1,12 @@ -type SubscribeFunction = ( +export type NotificationOptions = { + customTitle?: string; + details?: object | string; +}; + +export type SubscribeFunction = ( message: string, level: number, - customTitle?: string, + options: NotificationOptions, ) => void; const subscribers: SubscribeFunction[] = []; @@ -13,11 +18,11 @@ export function subscribe(fn: SubscribeFunction): void { export function dispatch( message: string, level: number, - customTitle?: string, + options: NotificationOptions, ): void { subscribers.forEach((fn) => { try { - fn(message, level, customTitle); + fn(message, level, options); } catch (e) { console.error("Notification event subscriber threw an error"); console.error(e); diff --git a/frontend/src/ts/pages/account.ts b/frontend/src/ts/pages/account.ts index 537725739..326193880 100644 --- a/frontend/src/ts/pages/account.ts +++ b/frontend/src/ts/pages/account.ts @@ -1107,7 +1107,7 @@ $(".pageAccount").on("click", ".miniResultChartButton", async (event) => { target.removeClass("loading"); if (response.status !== 200) { - Notifications.add("Error fetching result: " + response.body.message, -1); + Notifications.add("Error fetching result", -1, { response }); return; } diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts index 423266590..53fcdb878 100644 --- a/frontend/src/ts/test/test-logic.ts +++ b/frontend/src/ts/test/test-logic.ts @@ -1285,7 +1285,7 @@ async function saveResult( response.body.message = "Looks like your result data is using an incorrect schema. Please refresh the page to download the new update. If the problem persists, please contact support."; } - Notifications.add("Failed to save result: " + response.body.message, -1); + Notifications.add("Failed to save result", -1, { response }); return; } diff --git a/frontend/src/ts/utils/results.ts b/frontend/src/ts/utils/results.ts index fc10e6ba3..2b4806f67 100644 --- a/frontend/src/ts/utils/results.ts +++ b/frontend/src/ts/utils/results.ts @@ -14,10 +14,7 @@ export async function syncNotSignedInLastResult(uid: string): Promise { body: { result: notSignedInLastResult }, }); if (response.status !== 200) { - Notifications.add( - "Failed to save last result: " + response.body.message, - -1, - ); + Notifications.add("Failed to save last result", -1, { response }); return; } diff --git a/frontend/src/ts/utils/url-handler.ts b/frontend/src/ts/utils/url-handler.ts index 5a94abd00..861480df0 100644 --- a/frontend/src/ts/utils/url-handler.ts +++ b/frontend/src/ts/utils/url-handler.ts @@ -48,7 +48,7 @@ export async function linkDiscord(hashOverride: string): Promise { Loader.hide(); if (response.status !== 200) { - Notifications.add("Failed to link Discord: " + response.body.message, -1); + Notifications.add("Failed to link Discord", -1, { response }); return; } diff --git a/packages/contracts/src/util/api.ts b/packages/contracts/src/util/api.ts index 275e4b8fc..3bd5c92d7 100644 --- a/packages/contracts/src/util/api.ts +++ b/packages/contracts/src/util/api.ts @@ -77,6 +77,8 @@ export const MonkeyValidationErrorSchema = MonkeyResponseSchema.extend({ export type MonkeyValidationError = z.infer; export const MonkeyClientError = MonkeyResponseSchema; +export type MonkeyClientErrorType = z.infer; + export const MonkeyServerError = MonkeyClientError.extend({ errorId: z.string(), uid: z.string().optional(), @@ -130,3 +132,17 @@ export const CommonResponses = { "Endpoint disabled or server is under maintenance", ), }; + +export type CommonResponsesType = + | { + status: 400 | 401 | 403 | 429 | 470 | 471 | 472 | 479; + body: MonkeyClientErrorType; + } + | { + status: 422; + body: MonkeyValidationError; + } + | { + status: 500 | 503; + body: MonkeyServerErrorType; + }; From 96e30b141e125fbc75bfc8a2ee4a499317bde3c5 Mon Sep 17 00:00:00 2001 From: Miodec Date: Fri, 19 Dec 2025 09:53:50 +0100 Subject: [PATCH 14/23] chore: bump oxc packages --- backend/package.json | 4 +- frontend/package.json | 20 +- package.json | 26 +-- packages/contracts/package.json | 4 +- packages/funbox/package.json | 4 +- packages/release/package.json | 6 +- packages/schemas/package.json | 4 +- packages/tsup-config/package.json | 4 +- packages/util/package.json | 4 +- pnpm-lock.yaml | 304 +++++++++++++++--------------- 10 files changed, 190 insertions(+), 190 deletions(-) diff --git a/backend/package.json b/backend/package.json index 4e03ce68e..ba28bc4ce 100644 --- a/backend/package.json +++ b/backend/package.json @@ -80,8 +80,8 @@ "@vitest/coverage-v8": "4.0.15", "concurrently": "8.2.2", "openapi3-ts": "2.0.2", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "readline-sync": "1.4.10", "supertest": "7.1.4", "testcontainers": "11.10.0", diff --git a/frontend/package.json b/frontend/package.json index 0bd6fc1c3..fa7acf55b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,14 +21,6 @@ "tsc": "tsc", "docker": "docker compose -f docker/compose.dev.yml up" }, - "lint-staged": { - "*": [ - "oxfmt --no-error-on-unmatched-pattern" - ], - "*.{ts,js}": [ - "oxlint --type-aware --type-check" - ] - }, "dependencies": { "@date-fns/utc": "1.2.0", "@monkeytype/contracts": "workspace:*", @@ -89,8 +81,8 @@ "madge": "8.0.0", "magic-string": "0.30.17", "normalize.css": "8.0.1", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "postcss": "8.4.31", "sass": "1.70.0", "subset-font": "2.3.0", @@ -107,6 +99,14 @@ "vite-plugin-pwa": "1.1.0", "vitest": "4.0.15" }, + "lint-staged": { + "*": [ + "oxfmt --no-error-on-unmatched-pattern" + ], + "*.{ts,js}": [ + "oxlint --type-aware --type-check" + ] + }, "browserslist": [ "defaults", "not op_mini all", diff --git a/package.json b/package.json index f4b60cecf..09a4c3443 100644 --- a/package.json +++ b/package.json @@ -54,14 +54,6 @@ "check-assets-others": "turbo check-assets --filter @monkeytype/frontend -- others", "knip": "knip" }, - "lint-staged": { - "*": [ - "oxfmt --no-error-on-unmatched-pattern" - ], - "*.{ts,js}": [ - "oxlint --type-aware --type-check" - ] - }, "devDependencies": { "@commitlint/cli": "17.7.1", "@commitlint/config-conventional": "19.2.2", @@ -72,15 +64,23 @@ "knip": "2.19.2", "lint-staged": "13.2.3", "only-allow": "1.2.1", - "oxfmt": "0.18.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxfmt": "0.19.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "prettier": "3.7.1", "turbo": "2.5.6", "vitest": "4.0.15" }, - "packageManager": "pnpm@9.6.0", + "lint-staged": { + "*": [ + "oxfmt --no-error-on-unmatched-pattern" + ], + "*.{ts,js}": [ + "oxlint --type-aware --type-check" + ] + }, "engines": { "node": "24.11.0 || 22.21.0" - } + }, + "packageManager": "pnpm@9.6.0" } diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 4dc852c86..949ca01fb 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -29,8 +29,8 @@ "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "madge": "8.0.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "tsup": "8.4.0", "typescript": "5.9.3", "vitest": "4.0.15" diff --git a/packages/funbox/package.json b/packages/funbox/package.json index 815dd4497..1a4c488e4 100644 --- a/packages/funbox/package.json +++ b/packages/funbox/package.json @@ -25,8 +25,8 @@ "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "madge": "8.0.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "tsup": "8.4.0", "typescript": "5.9.3", "vitest": "4.0.15" diff --git a/packages/release/package.json b/packages/release/package.json index ab3c6b9bc..019e922a8 100644 --- a/packages/release/package.json +++ b/packages/release/package.json @@ -1,10 +1,10 @@ { "name": "@monkeytype/release", "private": true, - "type": "module", "bin": { "monkeytype-release": "./src/index.js" }, + "type": "module", "scripts": { "dev": "nodemon --watch src --exec \"node ./src/index.js --dry\"", "dev-hotfix": "nodemon --watch src --exec \"node ./src/index.js --dry --hotfix\"", @@ -20,7 +20,7 @@ }, "devDependencies": { "nodemon": "3.1.4", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0" + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2" } } diff --git a/packages/schemas/package.json b/packages/schemas/package.json index 684530263..36efdfea8 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -26,8 +26,8 @@ "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "madge": "8.0.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "tsup": "8.4.0", "typescript": "5.9.3", "vitest": "4.0.15" diff --git a/packages/tsup-config/package.json b/packages/tsup-config/package.json index 05f8d1ba6..f8d8e2fb9 100644 --- a/packages/tsup-config/package.json +++ b/packages/tsup-config/package.json @@ -17,8 +17,8 @@ }, "devDependencies": { "@monkeytype/typescript-config": "workspace:*", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "typescript": "5.9.3" }, "peerDependencies": { diff --git a/packages/util/package.json b/packages/util/package.json index 3a2c6c8a7..fa4d7dc7b 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -20,8 +20,8 @@ "@monkeytype/tsup-config": "workspace:*", "@monkeytype/typescript-config": "workspace:*", "madge": "8.0.0", - "oxlint": "1.33.0", - "oxlint-tsgolint": "0.9.0", + "oxlint": "1.34.0", + "oxlint-tsgolint": "0.9.2", "tsup": "8.4.0", "typescript": "5.9.3", "vitest": "4.0.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0577166a4..89ee68fc4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,14 +36,14 @@ importers: specifier: 1.2.1 version: 1.2.1 oxfmt: - specifier: 0.18.0 - version: 0.18.0 + specifier: 0.19.0 + version: 0.19.0 oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 prettier: specifier: 3.7.1 version: 3.7.1 @@ -232,11 +232,11 @@ importers: specifier: 2.0.2 version: 2.0.2 oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 readline-sync: specifier: 1.4.10 version: 1.4.10 @@ -431,11 +431,11 @@ importers: specifier: 8.0.1 version: 8.0.1 oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 postcss: specifier: 8.4.31 version: 8.4.31 @@ -462,7 +462,7 @@ importers: version: 1.2.1(rollup@2.79.2) vite-plugin-checker: specifier: 0.11.0 - version: 0.11.0(eslint@9.39.1)(meow@13.2.0)(optionator@0.9.4)(oxlint@1.33.0(oxlint-tsgolint@0.9.0))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + version: 0.11.0(eslint@9.39.1)(meow@13.2.0)(optionator@0.9.4)(oxlint@1.34.0(oxlint-tsgolint@0.9.2))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) vite-plugin-filter-replace: specifier: 0.1.14 version: 0.1.14 @@ -504,11 +504,11 @@ importers: specifier: 8.0.0 version: 8.0.0(typescript@5.9.3) oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 tsup: specifier: 8.4.0 version: 8.4.0(postcss@8.5.6)(tsx@4.16.2)(typescript@5.9.3)(yaml@2.8.1) @@ -538,11 +538,11 @@ importers: specifier: 8.0.0 version: 8.0.0(typescript@5.9.3) oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 tsup: specifier: 8.4.0 version: 8.4.0(postcss@8.5.6)(tsx@4.16.2)(typescript@5.9.3)(yaml@2.8.1) @@ -571,11 +571,11 @@ importers: specifier: 3.1.4 version: 3.1.4 oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 packages/schemas: dependencies: @@ -593,11 +593,11 @@ importers: specifier: 8.0.0 version: 8.0.0(typescript@5.9.3) oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 tsup: specifier: 8.4.0 version: 8.4.0(postcss@8.5.6)(tsx@4.16.2)(typescript@5.9.3)(yaml@2.8.1) @@ -618,11 +618,11 @@ importers: specifier: workspace:* version: link:../typescript-config oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 typescript: specifier: 5.9.3 version: 5.9.3 @@ -641,11 +641,11 @@ importers: specifier: 8.0.0 version: 8.0.0(typescript@5.9.3) oxlint: - specifier: 1.33.0 - version: 1.33.0(oxlint-tsgolint@0.9.0) + specifier: 1.34.0 + version: 1.34.0(oxlint-tsgolint@0.9.2) oxlint-tsgolint: - specifier: 0.9.0 - version: 0.9.0 + specifier: 0.9.2 + version: 0.9.2 tsup: specifier: 8.4.0 version: 8.4.0(postcss@8.5.6)(tsx@4.16.2)(typescript@5.9.3)(yaml@2.8.1) @@ -2558,113 +2558,113 @@ packages: resolution: {integrity: sha512-aKcOkyrorBGlajjRdVoJWHTxfxO1vCNHLJVlSDaRHDIdjU+pX8IYQPvPDkYiujKLbRnWU+1TBwEt0QRgSm4SGA==} engines: {node: '>=14'} - '@oxfmt/darwin-arm64@0.18.0': - resolution: {integrity: sha512-Dp3mOFUtLtG/0eDclI3rx2be5FEnUxh2T6zlq7Zx5Q1c+DS9LJF8H97nQx+O/OU+o0e0J90kN8Msf9v3j7Hhsg==} + '@oxfmt/darwin-arm64@0.19.0': + resolution: {integrity: sha512-FfNpn3ximwbBZCaS8WL2vAXcFuQFQgvv/brO6D1WdmL4pnFOgfBIpFfkeFnKfdmdpxtg9O0wF8NTcdw5Iyl2Bg==} cpu: [arm64] os: [darwin] - '@oxfmt/darwin-x64@0.18.0': - resolution: {integrity: sha512-Oe3KS4tdfLq+f1Ebje7D9BF7/0xmBYSMN6dWatSko1xfvu8oCZzKcNCcbwz0a8rBnoH/BnIiGFUEbgJDnA/JLQ==} + '@oxfmt/darwin-x64@0.19.0': + resolution: {integrity: sha512-31FWUHAgTdTzOslz0zoA60UDEdeZZpeM6wTzCdffetwLbHkK8ZuCuzd+DauHZPNlSU8G72iw4lF7Zve9pSkK9w==} cpu: [x64] os: [darwin] - '@oxfmt/linux-arm64-gnu@0.18.0': - resolution: {integrity: sha512-zW9z8LrAgL9bswgsaFOnQmoImCbq9YQST8B15FAoTeZYr9sb/VfdD6/PQEBLCptYlFmoTvmDNHjppi9g61NwHQ==} + '@oxfmt/linux-arm64-gnu@0.19.0': + resolution: {integrity: sha512-BkU9h39xKj/8uko82uFDJUharM8VxZO+uXKglpBnEC8XxWRzXocVCX4wpLT/Tfk4NyBy6fRM9DeISdZvQKZCjA==} cpu: [arm64] os: [linux] - '@oxfmt/linux-arm64-musl@0.18.0': - resolution: {integrity: sha512-mFnpcasZZv1s/+6YDPradhIgR2G7GWr+mnzgJMIKQOJAtX3b0vzG+FE4f7hDIZPEUhC0hvZSd8f31FjAtrX0aQ==} + '@oxfmt/linux-arm64-musl@0.19.0': + resolution: {integrity: sha512-wWYk6Z/3iC+0zZAUkVCcEHui/IsUqsl+GEm9o6H7oARPLisXajbwCQcmqYslUD7eK6OXdYoWriBkEvSX/5dU4A==} cpu: [arm64] os: [linux] - '@oxfmt/linux-x64-gnu@0.18.0': - resolution: {integrity: sha512-G1AXQNHVkPgVgRrAvDYdNi0x3gkvVyEmpL6WLMwoSjMnwYWQLt5/jrndHbS2Db8/CcmMo6VPP8lUQGvM4TRVaQ==} + '@oxfmt/linux-x64-gnu@0.19.0': + resolution: {integrity: sha512-EB/b3or437E3uDie8QxeU3eA502JcmR1koyIBcH9rFidY0cMik58xvw54tXCY3WpMRxEXf37aHZzUSDP3qJnZg==} cpu: [x64] os: [linux] - '@oxfmt/linux-x64-musl@0.18.0': - resolution: {integrity: sha512-M/KEIMHDr5lcK9gDxgIGr9xVwX7Pfl3PZs+6e8SySuckMgoJMkee6uWxhMdxws3dfU94saAkVmNQzM2+l3hvtA==} + '@oxfmt/linux-x64-musl@0.19.0': + resolution: {integrity: sha512-htMB45orYoa1oFSjSmoGgcBDsD47/0joDfqpa8TrTDI5qsW5kAedpFR5wSce8Is9oj7SJ07omhOj96P/QiekWA==} cpu: [x64] os: [linux] - '@oxfmt/win32-arm64@0.18.0': - resolution: {integrity: sha512-LunuNBTLOgfkYFjiHOeNHD/Ii5paO/GVyNPSEYBwEPULbdeMGnjHqxzQHLGU8QIQjwu55mp4uuBP0DAXTli2TQ==} + '@oxfmt/win32-arm64@0.19.0': + resolution: {integrity: sha512-x7+3Eh/VWdXEda+BUmAKYlhGrRJVera7RfWw47Yx8PJUGtNqBfeYGDbf0W59ceK8Z3bY3OinrmOO3d1jOuXzMQ==} cpu: [arm64] os: [win32] - '@oxfmt/win32-x64@0.18.0': - resolution: {integrity: sha512-0kJJwn4FPeGJ4uV6ofOCiY+oBKWg+RWpRjYc9bywTjDbxWR6JAqtuXkiARCJej8UD/rJeXnHYTF78sFALkwZyA==} + '@oxfmt/win32-x64@0.19.0': + resolution: {integrity: sha512-X+FKXBg2jx4CxF5SJs3xpx1msMw5JfxaGD5qBZYqlHGdryQsy6zUY+bQwDDcuy3Ic/WNGD8ZNEuggeYNE7jx/Q==} cpu: [x64] os: [win32] - '@oxlint-tsgolint/darwin-arm64@0.9.0': - resolution: {integrity: sha512-dztZZwzSzX58pgwQoYex5svEdISDNpEM0A0cB0ISeaD3mFsro7h2idMK6q373hxnhNw4Rs1eu/4dwPpKEz6ouw==} + '@oxlint-tsgolint/darwin-arm64@0.9.2': + resolution: {integrity: sha512-AujIYejzo2KgSWEn65FxkTwJ7Xkq106wUjyKYpljpEsrldO3QkW1yjj6osisoIjM/NdV0VO21YWjIGFIKd0/Ug==} cpu: [arm64] os: [darwin] - '@oxlint-tsgolint/darwin-x64@0.9.0': - resolution: {integrity: sha512-GK/TOqKTqP41/yHyCwO4tJ0jF4vAwlsTXhgGXMD6GgTXv4t8kPcX4EFHZOfgMf+E84vE4MsvLFNI+NXfVIqWzQ==} + '@oxlint-tsgolint/darwin-x64@0.9.2': + resolution: {integrity: sha512-rNLCdo5DjSBcMjRwND4eOaboPD30Ssp0sfqS3KpHvo/0acQmQVCJwOaDUO7s2a77YPDJ6jSaGDWYo8HLnEXALg==} cpu: [x64] os: [darwin] - '@oxlint-tsgolint/linux-arm64@0.9.0': - resolution: {integrity: sha512-Rc+ea/v2Y+AmZrCOMWMV2NxQ7h967ivELqn7+gCIw53dCnvwp0JsGfL4e1tNkLddXaCfrC11zW61Ayie224GNA==} + '@oxlint-tsgolint/linux-arm64@0.9.2': + resolution: {integrity: sha512-xVL66moJftyrvmFSwstWDQ1bqVjn9HCSz+QJLLInf3/b51xZl5QbbGEPX4bf4SKut+7v5ZEM527DbVGg1yRmKQ==} cpu: [arm64] os: [linux] - '@oxlint-tsgolint/linux-x64@0.9.0': - resolution: {integrity: sha512-i3QRMXHCIGxGQTvFyE8LTy96SBvToZpkHPirBs6dvtKfDFHeKMX2WSc44rUdoU/LhjF5qYyU0lTITzj4VFEY/w==} + '@oxlint-tsgolint/linux-x64@0.9.2': + resolution: {integrity: sha512-sVH2DADkdUmFZhggaMM/r8NOdn2Ud1Az/nJoGbAiV82Lipw3Kp6y6yV2w+NWfmETHZZ+0gwlWTZod7NSmQuR5w==} cpu: [x64] os: [linux] - '@oxlint-tsgolint/win32-arm64@0.9.0': - resolution: {integrity: sha512-D6jeKoCfNAwBXJyxab4gZk8Rw9BjzGgQPd7DdLRLjDRY6DkdK4PVUwNzbboZk2SAxluO/UkSUXpoEoTJdl+cIQ==} + '@oxlint-tsgolint/win32-arm64@0.9.2': + resolution: {integrity: sha512-0nvXzcY+W84Vo4pq5qh+0k3BsSb7ImzB9VaX5jIbjTNvx3EYS/KZzo0smhZ+drs43V4oNqJjgt2jnkeWF9Yj/g==} cpu: [arm64] os: [win32] - '@oxlint-tsgolint/win32-x64@0.9.0': - resolution: {integrity: sha512-v7yGASaDJ2GXrvlDLwhrASRyM8zpOxU8oi3BLqE1mqbKSNbWNeTAaBfRNLyCUztiV1eFvx54hnF0AElPfglVYQ==} + '@oxlint-tsgolint/win32-x64@0.9.2': + resolution: {integrity: sha512-OGx2CIyWjpOhFOL7tvVjLrKlkjJSOVZBe5S/jmBnxY+vRjFrXQr0zNUXy+S+ggIv6rsMlz3B+ixSriyUIKwDeQ==} cpu: [x64] os: [win32] - '@oxlint/darwin-arm64@1.33.0': - resolution: {integrity: sha512-PmEQDLHAxiAdyttQ1ZWXd+5VpHLbHf3FTMJL9bg5TZamDnhNiW/v0Pamv3MTAdymnoDI3H8IVLAN/SAseV/adw==} + '@oxlint/darwin-arm64@1.34.0': + resolution: {integrity: sha512-euz3Dtp5/UE9axFkQnllOWp3gOwoqaxfZPUUwiW8HBelqhI9PRMVIfQ/akmwl+G5XixQZIgXkXQ5SJxnb1+Qww==} cpu: [arm64] os: [darwin] - '@oxlint/darwin-x64@1.33.0': - resolution: {integrity: sha512-2R9aH3kR0X2M30z5agGikv3tfNTi8/uLhU5/tYktu33VGUXpbf0OLZSlD25UEuwOKAlf3RVtzV5oDyjoq93JuQ==} + '@oxlint/darwin-x64@1.34.0': + resolution: {integrity: sha512-XpmNviE5KOnHkhmQPwJJIBs+mJkr0qItTZBN4dz+O3p9gWN+gCqi3CBP71RiVahZw4qAEQSgY4wro+z0kx+erg==} cpu: [x64] os: [darwin] - '@oxlint/linux-arm64-gnu@1.33.0': - resolution: {integrity: sha512-yb/k8GaMDgnX2LyO6km33kKItZ/n573SlbiHBBFU2HmeU7tzEHL5jHkHQXXcysUkapmqHd7UsDhOZDqPmXaQRg==} + '@oxlint/linux-arm64-gnu@1.34.0': + resolution: {integrity: sha512-aCPdoEUGsJGF9y88vDYoaugG4IEGwSBa+usyuAvEVl3vTfuTmE0RDQEC1Z+WnJ3J/cIEpbgKOzS12VwbzFicjg==} cpu: [arm64] os: [linux] - '@oxlint/linux-arm64-musl@1.33.0': - resolution: {integrity: sha512-03pt9IO1C4ZfVOW6SQiOK26mzklAhLM3Kc79OXpX1kgZRlxk+rvFoMhlgCOzn7tEdrEgbePkBoxNnwDnJDFqJQ==} + '@oxlint/linux-arm64-musl@1.34.0': + resolution: {integrity: sha512-cMo72LQBFmdnVLRKLAHD94ZUBq5Z+aA9Y+RKzkjhCmJuef5ZAfKC24TJD/6c5LxGYzkwwmyySoQAHq5B69i3PQ==} cpu: [arm64] os: [linux] - '@oxlint/linux-x64-gnu@1.33.0': - resolution: {integrity: sha512-Z7ImLWM50FoVXzYvyxUQ+QwBkBfRyK4YdLEGonyAGMp7iT3DksonDaTK9ODnJ1qHyAyAZCvuqXD7AEDsDvzDbA==} + '@oxlint/linux-x64-gnu@1.34.0': + resolution: {integrity: sha512-+9xFhhkqgNIysEh+uHvcba8v4UtL1YzxuyDS2wTLdWrkGvIllCx5WjJItt3K/AhwatciksgNEXSo2Hh4fcQRog==} cpu: [x64] os: [linux] - '@oxlint/linux-x64-musl@1.33.0': - resolution: {integrity: sha512-idb55Uzu5kkqqpMiVUfI9nP7zOqPZinQKsIRQAIU40wILcf/ijvhNZKIu3ucDMmye0n6IWOaSnxIRL5W2fNoUQ==} + '@oxlint/linux-x64-musl@1.34.0': + resolution: {integrity: sha512-qa7TL2DfEDdMeSP5UiU5JMs6D2PW7ZJAQ0WZYTgqDV8BlZ6nMkIYVBVIk3QPxIfkyxvfJVbG1RB3PkSWDcfwpA==} cpu: [x64] os: [linux] - '@oxlint/win32-arm64@1.33.0': - resolution: {integrity: sha512-wKKFt7cubfrLelNzdmDsNSmtBrlSUe1fWus587+uSxDZdpFbQ7liU0gsUlCbcHvym0H1Tc2O3K3cnLrgQORLPQ==} + '@oxlint/win32-arm64@1.34.0': + resolution: {integrity: sha512-mSJumUveg1S3DiOgvsrVNAGuvenBbbC/zsfT1qhltT+GLhJ7RPBK2I/jz0fTdE+I7M9/as8yc0XJ/eY23y2amA==} cpu: [arm64] os: [win32] - '@oxlint/win32-x64@1.33.0': - resolution: {integrity: sha512-ReyR8rNHjKNnO7dxGny9RCPELRAdhm3y780FNBcA07E1wvxSCkB+Mn5db0Pa5bRmxrsU/MTZ/aaBFa+ERXDdXw==} + '@oxlint/win32-x64@1.34.0': + resolution: {integrity: sha512-izsDDt5WY4FSISCkPRLUYQD1aRaaEJkPLtEZe3DlioSUdUVAdvVbE+BGllFqR16DWfTTwO/6K4jDeooxQzTMjw==} cpu: [x64] os: [win32] @@ -3789,8 +3789,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.9.7: - resolution: {integrity: sha512-k9xFKplee6KIio3IDbwj+uaCLpqzOwakOgmqzPezM0sFJlFKcg30vk2wOiAJtkTSfx0SSQDSe8q+mWA/fSH5Zg==} + baseline-browser-mapping@2.9.10: + resolution: {integrity: sha512-2VIKvDx8Z1a9rTB2eCkdPE5nSe28XnA+qivGnWHoB40hMMt/h1hSz0960Zqsn6ZyxWXUie0EBdElKv8may20AA==} hasBin: true basic-auth-connect@1.0.0: @@ -3988,8 +3988,8 @@ packages: caniuse-lite@1.0.30001715: resolution: {integrity: sha512-7ptkFGMm2OAOgvZpwgA4yjQ5SQbrNVGdRjzH0pBdy1Fasvcr+KAeECmbCAECzTuDuoX0FCY8KzUxjf9+9kfZEw==} - caniuse-lite@1.0.30001760: - resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + caniuse-lite@1.0.30001761: + resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} canvas-confetti@1.5.1: resolution: {integrity: sha512-Ncz+oZJP6OvY7ti4E1slxVlyAV/3g7H7oQtcCDXgwGgARxPnwYY9PW5Oe+I8uvspYNtuHviAdgA0LfcKFWJfpg==} @@ -7223,21 +7223,21 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} - oxfmt@0.18.0: - resolution: {integrity: sha512-XqVS+LQi7ARsFlIftB69+jGN2qQaJNBjQU2b9ZQYt+iaUV9digh+dBIsU0isQIZhaQ1ihxGFfsTYQXE/Oak8Qw==} + oxfmt@0.19.0: + resolution: {integrity: sha512-tPTa3j4kXdJBzBRlK9wR0/Lnd4J21rzg29cRr/VVqqfvdhZs6M+Q6TkL+rxI/IQpq8ZY8L3c+KZvga/RgeuMsg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint-tsgolint@0.9.0: - resolution: {integrity: sha512-JzZ3w7DAgf3RE3O7a4uNLtLUy8Q46r4SN4NOsfRv7lwuhLNhaXoVwOpwdPvAdfQeIJ+HSMgNkjP44vdSdC7O9A==} + oxlint-tsgolint@0.9.2: + resolution: {integrity: sha512-UKLudgnuEPyAzpLk5FeZyCdjRaHQE6cSqmlTBwmEp2lGJ1wN59qGc4gfjAXVSCrKrd30jXDGf5/zQ3CTa6BNNQ==} hasBin: true - oxlint@1.33.0: - resolution: {integrity: sha512-4WCL0K8jiOshwJ8WrVk35VAuVaZHC0iX6asjKsrENOrynkAAGcTLLx0Urf0eXZ1Tq7r+qAe3Z9EyHMFPzVyUkg==} + oxlint@1.34.0: + resolution: {integrity: sha512-Ni0N8wAiKlgaYkI/Yz4VrutfVIZgd2shDtS+loQxyBTwO8YUAnk3+g7OQ1cyI/aatHiFwvFNfV/uvZyHUtyPpA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.9.0' + oxlint-tsgolint: '>=0.9.2' peerDependenciesMeta: oxlint-tsgolint: optional: true @@ -9026,8 +9026,8 @@ packages: peerDependencies: browserslist: '>= 4.21.0' - update-browserslist-db@1.2.2: - resolution: {integrity: sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -11749,70 +11749,70 @@ snapshots: '@opentelemetry/semantic-conventions@1.34.0': {} - '@oxfmt/darwin-arm64@0.18.0': + '@oxfmt/darwin-arm64@0.19.0': optional: true - '@oxfmt/darwin-x64@0.18.0': + '@oxfmt/darwin-x64@0.19.0': optional: true - '@oxfmt/linux-arm64-gnu@0.18.0': + '@oxfmt/linux-arm64-gnu@0.19.0': optional: true - '@oxfmt/linux-arm64-musl@0.18.0': + '@oxfmt/linux-arm64-musl@0.19.0': optional: true - '@oxfmt/linux-x64-gnu@0.18.0': + '@oxfmt/linux-x64-gnu@0.19.0': optional: true - '@oxfmt/linux-x64-musl@0.18.0': + '@oxfmt/linux-x64-musl@0.19.0': optional: true - '@oxfmt/win32-arm64@0.18.0': + '@oxfmt/win32-arm64@0.19.0': optional: true - '@oxfmt/win32-x64@0.18.0': + '@oxfmt/win32-x64@0.19.0': optional: true - '@oxlint-tsgolint/darwin-arm64@0.9.0': + '@oxlint-tsgolint/darwin-arm64@0.9.2': optional: true - '@oxlint-tsgolint/darwin-x64@0.9.0': + '@oxlint-tsgolint/darwin-x64@0.9.2': optional: true - '@oxlint-tsgolint/linux-arm64@0.9.0': + '@oxlint-tsgolint/linux-arm64@0.9.2': optional: true - '@oxlint-tsgolint/linux-x64@0.9.0': + '@oxlint-tsgolint/linux-x64@0.9.2': optional: true - '@oxlint-tsgolint/win32-arm64@0.9.0': + '@oxlint-tsgolint/win32-arm64@0.9.2': optional: true - '@oxlint-tsgolint/win32-x64@0.9.0': + '@oxlint-tsgolint/win32-x64@0.9.2': optional: true - '@oxlint/darwin-arm64@1.33.0': + '@oxlint/darwin-arm64@1.34.0': optional: true - '@oxlint/darwin-x64@1.33.0': + '@oxlint/darwin-x64@1.34.0': optional: true - '@oxlint/linux-arm64-gnu@1.33.0': + '@oxlint/linux-arm64-gnu@1.34.0': optional: true - '@oxlint/linux-arm64-musl@1.33.0': + '@oxlint/linux-arm64-musl@1.34.0': optional: true - '@oxlint/linux-x64-gnu@1.33.0': + '@oxlint/linux-x64-gnu@1.34.0': optional: true - '@oxlint/linux-x64-musl@1.33.0': + '@oxlint/linux-x64-musl@1.34.0': optional: true - '@oxlint/win32-arm64@1.33.0': + '@oxlint/win32-arm64@1.34.0': optional: true - '@oxlint/win32-x64@1.33.0': + '@oxlint/win32-x64@1.34.0': optional: true '@paralleldrive/cuid2@2.2.2': @@ -13001,7 +13001,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.9.7: {} + baseline-browser-mapping@2.9.10: {} basic-auth-connect@1.0.0: {} @@ -13138,11 +13138,11 @@ snapshots: browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.9.7 - caniuse-lite: 1.0.30001760 + baseline-browser-mapping: 2.9.10 + caniuse-lite: 1.0.30001761 electron-to-chromium: 1.5.267 node-releases: 2.0.27 - update-browserslist-db: 1.2.2(browserslist@4.28.0) + update-browserslist-db: 1.2.3(browserslist@4.28.0) bson@6.8.0: {} @@ -13267,7 +13267,7 @@ snapshots: caniuse-lite@1.0.30001715: {} - caniuse-lite@1.0.30001760: {} + caniuse-lite@1.0.30001761: {} canvas-confetti@1.5.1: {} @@ -17190,39 +17190,39 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - oxfmt@0.18.0: + oxfmt@0.19.0: dependencies: tinypool: 2.0.0 optionalDependencies: - '@oxfmt/darwin-arm64': 0.18.0 - '@oxfmt/darwin-x64': 0.18.0 - '@oxfmt/linux-arm64-gnu': 0.18.0 - '@oxfmt/linux-arm64-musl': 0.18.0 - '@oxfmt/linux-x64-gnu': 0.18.0 - '@oxfmt/linux-x64-musl': 0.18.0 - '@oxfmt/win32-arm64': 0.18.0 - '@oxfmt/win32-x64': 0.18.0 + '@oxfmt/darwin-arm64': 0.19.0 + '@oxfmt/darwin-x64': 0.19.0 + '@oxfmt/linux-arm64-gnu': 0.19.0 + '@oxfmt/linux-arm64-musl': 0.19.0 + '@oxfmt/linux-x64-gnu': 0.19.0 + '@oxfmt/linux-x64-musl': 0.19.0 + '@oxfmt/win32-arm64': 0.19.0 + '@oxfmt/win32-x64': 0.19.0 - oxlint-tsgolint@0.9.0: + oxlint-tsgolint@0.9.2: optionalDependencies: - '@oxlint-tsgolint/darwin-arm64': 0.9.0 - '@oxlint-tsgolint/darwin-x64': 0.9.0 - '@oxlint-tsgolint/linux-arm64': 0.9.0 - '@oxlint-tsgolint/linux-x64': 0.9.0 - '@oxlint-tsgolint/win32-arm64': 0.9.0 - '@oxlint-tsgolint/win32-x64': 0.9.0 + '@oxlint-tsgolint/darwin-arm64': 0.9.2 + '@oxlint-tsgolint/darwin-x64': 0.9.2 + '@oxlint-tsgolint/linux-arm64': 0.9.2 + '@oxlint-tsgolint/linux-x64': 0.9.2 + '@oxlint-tsgolint/win32-arm64': 0.9.2 + '@oxlint-tsgolint/win32-x64': 0.9.2 - oxlint@1.33.0(oxlint-tsgolint@0.9.0): + oxlint@1.34.0(oxlint-tsgolint@0.9.2): optionalDependencies: - '@oxlint/darwin-arm64': 1.33.0 - '@oxlint/darwin-x64': 1.33.0 - '@oxlint/linux-arm64-gnu': 1.33.0 - '@oxlint/linux-arm64-musl': 1.33.0 - '@oxlint/linux-x64-gnu': 1.33.0 - '@oxlint/linux-x64-musl': 1.33.0 - '@oxlint/win32-arm64': 1.33.0 - '@oxlint/win32-x64': 1.33.0 - oxlint-tsgolint: 0.9.0 + '@oxlint/darwin-arm64': 1.34.0 + '@oxlint/darwin-x64': 1.34.0 + '@oxlint/linux-arm64-gnu': 1.34.0 + '@oxlint/linux-arm64-musl': 1.34.0 + '@oxlint/linux-x64-gnu': 1.34.0 + '@oxlint/linux-x64-musl': 1.34.0 + '@oxlint/win32-arm64': 1.34.0 + '@oxlint/win32-x64': 1.34.0 + oxlint-tsgolint: 0.9.2 p-defer@3.0.0: {} @@ -19262,7 +19262,7 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 - update-browserslist-db@1.2.2(browserslist@4.28.0): + update-browserslist-db@1.2.3(browserslist@4.28.0): dependencies: browserslist: 4.28.0 escalade: 3.2.0 @@ -19349,7 +19349,7 @@ snapshots: dependencies: vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - vite-plugin-checker@0.11.0(eslint@9.39.1)(meow@13.2.0)(optionator@0.9.4)(oxlint@1.33.0(oxlint-tsgolint@0.9.0))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)): + vite-plugin-checker@0.11.0(eslint@9.39.1)(meow@13.2.0)(optionator@0.9.4)(oxlint@1.34.0(oxlint-tsgolint@0.9.2))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)): dependencies: '@babel/code-frame': 7.27.1 chokidar: 4.0.3 @@ -19364,7 +19364,7 @@ snapshots: eslint: 9.39.1 meow: 13.2.0 optionator: 0.9.4 - oxlint: 1.33.0(oxlint-tsgolint@0.9.0) + oxlint: 1.34.0(oxlint-tsgolint@0.9.2) typescript: 5.9.3 vite-plugin-filter-replace@0.1.14: From ca9dac17a7ce44e1cd4034147b49f9350e27a85b Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Fri, 19 Dec 2025 12:55:16 +0100 Subject: [PATCH 15/23] refactor: remove currentTarget from dom-utils event handlers (@fehmer) (#7270) --- frontend/src/ts/elements/input-validation.ts | 4 +-- frontend/src/ts/utils/dom.ts | 35 ++++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/frontend/src/ts/elements/input-validation.ts b/frontend/src/ts/elements/input-validation.ts index 21316ae46..42bb455a2 100644 --- a/frontend/src/ts/elements/input-validation.ts +++ b/frontend/src/ts/elements/input-validation.ts @@ -8,7 +8,7 @@ import { } from "@monkeytype/schemas/configs"; import Config, { setConfig } from "../config"; import * as Notifications from "../elements/notifications"; -import { ElementWithUtils } from "../utils/dom"; +import { DomUtilsEvent, ElementWithUtils } from "../utils/dom"; export type ValidationResult = { status: "checking" | "success" | "failed" | "warning"; @@ -60,7 +60,7 @@ export function createInputEventHandler( callback: (result: ValidationResult) => void, validation: Validation, inputValueConvert?: (val: string) => T, -): (e: Event) => Promise { +): (e: DomUtilsEvent) => Promise { let callIsValid = validation.isValid !== undefined ? debounceIfNeeded( diff --git a/frontend/src/ts/utils/dom.ts b/frontend/src/ts/utils/dom.ts index 025b7b049..14872fcfd 100644 --- a/frontend/src/ts/utils/dom.ts +++ b/frontend/src/ts/utils/dom.ts @@ -105,6 +105,12 @@ type ElementWithValue = | HTMLTextAreaElement | HTMLSelectElement; +export type DomUtilsEvent = Omit; + +type DomUtilsEventListenerOrEventListenerObject = + | { (evt: DomUtilsEvent): void } + | { handleEvent(object: DomUtilsEvent): void }; + export class ElementWithUtils { /** * The native dom element @@ -238,19 +244,19 @@ export class ElementWithUtils { */ on( event: K, - handler: (this: T, ev: HTMLElementEventMap[K]) => void, + handler: (this: T, ev: DomUtilsEvent) => void, ): this; - on(event: string, handler: EventListenerOrEventListenerObject): this; + on(event: string, handler: DomUtilsEventListenerOrEventListenerObject): this; on( event: keyof HTMLElementEventMap | string, handler: - | EventListenerOrEventListenerObject - | ((this: T, ev: Event) => void), + | DomUtilsEventListenerOrEventListenerObject + | ((this: T, ev: DomUtilsEvent) => void), ): this { // this type was some AI magic but if it works it works this.native.addEventListener( event, - handler as EventListenerOrEventListenerObject, + handler as DomUtilsEventListenerOrEventListenerObject, ); return this; } @@ -262,19 +268,22 @@ export class ElementWithUtils { onChild( event: K, query: string, - handler: (this: HTMLElement, ev: HTMLElementEventMap[K]) => void, + handler: ( + this: HTMLElement, + ev: DomUtilsEvent, + ) => void, ): this; onChild( event: string, query: string, - handler: EventListenerOrEventListenerObject, + handler: DomUtilsEventListenerOrEventListenerObject, ): this; onChild( event: keyof HTMLElementEventMap | string, query: string, handler: - | EventListenerOrEventListenerObject - | ((this: HTMLElement, ev: Event) => void), + | DomUtilsEventListenerOrEventListenerObject + | ((this: HTMLElement, ev: DomUtilsEvent) => void), ): this { // this type was some AI magic but if it works it works this.native.addEventListener(event, (e) => { @@ -671,14 +680,14 @@ export class ElementsWithUtils< */ on( event: K, - handler: (this: T, ev: HTMLElementEventMap[K]) => void, + handler: (this: T, ev: DomUtilsEvent) => void, ): this; - on(event: string, handler: EventListenerOrEventListenerObject): this; + on(event: string, handler: DomUtilsEventListenerOrEventListenerObject): this; on( event: keyof HTMLElementEventMap | string, handler: - | EventListenerOrEventListenerObject - | ((this: T, ev: Event) => void), + | DomUtilsEventListenerOrEventListenerObject + | ((this: T, ev: DomUtilsEvent) => void), ): this { for (const item of this) { item.on(event, handler); From 805c7ae7d9e0eb05dff06f11dab62af0a625dd9d Mon Sep 17 00:00:00 2001 From: Leonabcd123 <156839416+Leonabcd123@users.noreply.github.com> Date: Fri, 19 Dec 2025 18:00:02 +0200 Subject: [PATCH 16/23] fix(hide-extra-letters): extra letters with hideExtraLetters enabled causing caret problems (@Leonabcd123) (#7272) ### Description Same solution as #7254 --- frontend/src/ts/utils/caret.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/utils/caret.ts b/frontend/src/ts/utils/caret.ts index 1b2b658af..47408db78 100644 --- a/frontend/src/ts/utils/caret.ts +++ b/frontend/src/ts/utils/caret.ts @@ -300,7 +300,7 @@ export class Caret { if (options.letterIndex >= letters.length) { side = "afterLetter"; - if (Config.blindMode) { + if (Config.blindMode || Config.hideExtraLetters) { options.letterIndex = wordText?.length - 1; } else { options.letterIndex = letters.length - 1; From 65e490c96160ea33bddbf5be443a4fd364e94f59 Mon Sep 17 00:00:00 2001 From: Christian Fehmer Date: Fri, 19 Dec 2025 19:42:34 +0100 Subject: [PATCH 17/23] chore: use innerText on generate data modal (@fehmer) (#7276) --- frontend/src/ts/modals/simple-modals.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/modals/simple-modals.ts b/frontend/src/ts/modals/simple-modals.ts index c22d32068..1bf579504 100644 --- a/frontend/src/ts/modals/simple-modals.ts +++ b/frontend/src/ts/modals/simple-modals.ts @@ -1212,7 +1212,7 @@ list.devGenerateData = new SimpleModal({ const span = document.querySelector( "#devGenerateData_1 + span", ) as HTMLInputElement; - span.innerHTML = `if checked, user will be created with ${target.value}@example.com and password: password`; + span.innerText = `if checked, user will be created with ${target.value}@example.com and password: password`; return; }, validation: { From 47712857f9eb37cffae8f22fcaaf81511bb14148 Mon Sep 17 00:00:00 2001 From: Leonabcd123 <156839416+Leonabcd123@users.noreply.github.com> Date: Fri, 19 Dec 2025 20:42:51 +0200 Subject: [PATCH 18/23] fix(blind-mode): make blind mode "on" text invisible (@Leonabcd123) (#7274) ### Description Use ` ` instead of the current, visible character. --- frontend/src/html/pages/settings.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html index 3640a70d7..50d329dcb 100644 --- a/frontend/src/html/pages/settings.html +++ b/frontend/src/html/pages/settings.html @@ -192,7 +192,7 @@
From eb92e1af0df49c5f3eb6002095a200b1305cd0cc Mon Sep 17 00:00:00 2001 From: Jack Date: Fri, 19 Dec 2025 22:29:56 +0100 Subject: [PATCH 19/23] impr: replace vite-plugin-checker with ~~vibe~~ (@miodec) (#7271) vite plugin checker seems to happily spawn a new linting process per file save, causing issues. This vibe coded solution kills the previously running process. It also splits linting into two steps to get some fast fail behavior. I (AI) tried to merge it into one file but the overlay refused to show that way. !nuf --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- frontend/package.json | 1 - frontend/vite-plugins/oxlint-checker.ts | 312 ++++++++++++++++++++++++ frontend/vite-plugins/oxlint-overlay.ts | 126 ++++++++++ frontend/vite.config.ts | 13 +- pnpm-lock.yaml | 142 +++++------ 5 files changed, 500 insertions(+), 94 deletions(-) create mode 100644 frontend/vite-plugins/oxlint-checker.ts create mode 100644 frontend/vite-plugins/oxlint-overlay.ts diff --git a/frontend/package.json b/frontend/package.json index fa7acf55b..45e8aa050 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -91,7 +91,6 @@ "unplugin-inject-preload": "3.0.0", "vite": "7.1.12", "vite-bundle-visualizer": "1.2.1", - "vite-plugin-checker": "0.11.0", "vite-plugin-filter-replace": "0.1.14", "vite-plugin-html-inject": "1.1.2", "vite-plugin-inspect": "11.3.3", diff --git a/frontend/vite-plugins/oxlint-checker.ts b/frontend/vite-plugins/oxlint-checker.ts new file mode 100644 index 000000000..5af753d29 --- /dev/null +++ b/frontend/vite-plugins/oxlint-checker.ts @@ -0,0 +1,312 @@ +import { Plugin, ViteDevServer, normalizePath } from "vite"; +import { spawn, execSync, ChildProcess } from "child_process"; +import { fileURLToPath } from "url"; + +export type OxlintCheckerOptions = { + /** Debounce delay in milliseconds before running lint after file changes. @default 125 */ + debounceDelay?: number; + /** Run type-aware checks (slower but more thorough). @default true */ + typeAware?: boolean; + /** Show browser overlay with lint status. @default true */ + overlay?: boolean; + /** File extensions to watch for changes. @default ['.ts', '.tsx', '.js', '.jsx'] */ + extensions?: string[]; +}; + +type LintResult = { + errorCount: number; + warningCount: number; + running: boolean; + hadIssues: boolean; + typeAware?: boolean; +}; + +const OXLINT_SUMMARY_REGEX = /Found (\d+) warnings? and (\d+) errors?/; + +export function oxlintChecker(options: OxlintCheckerOptions = {}): Plugin { + const { + debounceDelay = 125, + typeAware = true, + overlay = true, + extensions = [".ts", ".tsx", ".js", ".jsx"], + } = options; + + let currentProcess: ChildProcess | null = null; + let debounceTimer: NodeJS.Timeout | null = null; + let server: ViteDevServer | null = null; + let isProduction = false; + let currentRunId = 0; + let lastLintResult: LintResult = { + errorCount: 0, + warningCount: 0, + running: false, + hadIssues: false, + }; + + const killCurrentProcess = (): boolean => { + if ((currentProcess && !currentProcess.killed) || currentProcess !== null) { + currentProcess.kill(); + currentProcess = null; + return true; + } + return false; + }; + + const clearDebounceTimer = (): void => { + if (debounceTimer) { + clearTimeout(debounceTimer); + debounceTimer = null; + } + }; + + const parseLintOutput = ( + output: string, + ): Pick => { + const summaryMatch = output.match(OXLINT_SUMMARY_REGEX); + if (summaryMatch?.[1] !== undefined && summaryMatch?.[2] !== undefined) { + return { + warningCount: parseInt(summaryMatch[1], 10), + errorCount: parseInt(summaryMatch[2], 10), + }; + } + return { errorCount: 0, warningCount: 0 }; + }; + + const sendLintResult = (result: Partial): void => { + const previousHadIssues = lastLintResult.hadIssues; + + const payload: LintResult = { + errorCount: result.errorCount ?? lastLintResult.errorCount, + warningCount: result.warningCount ?? lastLintResult.warningCount, + running: result.running ?? false, + hadIssues: previousHadIssues, + typeAware: result.typeAware, + }; + + // Only update hadIssues when we have actual lint results (not just running status) + if (result.running === false) { + const currentHasIssues = + (result.errorCount ?? 0) > 0 || (result.warningCount ?? 0) > 0; + lastLintResult = { ...payload, hadIssues: currentHasIssues }; + } else { + // Keep hadIssues unchanged when just updating running status + lastLintResult = { ...payload, hadIssues: previousHadIssues }; + } + + if (server) { + server.ws.send("vite-plugin-oxlint", payload); + } + }; + + /** + * Runs an oxlint process with the given arguments and captures its combined output. + * + * This function is responsible for managing the lifecycle of the current lint process: + * - It spawns a new child process via `npx oxlint . ...args`. + * - It assigns the spawned process to the shared {@link currentProcess} variable so that + * other parts of the plugin can cancel or track the active lint run. + * - On process termination (either "error" or "close"), it clears {@link currentProcess} + * if it still refers to this child, avoiding interference with any newer process that + * may have started in the meantime. + * + * @param args Additional command-line arguments to pass to `oxlint`. + * @returns A promise that resolves with the process exit code (or `null` if + * the process exited due to a signal) and the full stdout/stderr output + * produced by the lint run. + */ + const runLintProcess = async ( + args: string[], + ): Promise<{ code: number | null; output: string }> => { + return new Promise((resolve) => { + const childProcess = spawn("npx", ["oxlint", ".", ...args], { + cwd: process.cwd(), + shell: true, + env: { ...process.env, FORCE_COLOR: "3" }, + }); + + currentProcess = childProcess; + let output = ""; + + childProcess.stdout?.on("data", (data: Buffer) => { + output += data.toString(); + }); + + childProcess.stderr?.on("data", (data: Buffer) => { + output += data.toString(); + }); + + childProcess.on("error", (error: Error) => { + output += `\nError: ${error.message}`; + if (currentProcess === childProcess) { + currentProcess = null; + } + resolve({ code: 1, output }); + }); + + childProcess.on("close", (code: number | null) => { + if (currentProcess === childProcess) { + currentProcess = null; + } + resolve({ code, output }); + }); + }); + }; + + const runOxlint = async (): Promise => { + const wasKilled = killCurrentProcess(); + const runId = ++currentRunId; + + console.log( + wasKilled + ? "\x1b[36mRunning oxlint...\x1b[0m \x1b[90m(killed previous process)\x1b[0m" + : "\x1b[36mRunning oxlint...\x1b[0m", + ); + + sendLintResult({ running: true }); + + // First pass: fast oxlint without type checking + const { code, output } = await runLintProcess([]); + + // Check if we were superseded by a newer run + if (runId !== currentRunId) { + return; + } + + if (output) { + console.log(output); + } + + // If first pass had errors, send them immediately (fast-fail) + if (code !== 0) { + const counts = parseLintOutput(output); + if (counts.errorCount > 0 || counts.warningCount > 0) { + sendLintResult({ ...counts, running: false }); + return; + } + } + + // First pass clean - run type-aware check if enabled + if (!typeAware) { + sendLintResult({ errorCount: 0, warningCount: 0, running: false }); + return; + } + + console.log("\x1b[36mRunning type-aware checks...\x1b[0m"); + sendLintResult({ running: true, typeAware: true }); + const typeResult = await runLintProcess(["--type-check", "--type-aware"]); + + // Check if we were superseded by a newer run + if (runId !== currentRunId) { + return; + } + + if (typeResult.output) { + console.log(typeResult.output); + } + + const counts = + typeResult.code !== 0 + ? parseLintOutput(typeResult.output) + : { errorCount: 0, warningCount: 0 }; + sendLintResult({ ...counts, running: false }); + }; + + const debouncedLint = (): void => { + clearDebounceTimer(); + sendLintResult({ running: true }); + debounceTimer = setTimeout(() => void runOxlint(), debounceDelay); + }; + + return { + name: "vite-plugin-oxlint-checker", + + config(_, { command }) { + isProduction = command === "build"; + }, + + configureServer(devServer: ViteDevServer) { + server = devServer; + + // Send current lint status to new clients on connection + devServer.ws.on("connection", () => { + devServer.ws.send("vite-plugin-oxlint", lastLintResult); + }); + + // Run initial lint + void runOxlint(); + + // Listen for file changes + devServer.watcher.on("change", (file: string) => { + // Only lint on relevant file changes + if (extensions.some((ext) => file.endsWith(ext))) { + debouncedLint(); + } + }); + }, + + transformIndexHtml() { + if (!overlay) { + return []; + } + + // Inject import to the overlay module (actual .ts file processed by Vite) + const overlayPath = normalizePath( + fileURLToPath(new URL("./oxlint-overlay.ts", import.meta.url)), + ); + return [ + { + tag: "script", + attrs: { + type: "module", + src: `/@fs${overlayPath}`, + }, + injectTo: "body-prepend", + }, + ]; + }, + + buildStart() { + // Only run during production builds, not dev server startup + if (!isProduction) { + return; + } + + // Run oxlint synchronously during build + console.log("\n\x1b[1mRunning oxlint...\x1b[0m"); + + try { + const output = execSync( + "npx oxlint . && npx oxlint . --type-aware --type-check", + { + cwd: process.cwd(), + encoding: "utf-8", + env: { ...process.env, FORCE_COLOR: "3" }, + }, + ); + + if (output) { + console.log(output); + } + console.log(` \x1b[32m✓ No linting issues found\x1b[0m\n`); + } catch (error) { + // execSync throws on non-zero exit code (linting errors found) + if (error instanceof Error && "stdout" in error) { + const execError = error as Error & { + stdout?: string; + stderr?: string; + }; + if (execError.stdout !== undefined) console.log(execError.stdout); + if (execError.stderr !== undefined) console.error(execError.stderr); + } + console.error("\n\x1b[31mBuild aborted due to linting errors\x1b[0m\n"); + process.exit(1); + } + }, + + closeBundle() { + // Cleanup on server close + killCurrentProcess(); + clearDebounceTimer(); + }, + }; +} diff --git a/frontend/vite-plugins/oxlint-overlay.ts b/frontend/vite-plugins/oxlint-overlay.ts new file mode 100644 index 000000000..a1d734031 --- /dev/null +++ b/frontend/vite-plugins/oxlint-overlay.ts @@ -0,0 +1,126 @@ +// Oxlint overlay client-side code +let overlay: HTMLDivElement | null = null; +let hideTimeout: ReturnType | null = null; + +function createOverlay(): HTMLDivElement { + if (overlay) return overlay; + + overlay = document.createElement("div"); + overlay.id = "oxlint-error-overlay"; + overlay.style.cssText = ` + position: fixed; + bottom: 20px; + left: 20px; + background: #323437; + color: #e4dec8ff; + padding: 12px 16px; + border-radius: 8px; + font-family: 'Roboto Mono', monospace; + font-size: 14px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + z-index: 10000; + display: none; + align-items: center; + gap: 8px; + cursor: pointer; + transition: opacity 0.2s ease; + `; + + overlay.addEventListener("mouseenter", () => { + if (overlay) overlay.style.opacity = "0.5"; + }); + + overlay.addEventListener("mouseleave", () => { + if (overlay) overlay.style.opacity = "1"; + }); + + overlay.addEventListener("click", () => { + if (overlay) overlay.style.display = "none"; + }); + + document.body.appendChild(overlay); + return overlay; +} + +function updateOverlay(data: { + errorCount?: number; + warningCount?: number; + running?: boolean; + hadIssues?: boolean; + typeAware?: boolean; +}): void { + const overlayEl = createOverlay(); + + // Clear any pending hide timeout + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + hideTimeout = null; + } + + // Show running icon if linting is running and there were issues before + if (data.running) { + if (data.hadIssues) { + const message = data.typeAware ? "checking type aware..." : "checking..."; + overlayEl.innerHTML = ` + + oxlint: ${message} + `; + overlayEl.style.display = "flex"; + overlayEl.style.color = "#e4dec8ff"; + } else { + overlayEl.style.display = "none"; + } + return; + } + + const { errorCount = 0, warningCount = 0, hadIssues = false } = data; + const total = errorCount + warningCount; + + if (total > 0) { + overlayEl.innerHTML = ` + 🚨 + oxlint: ${errorCount} error${errorCount !== 1 ? "s" : ""}, ${warningCount} warning${warningCount !== 1 ? "s" : ""} + `; + overlayEl.style.display = "flex"; + + if (errorCount > 0) { + overlayEl.style.color = "#e4dec8ff"; + } + } else { + // Only show success if the previous lint had issues + if (hadIssues) { + overlayEl.innerHTML = ` + + oxlint: ok + `; + overlayEl.style.display = "flex"; + overlayEl.style.color = "#e4dec8ff"; + + // Hide after 3 seconds + hideTimeout = setTimeout(() => { + overlayEl.style.display = "none"; + hideTimeout = null; + }, 3000); + } else { + // Two good lints in a row - don't show anything + overlayEl.style.display = "none"; + } + } +} + +// Initialize overlay on load +createOverlay(); + +if (import.meta.hot) { + import.meta.hot.on( + "vite-plugin-oxlint", + (data: { + errorCount?: number; + warningCount?: number; + running?: boolean; + hadIssues?: boolean; + }) => { + updateOverlay(data); + }, + ); +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index ed0eb5c81..0b81b5466 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -19,7 +19,7 @@ import { languageHashes } from "./vite-plugins/language-hashes"; import { minifyJson } from "./vite-plugins/minify-json"; import { versionFile } from "./vite-plugins/version-file"; import { jqueryInject } from "./vite-plugins/jquery-inject"; -import { checker } from "vite-plugin-checker"; +import { oxlintChecker } from "./vite-plugins/oxlint-checker"; import Inspect from "vite-plugin-inspect"; import { ViteMinifyPlugin } from "vite-plugin-minify"; import { VitePWA } from "vite-plugin-pwa"; @@ -81,13 +81,10 @@ function getPlugins({ const plugins: PluginOption[] = [ envConfig({ isDevelopment, clientVersion, env }), languageHashes({ skip: isDevelopment }), - checker({ - oxlint: { - lintCommand: "oxlint . --type-aware --type-check", - }, - overlay: { - initialIsOpen: false, - }, + oxlintChecker({ + debounceDelay: 125, + typeAware: true, + overlay: true, }), jqueryInject(), injectHTML(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 89ee68fc4..17e59006f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -399,7 +399,7 @@ importers: version: 5.0.2 '@vitest/coverage-v8': specifier: 4.0.15 - version: 4.0.15(vitest@4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + version: 4.0.15(vitest@4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) autoprefixer: specifier: 10.4.20 version: 10.4.20(postcss@8.4.31) @@ -460,9 +460,6 @@ importers: vite-bundle-visualizer: specifier: 1.2.1 version: 1.2.1(rollup@2.79.2) - vite-plugin-checker: - specifier: 0.11.0 - version: 0.11.0(eslint@9.39.1)(meow@13.2.0)(optionator@0.9.4)(oxlint@1.34.0(oxlint-tsgolint@0.9.2))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) vite-plugin-filter-replace: specifier: 0.1.14 version: 0.1.14 @@ -480,7 +477,7 @@ importers: version: 1.1.0(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))(workbox-build@7.1.1)(workbox-window@7.1.0) vitest: specifier: 4.0.15 - version: 4.0.15(@opentelemetry/api@1.8.0)(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + version: 4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) packages/contracts: dependencies: @@ -7090,10 +7087,6 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} - engines: {node: '>=18'} - npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} deprecated: This package is no longer supported. @@ -8632,9 +8625,6 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - tiny-lru@8.0.2: resolution: {integrity: sha512-ApGvZ6vVvTNdsmt676grvCkUCGwzG9IqXma5Z07xJgiC5L7akUMof5U8G2JTI9Rz/ovtVhJBlY6mNhEvtjzOIg==} engines: {node: '>=6'} @@ -8959,10 +8949,6 @@ packages: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - unique-filename@3.0.0: resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -9107,43 +9093,6 @@ packages: peerDependencies: vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 - vite-plugin-checker@0.11.0: - resolution: {integrity: sha512-iUdO9Pl9UIBRPAragwi3as/BXXTtRu4G12L3CMrjx+WVTd9g/MsqNakreib9M/2YRVkhZYiTEwdH2j4Dm0w7lw==} - engines: {node: '>=16.11'} - peerDependencies: - '@biomejs/biome': '>=1.7' - eslint: '>=7' - meow: ^13.2.0 - optionator: ^0.9.4 - oxlint: '>=1' - stylelint: '>=16' - typescript: '*' - vite: '>=5.4.20' - vls: '*' - vti: '*' - vue-tsc: ~2.2.10 || ^3.0.0 - peerDependenciesMeta: - '@biomejs/biome': - optional: true - eslint: - optional: true - meow: - optional: true - optionator: - optional: true - oxlint: - optional: true - stylelint: - optional: true - typescript: - optional: true - vls: - optional: true - vti: - optional: true - vue-tsc: - optional: true - vite-plugin-filter-replace@0.1.14: resolution: {integrity: sha512-3onEAYgjJQTCJN+/f8+1vOUH3X/tRmXsfxmgH3QtpReGKi+xtP1jMpAEnYsNqw9mI2shZmxFm3LS+n52XczwYQ==} @@ -9254,9 +9203,6 @@ packages: vlq@0.2.3: resolution: {integrity: sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==} - vscode-uri@3.1.0: - resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} - walkdir@0.4.1: resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==} engines: {node: '>=6.0.0'} @@ -12589,6 +12535,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@4.0.15(vitest@4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1))': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.0.15 + ast-v8-to-istanbul: 0.3.8 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magicast: 0.5.1 + obug: 2.1.1 + std-env: 3.10.0 + tinyrainbow: 3.0.3 + vitest: 4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + transitivePeerDependencies: + - supports-color + '@vitest/expect@4.0.15': dependencies: '@standard-schema/spec': 1.0.0 @@ -17021,11 +16984,6 @@ snapshots: dependencies: path-key: 4.0.0 - npm-run-path@6.0.0: - dependencies: - path-key: 4.0.0 - unicorn-magic: 0.3.0 - npmlog@5.0.1: dependencies: are-we-there-yet: 2.0.0 @@ -18904,8 +18862,6 @@ snapshots: through@2.3.8: {} - tiny-invariant@1.3.3: {} - tiny-lru@8.0.2: {} tinybench@2.9.0: {} @@ -19196,8 +19152,6 @@ snapshots: unicorn-magic@0.1.0: {} - unicorn-magic@0.3.0: {} - unique-filename@3.0.0: dependencies: unique-slug: 4.0.0 @@ -19349,24 +19303,6 @@ snapshots: dependencies: vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - vite-plugin-checker@0.11.0(eslint@9.39.1)(meow@13.2.0)(optionator@0.9.4)(oxlint@1.34.0(oxlint-tsgolint@0.9.2))(typescript@5.9.3)(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)): - dependencies: - '@babel/code-frame': 7.27.1 - chokidar: 4.0.3 - npm-run-path: 6.0.0 - picocolors: 1.1.1 - picomatch: 4.0.3 - tiny-invariant: 1.3.3 - tinyglobby: 0.2.15 - vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) - vscode-uri: 3.1.0 - optionalDependencies: - eslint: 9.39.1 - meow: 13.2.0 - optionator: 0.9.4 - oxlint: 1.34.0(oxlint-tsgolint@0.9.2) - typescript: 5.9.3 - vite-plugin-filter-replace@0.1.14: dependencies: magic-string: 0.30.17 @@ -19514,9 +19450,45 @@ snapshots: - tsx - yaml - vlq@0.2.3: {} + vitest@4.0.15(@types/node@24.9.1)(happy-dom@20.0.10)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1): + dependencies: + '@vitest/expect': 4.0.15 + '@vitest/mocker': 4.0.15(vite@7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1)) + '@vitest/pretty-format': 4.0.15 + '@vitest/runner': 4.0.15 + '@vitest/snapshot': 4.0.15 + '@vitest/spy': 4.0.15 + '@vitest/utils': 4.0.15 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.1.12(@types/node@24.9.1)(sass@1.70.0)(terser@5.44.1)(tsx@4.16.2)(yaml@2.8.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.9.1 + happy-dom: 20.0.10 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml - vscode-uri@3.1.0: {} + vlq@0.2.3: {} walkdir@0.4.1: {} From 8148b05dd6aae0a6e668179e1bb1e417a20aa22a Mon Sep 17 00:00:00 2001 From: Leonabcd123 <156839416+Leonabcd123@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:40:48 +0200 Subject: [PATCH 20/23] fix(preset): Fix qsa warning on preset modal (@Leonabcd123) (#7277) ### Description Use a more specific selector. --- frontend/src/ts/modals/edit-preset.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/ts/modals/edit-preset.ts b/frontend/src/ts/modals/edit-preset.ts index 7f3771994..443df2f24 100644 --- a/frontend/src/ts/modals/edit-preset.ts +++ b/frontend/src/ts/modals/edit-preset.ts @@ -48,7 +48,7 @@ export function show(action: string, id?: string, name?: string): void { $("#editPresetModal .modal .text").addClass("hidden"); addCheckBoxes(); presetNameEl ??= new ValidatedHtmlInputElement( - qsr("#editPresetModal .modal input"), + qsr("#editPresetModal .modal input[type=text]"), { schema: PresetNameSchema, }, From 5093dbe128a1247525c7ffda4c5c1d020031637e Mon Sep 17 00:00:00 2001 From: Alex Rusbridge <6761721+AlexRusbridge@users.noreply.github.com> Date: Sat, 20 Dec 2025 09:15:55 +0000 Subject: [PATCH 21/23] fix(british-english): adds british english spelling for english-medical.json (@AlexRusbridge) (#7278) ### Description Adds British English spelling for words found in the english-medical word list. - Hematology terms (`hemo-`/`hema-` -> `haemo-`/`haema-`) - Blood condition suffixes (`-emia` -> `-aemia`) - White blood cell terms (`leuko-` -> `leuco-`) - Respiratory terms (`-pnea` -> `-pnoea`) - Other notable conversions (common patterns like `-ize` and other cases) ### Checks - [ ] ~Adding/modifying Typescript code?~ - [ ] ~Adding quotes?comment) so we can verify their content.~ - [ ] ~Adding a language?~ - [ ] ~Adding a theme?~ - [ ] ~Adding a layout?~ - [ ] ~Adding a font?~ - [x] Check if any open issues are related to this PR; if so, be sure to tag them below. - [x] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [x] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. --------- Co-authored-by: Jack --- frontend/src/ts/test/british-english.ts | 27 +++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/frontend/src/ts/test/british-english.ts b/frontend/src/ts/test/british-english.ts index b83122631..b97f3fed7 100644 --- a/frontend/src/ts/test/british-english.ts +++ b/frontend/src/ts/test/british-english.ts @@ -652,6 +652,33 @@ const replacementRules: BritishEnglishReplacements = { moisturizing: "moisturising", favoring: "favouring", marvelous: "marvellous", + hematuria: "haematuria", + hemoptysis: "haemoptysis", + hemorrhoid: "haemorrhoid", + hemorrhagic: "haemorrhagic", + hypercalcemia: "hypercalcaemia", + hyperglycemia: "hyperglycaemia", + hypoglycemia: "hypoglycaemia", + toxemia: "toxaemia", + hypoxemia: "hypoxaemia", + bacteremia: "bacteraemia", + hypernatremia: "hypernatraemia", + hyponatremia: "hyponatraemia", + leukocytosis: "leucocytosis", + leukocyte: "leucocyte", + leukopenia: "leucopenia", + apnea: "apnoea", + bradypnea: "bradypnoea", + tachypnea: "tachypnoea", + orthopnea: "orthopnoea", + ileocecal: "ileocaecal", + metastasize: "metastasise", + lymphedema: "lymphoedema", + neuron: "neurone", + hemianopsia: "hemianopia", + galactorrhea: "galactorrhoea", + nebulizer: "nebuliser", + paresthesia: "paraesthesia", }; export async function replace( From 1e2109bf6088adc0638232bfd6d5132d62d2ac9d Mon Sep 17 00:00:00 2001 From: Arjun <61285631+Zeden19@users.noreply.github.com> Date: Sat, 20 Dec 2025 05:04:34 -0500 Subject: [PATCH 22/23] impr(quotes): Add English quotes from XCOM: EU (@Zeden19) (#7264) ### Description ### Checks - [ ] Adding/modifying Typescript code? - [ ] I have used `qs`, `qsa` or `qsr` instead of JQuery selectors. - [X] Adding quotes? - [ ] Make sure to include translations for the quotes in the description (or another comment) so we can verify their content. - [ ] Adding a language? - Make sure to follow the [languages documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LANGUAGES.md) - [ ] Add language to `packages/schemas/src/languages.ts` - [ ] Add language to exactly one group in `frontend/src/ts/constants/languages.ts` - [ ] Add language json file to `frontend/static/languages` - [ ] Adding a theme? - Make sure to follow the [themes documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/THEMES.md) - [ ] Add theme to `packages/schemas/src/themes.ts` - [ ] Add theme to `frontend/src/ts/constants/themes.ts` - [ ] Add theme css file to `frontend/static/themes` - [ ] Add some screenshots of the theme, especially with different test settings (colorful, flip colors) to your pull request - [ ] Adding a layout? - [ ] Make sure to follow the [layouts documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/LAYOUTS.md) - [ ] Add layout to `packages/schemas/src/layouts.ts` - [ ] Add layout json file to `frontend/static/layouts` - [ ] Adding a font? - Make sure to follow the [themes documentation](https://github.com/monkeytypegame/monkeytype/blob/master/docs/FONTS.md) - [ ] Add font file to `frontend/static/webfonts` - [ ] Add font to `packages/schemas/src/fonts.ts` - [ ] Add font to `frontend/src/ts/constants/fonts.ts` - [X] Check if any open issues are related to this PR; if so, be sure to tag them below. - [X] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info) - [X] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title. Closes # --- frontend/static/quotes/english.json | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/frontend/static/quotes/english.json b/frontend/static/quotes/english.json index f75c31058..d5c3455c6 100644 --- a/frontend/static/quotes/english.json +++ b/frontend/static/quotes/english.json @@ -39111,6 +39111,36 @@ "source": "George Orwell, 1984", "id": 7731, "length": 109 + }, + { + "text": "Commander! You may want to instruct your men to exercise restraint when using explosives... while certainly effective at killing aliens, they also destroy the artifacts we're hoping to recover from the bodies. Just something to consider.", + "source": "XCOM: Enemy Unknown", + "id": 7732, + "length": 237 + }, + { + "text": "This is unlike anything else we've previously identified. Based on its physical appearance, I would assume this alien doesn't rely on brute strength. I recommend the troops exercise extreme caution, Commander.", + "source": "XCOM: Enemy Unknown", + "id": 7733, + "length": 209 + }, + { + "text": "Ever since mankind first looked up at the stars, we have wondered what lies beyond. So very few have dared to look inward... The depths of the human mind hold more secrets than we can possibly imagine. How ironic that the means to defeat our enemy comes not through weapons or machines of war, but from within. And if we have succeeded... we will have gained a glimpse of what we are to become. We will have created something... extraordinary.", + "source": "XCOM: Enemy Unknown", + "id": 7734, + "length": 443 + }, + { + "text": "Incredible! That alien seems to have... taken control of that soldier somehow. All of the advances we've made so far... they would be useless against this type of power.", + "source": "XCOM: Enemy Unknown", + "id": 7735, + "length": 169 + }, + { + "text": "I have difficulty understanding how such an advanced species could show so little empathy for the lives of other sentient beings... It goes against everything we have ever imagined. The technology is there, but with it comes a callousness we would never have expected. What could have brought them to this...", + "source": "XCOM: Enemy Unknown", + "id": 7736, + "length": 308 } ] } From 86ed9c2570ed82fb1011d1e60d393af4e7c635ca Mon Sep 17 00:00:00 2001 From: Miodec Date: Sat, 20 Dec 2025 10:26:40 +0100 Subject: [PATCH 23/23] chore: ignore pnpm-lock --- .oxfmtrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.oxfmtrc.json b/.oxfmtrc.json index a4d38b60b..0bc993fcb 100644 --- a/.oxfmtrc.json +++ b/.oxfmtrc.json @@ -7,6 +7,7 @@ "endOfLine": "lf", "trailingComma": "all", "ignorePatterns": [ + "pnpm-lock.yaml", "node_modules", ".turbo", "dist",