diff --git a/icons.config.js b/icons.config.js index 2ae7ef796..49deb2153 100644 --- a/icons.config.js +++ b/icons.config.js @@ -6,6 +6,7 @@ const config = { icons: [ "rocket_launch", "deployed_code_update", + "difference", "adjust", "add", "piano", diff --git a/package.json b/package.json index 314ba348e..ba83c111c 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "build:tauri": "tauri build", "tauri": "tauri", "test": "vitest run --coverage", + "test:chord-sync": "vitest chord-sync", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "minify-icons": "node src/tools/minify-icon-font.js", @@ -35,25 +36,28 @@ }, "devDependencies": { "@codemirror/autocomplete": "^6.20.0", + "@codemirror/collab": "^6.1.1", "@codemirror/commands": "^6.10.1", "@codemirror/lang-javascript": "^6.2.4", - "@codemirror/language": "^6.11.3", + "@codemirror/language": "^6.12.1", + "@codemirror/lint": "^6.9.2", "@codemirror/merge": "^6.11.2", - "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.39.4", + "@codemirror/search": "^6.6.0", + "@codemirror/state": "^6.5.3", + "@codemirror/view": "^6.39.9", "@fontsource-variable/material-symbols-rounded": "^5.2.30", "@fontsource-variable/noto-sans-mono": "^5.2.10", - "@lezer/common": "^1.4.0", + "@lezer/common": "^1.5.0", "@lezer/generator": "^1.8.0", "@lezer/highlight": "^1.2.3", - "@lezer/lr": "^1.4.5", + "@lezer/lr": "^1.4.7", "@material/material-color-utilities": "^0.3.0", "@melt-ui/pp": "^0.3.2", "@melt-ui/svelte": "^0.86.6", "@modyfi/vite-plugin-yaml": "^1.1.1", "@sveltejs/adapter-static": "^3.0.10", - "@sveltejs/kit": "^2.49.2", - "@sveltejs/vite-plugin-svelte": "^6.2.1", + "@sveltejs/kit": "^2.49.3", + "@sveltejs/vite-plugin-svelte": "^6.2.3", "@tauri-apps/api": "^1.6.0", "@tauri-apps/cli": "^1.6.0", "@types/dom-view-transitions": "^1.0.6", @@ -76,29 +80,29 @@ "matrix-js-sdk": "^37.12.0", "npm-run-all": "^4.1.5", "prettier": "^3.7.4", - "prettier-plugin-css-order": "^2.1.2", + "prettier-plugin-css-order": "^2.2.0", "prettier-plugin-svelte": "^3.4.1", "rxjs": "^7.8.2", - "sass": "^1.97.0", + "sass": "^1.97.2", "semver": "^7.7.3", - "socket.io-client": "^4.8.1", + "socket.io-client": "^4.8.3", "stylelint": "^16.26.1", "stylelint-config-html": "^1.1.0", "stylelint-config-prettier-scss": "^1.0.0", "stylelint-config-recommended-scss": "^16.0.2", "stylelint-config-standard-scss": "^16.0.0", - "svelte": "5.37.1", - "svelte-check": "^4.3.4", + "svelte": "5.46.1", + "svelte-check": "^4.3.5", "svelte-preprocess": "^6.0.3", "tippy.js": "^6.3.7", "typesafe-i18n": "^5.26.2", - "typescript": "^5.8.3", - "vite": "^7.0.6", + "typescript": "^5.9.3", + "vite": "^7.3.1", "vite-plugin-mkcert": "^1.17.9", - "vite-plugin-pwa": "^1.0.2", + "vite-plugin-pwa": "^1.2.0", "vitest": "^4.0.16", "web-serial-polyfill": "^1.0.15", - "workbox-window": "^7.3.0" + "workbox-window": "^7.4.0" }, "type": "module" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b2cdb3cc..3a0290e8b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@codemirror/autocomplete': specifier: ^6.20.0 version: 6.20.0 + '@codemirror/collab': + specifier: ^6.1.1 + version: 6.1.1 '@codemirror/commands': specifier: ^6.10.1 version: 6.10.1 @@ -18,17 +21,23 @@ importers: specifier: ^6.2.4 version: 6.2.4 '@codemirror/language': - specifier: ^6.11.3 - version: 6.11.3 + specifier: ^6.12.1 + version: 6.12.1 + '@codemirror/lint': + specifier: ^6.9.2 + version: 6.9.2 '@codemirror/merge': specifier: ^6.11.2 version: 6.11.2 + '@codemirror/search': + specifier: ^6.6.0 + version: 6.6.0 '@codemirror/state': - specifier: ^6.5.2 - version: 6.5.2 + specifier: ^6.5.3 + version: 6.5.3 '@codemirror/view': - specifier: ^6.39.4 - version: 6.39.4 + specifier: ^6.39.9 + version: 6.39.9 '@fontsource-variable/material-symbols-rounded': specifier: ^5.2.30 version: 5.2.30 @@ -36,8 +45,8 @@ importers: specifier: ^5.2.10 version: 5.2.10 '@lezer/common': - specifier: ^1.4.0 - version: 1.4.0 + specifier: ^1.5.0 + version: 1.5.0 '@lezer/generator': specifier: ^1.8.0 version: 1.8.0 @@ -45,29 +54,29 @@ importers: specifier: ^1.2.3 version: 1.2.3 '@lezer/lr': - specifier: ^1.4.5 - version: 1.4.5 + specifier: ^1.4.7 + version: 1.4.7 '@material/material-color-utilities': specifier: ^0.3.0 version: 0.3.0 '@melt-ui/pp': specifier: ^0.3.2 - version: 0.3.2(@melt-ui/svelte@0.86.6(svelte@5.37.1))(svelte@5.37.1) + version: 0.3.2(@melt-ui/svelte@0.86.6(svelte@5.46.1))(svelte@5.46.1) '@melt-ui/svelte': specifier: ^0.86.6 - version: 0.86.6(svelte@5.37.1) + version: 0.86.6(svelte@5.46.1) '@modyfi/vite-plugin-yaml': specifier: ^1.1.1 - version: 1.1.1(rollup@2.79.2)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) + version: 1.1.1(rollup@2.79.2)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) '@sveltejs/adapter-static': specifier: ^3.0.10 - version: 3.0.10(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1))) + version: 3.0.10(@sveltejs/kit@2.49.3(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(typescript@5.9.3)(vite@7.3.1(sass@1.97.2)(terser@5.44.1))) '@sveltejs/kit': - specifier: ^2.49.2 - version: 2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) + specifier: ^2.49.3 + version: 2.49.3(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(typescript@5.9.3)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) '@sveltejs/vite-plugin-svelte': - specifier: ^6.2.1 - version: 6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) + specifier: ^6.2.3 + version: 6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) '@tauri-apps/api': specifier: ^1.6.0 version: 1.6.0 @@ -94,7 +103,7 @@ importers: version: 2023.10.7 '@vite-pwa/sveltekit': specifier: ^1.1.0 - version: 1.1.0(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(vite@7.0.6(sass@1.97.0)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.3.0) + version: 1.1.0(@sveltejs/kit@2.49.3(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(typescript@5.9.3)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(vite@7.3.1(sass@1.97.2)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.4.0) autoprefixer: specifier: ^10.4.23 version: 10.4.23(postcss@8.5.6) @@ -135,81 +144,77 @@ importers: specifier: ^3.7.4 version: 3.7.4 prettier-plugin-css-order: - specifier: ^2.1.2 - version: 2.1.2(postcss@8.5.6)(prettier@3.7.4) + specifier: ^2.2.0 + version: 2.2.0(postcss@8.5.6)(prettier@3.7.4) prettier-plugin-svelte: specifier: ^3.4.1 - version: 3.4.1(prettier@3.7.4)(svelte@5.37.1) + version: 3.4.1(prettier@3.7.4)(svelte@5.46.1) rxjs: specifier: ^7.8.2 version: 7.8.2 sass: - specifier: ^1.97.0 - version: 1.97.0 + specifier: ^1.97.2 + version: 1.97.2 semver: specifier: ^7.7.3 version: 7.7.3 socket.io-client: - specifier: ^4.8.1 - version: 4.8.1 + specifier: ^4.8.3 + version: 4.8.3 stylelint: specifier: ^16.26.1 - version: 16.26.1(typescript@5.8.3) + version: 16.26.1(typescript@5.9.3) stylelint-config-html: specifier: ^1.1.0 - version: 1.1.0(postcss-html@1.7.0)(stylelint@16.26.1(typescript@5.8.3)) + version: 1.1.0(postcss-html@1.7.0)(stylelint@16.26.1(typescript@5.9.3)) stylelint-config-prettier-scss: specifier: ^1.0.0 - version: 1.0.0(stylelint@16.26.1(typescript@5.8.3)) + version: 1.0.0(stylelint@16.26.1(typescript@5.9.3)) stylelint-config-recommended-scss: specifier: ^16.0.2 - version: 16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.8.3)) + version: 16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)) stylelint-config-standard-scss: specifier: ^16.0.0 - version: 16.0.0(postcss@8.5.6)(stylelint@16.26.1(typescript@5.8.3)) + version: 16.0.0(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)) svelte: - specifier: 5.37.1 - version: 5.37.1 + specifier: 5.46.1 + version: 5.46.1 svelte-check: - specifier: ^4.3.4 - version: 4.3.4(picomatch@4.0.3)(svelte@5.37.1)(typescript@5.8.3) + specifier: ^4.3.5 + version: 4.3.5(picomatch@4.0.3)(svelte@5.46.1)(typescript@5.9.3) svelte-preprocess: specifier: ^6.0.3 - version: 6.0.3(@babel/core@7.28.5)(postcss@8.5.6)(sass@1.97.0)(svelte@5.37.1)(typescript@5.8.3) + version: 6.0.3(@babel/core@7.28.5)(postcss@8.5.6)(sass@1.97.2)(svelte@5.46.1)(typescript@5.9.3) tippy.js: specifier: ^6.3.7 version: 6.3.7 typesafe-i18n: specifier: ^5.26.2 - version: 5.26.2(typescript@5.8.3) + version: 5.26.2(typescript@5.9.3) typescript: - specifier: ^5.8.3 - version: 5.8.3 + specifier: ^5.9.3 + version: 5.9.3 vite: - specifier: ^7.0.6 - version: 7.0.6(sass@1.97.0)(terser@5.44.1) + specifier: ^7.3.1 + version: 7.3.1(sass@1.97.2)(terser@5.44.1) vite-plugin-mkcert: specifier: ^1.17.9 - version: 1.17.9(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) + version: 1.17.9(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) vite-plugin-pwa: - specifier: ^1.0.2 - version: 1.0.2(vite@7.0.6(sass@1.97.0)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.3.0) + specifier: ^1.2.0 + version: 1.2.0(vite@7.3.1(sass@1.97.2)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.4.0) vitest: specifier: ^4.0.16 - version: 4.0.16(jsdom@26.1.0)(sass@1.97.0)(terser@5.44.1) + version: 4.0.16(jsdom@26.1.0)(sass@1.97.2)(terser@5.44.1) web-serial-polyfill: specifier: ^1.0.15 version: 1.0.15 workbox-window: - specifier: ^7.3.0 - version: 7.3.0 + specifier: ^7.4.0 + version: 7.4.0 packages: - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@apideck/better-ajv-errors@0.3.6': resolution: {integrity: sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==} engines: {node: '>=10'} @@ -739,29 +744,32 @@ packages: '@codemirror/autocomplete@6.20.0': resolution: {integrity: sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==} + '@codemirror/collab@6.1.1': + resolution: {integrity: sha512-tkIn9Jguh98ie12dbBuba3lE8LHUkaMrIFuCVeVGhncSczFdKmX25vC12+58+yqQW5AXi3py6jWY0W+jelyglA==} + '@codemirror/commands@6.10.1': resolution: {integrity: sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==} '@codemirror/lang-javascript@6.2.4': resolution: {integrity: sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==} - '@codemirror/language@6.11.3': - resolution: {integrity: sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==} + '@codemirror/language@6.12.1': + resolution: {integrity: sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==} - '@codemirror/lint@6.8.1': - resolution: {integrity: sha512-IZ0Y7S4/bpaunwggW2jYqwLuHj0QtESf5xcROewY6+lDNwZ/NzvR4t+vpYgg9m7V8UXLPYqG+lu3DF470E5Oxg==} + '@codemirror/lint@6.9.2': + resolution: {integrity: sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==} '@codemirror/merge@6.11.2': resolution: {integrity: sha512-NO5EJd2rLRbwVWLgMdhIntDIhfDtMOKYEZgqV5WnkNUS2oXOCVWLPjG/kgl/Jth2fGiOuG947bteqxP9nBXmMg==} - '@codemirror/search@6.5.6': - resolution: {integrity: sha512-rpMgcsh7o0GuCDUXKPvww+muLA1pDJaFrpq/CCHtpQJYz8xopu4D1hPcKRoDD0YlF8gZaqTNIRa4VRBWyhyy7Q==} + '@codemirror/search@6.6.0': + resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==} - '@codemirror/state@6.5.2': - resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==} + '@codemirror/state@6.5.3': + resolution: {integrity: sha512-MerMzJzlXogk2fxWFU1nKp36bY5orBG59HnPiz0G9nLRebWa0zXuv2siH6PLIHBvv5TH8CkQRqjBs0MlxCZu+A==} - '@codemirror/view@6.39.4': - resolution: {integrity: sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==} + '@codemirror/view@6.39.9': + resolution: {integrity: sha512-miGSIfBOKC1s2oHoa80dp+BjtsL8sXsrgGlQnQuOcfvaedcQUtqddTmKbJSDkLl4mkgPvZyXuKic2HDNYcJLYA==} '@csstools/color-helpers@5.0.2': resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} @@ -828,152 +836,158 @@ packages: '@dual-bundle/import-meta-resolve@4.2.1': resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} - '@esbuild/aix-ppc64@0.25.2': - resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.2': - resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.2': - resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.2': - resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.2': - resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.2': - resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.2': - resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.2': - resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.2': - resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.2': - resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.2': - resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.2': - resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.2': - resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.2': - resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.2': - resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.2': - resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.2': - resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.2': - resolution: {integrity: sha512-talAIBoY5M8vHc6EeI2WW9d/CkiO9MQJ0IOWX8hrLhxGbro/vBXJvaQXefW2cP0z0nQVTdQ/eNyGFV1GSKrxfw==} + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.2': - resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.2': - resolution: {integrity: sha512-dcXYOC6NXOqcykeDlwId9kB6OkPUxOEqU+rkrYVqJbK2hagWOMrsTGsMr8+rW02M+d5Op5NNlgMmjzecaRf7Tg==} + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.2': - resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/sunos-x64@0.25.2': - resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.2': - resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.2': - resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.2': - resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -1011,10 +1025,6 @@ packages: '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - '@jridgewell/remapping@2.3.5': resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} @@ -1022,25 +1032,15 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.11': resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} @@ -1053,8 +1053,8 @@ packages: '@keyv/serialize@1.1.1': resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} - '@lezer/common@1.4.0': - resolution: {integrity: sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==} + '@lezer/common@1.5.0': + resolution: {integrity: sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==} '@lezer/generator@1.8.0': resolution: {integrity: sha512-/SF4EDWowPqV1jOgoGSGTIFsE7Ezdr7ZYxyihl5eMKVO5tlnpIhFcDavgm1hHY5GEonoOAEnJ0CU0x+tvuAuUg==} @@ -1066,8 +1066,8 @@ packages: '@lezer/javascript@1.4.17': resolution: {integrity: sha512-bYW4ctpyGK+JMumDApeUzuIezX01H76R1foD6LcRX224FWfyYit/HYxiPGDjXXe/wQWASjCvVGoukTH68+0HIA==} - '@lezer/lr@1.4.5': - resolution: {integrity: sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==} + '@lezer/lr@1.4.7': + resolution: {integrity: sha512-wNIFWdSUfX9Jc6ePMzxSPVgTVB4EOfDIwLQLWASyiUdHKaMsiilj9bYiGkGQCKVodd0x6bgQCV207PILGFCF9Q==} '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} @@ -1372,18 +1372,21 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.49.2': - resolution: {integrity: sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==} + '@sveltejs/kit@2.49.3': + resolution: {integrity: sha512-luTmE2Isk9GRJnitqanLoByKBiyLdfLpV2qV9a25JMxjbQt919TVqG8pibJDkxTvX9+w2k/9IL7o+/RtG++3QA==} engines: {node: '>=18.13'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.0.0 '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: ^5.3.3 vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 peerDependenciesMeta: '@opentelemetry/api': optional: true + typescript: + optional: true '@sveltejs/vite-plugin-svelte-inspector@5.0.0': resolution: {integrity: sha512-iwQ8Z4ET6ZFSt/gC+tVfcsSBHwsqc6RumSaiLUkAurW3BCpJam65cmHw0oOlDMTO0u+PZi9hilBRYN+LZNHTUQ==} @@ -1393,8 +1396,8 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || ^7.0.0 - '@sveltejs/vite-plugin-svelte@6.2.1': - resolution: {integrity: sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==} + '@sveltejs/vite-plugin-svelte@6.2.3': + resolution: {integrity: sha512-a+uxqQ9j6Lxmq4plbGaNdM9hgDCZyxAv/yvuyF5iWoA2H5icZkqD3rdK155ZQgFLX2lc3NvahHG4OgKpYqYPiQ==} engines: {node: ^20.19 || ^22.12 || >=24} peerDependencies: svelte: ^5.0.0 @@ -1493,9 +1496,6 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} - '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -1577,11 +1577,6 @@ packages: '@vitest/utils@4.0.16': resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -1961,8 +1956,8 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} - css-declaration-sorter@7.2.0: - resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} + css-declaration-sorter@7.3.1: + resolution: {integrity: sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==} engines: {node: ^14 || ^16 || >=18} peerDependencies: postcss: ^8.0.9 @@ -2345,8 +2340,8 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.25.2: - resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} hasBin: true @@ -2364,8 +2359,8 @@ packages: esptool-js@0.5.7: resolution: {integrity: sha512-k3pkXU9OTySCd58OUDjuJWNnFjM+QpPWAghxyWPm3zNfaLiP4ex2jNd7Rj0jWPu3/fgvwau236tetsTZrh4x5g==} - esrap@2.1.0: - resolution: {integrity: sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==} + esrap@2.2.1: + resolution: {integrity: sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==} estree-walker@1.0.1: resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} @@ -2434,14 +2429,6 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.4.2: - resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - fdir@6.4.6: resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: @@ -3144,9 +3131,6 @@ packages: magic-string@0.30.10: resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -3390,10 +3374,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} @@ -3464,8 +3444,8 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - prettier-plugin-css-order@2.1.2: - resolution: {integrity: sha512-vomxPjHI6pOMYcBuouSJHxxQClJXaUpU9rsV9IAO2wrSTZILRRlrxAAR8t9UF6wtczLkLfNRFUwM+ZbGXOONUA==} + prettier-plugin-css-order@2.2.0: + resolution: {integrity: sha512-GCkwEgQ2roT7le+zpUFQThPDO4x5EXcZmY9Rj6rvO++I/nATTGBWdZdsooha/BlvIBbZclJzXsgJdlKWrys9+w==} engines: {node: '>=16'} peerDependencies: prettier: 3.x @@ -3658,8 +3638,8 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sass@1.97.0: - resolution: {integrity: sha512-KR0igP1z4avUJetEuIeOdDlwaUDvkH8wSx7FdSjyYBS3dpyX3TzHfAMO0G1Q4/3cdjcmi3r7idh+KCmKqS+KeQ==} + sass@1.97.2: + resolution: {integrity: sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==} engines: {node: '>=14.0.0'} hasBin: true @@ -3770,8 +3750,8 @@ packages: smob@1.5.0: resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} - socket.io-client@4.8.1: - resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==} + socket.io-client@4.8.3: + resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} engines: {node: '>=10.0.0'} socket.io-parser@4.2.4: @@ -3964,8 +3944,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svelte-check@4.3.4: - resolution: {integrity: sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw==} + svelte-check@4.3.5: + resolution: {integrity: sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==} engines: {node: '>= 18.0.0'} hasBin: true peerDependencies: @@ -4009,8 +3989,8 @@ packages: typescript: optional: true - svelte@5.37.1: - resolution: {integrity: sha512-h8arWpQZ+3z8eahyBT5KkiBOUsG6xvI5Ykg0ozRr9xEdImgSMUPUlOFWRNkUsT7Ti0DSUCTEbPoped0aoxFyWA==} + svelte@5.46.1: + resolution: {integrity: sha512-ynjfCHD3nP2el70kN5Pmg37sSi0EjOm9FgHYQdC4giWG/hzO3AatzXXJJgP305uIhGQxSufJLuYWtkY8uK/8RA==} engines: {node: '>=18'} svg-tags@1.0.0: @@ -4055,10 +4035,6 @@ packages: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} - tinyglobby@0.2.10: - resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} - engines: {node: '>=12.0.0'} - tinyglobby@0.2.14: resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} engines: {node: '>=12.0.0'} @@ -4175,8 +4151,8 @@ packages: peerDependencies: typescript: '>=3.5.1' - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true @@ -4264,18 +4240,6 @@ packages: peerDependencies: vite: '>=3' - vite-plugin-pwa@1.0.2: - resolution: {integrity: sha512-O3UwjsCnoDclgJANoOgzzqW7SFgwXE/th2OmUP/ILxHKwzWxxKDBu+B/Xa9Cv4IgSVSnj2HgRVIJ7F15+vQFkA==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@vite-pwa/assets-generator': ^1.0.0 - vite: ^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 - workbox-build: ^7.3.0 - workbox-window: ^7.3.0 - peerDependenciesMeta: - '@vite-pwa/assets-generator': - optional: true - vite-plugin-pwa@1.2.0: resolution: {integrity: sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==} engines: {node: '>=16.0.0'} @@ -4288,8 +4252,8 @@ packages: '@vite-pwa/assets-generator': optional: true - vite@7.0.6: - resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -4455,8 +4419,8 @@ packages: workbox-core@7.1.0: resolution: {integrity: sha512-5KB4KOY8rtL31nEF7BfvU7FMzKT4B5TkbYa2tzkS+Peqj0gayMT9SytSFtNzlrvMaWgv6y/yvP9C0IbpFjV30Q==} - workbox-core@7.3.0: - resolution: {integrity: sha512-Z+mYrErfh4t3zi7NVTvOuACB0A/jA3bgxUN3PwtAVHvfEsZxV9Iju580VEETug3zYJRc0Dmii/aixI/Uxj8fmw==} + workbox-core@7.4.0: + resolution: {integrity: sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==} workbox-expiration@7.1.0: resolution: {integrity: sha512-m5DcMY+A63rJlPTbbBNtpJ20i3enkyOtSgYfv/l8h+D6YbbNiA0zKEkCUaMsdDlxggla1oOfRkyqTvl5Ni5KQQ==} @@ -4491,8 +4455,8 @@ packages: workbox-window@7.1.0: resolution: {integrity: sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==} - workbox-window@7.3.0: - resolution: {integrity: sha512-qW8PDy16OV1UBaUNGlTVcepzrlzyzNW/ZJvFQQs2j2TzGsg6IKjcpZC1RSquqQnTOafl5pCj5bGfAHlCjOOjdA==} + workbox-window@7.4.0: + resolution: {integrity: sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==} wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} @@ -4559,11 +4523,6 @@ packages: snapshots: - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - '@apideck/better-ajv-errors@0.3.6(ajv@8.17.1)': dependencies: ajv: 8.17.1 @@ -5265,64 +5224,68 @@ snapshots: '@codemirror/autocomplete@6.20.0': dependencies: - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.3 + '@codemirror/view': 6.39.9 + '@lezer/common': 1.5.0 + + '@codemirror/collab@6.1.1': + dependencies: + '@codemirror/state': 6.5.3 '@codemirror/commands@6.10.1': dependencies: - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.3 + '@codemirror/view': 6.39.9 + '@lezer/common': 1.5.0 '@codemirror/lang-javascript@6.2.4': dependencies: '@codemirror/autocomplete': 6.20.0 - '@codemirror/language': 6.11.3 - '@codemirror/lint': 6.8.1 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 + '@codemirror/language': 6.12.1 + '@codemirror/lint': 6.9.2 + '@codemirror/state': 6.5.3 + '@codemirror/view': 6.39.9 + '@lezer/common': 1.5.0 '@lezer/javascript': 1.4.17 - '@codemirror/language@6.11.3': + '@codemirror/language@6.12.1': dependencies: - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 - '@lezer/common': 1.4.0 + '@codemirror/state': 6.5.3 + '@codemirror/view': 6.39.9 + '@lezer/common': 1.5.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + '@lezer/lr': 1.4.7 style-mod: 4.1.2 - '@codemirror/lint@6.8.1': + '@codemirror/lint@6.9.2': dependencies: - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/state': 6.5.3 + '@codemirror/view': 6.39.9 crelt: 1.0.6 '@codemirror/merge@6.11.2': dependencies: - '@codemirror/language': 6.11.3 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/language': 6.12.1 + '@codemirror/state': 6.5.3 + '@codemirror/view': 6.39.9 '@lezer/highlight': 1.2.3 style-mod: 4.1.2 - '@codemirror/search@6.5.6': + '@codemirror/search@6.6.0': dependencies: - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/state': 6.5.3 + '@codemirror/view': 6.39.9 crelt: 1.0.6 - '@codemirror/state@6.5.2': + '@codemirror/state@6.5.3': dependencies: '@marijn/find-cluster-break': 1.0.2 - '@codemirror/view@6.39.4': + '@codemirror/view@6.39.9': dependencies: - '@codemirror/state': 6.5.2 + '@codemirror/state': 6.5.3 crelt: 1.0.6 style-mod: 4.1.2 w3c-keyname: 2.2.8 @@ -5394,79 +5357,82 @@ snapshots: '@dual-bundle/import-meta-resolve@4.2.1': {} - '@esbuild/aix-ppc64@0.25.2': + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm64@0.25.2': + '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-arm@0.25.2': + '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/android-x64@0.25.2': + '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.25.2': + '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/darwin-x64@0.25.2': + '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.25.2': + '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.25.2': + '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm64@0.25.2': + '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-arm@0.25.2': + '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-ia32@0.25.2': + '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-loong64@0.25.2': + '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-mips64el@0.25.2': + '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-ppc64@0.25.2': + '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.25.2': + '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-s390x@0.25.2': + '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/linux-x64@0.25.2': + '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-arm64@0.25.2': + '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.25.2': + '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-arm64@0.25.2': + '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.25.2': + '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.25.2': + '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/win32-arm64@0.25.2': + '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.25.2': + '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.25.2': + '@esbuild/win32-x64@0.27.2': optional: true '@floating-ui/core@1.6.7': @@ -5508,12 +5474,6 @@ snapshots: '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.31 - '@jridgewell/gen-mapping@0.3.5': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - '@jridgewell/remapping@2.3.5': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -5521,8 +5481,6 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} - '@jridgewell/source-map@0.3.11': dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -5530,15 +5488,8 @@ snapshots: '@jridgewell/sourcemap-codec@1.4.15': {} - '@jridgewell/sourcemap-codec@1.5.0': {} - '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping@0.3.31': dependencies: '@jridgewell/resolve-uri': 3.1.2 @@ -5552,26 +5503,26 @@ snapshots: '@keyv/serialize@1.1.1': {} - '@lezer/common@1.4.0': {} + '@lezer/common@1.5.0': {} '@lezer/generator@1.8.0': dependencies: - '@lezer/common': 1.4.0 - '@lezer/lr': 1.4.5 + '@lezer/common': 1.5.0 + '@lezer/lr': 1.4.7 '@lezer/highlight@1.2.3': dependencies: - '@lezer/common': 1.4.0 + '@lezer/common': 1.5.0 '@lezer/javascript@1.4.17': dependencies: - '@lezer/common': 1.4.0 + '@lezer/common': 1.5.0 '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.5 + '@lezer/lr': 1.4.7 - '@lezer/lr@1.4.5': + '@lezer/lr@1.4.7': dependencies: - '@lezer/common': 1.4.0 + '@lezer/common': 1.5.0 '@marijn/find-cluster-break@1.0.2': {} @@ -5579,14 +5530,14 @@ snapshots: '@matrix-org/matrix-sdk-crypto-wasm@15.1.0': {} - '@melt-ui/pp@0.3.2(@melt-ui/svelte@0.86.6(svelte@5.37.1))(svelte@5.37.1)': + '@melt-ui/pp@0.3.2(@melt-ui/svelte@0.86.6(svelte@5.46.1))(svelte@5.46.1)': dependencies: - '@melt-ui/svelte': 0.86.6(svelte@5.37.1) + '@melt-ui/svelte': 0.86.6(svelte@5.46.1) estree-walker: 3.0.3 magic-string: 0.30.10 - svelte: 5.37.1 + svelte: 5.46.1 - '@melt-ui/svelte@0.86.6(svelte@5.37.1)': + '@melt-ui/svelte@0.86.6(svelte@5.46.1)': dependencies: '@floating-ui/core': 1.6.7 '@floating-ui/dom': 1.6.10 @@ -5594,14 +5545,14 @@ snapshots: dequal: 2.0.3 focus-trap: 7.5.4 nanoid: 5.0.7 - svelte: 5.37.1 + svelte: 5.46.1 - '@modyfi/vite-plugin-yaml@1.1.1(rollup@2.79.2)(vite@7.0.6(sass@1.97.0)(terser@5.44.1))': + '@modyfi/vite-plugin-yaml@1.1.1(rollup@2.79.2)(vite@7.3.1(sass@1.97.2)(terser@5.44.1))': dependencies: '@rollup/pluginutils': 5.1.0(rollup@2.79.2) js-yaml: 4.1.0 tosource: 2.0.0-alpha.3 - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) + vite: 7.3.1(sass@1.97.2)(terser@5.44.1) transitivePeerDependencies: - rollup @@ -5809,55 +5760,53 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.12 - '@sveltejs/acorn-typescript@1.0.5(acorn@8.12.1)': - dependencies: - acorn: 8.12.1 - '@sveltejs/acorn-typescript@1.0.5(acorn@8.15.0)': dependencies: acorn: 8.15.0 - '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))': + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.49.3(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(typescript@5.9.3)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))': dependencies: - '@sveltejs/kit': 2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) + '@sveltejs/kit': 2.49.3(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(typescript@5.9.3)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) - '@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1))': + '@sveltejs/kit@2.49.3(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(typescript@5.9.3)(vite@7.3.1(sass@1.97.2)(terser@5.44.1))': dependencies: '@standard-schema/spec': 1.1.0 '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) + '@sveltejs/vite-plugin-svelte': 6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) '@types/cookie': 0.6.0 acorn: 8.15.0 cookie: 0.6.0 devalue: 5.6.1 esm-env: 1.2.2 kleur: 4.1.5 - magic-string: 0.30.17 + magic-string: 0.30.21 mrmime: 2.0.0 sade: 1.8.1 set-cookie-parser: 2.6.0 sirv: 3.0.0 - svelte: 5.37.1 - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) + svelte: 5.46.1 + vite: 7.3.1(sass@1.97.2)(terser@5.44.1) + optionalDependencies: + typescript: 5.9.3 - '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) - debug: 4.4.1(supports-color@8.1.1) - svelte: 5.37.1 - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) + '@sveltejs/vite-plugin-svelte': 6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) + debug: 4.4.3 + svelte: 5.46.1 + vite: 7.3.1(sass@1.97.2)(terser@5.44.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1))': + '@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) - debug: 4.4.1(supports-color@8.1.1) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) deepmerge: 4.3.1 - magic-string: 0.30.17 - svelte: 5.37.1 - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) - vitefu: 1.1.1(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) + magic-string: 0.30.21 + obug: 2.1.1 + svelte: 5.46.1 + vite: 7.3.1(sass@1.97.2)(terser@5.44.1) + vitefu: 1.1.1(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) transitivePeerDependencies: - supports-color @@ -5928,8 +5877,6 @@ snapshots: '@types/estree@1.0.6': {} - '@types/estree@1.0.7': {} - '@types/estree@1.0.8': {} '@types/events@3.0.3': {} @@ -5964,12 +5911,12 @@ snapshots: '@types/node': 20.14.10 optional: true - '@vite-pwa/sveltekit@1.1.0(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(vite@7.0.6(sass@1.97.0)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.3.0)': + '@vite-pwa/sveltekit@1.1.0(@sveltejs/kit@2.49.3(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(typescript@5.9.3)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(vite@7.3.1(sass@1.97.2)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.4.0)': dependencies: - '@sveltejs/kit': 2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)))(svelte@5.37.1)(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) + '@sveltejs/kit': 2.49.3(@sveltejs/vite-plugin-svelte@6.2.3(svelte@5.46.1)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)))(svelte@5.46.1)(typescript@5.9.3)(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) kolorist: 1.8.0 tinyglobby: 0.2.14 - vite-plugin-pwa: 1.2.0(vite@7.0.6(sass@1.97.0)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.3.0) + vite-plugin-pwa: 1.2.0(vite@7.3.1(sass@1.97.2)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.4.0) transitivePeerDependencies: - supports-color - vite @@ -5985,13 +5932,13 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.16(vite@7.0.6(sass@1.97.0)(terser@5.44.1))': + '@vitest/mocker@4.0.16(vite@7.3.1(sass@1.97.2)(terser@5.44.1))': dependencies: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) + vite: 7.3.1(sass@1.97.2)(terser@5.44.1) '@vitest/pretty-format@4.0.16': dependencies: @@ -6015,8 +5962,6 @@ snapshots: '@vitest/pretty-format': 4.0.16 tinyrainbow: 3.0.3 - acorn@8.12.1: {} - acorn@8.15.0: {} agent-base@7.1.3: {} @@ -6315,11 +6260,11 @@ snapshots: dependencies: '@codemirror/autocomplete': 6.20.0 '@codemirror/commands': 6.10.1 - '@codemirror/language': 6.11.3 - '@codemirror/lint': 6.8.1 - '@codemirror/search': 6.5.6 - '@codemirror/state': 6.5.2 - '@codemirror/view': 6.39.4 + '@codemirror/language': 6.12.1 + '@codemirror/lint': 6.9.2 + '@codemirror/search': 6.6.0 + '@codemirror/state': 6.5.3 + '@codemirror/view': 6.39.9 color-convert@1.9.3: dependencies: @@ -6366,14 +6311,14 @@ snapshots: core-util-is@1.0.2: {} - cosmiconfig@9.0.0(typescript@5.8.3): + cosmiconfig@9.0.0(typescript@5.9.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: - typescript: 5.8.3 + typescript: 5.9.3 crelt@1.0.6: {} @@ -6399,7 +6344,7 @@ snapshots: crypto-random-string@2.0.0: {} - css-declaration-sorter@7.2.0(postcss@8.5.6): + css-declaration-sorter@7.3.1(postcss@8.5.6): dependencies: postcss: 8.5.6 @@ -6945,33 +6890,34 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.25.2: + esbuild@0.27.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.2 - '@esbuild/android-arm': 0.25.2 - '@esbuild/android-arm64': 0.25.2 - '@esbuild/android-x64': 0.25.2 - '@esbuild/darwin-arm64': 0.25.2 - '@esbuild/darwin-x64': 0.25.2 - '@esbuild/freebsd-arm64': 0.25.2 - '@esbuild/freebsd-x64': 0.25.2 - '@esbuild/linux-arm': 0.25.2 - '@esbuild/linux-arm64': 0.25.2 - '@esbuild/linux-ia32': 0.25.2 - '@esbuild/linux-loong64': 0.25.2 - '@esbuild/linux-mips64el': 0.25.2 - '@esbuild/linux-ppc64': 0.25.2 - '@esbuild/linux-riscv64': 0.25.2 - '@esbuild/linux-s390x': 0.25.2 - '@esbuild/linux-x64': 0.25.2 - '@esbuild/netbsd-arm64': 0.25.2 - '@esbuild/netbsd-x64': 0.25.2 - '@esbuild/openbsd-arm64': 0.25.2 - '@esbuild/openbsd-x64': 0.25.2 - '@esbuild/sunos-x64': 0.25.2 - '@esbuild/win32-arm64': 0.25.2 - '@esbuild/win32-ia32': 0.25.2 - '@esbuild/win32-x64': 0.25.2 + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 escalade@3.2.0: {} @@ -6985,9 +6931,9 @@ snapshots: pako: 2.1.0 tslib: 2.6.3 - esrap@2.1.0: + esrap@2.2.1: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 estree-walker@1.0.1: {} @@ -7059,10 +7005,6 @@ snapshots: dependencies: pend: 1.2.0 - fdir@6.4.2(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 - fdir@6.4.6(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -7536,7 +7478,7 @@ snapshots: is-reference@3.0.3: dependencies: - '@types/estree': 1.0.7 + '@types/estree': 1.0.8 is-regex@1.1.4: dependencies: @@ -7780,10 +7722,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 - magic-string@0.30.17: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -8011,8 +7949,6 @@ snapshots: picomatch@2.3.1: {} - picomatch@4.0.2: {} - picomatch@4.0.3: {} pidtree@0.3.1: {} @@ -8065,19 +8001,19 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prettier-plugin-css-order@2.1.2(postcss@8.5.6)(prettier@3.7.4): + prettier-plugin-css-order@2.2.0(postcss@8.5.6)(prettier@3.7.4): dependencies: - css-declaration-sorter: 7.2.0(postcss@8.5.6) + css-declaration-sorter: 7.3.1(postcss@8.5.6) postcss-less: 6.0.0(postcss@8.5.6) postcss-scss: 4.0.9(postcss@8.5.6) prettier: 3.7.4 transitivePeerDependencies: - postcss - prettier-plugin-svelte@3.4.1(prettier@3.7.4)(svelte@5.37.1): + prettier-plugin-svelte@3.4.1(prettier@3.7.4)(svelte@5.46.1): dependencies: prettier: 3.7.4 - svelte: 5.37.1 + svelte: 5.46.1 prettier@3.7.4: {} @@ -8289,7 +8225,7 @@ snapshots: safer-buffer@2.1.2: {} - sass@1.97.0: + sass@1.97.2: dependencies: chokidar: 4.0.1 immutable: 5.1.1 @@ -8414,10 +8350,10 @@ snapshots: smob@1.5.0: {} - socket.io-client@4.8.1: + socket.io-client@4.8.3: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.6 + debug: 4.4.3 engine.io-client: 6.6.2 socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -8575,42 +8511,42 @@ snapshots: style-mod@4.1.2: {} - stylelint-config-html@1.1.0(postcss-html@1.7.0)(stylelint@16.26.1(typescript@5.8.3)): + stylelint-config-html@1.1.0(postcss-html@1.7.0)(stylelint@16.26.1(typescript@5.9.3)): dependencies: postcss-html: 1.7.0 - stylelint: 16.26.1(typescript@5.8.3) + stylelint: 16.26.1(typescript@5.9.3) - stylelint-config-prettier-scss@1.0.0(stylelint@16.26.1(typescript@5.8.3)): + stylelint-config-prettier-scss@1.0.0(stylelint@16.26.1(typescript@5.9.3)): dependencies: - stylelint: 16.26.1(typescript@5.8.3) + stylelint: 16.26.1(typescript@5.9.3) - stylelint-config-recommended-scss@16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.8.3)): + stylelint-config-recommended-scss@16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)): dependencies: postcss-scss: 4.0.9(postcss@8.5.6) - stylelint: 16.26.1(typescript@5.8.3) - stylelint-config-recommended: 17.0.0(stylelint@16.26.1(typescript@5.8.3)) - stylelint-scss: 6.12.1(stylelint@16.26.1(typescript@5.8.3)) + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended: 17.0.0(stylelint@16.26.1(typescript@5.9.3)) + stylelint-scss: 6.12.1(stylelint@16.26.1(typescript@5.9.3)) optionalDependencies: postcss: 8.5.6 - stylelint-config-recommended@17.0.0(stylelint@16.26.1(typescript@5.8.3)): + stylelint-config-recommended@17.0.0(stylelint@16.26.1(typescript@5.9.3)): dependencies: - stylelint: 16.26.1(typescript@5.8.3) + stylelint: 16.26.1(typescript@5.9.3) - stylelint-config-standard-scss@16.0.0(postcss@8.5.6)(stylelint@16.26.1(typescript@5.8.3)): + stylelint-config-standard-scss@16.0.0(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)): dependencies: - stylelint: 16.26.1(typescript@5.8.3) - stylelint-config-recommended-scss: 16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.8.3)) - stylelint-config-standard: 39.0.1(stylelint@16.26.1(typescript@5.8.3)) + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended-scss: 16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)) + stylelint-config-standard: 39.0.1(stylelint@16.26.1(typescript@5.9.3)) optionalDependencies: postcss: 8.5.6 - stylelint-config-standard@39.0.1(stylelint@16.26.1(typescript@5.8.3)): + stylelint-config-standard@39.0.1(stylelint@16.26.1(typescript@5.9.3)): dependencies: - stylelint: 16.26.1(typescript@5.8.3) - stylelint-config-recommended: 17.0.0(stylelint@16.26.1(typescript@5.8.3)) + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended: 17.0.0(stylelint@16.26.1(typescript@5.9.3)) - stylelint-scss@6.12.1(stylelint@16.26.1(typescript@5.8.3)): + stylelint-scss@6.12.1(stylelint@16.26.1(typescript@5.9.3)): dependencies: css-tree: 3.1.0 is-plain-object: 5.0.0 @@ -8620,9 +8556,9 @@ snapshots: postcss-resolve-nested-selector: 0.1.6 postcss-selector-parser: 7.1.0 postcss-value-parser: 4.2.0 - stylelint: 16.26.1(typescript@5.8.3) + stylelint: 16.26.1(typescript@5.9.3) - stylelint@16.26.1(typescript@5.8.3): + stylelint@16.26.1(typescript@5.9.3): dependencies: '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) '@csstools/css-syntax-patches-for-csstree': 1.0.21 @@ -8632,7 +8568,7 @@ snapshots: '@dual-bundle/import-meta-resolve': 4.2.1 balanced-match: 2.0.0 colord: 2.9.3 - cosmiconfig: 9.0.0(typescript@5.8.3) + cosmiconfig: 9.0.0(typescript@5.9.3) css-functions-list: 3.2.3 css-tree: 3.1.0 debug: 4.4.3 @@ -8686,42 +8622,43 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.37.1)(typescript@5.8.3): + svelte-check@4.3.5(picomatch@4.0.3)(svelte@5.46.1)(typescript@5.9.3): dependencies: - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.1 - fdir: 6.4.6(picomatch@4.0.3) + fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.37.1 - typescript: 5.8.3 + svelte: 5.46.1 + typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte-preprocess@6.0.3(@babel/core@7.28.5)(postcss@8.5.6)(sass@1.97.0)(svelte@5.37.1)(typescript@5.8.3): + svelte-preprocess@6.0.3(@babel/core@7.28.5)(postcss@8.5.6)(sass@1.97.2)(svelte@5.46.1)(typescript@5.9.3): dependencies: - svelte: 5.37.1 + svelte: 5.46.1 optionalDependencies: '@babel/core': 7.28.5 postcss: 8.5.6 - sass: 1.97.0 - typescript: 5.8.3 + sass: 1.97.2 + typescript: 5.9.3 - svelte@5.37.1: + svelte@5.46.1: dependencies: - '@ampproject/remapping': 2.3.0 - '@jridgewell/sourcemap-codec': 1.5.0 - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.12.1) - '@types/estree': 1.0.7 - acorn: 8.12.1 + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.15.0) + '@types/estree': 1.0.8 + acorn: 8.15.0 aria-query: 5.3.2 axobject-query: 4.1.0 clsx: 2.1.1 + devalue: 5.6.1 esm-env: 1.2.2 - esrap: 2.1.0 + esrap: 2.2.1 is-reference: 3.0.3 locate-character: 3.0.0 - magic-string: 0.30.17 + magic-string: 0.30.21 zimmerframe: 1.1.2 svg-tags@1.0.0: {} @@ -8764,11 +8701,6 @@ snapshots: tinyexec@1.0.2: {} - tinyglobby@0.2.10: - dependencies: - fdir: 6.4.2(picomatch@4.0.2) - picomatch: 4.0.2 - tinyglobby@0.2.14: dependencies: fdir: 6.4.6(picomatch@4.0.3) @@ -8898,11 +8830,11 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typesafe-i18n@5.26.2(typescript@5.8.3): + typesafe-i18n@5.26.2(typescript@5.9.3): dependencies: - typescript: 5.8.3 + typescript: 5.9.3 - typescript@5.8.3: {} + typescript@5.9.3: {} unbox-primitive@1.0.2: dependencies: @@ -8981,58 +8913,47 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 - vite-plugin-mkcert@1.17.9(vite@7.0.6(sass@1.97.0)(terser@5.44.1)): + vite-plugin-mkcert@1.17.9(vite@7.3.1(sass@1.97.2)(terser@5.44.1)): dependencies: axios: 1.13.2(debug@4.4.3) debug: 4.4.3 picocolors: 1.1.1 - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) - transitivePeerDependencies: - - supports-color - - vite-plugin-pwa@1.0.2(vite@7.0.6(sass@1.97.0)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.3.0): - dependencies: - debug: 4.4.0(supports-color@8.1.1) - pretty-bytes: 6.1.1 - tinyglobby: 0.2.10 - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) - workbox-build: 7.1.1 - workbox-window: 7.3.0 + vite: 7.3.1(sass@1.97.2)(terser@5.44.1) transitivePeerDependencies: - supports-color - vite-plugin-pwa@1.2.0(vite@7.0.6(sass@1.97.0)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.3.0): + vite-plugin-pwa@1.2.0(vite@7.3.1(sass@1.97.2)(terser@5.44.1))(workbox-build@7.1.1)(workbox-window@7.4.0): dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.3 pretty-bytes: 6.1.1 - tinyglobby: 0.2.14 - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) + tinyglobby: 0.2.15 + vite: 7.3.1(sass@1.97.2)(terser@5.44.1) workbox-build: 7.1.1 - workbox-window: 7.3.0 + workbox-window: 7.4.0 transitivePeerDependencies: - supports-color - vite@7.0.6(sass@1.97.0)(terser@5.44.1): + vite@7.3.1(sass@1.97.2)(terser@5.44.1): dependencies: - esbuild: 0.25.2 - fdir: 6.4.6(picomatch@4.0.3) + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 rollup: 4.46.1 - tinyglobby: 0.2.14 + tinyglobby: 0.2.15 optionalDependencies: fsevents: 2.3.3 - sass: 1.97.0 + sass: 1.97.2 terser: 5.44.1 - vitefu@1.1.1(vite@7.0.6(sass@1.97.0)(terser@5.44.1)): + vitefu@1.1.1(vite@7.3.1(sass@1.97.2)(terser@5.44.1)): optionalDependencies: - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) + vite: 7.3.1(sass@1.97.2)(terser@5.44.1) - vitest@4.0.16(jsdom@26.1.0)(sass@1.97.0)(terser@5.44.1): + vitest@4.0.16(jsdom@26.1.0)(sass@1.97.2)(terser@5.44.1): dependencies: '@vitest/expect': 4.0.16 - '@vitest/mocker': 4.0.16(vite@7.0.6(sass@1.97.0)(terser@5.44.1)) + '@vitest/mocker': 4.0.16(vite@7.3.1(sass@1.97.2)(terser@5.44.1)) '@vitest/pretty-format': 4.0.16 '@vitest/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -9049,7 +8970,7 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.0.6(sass@1.97.0)(terser@5.44.1) + vite: 7.3.1(sass@1.97.2)(terser@5.44.1) why-is-node-running: 2.3.0 optionalDependencies: jsdom: 26.1.0 @@ -9223,7 +9144,7 @@ snapshots: workbox-core@7.1.0: {} - workbox-core@7.3.0: {} + workbox-core@7.4.0: {} workbox-expiration@7.1.0: dependencies: @@ -9280,10 +9201,10 @@ snapshots: '@types/trusted-types': 2.0.7 workbox-core: 7.1.0 - workbox-window@7.3.0: + workbox-window@7.4.0: dependencies: '@types/trusted-types': 2.0.7 - workbox-core: 7.3.0 + workbox-core: 7.4.0 wrap-ansi@6.2.0: dependencies: diff --git a/src/lib/backup/backup.ts b/src/lib/backup/backup.ts index 2bfb9012c..0c34e0f67 100644 --- a/src/lib/backup/backup.ts +++ b/src/lib/backup/backup.ts @@ -6,15 +6,9 @@ import type { CharaSettingsFile, } from "$lib/share/chara-file.js"; import type { Change } from "$lib/undo-redo.js"; -import { - changes, - ChangeType, - chords, - layout, - settings, -} from "$lib/undo-redo.js"; +import { changes, ChangeType, layout, settings } from "$lib/undo-redo.js"; import { get } from "svelte/store"; -import { activeProfile, serialPort } from "../serial/connection"; +import { activeProfile, deviceChords, serialPort } from "../serial/connection"; import { csvLayoutToJson, isCsvLayout } from "$lib/backup/compat/legacy-layout"; import { isCsvChords, csvChordsToJson } from "./compat/legacy-chords"; @@ -60,7 +54,7 @@ export function createChordBackup(): CharaChordFile { return { charaVersion: 1, type: "chords", - chords: get(chords).map((it) => [it.actions, it.phrase]), + chords: get(deviceChords).map((it) => [it.actions, it.phrase]), }; } @@ -168,7 +162,9 @@ export function restoreFromFile( export function getChangesFromChordFile(file: CharaChordFile) { const changes: Change[] = []; const existingChords = new Set( - get(chords).map(({ phrase, actions }) => JSON.stringify([actions, phrase])), + get(deviceChords).map(({ phrase, actions }) => + JSON.stringify([actions, phrase]), + ), ); for (const [input, output] of file.chords) { if (existingChords.has(JSON.stringify([input, output]))) { diff --git a/src/lib/chord-editor/ChangesPanel.svelte b/src/lib/chord-editor/ChangesPanel.svelte new file mode 100644 index 000000000..cc9955379 --- /dev/null +++ b/src/lib/chord-editor/ChangesPanel.svelte @@ -0,0 +1,149 @@ + + +
+ {#if added + changed + removed !== 0 || $syncStatus === "uploading" || $syncStatus === "error"} +
+ + save + {$LL.saveActions.SAVE()} + +
+ {/if} + +
+ {#if added} + +{added} + {/if} + {#if changed} + ~{changed} + {/if} + {#if removed} + -{removed} + {/if} +
+ + {#if parsed.aliases.size > 0} +
+ content_copy + {parsed.aliases.size} +
+ {/if} +
+ + diff --git a/src/lib/chord-editor/action-linter.ts b/src/lib/chord-editor/action-linter.ts new file mode 100644 index 000000000..3930eb4be --- /dev/null +++ b/src/lib/chord-editor/action-linter.ts @@ -0,0 +1,156 @@ +import { linter, type Diagnostic } from "@codemirror/lint"; +import { parsedChordsField } from "./parsed-chords-plugin"; + +export function actionLinter(config?: Parameters[1]) { + const finalConfig: Parameters[1] = { + ...config, + needsRefresh(update) { + return ( + update.startState.field(parsedChordsField) !== + update.state.field(parsedChordsField) + ); + }, + }; + return linter((view) => { + console.log("lint"); + const diagnostics: Diagnostic[] = []; + const parsed = view.state.field(parsedChordsField); + + for (const chord of parsed.chords) { + if (chord.disabled) { + diagnostics.push({ + from: chord.range[0], + to: chord.range[1], + severity: "info", + markClass: "chord-ignored", + message: `Chord disabled`, + }); + } + if (chord.compounds) { + for (const compound of chord.compounds) { + if (compound.actions.length === 0 && compound.parent) { + const replacement = view.state.doc.sliceString( + compound.parent.range[0], + compound.parent.input!.range[1], + ); + diagnostics.push({ + from: compound.range[0], + to: compound.range[1], + severity: "warning", + message: `Compound literal can be replaced with "${replacement}"`, + actions: [ + { + name: "Replace", + apply(view, from, to) { + view.dispatch({ + changes: { + from, + to, + insert: replacement + "|", + }, + }); + }, + }, + ], + }); + } + } + const lastCompound = chord.compounds.at(-1); + if (lastCompound) { + const from = chord.range[0]; + const to = lastCompound.range[1]; + if (lastCompound.parent) { + diagnostics.push({ + from, + to, + severity: "info", + markClass: "chord-child", + message: `Child of ${view.state.doc.sliceString(lastCompound.parent.range[0], lastCompound.parent.range[1])}`, + actions: [ + { + name: "Select Parent", + apply(view) { + view.dispatch({ + selection: { + anchor: lastCompound.parent!.range[0], + }, + scrollIntoView: true, + }); + }, + }, + ], + }); + } else { + diagnostics.push({ + from, + to, + severity: "warning", + message: `Orphan compound`, + }); + } + } + } + if (chord.children) { + diagnostics.push({ + from: chord.range[0], + to: chord.range[1], + severity: "info", + markClass: "chord-parent", + message: `Parent of ${chord.children.length} compound(s)`, + actions: chord.children.map((child) => ({ + name: `Go to ${view.state.doc.sliceString(child.range[0], child.range[1])}`, + apply(view) { + view.dispatch({ + selection: { + anchor: child.range[0], + }, + scrollIntoView: true, + }); + }, + })), + }); + } + if (chord.phrase) { + if (!chord.phrase.originalValue) { + diagnostics.push({ + from: chord.range[0], + to: chord.range[1], + severity: "info", + markClass: "chord-new", + message: `New Chord`, + }); + } else if (chord.phrase.originalValue !== chord.phrase.value) { + diagnostics.push({ + from: chord.range[0], + to: chord.range[1], + severity: "info", + markClass: "chord-unchanged", + message: `Phrase changed`, + }); + } + + if (chord.aliases) { + diagnostics.push({ + from: chord.phrase.range[0], + to: chord.phrase.range[1], + severity: "warning", + markClass: "chord-alias", + message: `Alias of ${chord.aliases.length} chord(s)`, + actions: chord.aliases.map((alias) => ({ + name: `Go to ${view.state.doc.sliceString(alias.range[0], alias.input?.range[1] ?? alias.range[1])}`, + apply(view) { + view.dispatch({ + selection: { + anchor: alias.range[0], + }, + scrollIntoView: true, + }); + }, + })), + }); + } + } + } + return diagnostics; + }, finalConfig); +} diff --git a/src/lib/chord-editor/action-meta-plugin.ts b/src/lib/chord-editor/action-meta-plugin.ts new file mode 100644 index 000000000..446ec2c4c --- /dev/null +++ b/src/lib/chord-editor/action-meta-plugin.ts @@ -0,0 +1,10 @@ +import { KEYMAP_CODES, KEYMAP_IDS } from "$lib/serial/keymap-codes"; +import { derived } from "svelte/store"; +import { reactiveStateField } from "./store-state-field"; + +const actionMeta = derived([KEYMAP_IDS, KEYMAP_CODES], ([ids, codes]) => ({ + ids, + codes, +})); + +export const actionMetaPlugin = reactiveStateField(actionMeta); diff --git a/src/lib/chord-editor/action-plugin.ts b/src/lib/chord-editor/action-plugin.ts index cbf8a10a1..b2377fcbd 100644 --- a/src/lib/chord-editor/action-plugin.ts +++ b/src/lib/chord-editor/action-plugin.ts @@ -7,33 +7,34 @@ import { } from "@codemirror/view"; import { mount, unmount } from "svelte"; import Action from "$lib/components/Action.svelte"; -import { syntaxTree } from "@codemirror/language"; import type { Range } from "@codemirror/state"; +import { parsedChordsField } from "./parsed-chords-plugin"; +import { iterActions } from "./parse-meta"; export class ActionWidget extends WidgetType { component?: {}; - element?: HTMLElement; constructor(readonly id: string | number) { super(); this.id = id; } - override eq(other: ActionWidget) { + /*override eq(other: ActionWidget) { return this.id == other.id; - } + }*/ toDOM() { - if (!this.element) { - this.element = document.createElement("span"); - this.element.style.paddingInline = "2px"; - - this.component = mount(Action, { - target: this.element, - props: { action: this.id, display: "keys", inText: true }, - }); + if (this.component) { + unmount(this.component); } - return this.element; + const element = document.createElement("span"); + element.style.paddingInline = "2px"; + + this.component = mount(Action, { + target: element, + props: { action: this.id, display: "keys", inText: true }, + }); + return element; } override ignoreEvent() { @@ -50,29 +51,24 @@ export class ActionWidget extends WidgetType { function actionWidgets(view: EditorView) { const widgets: Range[] = []; for (const { from, to } of view.visibleRanges) { - syntaxTree(view.state).iterate({ - from, - to, - enter: (node) => { - if (node.name !== "ExplicitAction") return; - const value = - node.node.getChild("ActionId") ?? - node.node.getChild("HexNumber") ?? - node.node.getChild("DecimalNumber"); - if (!value) return; - if (!node.node.getChild("ExplicitDelimEnd")) { + for (const chord of view.state.field(parsedChordsField).chords) { + if (chord.range[1] < from || chord.range[0] > to) continue; + iterActions(chord, (action) => { + if ( + view.state.selection.ranges.some( + (r) => r.from <= action.range[1] && r.to > action.range[0], + ) + ) { return; } - - const id = view.state.doc.sliceString(value.from, value.to); - let deco = Decoration.replace({ - widget: new ActionWidget( - value.name === "ActionId" ? id : parseInt(id), - ), - }); - widgets.push(deco.range(node.from, node.to)); - }, - }); + if (action.info && action.explicit) { + const deco = Decoration.replace({ + widget: new ActionWidget(action.code), + }); + widgets.push(deco.range(action.range[0], action.range[1])); + } + }); + } } return Decoration.set(widgets); } @@ -89,7 +85,9 @@ export const actionPlugin = ViewPlugin.fromClass( if ( update.docChanged || update.viewportChanged || - syntaxTree(update.startState) != syntaxTree(update.state) + update.selectionSet || + update.startState.field(parsedChordsField) != + update.state.field(parsedChordsField) ) this.decorations = actionWidgets(update.view); } diff --git a/src/lib/chord-editor/action-serializer.ts b/src/lib/chord-editor/action-serializer.ts index d2781d637..7e4b746ac 100644 --- a/src/lib/chord-editor/action-serializer.ts +++ b/src/lib/chord-editor/action-serializer.ts @@ -1,16 +1,263 @@ -import { KEYMAP_CODES, type KeyInfo } from "$lib/serial/keymap-codes"; -import { get } from "svelte/store"; +import type { KeyInfo } from "$lib/serial/keymap-codes"; +import type { CharaChordFile } from "$lib/share/chara-file"; +import { + composeChordInput, + hasConcatenator, + hashChord, + willBeValidChordInput, +} from "$lib/serial/chord"; +import type { + ActionMeta, + ChordMeta, + MetaRange, + ParseResult, +} from "./parse-meta"; +import type { Tree } from "@lezer/common"; -export function canUseIdAsString(info: KeyInfo): boolean { - return !!info.id && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(info.id); +function parseChordMeta( + tree: Tree, + ids: Map, + codes: Map, + sliceString: (from: number, to: number) => string, +): ChordMeta[] { + console.time("parseChordTree"); + const result: ChordMeta[] = []; + + let current: ChordMeta = { range: [0, 0], valid: false }; + let actions: ActionMeta[] = []; + let actionRange: MetaRange | undefined = undefined; + + tree.cursor().iterate( + (node) => { + if (node.name === "Action") { + actionRange = [node.from, node.to]; + } else if (node.name === "ChordPhrase") { + current.phrase = { + range: [node.from, node.to], + value: [], + valid: true, + actions: [], + hasConcatenator: false, + }; + } else if (node.name === "Chord") { + current = { range: [node.from, node.to], valid: false }; + } else if (node.name === "ActionString") { + actions = []; + } else if (node.name === "HexNumber") { + const hexString = sliceString(node.from, node.to); + const code = Number.parseInt(hexString, 16); + const parentNode = node.node.parent; + if (parentNode?.type.name === "CompoundLiteral") { + current.compounds ??= []; + current.compounds.push({ + range: [parentNode.from, parentNode.to], + value: code, + actions: [], + valid: true, // TODO: validate compound literal + }); + } else { + const valid = !(Number.isNaN(code) || code < 0 || code > 1023); + actions.push({ + code, + info: codes.get(code), + explicit: true, + valid, + range: actionRange!, + }); + } + } else if ( + node.name === "ActionId" || + node.name === "SingleLetter" || + node.name === "EscapedLetter" + ) { + const id = sliceString(node.from, node.to); + const info = ids.get(id); + const value: ActionMeta = { + code: info?.code ?? Number.NaN, + info, + valid: info !== undefined, + range: actionRange!, + }; + if (node.name === "ActionId") { + value.explicit = true; + } + actions.push(value); + } + }, + (node) => { + if (node.name === "Chord") { + result.push(current); + if (current.phrase) { + current.phrase.actions = actions; + current.phrase.value = actions.map(({ code }) => code); + current.phrase.valid = actions.every(({ valid }) => valid); + current.phrase.hasConcatenator = hasConcatenator( + current.phrase.value, + codes, + ); + } + current.valid = + (current.phrase?.valid ?? false) && (current.input?.valid ?? false); + if (!current.valid) { + current.disabled = true; + } + } else if (node.name === "CompoundInput") { + const lastCompound = current.compounds?.at(-1); + current.compounds ??= []; + current.compounds.push({ + range: [node.from, node.to], + value: hashChord( + composeChordInput( + actions.map(({ code }) => code), + lastCompound?.value, + ), + ), + actions, + valid: + willBeValidChordInput(actions.length, lastCompound !== undefined) && + actions.every(({ valid }) => valid), + }); + } else if (node.name === "ChordInput") { + const lastCompound = current.compounds?.at(-1); + current.input = { + range: [node.from, node.to], + value: composeChordInput( + actions.map(({ code }) => code), + lastCompound?.value, + ), + valid: + willBeValidChordInput(actions.length, lastCompound !== undefined) && + actions.every(({ valid }) => valid), + actions, + }; + } + }, + ); + + console.timeEnd("parseChordTree"); + return result; +} + +function resolveChordOverrides(chords: ChordMeta[]): Map { + console.time("resolveOverrides"); + const seen = new Map(); + for (const info of chords) { + if (!info.input || info.disabled) continue; + const key = JSON.stringify(info.input.value); + const override = seen.get(key); + if (override) { + override.overrides ??= []; + override.overrides.push(info); + info.overriddenBy = override; + info.disabled = true; + } else { + seen.set(key, info); + } + } + console.timeEnd("resolveOverrides"); + return seen; +} + +function resolveChordAliases(chords: ChordMeta[]): Map { + console.time("resolveAliases"); + const aliases = new Map(); + for (const info of chords) { + if (!info.phrase) continue; + const key = JSON.stringify(info.phrase.value); + const list = aliases.get(key) ?? []; + list.push(info); + aliases.set(key, list); + } + for (const [key, value] of aliases) { + if (value.length <= 1) { + aliases.delete(key); + } else { + for (const info of value) { + info.aliases = value.filter((i) => i !== info); + } + } + } + console.timeEnd("resolveAliases"); + return aliases; } -export function actionToValue(action: number | KeyInfo) { - const info = - typeof action === "number" ? get(KEYMAP_CODES).get(action) : action; - if (info && info.id?.length === 1) - return /^[<>\\\s]$/.test(info.id) ? `\\${info.id}` : info.id; - if (!info || !canUseIdAsString(info)) - return `<0x${(info?.code ?? action).toString(16).padStart(2, "0")}>`; - return `<${info.id}>`; +function resolveCompoundParents(chords: ChordMeta[]): Map { + console.time("resolveCompoundParents"); + const compounds = new Map(); + for (const chord of chords) { + if (chord.input && !chord.disabled) { + compounds.set(hashChord(chord.input.value), chord); + } + } + for (const chord of chords) { + if (chord.compounds) { + for (const compound of chord.compounds) { + const parent = compounds.get(compound.value); + if (parent) { + compound.parent = parent; + } + } + const lastCompound = chord.compounds?.at(-1); + if (lastCompound && lastCompound.parent) { + lastCompound.parent.children ??= []; + lastCompound.parent.children.push(chord); + } + } + } + console.timeEnd("resolveCompoundParents"); + return compounds; +} + +export function resolveChanges( + chords: ChordMeta[], + inputs: Map, + deviceChords: CharaChordFile["chords"], +): [CharaChordFile["chords"], Map] { + console.time("resolveChanges"); + const removed: CharaChordFile["chords"] = []; + const exact = new Map(); + for (const chord of chords) { + if (chord.input && chord.phrase && !chord.disabled) { + exact.set( + JSON.stringify([chord.input.value, chord.phrase?.value ?? []]), + chord, + ); + } + } + for (const deviceChord of deviceChords) { + const exactMatch = exact.get(JSON.stringify(deviceChord)); + if (exactMatch) { + exactMatch.phrase!.originalValue = exactMatch.phrase!.value; + continue; + } + const byInput = inputs.get(JSON.stringify(deviceChord[0])); + if (byInput) { + byInput.phrase!.originalValue = deviceChord[1]; + continue; + } + removed.push(deviceChord); + } + + console.timeEnd("resolveChanges"); + return [removed, exact]; +} + +export function parseCharaChords( + tree: Tree, + ids: Map, + codes: Map, + deviceChords: CharaChordFile["chords"], + sliceString: (from: number, to: number) => string, +): ParseResult { + console.time("parseTotal"); + + const chords = parseChordMeta(tree, ids, codes, sliceString); + const inputs = resolveChordOverrides(chords); + const aliases = resolveChordAliases(chords); + const compounds = resolveCompoundParents(chords); + const [removed, exact] = resolveChanges(chords, inputs, deviceChords); + + console.timeEnd("parseTotal"); + + return { chords, removed, aliases, compounds, inputs, exact }; } diff --git a/src/lib/chord-editor/autocomplete.ts b/src/lib/chord-editor/autocomplete.ts index abb796e53..42697d6d6 100644 --- a/src/lib/chord-editor/autocomplete.ts +++ b/src/lib/chord-editor/autocomplete.ts @@ -1,72 +1,39 @@ -import { KEYMAP_CATEGORIES, KEYMAP_CODES } from "$lib/serial/keymap-codes"; -import type { - Completion, - CompletionSection, - CompletionSource, -} from "@codemirror/autocomplete"; -import { derived, get } from "svelte/store"; -import { actionToValue, canUseIdAsString } from "./action-serializer"; +import { + EditorView, + ViewPlugin, + ViewUpdate, + type PluginValue, +} from "@codemirror/view"; +import { syntaxTree } from "@codemirror/language"; +import type { EditorState } from "@codemirror/state"; -const completionSections = derived( - KEYMAP_CATEGORIES, - (categories) => - new Map( - categories.map( - (category) => - [ - category, - { - name: category.name, - } satisfies CompletionSection, - ] as const, - ), - ), -); +export function actionAutocompletePlugin( + query: (query: string | undefined) => void, +) { + return ViewPlugin.fromClass( + class implements PluginValue { + constructor(readonly view: EditorView) {} -export const actionAutocompleteItems = derived( - [KEYMAP_CODES, completionSections], - ([codes, sections]) => - codes - .values() - .map((info) => { - const canUseId = canUseIdAsString(info); - const completionValue = - (canUseId && info.id) || - `0x${info.code.toString(16).padStart(2, "0")}`; - return { - label: - [ - canUseId || !info.id ? undefined : `"${info.id}"`, - info.title, - info.variant?.replace(/^[a-z]/g, (c) => c.toUpperCase()), - ] - .filter(Boolean) - .join(" ") || completionValue, - detail: actionToValue(info), - section: info.category ? sections.get(info.category) : undefined, - info: info.description, - type: "keyword", - apply: completionValue + ">", - } satisfies Completion; - }) - .filter( - (item) => typeof item.label === "string" && item.apply !== undefined, - ) - .toArray(), -); + update(update: ViewUpdate) { + query(this.resolveAutocomplete(update.state)); + } -export const actionAutocomplete = ((context) => { - let word = context.tokenBefore([ - "ExplicitDelimStart", - "ActionId", - "HexNumber", - "DecimalNumber", - ]); - if (!word) return null; - console.log(get(actionAutocompleteItems)); - return { - from: word.type.name === "ExplicitDelimStart" ? word.to : word.from, - validFor: /^ { + if ( + update.startState.field(parsedChordsField) !== + update.state.field(parsedChordsField) + ) { + console.log("update changes panel"); + parsed = update.state.field(parsedChordsField); + } + }, + destroy() { + unmount(component); + }, + }; +} + +export function changesPanel() { + return showPanel.of(changesPanelFunc); +} diff --git a/src/lib/chord-editor/changes-plugin.ts b/src/lib/chord-editor/changes-plugin.ts deleted file mode 100644 index 7fd73597c..000000000 --- a/src/lib/chord-editor/changes-plugin.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { - EditorView, - ViewPlugin, - ViewUpdate, - type PluginValue, -} from "@codemirror/view"; - -export const changesPlugin = ViewPlugin.fromClass( - class implements PluginValue { - constructor(readonly view: EditorView) {} - - update(update: ViewUpdate) {} - }, - { - eventHandlers: {}, - }, -); diff --git a/src/lib/chord-editor/chord-delim-plugin.ts b/src/lib/chord-editor/chord-delim-plugin.ts index 089d35b44..2a8444872 100644 --- a/src/lib/chord-editor/chord-delim-plugin.ts +++ b/src/lib/chord-editor/chord-delim-plugin.ts @@ -26,7 +26,7 @@ export class DelimWidget extends WidgetType { toDOM() { if (!this.element) { - this.element = document.createElement("span"); + /*this.element = document.createElement("span"); this.element.innerHTML = " ⇛" + (this.hasConcatenator ? "" : " "); this.element.style.scale = "1.8"; @@ -41,7 +41,9 @@ export class DelimWidget extends WidgetType { props: { action: 574, display: "keys", inText: true, ghost: true }, }); this.element.appendChild(button); - } + }*/ + this.element = document.createElement("div"); + this.element.style.breakAfter = "column"; } return this.element; } diff --git a/src/lib/chord-editor/chord-sync-plugin.ts b/src/lib/chord-editor/chord-sync-plugin.ts new file mode 100644 index 000000000..7406d79ef --- /dev/null +++ b/src/lib/chord-editor/chord-sync-plugin.ts @@ -0,0 +1,44 @@ +import type { CharaChordFile } from "$lib/share/chara-file"; +import { StateEffect, StateField } from "@codemirror/state"; +import { actionMetaPlugin } from "./action-meta-plugin"; +import { syncCharaChords } from "./chord-sync"; +import type { EditorView } from "@codemirror/view"; + +const chordSyncEffect = StateEffect.define(); + +export function editorSyncChords( + view: EditorView, + newDeviceChords: CharaChordFile["chords"], +) { + const { ids, codes } = view.state.field(actionMetaPlugin.field); + const oldDeviceChords = view.state.field(deviceChordField); + const changes = syncCharaChords( + oldDeviceChords, + newDeviceChords, + ids, + codes, + view.state.doc.toString(), + ); + view.dispatch({ + effects: chordSyncEffect.of(newDeviceChords), + changes, + }); +} + +export const deviceChordField = StateField.define({ + create() { + return []; + }, + update(value, transaction) { + return ( + transaction.effects.findLast((it) => it.is(chordSyncEffect))?.value ?? + value + ); + }, + toJSON(value) { + return value; + }, + fromJSON(value) { + return value; + }, +}); diff --git a/src/lib/chord-editor/chord-sync.spec.ts b/src/lib/chord-editor/chord-sync.spec.ts new file mode 100644 index 000000000..f739655d8 --- /dev/null +++ b/src/lib/chord-editor/chord-sync.spec.ts @@ -0,0 +1,135 @@ +import type { KeyInfo } from "$lib/serial/keymap-codes"; +import type { CharaChordFile } from "$lib/share/chara-file"; +import { describe, it, expect } from "vitest"; +import { parseCharaChords } from "./action-serializer"; +import { parser } from "./chords.grammar"; +import { syncCharaChords } from "./chord-sync"; +import { Text } from "@codemirror/state"; + +const asciiInfo: KeyInfo[] = Array.from( + { length: 0x7f - 0x20 }, + (_, i) => + ({ + code: i + 0x20, + id: String.fromCharCode(i + 0x20), + }) satisfies KeyInfo, +); +const asciiCodes = new Map( + asciiInfo.map((info) => [info.code, info]), +); +const asciiIds = new Map( + asciiInfo.map((info) => [info.id!, info]), +); + +function chords(...strings: string[]): string { + return strings.join("\n"); +} + +function backup(doc: string): CharaChordFile["chords"] { + const tree = parser.parse(doc); + const result = parseCharaChords(tree, asciiIds, asciiCodes, [], (from, to) => + doc.slice(from, to), + ); + return result.chords + .filter((chord) => !chord.disabled) + .map((chord) => [chord.input?.value ?? [], chord.phrase?.value ?? []]); +} + +function expectSync(options: { + org: string[]; + mod: string[]; + cur: string[]; + exp: string[]; +}) { + expect( + syncCharaChords( + backup(chords(...options.org)), + backup(chords(...options.mod)), + asciiIds, + asciiCodes, + chords(...options.cur), + ) + .apply(Text.of(options.cur)) + .toString() + .replace(/\n$/, ""), + ).toEqual(chords(...options.exp)); +} + +describe("chord sync", function () { + it("should not do anything when no changes happened", function () { + expectSync({ + org: ["abc=>def", "def=>ghi", "jkl=>mno"], + mod: ["abc=>def", "def=>ghi", "jkl=>mno"], + cur: ["abc=>def", "def=>ghi", "jkl=>mno"], + exp: ["abc=>def", "def=>ghi", "jkl=>mno"], + }); + }); + + it("should not touch the doc if device chords are unchanged", function () { + expectSync({ + org: ["abc=>def", "def=>ghi", "jkl=>mno"], + mod: ["abc=>def", "def=>ghi", "jkl=>mno"], + cur: ["ab=>def", "def=>gh"], + exp: ["ab=>def", "def=>gh"], + }); + }); + + it("should apply removals to unchanged chords only", function () { + expectSync({ + org: ["abc=>def", "def=>ghi", "jkl=>mno", "mno=>pqr"], + mod: ["abc=>def"], + cur: ["abc=>def", "def=>ghij", "jkl=>mno", "mno=>pqr"], + exp: ["abc=>def", "def=>ghij"], + }); + }); + + it("should keep user modifications over device modifications", function () { + expectSync({ + org: ["abc=>def", "def=>ghi", "jkl=>mno", "mno=>pqr"], + mod: ["abc=>def", "def=>ghijk", "jkl=>mnop", "mno=>pqr"], + cur: ["abc=>def", "def=>ghij", "jkl=>mno", "mno=>pqr"], + exp: ["abc=>def", "def=>ghij", "jkl=>mnop", "mno=>pqr"], + }); + }); + + it("should handle complex changes", function () { + expectSync({ + org: [ + "unchanged=>unchanged", + "usermod=>usermod", + "devmod=>devmod", + "userremoval=>userremoval", + "devremoval=>devremoval", + "devremusermod=>devremusermod", + ], + mod: [ + "unchanged=>unchanged", + "devadd=>devadd", + "usermod=>usermod", + "userremoval=>userremoval", + "devmod=>devmod1", + "sameadd=>sameadd", + ], + cur: [ + "useradd1=>useradd1", + "unchanged=>unchanged", + "usermod=>use", + "devremusermod=>xyz", + "devmod=>devmod", + "sameadd=>sameadd", + "devremoval=>devremoval", + "useradd=>useradd", + ], + exp: [ + "devadd=>devadd", + "useradd1=>useradd1", + "unchanged=>unchanged", + "usermod=>use", + "devremusermod=>xyz", + "devmod=>devmod1", + "sameadd=>sameadd", + "useradd=>useradd", + ], + }); + }); +}); diff --git a/src/lib/chord-editor/chord-sync.ts b/src/lib/chord-editor/chord-sync.ts new file mode 100644 index 000000000..8446dfa8a --- /dev/null +++ b/src/lib/chord-editor/chord-sync.ts @@ -0,0 +1,130 @@ +import type { KeyInfo } from "$lib/serial/keymap-codes"; +import { ChangeSet, type ChangeSpec } from "@codemirror/state"; +import { parseCharaChords } from "./action-serializer"; +import { parser } from "./chords.grammar"; +import type { CharaChordFile } from "$lib/share/chara-file"; +import { splitCompound } from "$lib/serial/chord"; + +function canUseIdAsString(info: KeyInfo): boolean { + return !!info.id && /^[^>\n]+$/.test(info.id); +} + +export function actionToValue(code: number, info?: KeyInfo) { + if (info && info.id?.length === 1) + return /^[<>|\\\s]$/.test(info.id) ? `\\${info.id}` : info.id; + if (!info || !canUseIdAsString(info)) + return `<0x${code.toString(16).padStart(2, "0")}>`; + return `<${info.id}>`; +} + +function canonicalInputSorting(input: number[], phrase: number[]): number[] { + const tail = [...input]; + const prefix = phrase.filter((code) => { + const index = tail.indexOf(code); + if (index !== -1) { + tail.splice(index, 1); + return true; + } + return false; + }); + return [...prefix, ...tail]; +} + +export interface ChangeType { + from: number; + to: number; + insert: string; +} + +export function syncCharaChords( + originalDeviceChords: CharaChordFile["chords"], + newDeviceChords: CharaChordFile["chords"], + ids: Map, + codes: Map, + doc: string, +): ChangeSet { + const tree = parser.parse(doc); + const result = parseCharaChords( + tree, + ids, + codes, + originalDeviceChords, + (from, to) => doc.slice(from, to), + ); + + const exactChords = new Map(); + for (const chord of originalDeviceChords) { + const key = JSON.stringify(chord); + const count = exactChords.get(key) ?? 0; + exactChords.set(key, count + 1); + } + + const changes: ChangeType[] = []; + + const inputModified = new Set(); + for (const chord of newDeviceChords) { + const key = JSON.stringify(chord); + const count = exactChords.get(key) ?? 0; + if (count > 0) { + exactChords.set(key, count - 1); + continue; + } + + const inputKey = JSON.stringify(chord[0]); + inputModified.add(inputKey); + const byInput = result.inputs.get(inputKey); + if (byInput) { + if ( + byInput.phrase?.originalValue && + byInput.phrase.originalValue === byInput.phrase.value + ) { + changes.push({ + from: byInput.phrase.range[0], + to: byInput.phrase.range[1], + insert: chord[1] + .map((code) => actionToValue(code, codes.get(code))) + .join(""), + }); + } + } else { + const [inputs, compound] = splitCompound(chord[0]); + const sortedInput = canonicalInputSorting(inputs, chord[1]); + changes.push({ + from: 0, + to: 0, + insert: + (compound ? `|0x${compound.toString(16)}|` : "") + + sortedInput + .map((code) => actionToValue(code, codes.get(code))) + .join("") + + "=>" + + chord[1] + .map((code) => actionToValue(code, codes.get(code))) + .join("") + + "\n", + }); + } + } + + changes.push( + ...exactChords + .entries() + .filter(([, count]) => count > 0) + .map(([key]) => result.exact.get(key)) + .filter((chord) => chord !== undefined) + .filter( + (chord) => + chord.input && !inputModified.has(JSON.stringify(chord.input.value)), + ) + .map( + (chord) => + ({ + from: chord.range[0], + to: chord.range[1], + insert: "", + }) satisfies ChangeSpec, + ), + ); + + return ChangeSet.of(changes, doc.length); +} diff --git a/src/lib/chord-editor/chords-grammar-plugin.ts b/src/lib/chord-editor/chords-grammar-plugin.ts index 6d524be59..071be7f13 100644 --- a/src/lib/chord-editor/chords-grammar-plugin.ts +++ b/src/lib/chord-editor/chords-grammar-plugin.ts @@ -5,7 +5,6 @@ import { HighlightStyle, } from "@codemirror/language"; import { styleTags, tags } from "@lezer/highlight"; -import { actionAutocomplete } from "./autocomplete"; export const chordHighlightStyle = HighlightStyle.define([ { @@ -51,7 +50,5 @@ export const chordLanguage = LRLanguage.define({ }); export function chordLanguageSupport() { - return new LanguageSupport(chordLanguage, [ - chordLanguage.data.of({ autocomplete: actionAutocomplete }), - ]); + return new LanguageSupport(chordLanguage, [chordLanguage.data.of({})]); } diff --git a/src/lib/chord-editor/chords.grammar b/src/lib/chord-editor/chords.grammar index c63d99525..c0283079d 100644 --- a/src/lib/chord-editor/chords.grammar +++ b/src/lib/chord-editor/chords.grammar @@ -1,27 +1,43 @@ @top Program { Chord* } -ExplicitAction { ExplicitDelimStart (HexNumber | DecimalNumber | ActionId) ExplicitDelimEnd } +ExplicitAction { ExplicitDelimStart (HexNumber | ActionId) ExplicitDelimEnd } EscapedSingleAction { Escape EscapedLetter } Action { SingleLetter | ExplicitAction | EscapedSingleAction } -ActionString { Action* } -ChordInput { (ActionString CompoundDelim)* ActionString } + +ActionString { Action+ } + +CompoundLiteral { CompoundDelim HexNumber CompoundDelim } +CompoundInput { ActionString CompoundDelim } + +ChordInput { CompoundLiteral? CompoundInput* ActionString } ChordPhrase { ActionString } + Chord { ChordInput PhraseDelim ChordPhrase ChordDelim } +@skip { + Space +} + @tokens { - @precedence {HexNumber, DecimalNumber} - @precedence {CompoundDelim, PhraseDelim, ExplicitDelimStart, ChordDelim, SingleLetter} - @precedence {EscapedLetter} + @precedence { HexNumber, ActionId } + @precedence { Space, Escape } + @precedence { Space, SingleLetter } + @precedence { Escape, SingleLetter } + @precedence { CompoundDelim, SingleLetter } + @precedence { ActionId, Space } + @precedence { EscapedLetter, Space } + + Space {" "} ExplicitDelimStart {"<"} ExplicitDelimEnd {">"} - CompoundDelim {"+>"} + CompoundDelim {"|"} PhraseDelim {"=>"} Escape { "\\" } HexNumber { "0x" $[a-fA-F0-9]+ } - DecimalNumber { $[0-9]+ } - ActionId { $[a-zA-Z_]$[a-zA-Z0-9_]* } - SingleLetter { ![\\] } - EscapedLetter { ![] } - ChordDelim { ($[\n] | @eof) } + ActionId { ![\n>]+ } + SingleLetter { ![\n<] } + EscapedLetter { ![\n] } + ChordDelim { ("\n" | @eof) } } +@detectDelim diff --git a/src/lib/chord-editor/parse-meta.ts b/src/lib/chord-editor/parse-meta.ts new file mode 100644 index 000000000..3f503113f --- /dev/null +++ b/src/lib/chord-editor/parse-meta.ts @@ -0,0 +1,176 @@ +import type { KeyInfo } from "$lib/serial/keymap-codes"; +import type { CharaChordFile } from "$lib/share/chara-file"; +import type { ChangeDesc } from "@codemirror/state"; + +export type MetaRange = [from: number, to: number]; + +function mapMetaRange(range: MetaRange, change: ChangeDesc): MetaRange { + const newFrom = change.mapPos(range[0]); + const newTo = change.mapPos(range[1]); + if (newFrom === range[0] && newTo === range[1]) { + return range; + } + return [newFrom, newTo]; +} + +export interface ActionMeta { + code: number; + info?: KeyInfo; + explicit?: boolean; + range: MetaRange; + valid: boolean; +} + +function mapActionMeta(action: ActionMeta, change: ChangeDesc): ActionMeta { + const newRange = mapMetaRange(action.range, change); + if (newRange === action.range) { + return action; + } + return { + ...action, + range: newRange, + }; +} + +function mapArray( + array: T[], + change: ChangeDesc, + mapFn: (action: T, change: ChangeDesc) => T, +): T[] { + let changed = false; + const newArray = array.map((value) => { + const newValue = mapFn(value, change); + if (newValue !== value) { + changed = true; + return newValue; + } + return value; + }); + if (changed) { + return newArray; + } + return array; +} + +export interface ActionStringMeta { + range: MetaRange; + value: T; + valid: boolean; + actions: ActionMeta[]; +} + +function mapActionStringMeta>( + actionString: T, + change: ChangeDesc, +) { + const newRange = mapMetaRange(actionString.range, change); + const newActions = mapArray(actionString.actions, change, mapActionMeta); + if (newRange === actionString.range && newActions === actionString.actions) { + return actionString; + } + return { + ...actionString, + range: newRange, + actions: newActions, + }; +} + +export interface PhraseMeta extends ActionStringMeta { + hasConcatenator: boolean; + originalValue?: number[]; +} + +export interface CompoundMeta extends ActionStringMeta { + parent?: ChordMeta; +} + +export interface InputMeta extends ActionStringMeta {} + +export interface ChordMeta { + range: MetaRange; + valid: boolean; + disabled?: boolean; + compounds?: CompoundMeta[]; + input?: InputMeta; + phrase?: PhraseMeta; + children?: ChordMeta[]; + overrides?: ChordMeta[]; + aliases?: ChordMeta[]; + overriddenBy?: ChordMeta; +} + +export function mapChordMeta(chord: ChordMeta, change: ChangeDesc): ChordMeta { + const newRange = mapMetaRange(chord.range, change); + const newCompounds = chord.compounds + ? mapArray(chord.compounds, change, mapActionStringMeta) + : undefined; + const newInput = chord.input + ? mapActionStringMeta(chord.input, change) + : undefined; + const newPhrase = chord.phrase + ? mapActionStringMeta(chord.phrase, change) + : undefined; + if ( + newRange === chord.range && + newCompounds === chord.compounds && + newInput === chord.input && + newPhrase === chord.phrase + ) { + return chord; + } + + const newChord: ChordMeta = { + ...chord, + range: newRange, + }; + if (newCompounds) newChord.compounds = newCompounds; + if (newInput) newChord.input = newInput; + if (newPhrase) newChord.phrase = newPhrase; + return newChord; +} + +export interface ParseResult { + chords: ChordMeta[]; + removed: CharaChordFile["chords"]; + aliases: Map; + compounds: Map; + inputs: Map; + exact: Map; +} + +export function mapParseResult( + result: ParseResult, + change: ChangeDesc, +): ParseResult { + const newChords = mapArray(result.chords, change, mapChordMeta); + if (newChords === result.chords) { + return result; + } + return { + ...result, + chords: newChords, + }; +} + +export function iterActions( + chord: ChordMeta, + callback: (action: ActionMeta) => void, +) { + if (chord.input) { + for (const action of chord.input.actions) { + callback(action); + } + } + if (chord.compounds) { + for (const compound of chord.compounds) { + for (const action of compound.actions) { + callback(action); + } + } + } + if (chord.phrase) { + for (const action of chord.phrase.actions) { + callback(action); + } + } +} diff --git a/src/lib/chord-editor/parsed-chords-plugin.ts b/src/lib/chord-editor/parsed-chords-plugin.ts new file mode 100644 index 000000000..9e9004439 --- /dev/null +++ b/src/lib/chord-editor/parsed-chords-plugin.ts @@ -0,0 +1,40 @@ +import { StateField } from "@codemirror/state"; +import { parseCharaChords } from "./action-serializer"; +import { actionMetaPlugin } from "./action-meta-plugin"; +import { syntaxTree } from "@codemirror/language"; +import { deviceChordField } from "./chord-sync-plugin"; +import { mapParseResult, type ParseResult } from "./parse-meta"; + +export const parsedChordsField = StateField.define({ + create() { + return { + chords: [], + removed: [], + aliases: new Map(), + compounds: new Map(), + inputs: new Map(), + exact: new Map(), + }; + }, + update(value, transaction) { + const tree = syntaxTree(transaction.state); + const ids = transaction.state.field(actionMetaPlugin.field).ids; + const codes = transaction.state.field(actionMetaPlugin.field).codes; + const deviceChords = transaction.state.field(deviceChordField); + if ( + tree !== syntaxTree(transaction.startState) || + ids !== transaction.startState.field(actionMetaPlugin.field).ids || + codes !== transaction.startState.field(actionMetaPlugin.field).codes || + deviceChords !== transaction.startState.field(deviceChordField) + ) { + return parseCharaChords( + syntaxTree(transaction.state), + ids, + codes, + deviceChords, + (from, to) => transaction.state.doc.sliceString(from, to), + ); + } + return mapParseResult(value, transaction.changes); + }, +}); diff --git a/src/lib/chord-editor/persistent-state-plugin.ts b/src/lib/chord-editor/persistent-state-plugin.ts new file mode 100644 index 000000000..1af0660ab --- /dev/null +++ b/src/lib/chord-editor/persistent-state-plugin.ts @@ -0,0 +1,185 @@ +import { + EditorView, + highlightActiveLine, + keymap, + lineNumbers, + ViewPlugin, + ViewUpdate, +} from "@codemirror/view"; +import { + history, + historyField, + historyKeymap, + standardKeymap, +} from "@codemirror/commands"; +import { debounceTime, mergeMap, Subject } from "rxjs"; +import { EditorState, type EditorStateConfig } from "@codemirror/state"; +import { lintGutter } from "@codemirror/lint"; +import { + chordHighlightStyle, + chordLanguageSupport, +} from "./chords-grammar-plugin"; +import { actionLinter } from "./action-linter"; +import { actionAutocompletePlugin } from "./autocomplete"; +import { delimPlugin } from "./chord-delim-plugin"; +import { actionPlugin } from "./action-plugin"; +import { syntaxHighlighting } from "@codemirror/language"; +import { deviceChordField } from "./chord-sync-plugin"; +import { actionMetaPlugin } from "./action-meta-plugin"; +import { parsedChordsField } from "./parsed-chords-plugin"; +import { changesPanel } from "./changes-panel.svelte"; +import { searchKeymap } from "@codemirror/search"; + +const serializedFields = { + history: historyField, + deviceChords: deviceChordField, +}; + +export interface EditorConfig { + rawCode?: boolean; + storeName: string; + autocomplete(query: string | undefined): void; +} + +export function createConfig(params: EditorConfig) { + return { + extensions: [ + actionMetaPlugin.plugin, + deviceChordField, + parsedChordsField, + changesPanel(), + lintGutter(), + params.rawCode ? [lineNumbers()] : [delimPlugin, actionPlugin], + chordLanguageSupport(), + actionLinter({ + delay: 100, + markerFilter(diagnostics) { + return diagnostics.filter((it) => it.from !== it.to); + }, + }), + actionAutocompletePlugin(params.autocomplete), + persistentStatePlugin(params.storeName), + history(), + syntaxHighlighting(chordHighlightStyle), + highlightActiveLine(), + EditorView.theme({ + ".cm-line": { + borderBottom: "1px solid transparent", + caretColor: "var(--md-sys-color-on-surface)", + }, + ".cm-scroller": { + overflow: "auto", + width: "100%", + fontFamily: "inherit !important", + gap: "8px", + }, + ".cm-content": { + flex: 1, + }, + ".cm-cursor": { + borderColor: "var(--md-sys-color-on-surface)", + }, + }), + keymap.of([...standardKeymap, ...historyKeymap, ...searchKeymap]), + ], + } satisfies EditorStateConfig; +} + +export async function loadPersistentState( + params: EditorConfig, +): Promise { + const stored = await getState(params.storeName); + const config = createConfig(params); + + if (stored) { + try { + return EditorState.fromJSON(stored, config, serializedFields); + } catch (e) { + console.error("Failed to parse persistent state:", e); + } + } + return EditorState.create(config); +} + +export function persistentStatePlugin(storeName: string) { + return ViewPlugin.fromClass( + class { + updateSubject = new Subject(); + subscription = this.updateSubject + .pipe( + debounceTime(500), + mergeMap(() => + storeState(storeName, this.view.state.toJSON(serializedFields)), + ), + ) + .subscribe(() => {}); + + constructor(readonly view: EditorView) {} + + update(update: ViewUpdate) { + if (update.state !== update.startState) { + this.updateSubject.next(); + } + } + + destroy() { + this.subscription.unsubscribe(); + } + }, + ); +} + +const dbName = "chord-state"; +const dbVersion = 1; +const storeName = "state"; + +async function openDb(): Promise { + const dbRequest = indexedDB.open(dbName, dbVersion); + return new Promise((resolve, reject) => { + dbRequest.onsuccess = () => resolve(dbRequest.result); + dbRequest.onerror = () => reject(dbRequest.error); + dbRequest.onupgradeneeded = () => { + const db = dbRequest.result; + if (!db.objectStoreNames.contains(storeName)) { + db.createObjectStore(storeName); + } + }; + }); +} + +async function getState(name: string): Promise { + const db = await openDb(); + try { + const readTransaction = db.transaction([storeName], "readonly"); + const store = readTransaction.objectStore(storeName); + const itemRequest = store.get(name); + const result = await new Promise((resolve) => { + itemRequest.onsuccess = () => resolve(itemRequest.result); + itemRequest.onerror = () => resolve(undefined); + }); + return result; + } catch (e) { + console.error(e); + return undefined; + } finally { + db.close(); + } +} + +async function storeState(name: string, state: T): Promise { + const db = await openDb(); + try { + const putTransaction = db.transaction([storeName], "readwrite"); + const putStore = putTransaction.objectStore(storeName); + const putRequest = putStore.put(state, name); + await new Promise((resolve, reject) => { + putRequest.onsuccess = () => resolve(); + putRequest.onerror = () => reject(putRequest.error); + }); + putTransaction.commit(); + } catch (e) { + console.error(e); + } finally { + db.close(); + } +} diff --git a/src/lib/chord-editor/save-chords.ts b/src/lib/chord-editor/save-chords.ts new file mode 100644 index 000000000..efc82a564 --- /dev/null +++ b/src/lib/chord-editor/save-chords.ts @@ -0,0 +1,58 @@ +import type { EditorView } from "@codemirror/view"; +import { parser } from "./chords.grammar"; +import { parseCharaChords } from "./action-serializer"; +import { actionMetaPlugin } from "./action-meta-plugin"; +import { deviceChordField } from "./chord-sync-plugin"; +import type { CharaChordFile } from "$lib/share/chara-file"; + +export interface SaveChordsTask { + remove: number[][]; + set: [number[], number[]][]; +} + +export function createSaveTask(view: EditorView): SaveChordsTask { + const tree = parser.parse(view.state.doc.toString()); + const { ids, codes } = view.state.field(actionMetaPlugin.field); + const deviceChords = view.state.field(deviceChordField); + const result = parseCharaChords(tree, ids, codes, deviceChords, (from, to) => + view.state.doc.sliceString(from, to), + ); + + return { + remove: result.removed.map((chord) => chord[0]), + set: result.chords + .filter( + (chord) => + !chord.disabled && + (!chord.phrase || + chord.phrase?.originalValue !== chord.phrase?.value), + ) + .map((chord) => [chord.input?.value ?? [], chord.phrase?.value ?? []]), + }; +} + +export function applySaveTask( + backup: CharaChordFile["chords"], + task: SaveChordsTask, +): CharaChordFile["chords"] { + const newBackup = [...backup]; + for (const input of task.remove) { + const index = newBackup.findIndex((chord) => { + return JSON.stringify(chord[0]) === JSON.stringify(input); + }); + if (index !== -1) { + newBackup.splice(index, 1); + } + } + for (const [input, phrase] of task.set) { + const index = newBackup.findIndex((chord) => { + return JSON.stringify(chord[0]) === JSON.stringify(input); + }); + if (index !== -1) { + newBackup[index] = [input, phrase]; + } else { + newBackup.push([input, phrase]); + } + } + return newBackup; +} diff --git a/src/lib/chord-editor/store-state-field.ts b/src/lib/chord-editor/store-state-field.ts new file mode 100644 index 000000000..42ff80983 --- /dev/null +++ b/src/lib/chord-editor/store-state-field.ts @@ -0,0 +1,35 @@ +import { StateEffect, StateField } from "@codemirror/state"; +import { EditorView, ViewPlugin } from "@codemirror/view"; +import { get, type Readable } from "svelte/store"; + +export function reactiveStateField(store: Readable) { + const effect = StateEffect.define(); + const field = StateField.define({ + create() { + return get(store); + }, + update(value, transaction) { + return ( + transaction.effects.findLast((it) => it.is(effect))?.value ?? value + ); + }, + }); + const plugin = ViewPlugin.fromClass( + class { + unsubscribe: () => void; + + constructor(readonly view: EditorView) { + this.unsubscribe = store.subscribe((value) => { + setTimeout(() => { + view.dispatch({ effects: effect.of(value) }); + }); + }); + } + + destroy() { + this.unsubscribe(); + } + }, + ); + return { field, plugin: [field, plugin] }; +} diff --git a/src/lib/chord-editor/test.txt b/src/lib/chord-editor/test.txt index dd377111e..3bafe340a 100644 --- a/src/lib/chord-editor/test.txt +++ b/src/lib/chord-editor/test.txt @@ -1,16 +1,16 @@ -.= => => -;ims => <0x219> --; => <0x23e>_<0x23e> -.;g => <0x23e>...<0x23e> -'dg => <0x23e>'<0x23e> -'gl => <0x23e>'ll<0x23e> -'ar => <0x23e>'re<0x23e> -'gs => <0x23e>'s<0x23e> -'ev => <0x23e>'ve<0x23e> --; => <0x23e><0x223>-<0x223> -; => <0x23e><0x223><0x23d><0x223> -;g => <0x23e><0x223><0x223> -deg => <0x23e>ed<0x23e> -;gr => <0x23e>er<0x23e> -;es => <0x23e>es<0x23e> -;est => <0x23e>est<0x23e> +a|.==>t=t +;ims=<0x219> +-;=><0x23e>_<0x23e> +.;g=><0x23e>...<0x23e> +'dg=><0x23e>'<0x23e> +'gl=><0x23e>'ll<0x23e> +'ar=><0x23e>'re<0x23e> +'gs=><0x23e>'s<0x23e> +'ev=><0x23e>'ve<0x23e> +-;=><0x23e><0x223>-<0x223> +;=><0x23e><0x223><0x23d><0x223> +;g=><0x23e><0x223><0x223> +deg=><0x23e>ed<0x23e> +;gr=><0x23e>er<0x23e> +;es=><0x23e>es<0x23e> +;est=><0x23e>est<0x23e> diff --git a/src/lib/components/Action.svelte b/src/lib/components/Action.svelte index 3fa99fe39..82abc83dc 100644 --- a/src/lib/components/Action.svelte +++ b/src/lib/components/Action.svelte @@ -8,10 +8,12 @@ let { action, display, + ignoreIcon = false, inText = false, }: { action: string | number | KeyInfo; display: "inline-keys" | "keys" | "verbose"; + ignoreIcon?: boolean; inText?: boolean; } = $props(); @@ -30,6 +32,7 @@ ? ({ code: 1024, id: action } satisfies KeyInfo) : action), ); + let icon = $derived(ignoreIcon ? undefined : info.icon); let dynamicMapping = $derived(info.keyCode && $osLayout.get(info.keyCode)); let hasPopover = $derived( !retrievedInfo || !info.id || info.title || info.description, @@ -63,7 +66,7 @@ {#snippet kbdText()} {dynamicMapping ?? - info.icon ?? + icon ?? info.display ?? info.id ?? `0x${info.code.toString(16)}`} @@ -71,7 +74,7 @@ {#snippet kbdSnippet(withPopover = true)} 1023} @@ -91,7 +94,7 @@ class:left={info.variant === "left"} class:right={info.variant === "right"}>{dynamicMapping} - {:else if !info.icon && info.id?.length === 1} + {:else if !icon && info.id?.length === 1} 1023} {@attach hasPopover ? actionTooltip(popover) : null} @@ -155,21 +158,50 @@ text-decoration: line-through; } - $variant-offset: 12px; - $variant-padding: calc(2px + $variant-offset); $variant-color: color-mix( in srgb, var(--md-sys-color-on-surface) 50%, transparent ); + .left, + .right { + background-color: transparent; + + &::before { + position: absolute; + inset: 0; + outline: 2px dashed + color-mix(in srgb, var(--bg-color), var(--md-sys-color-outline) 40%); + outline-offset: -2px; + border-radius: var(--border-radius); + content: ""; + } + } + + $cutoff: 60%; + .left { - padding-inline-end: $variant-padding; - text-shadow: $variant-offset 0 2px $variant-color; + background-image: linear-gradient( + to right, + var(--bg-color) $cutoff, + transparent $cutoff + ); + + &::before { + clip-path: inset(0 0 0 $cutoff); + } } .right { - padding-inline-start: $variant-padding; - text-shadow: -$variant-offset 0 2px $variant-color; + background-image: linear-gradient( + to left, + var(--bg-color) $cutoff, + transparent $cutoff + ); + + &::before { + clip-path: inset(0 $cutoff 0 0); + } } .inline-kbd { diff --git a/src/lib/components/layout/ActionList.svelte b/src/lib/components/layout/ActionList.svelte index 15d116afd..03905e5c5 100644 --- a/src/lib/components/layout/ActionList.svelte +++ b/src/lib/components/layout/ActionList.svelte @@ -14,18 +14,22 @@ import type { KeymapCategory } from "$lib/meta/types/actions"; import Action from "../Action.svelte"; import { isVerbose } from "../verbose-action"; - import { actionToValue } from "$lib/chord-editor/action-serializer"; + import { actionToValue } from "$lib/chord-editor/chord-sync"; let { currentAction = undefined, nextAction = undefined, + queryFilter = undefined, + ignoreIcon, autofocus = false, onselect, onclose, }: { currentAction?: number; + queryFilter?: string; nextAction?: number; autofocus?: boolean; + ignoreIcon?: boolean; onselect?: (id: number) => void; onclose?: () => void; } = $props(); @@ -43,6 +47,14 @@ createIndex($KEYMAP_CODES); }); + let didClear = true; + $effect(() => { + if (queryFilter !== undefined || !didClear) { + searchBox.value = queryFilter ?? ""; + search(); + } + }); + async function createIndex(codes: Map) { for (const [, action] of codes) { await index?.addAsync( @@ -60,6 +72,7 @@ (category) => [category, []] as [KeymapCategory, KeyInfo[]], ), ); + didClear = searchBox.value === ""; const result = searchBox.value === "" ? Array.from($KEYMAP_CODES.keys()) @@ -167,7 +180,7 @@
  • Action code is out of range
  • {/if} {/if} - {#each results as [category, actions] (category)} + {#each results as [category, actions] (actions)} {#if actions.length > 0}

    {category.name}

    @@ -191,7 +204,7 @@ } : undefined} > - + {/each} diff --git a/src/lib/learn/chords.ts b/src/lib/learn/chords.ts deleted file mode 100644 index d191eb924..000000000 --- a/src/lib/learn/chords.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { osLayout } from "$lib/os-layout"; -import { KEYMAP_CODES } from "$lib/serial/keymap-codes"; -import { persistentWritable } from "$lib/storage"; -import { type ChordInfo, chords } from "$lib/undo-redo"; -import { derived } from "svelte/store"; - -export const words = derived( - [chords, osLayout, KEYMAP_CODES], - ([chords, layout, KEYMAP_CODES]) => - new Map( - chords - .map((chord) => ({ - chord, - output: chord.phrase.map((action) => - layout.get(KEYMAP_CODES.get(action)?.keyCode ?? ""), - ), - })) - .filter(({ output }) => output.every((it) => !!it)) - .map(({ chord, output }) => [output.join("").trim(), chord] as const), - ), -); - -interface Score { - lastTyped: number; - score: number; - total: number; -} - -export const scores = persistentWritable>("scores", {}); - -export const learnConfigDefault = { - maxScore: 3, - minScore: -3, - scoreBlend: 0.5, - weakRate: 0.8, - weakBoost: 0.5, - maxWeak: 3, - newRate: 0.3, - initialNewRate: 0.9, - initialCount: 10, -}; -export const learnConfigStored = persistentWritable< - Partial ->("learn-config", {}); -export const learnConfig = derived(learnConfigStored, (config) => ({ - ...learnConfigDefault, - ...config, -})); - -let lastWord: string | undefined; - -function shuffle(array: T[]): T[] { - for (let i = array.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - [array[i], array[j]] = [array[j]!, array[i]!]; - } - return array; -} - -function randomLog2(array: T[], max = array.length): T | undefined { - return array[ - Math.floor(Math.pow(2, Math.log2(Math.random() * Math.log2(max)))) - ]; -} - -export const nextWord = derived( - [words, scores, learnConfig], - ([words, scores, config]) => { - const values = Object.entries(scores).filter(([it]) => it !== lastWord); - - values.sort(([, a], [, b]) => a.score - b.score); - const weakCount = - (values.findIndex(([, { score }]) => score > 0) + 1 || - values.length + 1) - 1; - const weak = randomLog2(values, weakCount); - if (weak && Math.random() / weakCount < config.weakRate) { - lastWord = weak[0]; - return weak[0]; - } - - values.sort(([, { lastTyped: a }], [, { lastTyped: b }]) => a - b); - const recent = randomLog2(values); - const newRate = - values.length < config.initialCount - ? config.initialNewRate - : config.newRate; - if ( - recent && - (Math.random() < Math.min(1, Math.max(0, weakCount / config.maxWeak)) || - Math.random() > newRate) - ) { - lastWord = recent[0]; - return recent[0]; - } - - const newWord = shuffle(Array.from(words.keys())).find((it) => !scores[it]); - const word = newWord || recent?.[0] || weak?.[0]; - lastWord = word; - return word; - }, -); diff --git a/src/lib/learn/stats.ts b/src/lib/learn/stats.ts deleted file mode 100644 index 05df68a2c..000000000 --- a/src/lib/learn/stats.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { persistentWritable } from "$lib/storage"; - -interface ChordStats { - level: number; - lastUprank: number; -} - -export const chordStats = persistentWritable>( - "chord-stats", - {}, -); diff --git a/src/lib/serial/chord.ts b/src/lib/serial/chord.ts index 532e2ca35..8b18030ff 100644 --- a/src/lib/serial/chord.ts +++ b/src/lib/serial/chord.ts @@ -1,4 +1,5 @@ import { compressActions, decompressActions } from "../serialization/actions"; +import type { KeyInfo } from "./keymap-codes"; export interface Chord { actions: number[]; @@ -56,6 +57,103 @@ export function deserializeActions(native: bigint): number[] { return actions; } +const compoundHashItems = 3; +const maxChordInputItems = 12; +const actionBits = 10; +const actionMask = (1 << actionBits) - 1; + +/** + * Applies the compound value to a **valid** chord input + */ +export function applyCompound(actions: number[], compound: number): number[] { + const result = [...actions]; + for (let i = 0; i < compoundHashItems; i++) { + result[i] = (compound >>> (i * actionBits)) & actionMask; + } + result[compoundHashItems] = 0; + return result; +} + +/** + * Extracts the compound value from a chord input, if present + */ +export function splitCompound( + actions: number[], +): [inputs: number[], compound: number | undefined] { + if (actions[compoundHashItems] != 0) { + return [ + actions.slice( + Math.max( + 0, + actions.findIndex((it) => it !== 0), + ), + ), + undefined, + ]; + } + + let compound = 0; + for (let i = 0; i < compoundHashItems; i++) { + compound |= (actions[i] ?? 0) << (i * actionBits); + } + + return [ + actions.slice( + actions.findIndex((it, i) => i > compoundHashItems && it !== 0), + ), + compound === 0 ? undefined : compound, + ]; +} + +export function willBeValidChordInput( + inputCount: number, + hasCompound: boolean, +): boolean { + return ( + inputCount > 0 && + inputCount <= maxChordInputItems - (hasCompound ? compoundHashItems + 1 : 0) + ); +} + +const ACTION_JOIN = 574; +const ACTION_KSC_00 = 256; + +export function hasConcatenator( + actions: number[], + ids: Map, +): boolean { + const lastAction = actions.at(-1); + for (const action of actions) { + if (!ids.get(action)?.printable) { + if (actions.length == 0) { + return false; + } + return lastAction == ACTION_JOIN; + } + } + return lastAction != ACTION_KSC_00; +} + +/** + * Composes a chord input from a list of actions and an optional compound value + * to a valid chord input + */ +export function composeChordInput( + actions: number[], + compound?: number, +): number[] { + const result = [ + ...Array.from( + { + length: Math.max(0, maxChordInputItems - actions.length), + }, + () => 0, + ), + ...actions.slice(0, maxChordInputItems).sort((a, b) => a - b), + ]; + return compound !== undefined ? applyCompound(result, compound) : result; +} + /** * Hashes a chord input the same way as CCOS */ @@ -72,5 +170,6 @@ export function hashChord(actions: number[]) { if ((hash & 0xff) === 0xff) { hash ^= 0xff; } - return hash & 0x3fff_ffff; + hash &= 0x3fff_ffff; + return hash === 0 ? 1 : hash; } diff --git a/src/lib/serial/connection.ts b/src/lib/serial/connection.ts index f0196aa1b..55ecf933f 100644 --- a/src/lib/serial/connection.ts +++ b/src/lib/serial/connection.ts @@ -55,7 +55,10 @@ export const syncStatus: Writable< "done" | "error" | "downloading" | "uploading" > = writable("done"); -export const deviceMeta = writable(undefined); +export const deviceMeta = persistentWritable( + "current-meta", + undefined, +); export interface ProgressInfo { max: number; diff --git a/src/lib/style/_kbd.scss b/src/lib/style/_kbd.scss index 0ab2e5dab..a4ae2ba46 100644 --- a/src/lib/style/_kbd.scss +++ b/src/lib/style/_kbd.scss @@ -1,22 +1,22 @@ kbd { - display: inline-flex; - justify-content: center; - align-items: center; - margin-block: 6px; - border-radius: 4px; - - //border: 1px solid currentcolor; - background: color-mix( + --bg-color: color-mix( in srgb, var(--md-sys-color-surface-variant) 50%, transparent ); - padding: 4px; + --border-radius: 4px; + display: inline-flex; + position: relative; + justify-content: center; + align-items: center; + margin-block: 6px; + border-radius: var(--border-radius); + background: var(--bg-color); + padding: 4px; height: 20px; color: currentcolor; font-weight: normal; - font-size: 14px; &.icon { diff --git a/src/lib/undo-redo.ts b/src/lib/undo-redo.ts index cfbf743ad..e89582464 100644 --- a/src/lib/undo-redo.ts +++ b/src/lib/undo-redo.ts @@ -1,16 +1,9 @@ import { persistentWritable } from "$lib/storage"; import { derived } from "svelte/store"; -import { hashChord, type Chord } from "$lib/serial/chord"; -import { - deviceChords, - deviceLayout, - deviceSettings, -} from "$lib/serial/connection"; -import { KEYMAP_CODES } from "$lib/serial/keymap-codes"; +import { deviceLayout, deviceSettings } from "$lib/serial/connection"; export enum ChangeType { Layout, - Chord, Setting, } @@ -22,14 +15,6 @@ export interface LayoutChange { profile?: number; } -export interface ChordChange { - type: ChangeType.Chord; - deleted?: true; - id: number[]; - actions: number[]; - phrase: number[]; -} - export interface SettingChange { type: ChangeType.Setting; id: number; @@ -42,20 +27,18 @@ export interface ChangeInfo { isCommitted?: boolean; } -export type Change = LayoutChange | ChordChange | SettingChange; +export type Change = LayoutChange | SettingChange; export const changes = persistentWritable("changes", []); export interface Overlay { layout: Array | undefined> | undefined>; - chords: Map; settings: Array | undefined>; } export const overlay = derived(changes, (changes) => { const overlay: Overlay = { layout: [], - chords: new Map(), settings: [], }; @@ -71,13 +54,6 @@ export const overlay = derived(changes, (changes) => { change.action, ); break; - case ChangeType.Chord: - overlay.chords.set(JSON.stringify(change.id), { - actions: change.actions, - phrase: change.phrase, - deleted: change.deleted ?? false, - }); - break; case ChangeType.Setting: change.profile ??= 0; overlay.settings[change.profile] ??= new Map(); @@ -113,90 +89,3 @@ export const layout = derived([overlay, deviceLayout], ([overlay, profiles]) => ), ), ); - -export type ChordInfo = Chord & - ChangeInfo & { - phraseChanged: boolean; - actionsChanged: boolean; - sortBy: string; - } & { - id: number[]; - deleted: boolean; - }; -export const chords = derived( - [overlay, deviceChords, KEYMAP_CODES], - ([overlay, chords, codes]) => { - const newChords = new Set(overlay.chords.keys()); - - const changedChords = chords.map((chord) => { - const id = JSON.stringify(chord.actions); - if (overlay.chords.has(id)) { - newChords.delete(id); - const changedChord = overlay.chords.get(id)!; - return { - id: chord.actions, - // use the old phrase for stable editing - sortBy: chord.phrase.map((it) => codes.get(it)?.id ?? it).join(), - actions: changedChord.actions, - phrase: changedChord.phrase, - actionsChanged: id !== JSON.stringify(changedChord.actions), - phraseChanged: - JSON.stringify(chord.phrase) !== - JSON.stringify(changedChord.phrase), - isApplied: false, - deleted: changedChord.deleted, - }; - } else { - return { - id: chord.actions, - sortBy: chord.phrase.map((it) => codes.get(it)?.id ?? it).join(), - actions: chord.actions, - phrase: chord.phrase, - phraseChanged: false, - actionsChanged: false, - isApplied: true, - deleted: false, - }; - } - }); - for (const id of newChords) { - const chord = overlay.chords.get(id)!; - changedChords.push({ - sortBy: "", - isApplied: false, - actionsChanged: true, - phraseChanged: false, - deleted: chord.deleted, - id: JSON.parse(id), - phrase: chord.phrase, - actions: chord.actions, - }); - } - - return changedChords.sort(({ sortBy: a }, { sortBy: b }) => - a.localeCompare(b), - ); - }, -); - -export const duplicateChords = derived(chords, (chords) => { - const duplicates = new Set(); - const seen = new Set(); - - for (const chord of chords) { - const key = JSON.stringify(chord.actions); - if (seen.has(key)) { - duplicates.add(key); - } else { - seen.add(key); - } - } - - return duplicates; -}); - -export const chordHashes = derived( - chords, - (chords) => - new Map(chords.map((chord) => [hashChord(chord.actions), chord] as const)), -); diff --git a/src/routes/(app)/PageTransition.svelte b/src/routes/(app)/PageTransition.svelte index 840a94d6e..477a341c1 100644 --- a/src/routes/(app)/PageTransition.svelte +++ b/src/routes/(app)/PageTransition.svelte @@ -13,14 +13,7 @@ let isNavigating = $state(false); - const routeOrder = [ - "/config", - "/learn", - "/docs", - "/editor", - "/chat", - "/plugin", - ]; + const routeOrder = ["/config", "/docs", "/editor", "/chat", "/plugin"]; function routeIndex(route: string | undefined): number { return routeOrder.findIndex((it) => route?.startsWith(it)); diff --git a/src/routes/(app)/Sidebar.svelte b/src/routes/(app)/Sidebar.svelte index bf3cc62bb..d62a4e35c 100644 --- a/src/routes/(app)/Sidebar.svelte +++ b/src/routes/(app)/Sidebar.svelte @@ -39,7 +39,12 @@ [ { href: "/editor", icon: "edit_document", title: "Editor", wip: true }, { href: "/chat", icon: "chat", title: "Chat", wip: true }, - { href: "/learn", icon: "school", title: "Learn", wip: true }, + { + href: "https://adventure.charachorder.io/", + icon: "school", + title: "Learn", + external: true, + }, ], /*[ { href: "/plugin", icon: "code", title: "Plugin", wip: true }, diff --git a/src/routes/(app)/config/EditActions.svelte b/src/routes/(app)/config/EditActions.svelte index dc3efeba1..58d317857 100644 --- a/src/routes/(app)/config/EditActions.svelte +++ b/src/routes/(app)/config/EditActions.svelte @@ -6,18 +6,15 @@ layout, overlay, settings, - duplicateChords, } from "$lib/undo-redo"; - import type { Change, ChordChange } from "$lib/undo-redo"; + import type { Change } from "$lib/undo-redo"; import { fly } from "svelte/transition"; import { actionTooltip } from "$lib/title"; import { - deviceChords, deviceLayout, deviceSettings, serialLog, serialPort, - sync, syncProgress, syncStatus, } from "$lib/serial/connection"; @@ -106,115 +103,7 @@ return true; } - async function safeDeleteChord(actions: number[]): Promise { - const port = $serialPort; - if (!port) return false; - try { - await port.deleteChord({ actions }); - return true; - } catch (e) { - console.error(e); - try { - if ((await port.getChordPhrase(actions)) === undefined) { - return true; - } - } catch (e) { - console.error(e); - } - } - return false; - } - - async function saveChords(progress: () => void): Promise { - const port = $serialPort; - if (!port) return false; - let ok = true; - - const empty = new Set(); - for (const [id, chord] of $overlay.chords) { - if (chord.actions.length === 0 || chord.phrase.length === 0) { - empty.add(id); - } - } - changes.update((changes) => { - changes.push([ - ...empty.keys().map( - (id) => - ({ - type: ChangeType.Chord, - id: JSON.parse(id), - deleted: true, - actions: [], - phrase: [], - }) satisfies ChordChange, - ), - ]); - return changes; - }); - await tick(); - - const deleted = new Set(); - const changed = new Map(); - for (const [id, chord] of $overlay.chords) { - if (!chord.deleted) continue; - if (await safeDeleteChord(JSON.parse(id))) { - deleted.add(id); - } else { - ok = false; - } - progress(); - } - deviceChords.update((chords) => - chords.filter((chord) => !deleted.has(JSON.stringify(chord.actions))), - ); - deleted.clear(); - await tick(); - - for (const [id, chord] of $overlay.chords) { - if (chord.deleted) continue; - if ($duplicateChords.has(JSON.stringify(chord.actions))) { - ok = false; - } else { - let skip = false; - if (id !== JSON.stringify(chord.actions)) { - if (await safeDeleteChord(JSON.parse(id))) { - deleted.add(id); - } else { - skip = true; - ok = false; - } - } - if (!skip) { - try { - await port.setChord({ - actions: chord.actions, - phrase: chord.phrase, - }); - deleted.add(JSON.stringify(chord.actions)); - changed.set(JSON.stringify(chord.actions), chord.phrase); - } catch (e) { - console.error(e); - ok = false; - } - } else { - ok = false; - } - } - progress(); - } - deviceChords.update((chords) => { - chords.filter((chord) => !deleted.has(JSON.stringify(chord.actions))); - for (const [id, phrase] of changed) { - chords.push({ actions: JSON.parse(id), phrase }); - } - return chords; - }); - await tick(); - return ok; - } - async function save() { - let needsSync = false; try { const port = $serialPort; if (!port) { @@ -235,10 +124,8 @@ (acc, profile) => acc + (profile?.size ?? 0), 0, ); - const chordChanges = $overlay.chords.size; - needsSync = chordChanges > 0; const needsCommit = settingChanges > 0 || layoutChanges > 0; - const progressMax = layoutChanges + settingChanges + chordChanges; + const progressMax = layoutChanges + settingChanges; let progressCurrent = 0; @@ -261,11 +148,9 @@ layoutSuccess = false; } } - let chordsSuccess = await saveChords(updateProgress); - if (layoutSuccess && settingsSuccess && chordsSuccess) { + if (layoutSuccess && settingsSuccess) { changes.set([]); - needsSync = true; } else { throw new Error("Some changes could not be saved."); } @@ -280,10 +165,6 @@ } finally { $syncStatus = "done"; } - - if (needsSync) { - await sync(); - } } let progressPopover: HTMLElement | undefined = $state(); diff --git a/src/routes/(app)/config/chords/+page.svelte b/src/routes/(app)/config/chords/+page.svelte index 5f65f8d11..fffaee8f2 100644 --- a/src/routes/(app)/config/chords/+page.svelte +++ b/src/routes/(app)/config/chords/+page.svelte @@ -1,468 +1,278 @@ - - Chord Manager - CharaChorder Device Manager - - - -
    - $searchIndex && search($searchIndex, event)} - class:loading={progress !== $chords.length} - /> -
    - {#if $lastPage !== -1} - {page + 1} / {$lastPage + 1} - {:else} - - / - - {/if} +
    +
    + + + + + +
    - - -
    -
    - - {#await tick() then} -
    - - {#if $lastPage !== -1} - - {#if page === 0} - - {/if} - {#each $items.slice(page * $pageSize - (page === 0 ? 0 : 1), (page + 1) * $pageSize - 1) as [chord]} - {#if chord} - (page = 0)} /> - {/if} - {/each} - {:else} - - {/if} -
    insertChord(action)} - />
    {$LL.configure.chords.search.NO_RESULTS()}
    -
    - - {/await} -
    +
    +
    + +
    +
    diff --git a/src/routes/(app)/config/chords/ChordActionEdit.svelte b/src/routes/(app)/config/chords/ChordActionEdit.svelte deleted file mode 100644 index f1363de51..000000000 --- a/src/routes/(app)/config/chords/ChordActionEdit.svelte +++ /dev/null @@ -1,240 +0,0 @@ - - - - - diff --git a/src/routes/(app)/config/chords/ChordEdit.svelte b/src/routes/(app)/config/chords/ChordEdit.svelte deleted file mode 100644 index 032d2fa3c..000000000 --- a/src/routes/(app)/config/chords/ChordEdit.svelte +++ /dev/null @@ -1,172 +0,0 @@ - - - - - {}} /> - - - - - -
    - {#if !chord.deleted} - - {:else} - - {/if} - - -
    - -
    - - - - diff --git a/src/routes/(app)/config/chords/ChordPhraseEdit.svelte b/src/routes/(app)/config/chords/ChordPhraseEdit.svelte deleted file mode 100644 index 8200962c8..000000000 --- a/src/routes/(app)/config/chords/ChordPhraseEdit.svelte +++ /dev/null @@ -1,376 +0,0 @@ - - - - -
    { - box?.focus(); - }} -> - {#if supportsAutospace} - { - const autospace = hasAutospace; - if ((event.target as HTMLInputElement).checked) { - if (chord.phrase[0] === JOIN_ACTION) { - deleteAction(0, 1); - await tick(); - moveCursor(cursorPosition - 1, true); - } - } else { - if (chord.phrase[0] !== JOIN_ACTION) { - insertAction(0, JOIN_ACTION); - moveCursor(cursorPosition + 1, true); - } - } - await tick(); - resolveAutospace(autospace); - }} - /> - {/if} -
    (hasFocus = true)} - onfocusout={(event) => { - if (event.relatedTarget !== button) hasFocus = false; - }} - > - {#if hasFocus} -
    - -
    - {:else} -
    - - {/if} - {#each chord.phrase as action, i} - {#if isHidden(action, i, chord.phrase)} - - {:else} - - {/if} - {/each} -
    - {#if supportsAutospace} - - resolveAutospace((event.target as HTMLInputElement).checked)} - /> - {/if} - -
    - - diff --git a/src/routes/(app)/config/chords/action-selector.ts b/src/routes/(app)/config/chords/action-selector.ts deleted file mode 100644 index 51b8c41d9..000000000 --- a/src/routes/(app)/config/chords/action-selector.ts +++ /dev/null @@ -1,56 +0,0 @@ -import ActionSelector from "$lib/components/layout/ActionSelector.svelte"; -import { mount, unmount, tick } from "svelte"; - -export function selectAction( - event: MouseEvent | KeyboardEvent, - select: (action: number) => void, - dismissed?: () => void, -) { - const component = mount(ActionSelector, { - target: document.body, - props: { - onclose: () => closed(), - onselect: (action: number) => { - select(action); - closed(); - }, - }, - }); - const dialog = document.querySelector("dialog > div") as HTMLDivElement; - const backdrop = document.querySelector("dialog") as HTMLDialogElement; - const dialogRect = dialog.getBoundingClientRect(); - const groupRect = (event.target as HTMLElement).getBoundingClientRect(); - - const scale = 0.5; - const dialogScale = `${ - 1 - scale * (1 - groupRect.width / dialogRect.width) - } ${1 - scale * (1 - groupRect.height / dialogRect.height)}`; - const dialogTranslate = `${scale * (groupRect.x - dialogRect.x)}px ${ - scale * (groupRect.y - dialogRect.y) - }px`; - - const duration = 150; - const options = { duration, easing: "ease" }; - const dialogAnimation = dialog.animate( - [ - { scale: dialogScale, translate: dialogTranslate }, - { translate: "0 0", scale: "1" }, - ], - options, - ); - const backdropAnimation = backdrop.animate( - [{ opacity: 0 }, { opacity: 1 }], - options, - ); - - async function closed() { - dialogAnimation.reverse(); - backdropAnimation.reverse(); - - await dialogAnimation.finished; - - unmount(component); - await tick(); - dismissed?.(); - } -} diff --git a/src/routes/(app)/config/chords/input-converter.ts b/src/routes/(app)/config/chords/input-converter.ts deleted file mode 100644 index 5619fae48..000000000 --- a/src/routes/(app)/config/chords/input-converter.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { KEYMAP_IDS, KEYMAP_KEYCODES } from "$lib/serial/keymap-codes"; -import { get } from "svelte/store"; - -export function inputToAction( - event: KeyboardEvent, - useKeycodes?: boolean, -): number | undefined { - if (useKeycodes) { - return get(KEYMAP_KEYCODES).get(event.code); - } else { - return ( - get(KEYMAP_IDS).get(event.key)?.code ?? - get(KEYMAP_KEYCODES).get(event.code) - ); - } -} diff --git a/src/routes/(app)/config/chords/will-my-compound-break/+page.svelte b/src/routes/(app)/config/chords/will-my-compound-break/+page.svelte index a7e5b4a77..6ad0145f4 100644 --- a/src/routes/(app)/config/chords/will-my-compound-break/+page.svelte +++ b/src/routes/(app)/config/chords/will-my-compound-break/+page.svelte @@ -1,7 +1,5 @@ @@ -35,7 +34,7 @@ >, your library might have been corrupted.

    {#each broken as chord} - {}} /> + {/each} {:else}

    No problematic chords found

    diff --git a/src/routes/(app)/config/cv2/+page.svelte b/src/routes/(app)/config/cv2/+page.svelte deleted file mode 100644 index 9713d609e..000000000 --- a/src/routes/(app)/config/cv2/+page.svelte +++ /dev/null @@ -1,200 +0,0 @@ - - - - - -
    - -
    -
    - - diff --git a/src/routes/(app)/learn/+page.svelte b/src/routes/(app)/learn/+page.svelte deleted file mode 100644 index 0ed68878f..000000000 --- a/src/routes/(app)/learn/+page.svelte +++ /dev/null @@ -1,24 +0,0 @@ - - - - - diff --git a/src/routes/(app)/learn/chords/+page.svelte b/src/routes/(app)/learn/chords/+page.svelte deleted file mode 100644 index bb009fe24..000000000 --- a/src/routes/(app)/learn/chords/+page.svelte +++ /dev/null @@ -1,232 +0,0 @@ - - -

    WIP

    - - - -{#key $nextWord} -

    - {$nextWord} - {#if $scores[$nextWord!] === undefined} - new - {:else if ($scores[$nextWord!]?.score ?? 0) < 0} - weak - {/if} -

    - -
    - - - -
    -{/key} - -{#key $nextWord} -
    - {}} /> -
    -{/key} - - -
    - - - - - - {#each Object.entries($scores) - .sort(([, a], [, b]) => a.score - b.score) - .splice(0, 10) as [word, score]} - - - - - {/each} - -
    Weak
    {word}{score.score.toFixed(2)}
    - - - - - - - {#each Object.entries($scores) - .sort(([, a], [, b]) => b.score - a.score) - .splice(0, 10) as [word, score]} - - - - - {/each} - -
    Strong
    {word}{score.score.toFixed(2)}
    - - - - - - - {#each Object.entries($scores) - .sort(([, a], [, b]) => b.lastTyped - a.lastTyped) - .splice(0, 10) as [word, _score]} - - - - {/each} - -
    Rehearse
    {word}
    -
    - -
    - Settings - - - - {#each Object.entries(learnConfigDefault) as [key, value]} - - - - - - {/each} - -
    {key} - ($learnConfigStored[key as keyof typeof $learnConfig] = ( - event.target as HTMLInputElement - ).value as any)} - /> - - -
    -
    - - diff --git a/src/routes/(app)/learn/layout/+page.svelte b/src/routes/(app)/learn/layout/+page.svelte deleted file mode 100644 index a495383d6..000000000 --- a/src/routes/(app)/learn/layout/+page.svelte +++ /dev/null @@ -1,124 +0,0 @@ - - -
    -
    - -
    - - -
    - - diff --git a/src/routes/(app)/learn/sentence/+page.svelte b/src/routes/(app)/learn/sentence/+page.svelte deleted file mode 100644 index 629ed3ba5..000000000 --- a/src/routes/(app)/learn/sentence/+page.svelte +++ /dev/null @@ -1,652 +0,0 @@ - - -
    -

    Sentence Trainer

    - - -
    - {#each masteryThresholds as [, , title], i} - - {/each} - - {#each masteryThresholds as _, i} -
    - {/each} -
    -
    -
    - {#each sentenceWords as _, i} - {#if i !== sentenceWords.length - 1} - {@const word = sentenceWords.slice(i, i + 2).join(" ")} - {@const mastery = wordMastery.get(word) ?? 0} -
    - {/if} - {/each} - {#each sentenceWords as word, i} - {@const mastery = wordMastery.get(word)} -
    - {word} -
    - {/each} - {#each sentenceWords as _, i} - {#if i < sentenceWords.length - 2} - {@const word = sentenceWords.slice(i, i + 3).join(" ")} - {@const mastery = wordMastery.get(word) ?? 0} -
    - {/if} - {/each} -
    - {#if level === masteryThresholds.length} - {@const maxDigits = 4} - {@const indices = Array.from({ length: maxDigits }, (_, i) => i)} - {@const wpmString = Math.floor(bestWPM).toString().padStart(maxDigits, " ")} -
    -
    - {#each indices as i} - {@const char = wpmString[i]} - {#key char} -
    - {char} -
    - {/key} - {/each} -
    - WPM -
    -
    -
    - {#key wpm} -
    - {Math.floor(wpm)} -
    - {/key} -
    WPM
    -
    -
    - {/if} - -
    -
    - {#key recorder} -
    - - - - -
    - {/key} -
    -
    - {#if devTools} -
    Dev Tools
    - - - - - - - - - - - - - - - - - -
    Total{Math.round(totalMs)}ms -
    Char{Math.round(msPerChar)}ms -
    Word{Math.round(msPerWord)}ms -
    - - - {#each masteryThresholds as _, i} - - - - - - - {/each} - -
    L{i + 1}
    - - - {#each wordStats.entries() as [word, stats]} - {@const mastery = wordMastery.get(word) ?? 0} - - - - {#each stats as stat} - - {/each} - - {/each} - -
    {word}{Math.round(mastery * 100)}% - {stat}
    - {/if} -
    - - diff --git a/src/routes/(app)/learn/sentence/configuration.ts b/src/routes/(app)/learn/sentence/configuration.ts deleted file mode 100644 index 2d932e903..000000000 --- a/src/routes/(app)/learn/sentence/configuration.ts +++ /dev/null @@ -1,32 +0,0 @@ -export interface PageParam { - key: string; - default: T; - parse?: (value: string) => T; -} - -export const SENTENCE_TRAINER_PAGE_PARAMS: { - sentence: PageParam; - wpm: PageParam; - showDevTools: PageParam; - textAreaDebounceInMillis: PageParam; -} = { - sentence: { - key: "sentence", - default: "This text has been typed at the speed of thought", - }, - wpm: { - key: "wpm", - default: 250, - parse: (value) => Number(value), - }, - showDevTools: { - key: "dev", - default: false, - parse: (value) => value === "true", - }, - textAreaDebounceInMillis: { - key: "debounceMillis", - default: 5000, - parse: (value) => Number(value), - }, -}; diff --git a/src/routes/(app)/learn/sentence/constants.ts b/src/routes/(app)/learn/sentence/constants.ts deleted file mode 100644 index faf41187b..000000000 --- a/src/routes/(app)/learn/sentence/constants.ts +++ /dev/null @@ -1,8 +0,0 @@ -// Domain constants -export const AVG_WORD_LENGTH = 5; -export const SECONDS_IN_MINUTE = 60; -export const MILLIS_IN_SECOND = 1000; - -// Error messages. -export const TOO_SHORT_SENTENCE_FOR_NGRAMS_MESSAGE = - "The sentence is too short to make N-Grams, please enter longer sentence"; diff --git a/src/routes/(app)/learn/sentence/types.ts b/src/routes/(app)/learn/sentence/types.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/routes/(app)/learn/sentence/word-selector.spec.ts b/src/routes/(app)/learn/sentence/word-selector.spec.ts deleted file mode 100644 index 2e46a9f01..000000000 --- a/src/routes/(app)/learn/sentence/word-selector.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { describe, it, beforeEach, expect, vi } from "vitest"; -import { pickNextWord } from "./word-selector"; -import { untrack } from "svelte"; -import { SvelteMap } from "svelte/reactivity"; -import { TOO_SHORT_SENTENCE_FOR_NGRAMS_MESSAGE } from "./constants"; - -// Mock untrack so it simply executes the callback, allowing us to spy on its usage. -vi.mock("svelte", () => ({ - untrack: vi.fn((fn: any) => fn()), -})); - -describe("pickNextWord", () => { - let words: string[]; - let wordMastery: SvelteMap; - let currentWord: string; - - beforeEach(() => { - vi.clearAllMocks(); - - // Set up sample words and mastery values. - words = ["alpha", "beta", "gamma"]; - wordMastery = new SvelteMap(); - // For this test, assume none of the words are mastered. - words.forEach((word) => wordMastery.set(word, 0)); - currentWord = "alpha"; - }); - - it("should return a word different from current", () => { - // Force Math.random() to return a predictable value. - vi.spyOn(Math, "random").mockReturnValueOnce(0.3); - - const nextWord = pickNextWord(words, wordMastery, currentWord); - - // Since currentWord ("alpha") should be skipped, we expect next word. - expect(nextWord).toBe("beta"); - }); - - it("should randomly skip words", () => { - // Force Math.random() to return a predictable value. - vi.spyOn(Math, "random").mockReturnValueOnce(0.6).mockReturnValueOnce(0.3); - - const nextWord = pickNextWord(words, wordMastery, currentWord); - - // Since currentWord ("alpha") should be skipped as current - // and "beta" should be randomly skipped we expect "gamma". - expect(nextWord).toBe("gamma"); - }); - - it("should return current word if all other words were randomly skipped", () => { - // Force Math.random() to return a predictable value. - vi.spyOn(Math, "random").mockReturnValueOnce(0.6).mockReturnValueOnce(0.6); - - const nextWord = pickNextWord(words, wordMastery, currentWord); - - // Since all other words have been randomly skipped, we expect - // current word to be returned. - expect(nextWord).toBe("alpha"); - }); - - it("current word should be passed untracked", () => { - pickNextWord(words, wordMastery, currentWord); - expect(untrack).toHaveBeenCalledTimes(0); - }); - - it("should return TOO_SHORT_SENTENCE_FOR_NGRAMS_MESSAGE if the words array is empty", () => { - const result = pickNextWord([], wordMastery, currentWord); - expect(result).toBe(TOO_SHORT_SENTENCE_FOR_NGRAMS_MESSAGE); - }); -}); diff --git a/src/routes/(app)/learn/sentence/word-selector.ts b/src/routes/(app)/learn/sentence/word-selector.ts deleted file mode 100644 index 21603af7f..000000000 --- a/src/routes/(app)/learn/sentence/word-selector.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { TOO_SHORT_SENTENCE_FOR_NGRAMS_MESSAGE } from "./constants"; -import { SvelteMap } from "svelte/reactivity"; - -export function pickNextWord( - words: string[], - wordMastery: SvelteMap, - untrackedCurrentWord: string, -) { - const unmasteredWords = words - .map((it) => [it, wordMastery.get(it) ?? 0] as const) - .filter(([, it]) => it !== 1); - unmasteredWords.sort(([, a], [, b]) => a - b); - let nextWord = - unmasteredWords[0]?.[0] ?? - words[0] ?? - TOO_SHORT_SENTENCE_FOR_NGRAMS_MESSAGE; - // This is important to break infinite loop created by - // reading and writing `currentWord` inside $effect rune - for (const [word] of unmasteredWords) { - if (word === untrackedCurrentWord || Math.random() > 0.5) continue; - nextWord = word; - break; - } - return nextWord; -} diff --git a/vitest.config.ts b/vitest.config.ts index 6a77c9090..35f27e692 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,8 +1,10 @@ import { defineConfig } from "vitest/config"; -import { svelte } from "@sveltejs/vite-plugin-svelte"; +import { sveltekit } from "@sveltejs/kit/vite"; +import { lezerGrammarPlugin } from "./vite-plugin-lezer"; +import { layoutPlugin } from "./vite-plugin-layout"; export default defineConfig({ - plugins: [svelte({ hot: !process.env.VITEST })], + plugins: [layoutPlugin(), sveltekit(), lezerGrammarPlugin()], test: { globals: true, environment: "jsdom",