diff --git a/bun.lock b/bun.lock index 4c3ff7c..fe71b8a 100644 --- a/bun.lock +++ b/bun.lock @@ -9,7 +9,7 @@ "name": "web", "version": "0.0.1", "dependencies": { - "bits-ui": "^2.14.2", + "bits-ui": "^2.14.4", "chroma-js": "^3.1.2", "diff": "^8.0.2", "luxon": "^3.7.2", @@ -22,12 +22,12 @@ "devDependencies": { "@eslint/compat": "^2.0.0", "@eslint/js": "^9.39.1", - "@iconify-json/octicon": "^1.2.17", + "@iconify-json/octicon": "^1.2.19", "@iconify/tailwind4": "^1.1.0", "@octokit/openapi-types": "^27.0.0", "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/adapter-cloudflare": "^7.2.4", - "@sveltejs/kit": "^2.48.4", + "@sveltejs/kit": "^2.49.0", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.1.17", "@types/chroma-js": "^3.1.2", @@ -40,15 +40,15 @@ "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", "prettier-plugin-tailwindcss": "^0.7.1", - "svelte": "^5.43.4", + "svelte": "^5.43.14", "svelte-adapter-bun": "^1.0.1", - "svelte-check": "^4.3.3", + "svelte-check": "^4.3.4", "tailwindcss": "^4.1.17", "tw-animate-css": "^1.4.0", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.3", - "vite": "^7.2.2", - "vitest": "^4.0.8", + "typescript-eslint": "^8.48.0", + "vite": "^7.2.4", + "vitest": "^4.0.13", }, }, "web-extension": { @@ -62,17 +62,17 @@ "devDependencies": { "@eslint/compat": "^2.0.0", "@eslint/js": "^9.39.1", - "@types/bun": "^1.3.2", + "@types/bun": "^1.3.3", "@types/webextension-polyfill": "^0.12.4", - "chrome-types": "^0.1.387", + "chrome-types": "^0.1.390", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "globals": "^16.5.0", "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.7.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.3", - "vite": "^7.2.2", + "typescript-eslint": "^8.48.0", + "vite": "^7.2.4", }, }, }, @@ -193,7 +193,7 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], - "@iconify-json/octicon": ["@iconify-json/octicon@1.2.17", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-U/mznjeCeZzuqpP25zWGcF4amLaYnNLs9sTN2hYALa+28n33KUXj/XjLmpAjIUvsyvn91jxfwdxSE79HfM4jCg=="], + "@iconify-json/octicon": ["@iconify-json/octicon@1.2.19", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-q1a9fpyg0Cw/Bt9hEfP86eJlgKtMXzNIRQnsbPZi1MBoHlPyi056TdzV72zY/F+oJSJ8b5Ub8njL2fWs/iLJAg=="], "@iconify/tailwind4": ["@iconify/tailwind4@1.1.0", "", { "dependencies": { "@iconify/tools": "^4.1.4", "@iconify/types": "^2.0.0", "@iconify/utils": "^2.3.0" }, "peerDependencies": { "tailwindcss": ">= 4.0.0" } }, "sha512-HqgAYtYk4eFtLvdYfhQrBRT9ohToh+VJJVhHtJ7B4Qhw+J+mRPvGC9Wr6Cgtb36YbIWqBxWuBaAUw9TE/8m2/w=="], @@ -255,12 +255,6 @@ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], - "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], - - "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], - - "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], - "@octokit/openapi-types": ["@octokit/openapi-types@27.0.0", "", {}, "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA=="], "@oxc-project/runtime": ["@oxc-project/runtime@0.71.0", "", {}, "sha512-QwoF5WUXIGFQ+hSxWEib4U/aeLoiDN9JlP18MnBgx9LLPRDfn1iICtcow7Jgey6HLH4XFceWXQD5WBJ39dyJcw=="], @@ -367,7 +361,7 @@ "@sveltejs/adapter-cloudflare": ["@sveltejs/adapter-cloudflare@7.2.4", "", { "dependencies": { "@cloudflare/workers-types": "^4.20250507.0", "worktop": "0.8.0-next.18" }, "peerDependencies": { "@sveltejs/kit": "^2.0.0", "wrangler": "^4.0.0" } }, "sha512-uD8VlOuGXGuZWL+zbBYSjtmC4WDtlonUodfqAZ/COd5uIy2Z0QptIicB/nkTrGNI9sbmzgf7z0N09CHyWYlUvQ=="], - "@sveltejs/kit": ["@sveltejs/kit@2.48.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "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", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-TGFX1pZUt9qqY20Cv5NyYvy0iLWHf2jXi8s+eCGsig7jQMdwZWKUFMR6TbvFNhfDSUpc1sH/Y5EHv20g3HHA3g=="], + "@sveltejs/kit": ["@sveltejs/kit@2.49.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/cookie": "^0.6.0", "acorn": "^8.14.1", "cookie": "^0.6.0", "devalue": "^5.3.2", "esm-env": "^1.2.2", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", "sirv": "^3.0.0" }, "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", "vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["@opentelemetry/api"], "bin": { "svelte-kit": "svelte-kit.js" } }, "sha512-oH8tXw7EZnie8FdOWYrF7Yn4IKrqTFHhXvl8YxXxbKwTMcD/5NNCryUSEXRk2ZR4ojnub0P8rNrsVGHXWqIDtA=="], "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.1", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "debug": "^4.4.1", "deepmerge": "^4.3.1", "magic-string": "^0.30.17", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ=="], @@ -409,7 +403,7 @@ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], - "@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="], + "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], "@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="], @@ -431,8 +425,6 @@ "@types/node": ["@types/node@24.0.13", "", { "dependencies": { "undici-types": "~7.8.0" } }, "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ=="], - "@types/react": ["@types/react@19.1.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g=="], - "@types/tar": ["@types/tar@6.1.13", "", { "dependencies": { "@types/node": "*", "minipass": "^4.0.0" } }, "sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], @@ -443,41 +435,41 @@ "@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.46.3", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/type-utils": "8.46.3", "@typescript-eslint/utils": "8.46.3", "@typescript-eslint/visitor-keys": "8.46.3", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.46.3", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.48.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/type-utils": "8.48.0", "@typescript-eslint/utils": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.48.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.46.3", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", "@typescript-eslint/typescript-estree": "8.46.3", "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.48.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.46.3", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.46.3", "@typescript-eslint/types": "^8.46.3", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.48.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.48.0", "@typescript-eslint/types": "^8.48.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.46.3", "", { "dependencies": { "@typescript-eslint/types": "8.46.3", "@typescript-eslint/visitor-keys": "8.46.3" } }, "sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0" } }, "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.46.3", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.48.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.46.3", "", { "dependencies": { "@typescript-eslint/types": "8.46.3", "@typescript-eslint/typescript-estree": "8.46.3", "@typescript-eslint/utils": "8.46.3", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/utils": "8.48.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.46.3", "", {}, "sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.48.0", "", {}, "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.46.3", "", { "dependencies": { "@typescript-eslint/project-service": "8.46.3", "@typescript-eslint/tsconfig-utils": "8.46.3", "@typescript-eslint/types": "8.46.3", "@typescript-eslint/visitor-keys": "8.46.3", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.48.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.48.0", "@typescript-eslint/tsconfig-utils": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/visitor-keys": "8.48.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.46.3", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", "@typescript-eslint/typescript-estree": "8.46.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.48.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.48.0", "@typescript-eslint/types": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.3", "", { "dependencies": { "@typescript-eslint/types": "8.46.3", "eslint-visitor-keys": "^4.2.1" } }, "sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.48.0", "", { "dependencies": { "@typescript-eslint/types": "8.48.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], - "@vitest/expect": ["@vitest/expect@4.0.8", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.8", "@vitest/utils": "4.0.8", "chai": "^6.2.0", "tinyrainbow": "^3.0.3" } }, "sha512-Rv0eabdP/xjAHQGr8cjBm+NnLHNoL268lMDK85w2aAGLFoVKLd8QGnVon5lLtkXQCoYaNL0wg04EGnyKkkKhPA=="], + "@vitest/expect": ["@vitest/expect@4.0.13", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.0.13", "@vitest/utils": "4.0.13", "chai": "^6.2.1", "tinyrainbow": "^3.0.3" } }, "sha512-zYtcnNIBm6yS7Gpr7nFTmq8ncowlMdOJkWLqYvhr/zweY6tFbDkDi8BPPOeHxEtK1rSI69H7Fd4+1sqvEGli6w=="], - "@vitest/mocker": ["@vitest/mocker@4.0.8", "", { "dependencies": { "@vitest/spy": "4.0.8", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-9FRM3MZCedXH3+pIh+ME5Up2NBBHDq0wqwhOKkN4VnvCiKbVxddqH9mSGPZeawjd12pCOGnl+lo/ZGHt0/dQSg=="], + "@vitest/mocker": ["@vitest/mocker@4.0.13", "", { "dependencies": { "@vitest/spy": "4.0.13", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0-0" }, "optionalPeers": ["msw", "vite"] }, "sha512-eNCwzrI5djoauklwP1fuslHBjrbR8rqIVbvNlAnkq1OTa6XT+lX68mrtPirNM9TnR69XUPt4puBCx2Wexseylg=="], - "@vitest/pretty-format": ["@vitest/pretty-format@4.0.8", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-qRrjdRkINi9DaZHAimV+8ia9Gq6LeGz2CgIEmMLz3sBDYV53EsnLZbJMR1q84z1HZCMsf7s0orDgZn7ScXsZKg=="], + "@vitest/pretty-format": ["@vitest/pretty-format@4.0.13", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-ooqfze8URWbI2ozOeLDMh8YZxWDpGXoeY3VOgcDnsUxN0jPyPWSUvjPQWqDGCBks+opWlN1E4oP1UYl3C/2EQA=="], - "@vitest/runner": ["@vitest/runner@4.0.8", "", { "dependencies": { "@vitest/utils": "4.0.8", "pathe": "^2.0.3" } }, "sha512-mdY8Sf1gsM8hKJUQfiPT3pn1n8RF4QBcJYFslgWh41JTfrK1cbqY8whpGCFzBl45LN028g0njLCYm0d7XxSaQQ=="], + "@vitest/runner": ["@vitest/runner@4.0.13", "", { "dependencies": { "@vitest/utils": "4.0.13", "pathe": "^2.0.3" } }, "sha512-9IKlAru58wcVaWy7hz6qWPb2QzJTKt+IOVKjAx5vb5rzEFPTL6H4/R9BMvjZ2ppkxKgTrFONEJFtzvnyEpiT+A=="], - "@vitest/snapshot": ["@vitest/snapshot@4.0.8", "", { "dependencies": { "@vitest/pretty-format": "4.0.8", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-Nar9OTU03KGiubrIOFhcfHg8FYaRaNT+bh5VUlNz8stFhCZPNrJvmZkhsr1jtaYvuefYFwK2Hwrq026u4uPWCw=="], + "@vitest/snapshot": ["@vitest/snapshot@4.0.13", "", { "dependencies": { "@vitest/pretty-format": "4.0.13", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-hb7Usvyika1huG6G6l191qu1urNPsq1iFc2hmdzQY3F5/rTgqQnwwplyf8zoYHkpt7H6rw5UfIw6i/3qf9oSxQ=="], - "@vitest/spy": ["@vitest/spy@4.0.8", "", {}, "sha512-nvGVqUunyCgZH7kmo+Ord4WgZ7lN0sOULYXUOYuHr55dvg9YvMz3izfB189Pgp28w0vWFbEEfNc/c3VTrqrXeA=="], + "@vitest/spy": ["@vitest/spy@4.0.13", "", {}, "sha512-hSu+m4se0lDV5yVIcNWqjuncrmBgwaXa2utFLIrBkQCQkt+pSwyZTPFQAZiiF/63j8jYa8uAeUZ3RSfcdWaYWw=="], - "@vitest/utils": ["@vitest/utils@4.0.8", "", { "dependencies": { "@vitest/pretty-format": "4.0.8", "tinyrainbow": "^3.0.3" } }, "sha512-pdk2phO5NDvEFfUTxcTP8RFYjVj/kfLSPIN5ebP2Mu9kcIMeAQTbknqcFEyBcC4z2pJlJI9aS5UQjcYfhmKAow=="], + "@vitest/utils": ["@vitest/utils@4.0.13", "", { "dependencies": { "@vitest/pretty-format": "4.0.13", "tinyrainbow": "^3.0.3" } }, "sha512-ydozWyQ4LZuu8rLp47xFUWis5VOKMdHjXCWhs1LuJsTNKww+pTHQNK4e0assIB9K80TxFyskENL6vCu3j34EYA=="], "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], @@ -503,7 +495,7 @@ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "bits-ui": ["bits-ui@2.14.2", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-YqpAJj/nRTZjf7IlgUC3QlepVZ7YFiAQWpZaYUOAZFW5Py+g5DYkhEDTdNFI5SReo7l1rct/nRpMK4pfL9Xffw=="], + "bits-ui": ["bits-ui@2.14.4", "", { "dependencies": { "@floating-ui/core": "^1.7.1", "@floating-ui/dom": "^1.7.1", "esm-env": "^1.1.2", "runed": "^0.35.1", "svelte-toolbelt": "^0.10.6", "tabbable": "^6.2.0" }, "peerDependencies": { "@internationalized/date": "^3.8.1", "svelte": "^5.33.0" } }, "sha512-W6kenhnbd/YVvur+DKkaVJ6GldE53eLewur5AhUCqslYQ0vjZr8eWlOfwZnMiPB+PF5HMVqf61vXBvmyrAmPWg=="], "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], @@ -511,11 +503,9 @@ "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], - "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], - "buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="], - "bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="], + "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], @@ -523,7 +513,7 @@ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], - "chai": ["chai@6.2.0", "", {}, "sha512-aUTnJc/JipRzJrNADXVvpVqi6CO0dn3nx4EVPxijri+fj3LUUDyZQOgVeW54Ob3Y1Xh9Iz8f+CgaCl8v0mn9bA=="], + "chai": ["chai@6.2.1", "", {}, "sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -541,7 +531,7 @@ "chroma-js": ["chroma-js@3.1.2", "", {}, "sha512-IJnETTalXbsLx1eKEgx19d5L6SRM7cH4vINw/99p/M11HCuXGRWL+6YmCm7FWFGIo6dtWuQoQi1dc5yQ7ESIHg=="], - "chrome-types": ["chrome-types@0.1.387", "", {}, "sha512-AK8Zm8JUb0dTu1ftcqsjcRpZTEaCjHnIfeWNVoRBVMSV4WRJDaWRbTBPIVCRmKZY/kc5Qiw5yfi0gwC7PcuQ5Q=="], + "chrome-types": ["chrome-types@0.1.390", "", {}, "sha512-D5WVDIXTFORzF+zjsDkLy7RJBN0bqEDtMovwSOW22w+mAY3PH85zViqwz6vfWPoznLzFvFjARDT3iEVoxAmF+Q=="], "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], @@ -577,8 +567,6 @@ "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], - "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], @@ -669,22 +657,16 @@ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], - "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], - "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], - "fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], - "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], @@ -751,8 +733,6 @@ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], - "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], - "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], @@ -815,7 +795,7 @@ "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], - "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -823,8 +803,6 @@ "mdn-data": ["mdn-data@2.0.30", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="], - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], - "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], @@ -835,8 +813,6 @@ "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], - "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], - "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], @@ -939,8 +915,6 @@ "quansync": ["quansync@0.2.10", "", {}, "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A=="], - "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], - "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "regex": ["regex@6.0.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA=="], @@ -953,14 +927,10 @@ "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], - "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], - "rolldown": ["rolldown@1.0.0-beta.9-commit.d91dfb5", "", { "dependencies": { "@oxc-project/runtime": "0.71.0", "@oxc-project/types": "0.71.0", "@rolldown/pluginutils": "1.0.0-beta.9-commit.d91dfb5", "ansis": "^4.0.0" }, "optionalDependencies": { "@rolldown/binding-darwin-arm64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-darwin-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-freebsd-x64": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.9-commit.d91dfb5", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.9-commit.d91dfb5" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-FHkj6gGEiEgmAXQchglofvUUdwj2Oiw603Rs+zgFAnn9Cb7T7z3fiaEc0DbN3ja4wYkW6sF2rzMEtC1V4BGx/g=="], "rollup": ["rollup@4.44.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.44.2", "@rollup/rollup-android-arm64": "4.44.2", "@rollup/rollup-darwin-arm64": "4.44.2", "@rollup/rollup-darwin-x64": "4.44.2", "@rollup/rollup-freebsd-arm64": "4.44.2", "@rollup/rollup-freebsd-x64": "4.44.2", "@rollup/rollup-linux-arm-gnueabihf": "4.44.2", "@rollup/rollup-linux-arm-musleabihf": "4.44.2", "@rollup/rollup-linux-arm64-gnu": "4.44.2", "@rollup/rollup-linux-arm64-musl": "4.44.2", "@rollup/rollup-linux-loongarch64-gnu": "4.44.2", "@rollup/rollup-linux-powerpc64le-gnu": "4.44.2", "@rollup/rollup-linux-riscv64-gnu": "4.44.2", "@rollup/rollup-linux-riscv64-musl": "4.44.2", "@rollup/rollup-linux-s390x-gnu": "4.44.2", "@rollup/rollup-linux-x64-gnu": "4.44.2", "@rollup/rollup-linux-x64-musl": "4.44.2", "@rollup/rollup-win32-arm64-msvc": "4.44.2", "@rollup/rollup-win32-ia32-msvc": "4.44.2", "@rollup/rollup-win32-x64-msvc": "4.44.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg=="], - "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], - "runed": ["runed@0.36.0", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0", "zod": "^4.1.0" }, "optionalPeers": ["@sveltejs/kit", "zod"] }, "sha512-CK84KPwAausPQEyWF9t6miCuNW5isAKPMswDsz7jhdueiZZ9du/UrgWc/aggLts8QuppT8KucryrHDFBAqk9Ww=="], "sade": ["sade@1.8.1", "", { "dependencies": { "mri": "^1.1.0" } }, "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A=="], @@ -1003,11 +973,11 @@ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], - "svelte": ["svelte@5.43.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-tPNp21nDWB0PSHE+VrTvEy9cFtDp2Q+ATxQoFomISEVdikZ1QZ69UqBPz/LlT+Oc8/LYS/COYwDQZrmZEUr+JQ=="], + "svelte": ["svelte@5.43.14", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-pHeUrp1A5S6RGaXhJB7PtYjL1VVjbVrJ2EfuAoPu9/1LeoMaJa/pcdCsCSb0gS4eUHAHnhCbUDxORZyvGK6kOQ=="], "svelte-adapter-bun": ["svelte-adapter-bun@1.0.1", "", { "dependencies": { "rolldown": "^1.0.0-beta.38" }, "peerDependencies": { "@sveltejs/kit": "^2.4.0", "typescript": "^5" } }, "sha512-tNOvfm8BGgG+rmEA7hkmqtq07v7zoo4skLQc+hIoQ79J+1fkEMpJEA2RzCIe3aPc8JdrsMJkv3mpiZPMsgahjA=="], - "svelte-check": ["svelte-check@4.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg=="], + "svelte-check": ["svelte-check@4.3.4", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-DVWvxhBrDsd+0hHWKfjP99lsSXASeOhHJYyuKOFYJcP7ThfSCKgjVarE8XfuMWpS5JV3AlDf+iK1YGGo2TACdw=="], "svelte-eslint-parser": ["svelte-eslint-parser@1.4.0", "", { "dependencies": { "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.0", "postcss": "^8.4.49", "postcss-scss": "^4.0.9", "postcss-selector-parser": "^7.0.0" }, "peerDependencies": { "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0" }, "optionalPeers": ["svelte"] }, "sha512-fjPzOfipR5S7gQ/JvI9r2H8y9gMGXO3JtmrylHLLyahEMquXI0lrebcjT+9/hNgDej0H7abTyox5HpHmW1PSWA=="], @@ -1031,8 +1001,6 @@ "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="], - "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - "totalist": ["totalist@3.0.1", "", {}, "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ=="], "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], @@ -1047,7 +1015,7 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.46.3", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.46.3", "@typescript-eslint/parser": "8.46.3", "@typescript-eslint/typescript-estree": "8.46.3", "@typescript-eslint/utils": "8.46.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA=="], + "typescript-eslint": ["typescript-eslint@8.48.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.48.0", "@typescript-eslint/parser": "8.48.0", "@typescript-eslint/typescript-estree": "8.48.0", "@typescript-eslint/utils": "8.48.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw=="], "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], @@ -1077,11 +1045,11 @@ "virtua": ["virtua@0.47.1", "", { "peerDependencies": { "react": ">=16.14.0", "react-dom": ">=16.14.0", "solid-js": ">=1.0", "svelte": ">=5.0", "vue": ">=3.2" }, "optionalPeers": ["react", "react-dom", "solid-js", "svelte", "vue"] }, "sha512-IGe/hnZJdywFtFqpmtvS25II/Ov7i4vWnyagdguxLPvM8bSLmfEZULChdmTYwfcWI2XnxX+VGV4GpgaOvGp+7g=="], - "vite": ["vite@7.2.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ=="], + "vite": ["vite@7.2.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w=="], "vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], - "vitest": ["vitest@4.0.8", "", { "dependencies": { "@vitest/expect": "4.0.8", "@vitest/mocker": "4.0.8", "@vitest/pretty-format": "4.0.8", "@vitest/runner": "4.0.8", "@vitest/snapshot": "4.0.8", "@vitest/spy": "4.0.8", "@vitest/utils": "4.0.8", "debug": "^4.4.3", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.8", "@vitest/browser-preview": "4.0.8", "@vitest/browser-webdriverio": "4.0.8", "@vitest/ui": "4.0.8", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-urzu3NCEV0Qa0Y2PwvBtRgmNtxhj5t5ULw7cuKhIHh3OrkKTLlut0lnBOv9qe5OvbkMH2g38G7KPDCTpIytBVg=="], + "vitest": ["vitest@4.0.13", "", { "dependencies": { "@vitest/expect": "4.0.13", "@vitest/mocker": "4.0.13", "@vitest/pretty-format": "4.0.13", "@vitest/runner": "4.0.13", "@vitest/snapshot": "4.0.13", "@vitest/spy": "4.0.13", "@vitest/utils": "4.0.13", "debug": "^4.4.3", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/debug": "^4.1.12", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.13", "@vitest/browser-preview": "4.0.13", "@vitest/browser-webdriverio": "4.0.13", "@vitest/ui": "4.0.13", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/debug", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-QSD4I0fN6uZQfftryIXuqvqgBxTvJ3ZNkF6RWECd82YGAYAfhcppBLFXzXJHQAAhVFyYEuFTrq6h0hQqjB7jIQ=="], "web": ["web@workspace:web"], @@ -1161,8 +1129,6 @@ "@sveltejs/vite-plugin-svelte-inspector/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], - "@tailwindcss/node/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="], @@ -1179,10 +1145,6 @@ "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], - "@vitest/mocker/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "@vitest/snapshot/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "bits-ui/runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="], "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], @@ -1193,12 +1155,8 @@ "esrap/@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.4", "", {}, "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw=="], - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], - "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "miniflare/acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="], "miniflare/undici": ["undici@5.29.0", "", { "dependencies": { "@fastify/busboy": "^2.0.0" } }, "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg=="], @@ -1211,14 +1169,10 @@ "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "svelte-check/fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], - "svelte-toolbelt/runed": ["runed@0.35.1", "", { "dependencies": { "dequal": "^2.0.3", "esm-env": "^1.0.0", "lz-string": "^1.5.0" }, "peerDependencies": { "@sveltejs/kit": "^2.21.0", "svelte": "^5.7.0" }, "optionalPeers": ["@sveltejs/kit"] }, "sha512-2F4Q/FZzbeJTFdIS/PuOoPRSm92sA2LhzTnv6FXhCoENb3huf5+fDuNOg1LNvGOouy3u/225qxmuJvcV3IZK5Q=="], "tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], - "vitest/magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - "wrangler/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], "youch/cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], diff --git a/web-extension/package.json b/web-extension/package.json index d8dbae3..57f1023 100644 --- a/web-extension/package.json +++ b/web-extension/package.json @@ -18,17 +18,17 @@ "devDependencies": { "@eslint/compat": "^2.0.0", "@eslint/js": "^9.39.1", - "@types/bun": "^1.3.2", + "@types/bun": "^1.3.3", "@types/webextension-polyfill": "^0.12.4", - "chrome-types": "^0.1.387", + "chrome-types": "^0.1.390", "eslint": "^9.39.1", "eslint-config-prettier": "^10.1.8", "globals": "^16.5.0", "prettier": "^3.6.2", "prettier-plugin-tailwindcss": "^0.7.1", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.3", - "vite": "^7.2.2" + "typescript-eslint": "^8.48.0", + "vite": "^7.2.4" }, "dependencies": { "@tailwindcss/vite": "^4.1.17", diff --git a/web/package.json b/web/package.json index 680ce18..1c88cae 100644 --- a/web/package.json +++ b/web/package.json @@ -19,12 +19,12 @@ "devDependencies": { "@eslint/compat": "^2.0.0", "@eslint/js": "^9.39.1", - "@iconify-json/octicon": "^1.2.17", + "@iconify-json/octicon": "^1.2.19", "@iconify/tailwind4": "^1.1.0", "@octokit/openapi-types": "^27.0.0", "@sveltejs/adapter-auto": "^7.0.0", "@sveltejs/adapter-cloudflare": "^7.2.4", - "@sveltejs/kit": "^2.48.4", + "@sveltejs/kit": "^2.49.0", "@sveltejs/vite-plugin-svelte": "^6.2.1", "@tailwindcss/vite": "^4.1.17", "@types/chroma-js": "^3.1.2", @@ -37,18 +37,18 @@ "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", "prettier-plugin-tailwindcss": "^0.7.1", - "svelte": "^5.43.4", + "svelte": "^5.43.14", "svelte-adapter-bun": "^1.0.1", - "svelte-check": "^4.3.3", + "svelte-check": "^4.3.4", "tailwindcss": "^4.1.17", "tw-animate-css": "^1.4.0", "typescript": "^5.9.3", - "typescript-eslint": "^8.46.3", - "vite": "^7.2.2", - "vitest": "^4.0.8" + "typescript-eslint": "^8.48.0", + "vite": "^7.2.4", + "vitest": "^4.0.13" }, "dependencies": { - "bits-ui": "^2.14.2", + "bits-ui": "^2.14.4", "chroma-js": "^3.1.2", "diff": "^8.0.2", "luxon": "^3.7.2", diff --git a/web/src/app.d.ts b/web/src/app.d.ts index d76242a..19b3cb1 100644 --- a/web/src/app.d.ts +++ b/web/src/app.d.ts @@ -5,7 +5,19 @@ declare global { // interface Error {} // interface Locals {} // interface PageData {} - // interface PageState {} + interface PageState { + /** + * whether this state entry loaded patches. when true, selection is likely to be unresolved, + * and scrollOffset is likely to be 0. + */ + initialLoad?: boolean; + scrollOffset?: number; + selection?: { + fileIdx: number; + lines?: LineSelection; + unresolvedLines?: UnresolvedLineSelection; + }; + } // interface Platform {} } } diff --git a/web/src/lib/components/diff/ConciseDiffView.svelte b/web/src/lib/components/diff/ConciseDiffView.svelte index bd1724c..068622a 100644 --- a/web/src/lib/components/diff/ConciseDiffView.svelte +++ b/web/src/lib/components/diff/ConciseDiffView.svelte @@ -2,6 +2,7 @@ import { type ConciseDiffViewProps, ConciseDiffViewState, + type DiffViewerPatchHunk, innerPatchLineTypeProps, type InnerPatchLineTypeProps, makeSearchSegments, @@ -13,9 +14,9 @@ type SearchSegment, } from "$lib/components/diff/concise-diff-view.svelte"; import Spinner from "$lib/components/Spinner.svelte"; - import { onDestroy } from "svelte"; import { type MutableValue } from "$lib/util"; import { box } from "svelte-toolbelt"; + import { boolAttr } from "runed"; let { rawPatchContent, @@ -28,10 +29,16 @@ searchQuery, searchMatchingLines, activeSearchResult = -1, + jumpToSearchResult = $bindable(false), cache, cacheKey, + unresolvedSelection, + selection = $bindable(), + jumpToSelection = $bindable(false), }: ConciseDiffViewProps = $props(); + const uid = $props.id(); + const parsedPatch = $derived.by(() => { if (rawPatchContent !== undefined) { return parseSinglePatch(rawPatchContent); @@ -42,12 +49,20 @@ }); const view = new ConciseDiffViewState({ + rootElementId: uid, + patch: box.with(() => parsedPatch), syntaxHighlighting: box.with(() => syntaxHighlighting), syntaxHighlightingTheme: box.with(() => syntaxHighlightingTheme), omitPatchHeaderOnlyHunks: box.with(() => omitPatchHeaderOnlyHunks), wordDiffs: box.with(() => wordDiffs), + unresolvedSelection: box.with(() => unresolvedSelection), + selection: box.with( + () => selection, + (v) => (selection = v), + ), + cache: box.with(() => cache), cacheKey: box.with(() => cacheKey), }); @@ -60,39 +75,6 @@ } } - let searchResultElements: HTMLSpanElement[] = $state([]); - let didInitialJump = $state(false); - let scheduledJump: ReturnType | undefined = undefined; - $effect(() => { - if (didInitialJump) { - return; - } - if (activeSearchResult >= 0 && searchResultElements[activeSearchResult] !== undefined) { - const element = searchResultElements[activeSearchResult]; - const anchorElement = element.closest("tr"); - // This is an exceptionally stupid and unreliable hack, but at least - // jumping to a result in a not-yet-loaded file works most of the time with a delay - // instead of never. - scheduledJump = setTimeout(() => { - if (scheduledJump !== undefined) { - clearTimeout(scheduledJump); - scheduledJump = undefined; - } - - if (anchorElement !== null) { - anchorElement.scrollIntoView({ block: "center", inline: "center" }); - } - }, 200); - didInitialJump = true; - } - }); - onDestroy(() => { - if (scheduledJump !== undefined) { - clearTimeout(scheduledJump); - scheduledJump = undefined; - } - }); - let searchSegments: Promise = $derived.by(async () => { if (!searchQuery || !searchMatchingLines) { return []; @@ -134,6 +116,21 @@ } return segments; }); + + let selectionMidpoint = $derived.by(() => { + if (!selection) return null; + const startIdx = selection.start.idx; + const endIdx = selection.end.idx; + return Math.floor((startIdx + endIdx) / 2); + }); + + let heightEstimateRem = $derived.by(() => { + if (!parsedPatch) return 1.25; + const rawLineCount = parsedPatch.hunks.reduce((sum, hunk) => sum + hunk.lines.length, 0); + const headerAndSpacerLines = parsedPatch.hunks.length * 2; + const totalLines = rawLineCount + headerAndSpacerLines; + return totalLines * 1.25; + }); {#snippet lineContent(line: PatchLine, lineType: PatchLineTypeProps, innerLineType: InnerPatchLineTypeProps)} @@ -165,7 +162,21 @@ {#each lineSearchSegments as searchSegment, index (index)} {#if searchSegment.highlighted} { + if (jumpToSearchResult && searchSegment.id === activeSearchResult) { + element.scrollIntoView({ block: "center", inline: "center" }); + jumpToSearchResult = false; + // See similar code & comment below around jumping to selections + //const scheduledJump = setTimeout(() => { + // jumpToSearchResult = false; + // element.scrollIntoView({ block: "center", inline: "center" }); + //}, 200); + //return () => { + // jumpToSearchResult = false; + // clearTimeout(scheduledJump); + //}; + } + }} class={{ "bg-[#d4a72c66]": searchSegment.id !== activeSearchResult, "bg-[#ff9632]": searchSegment.id === activeSearchResult, @@ -186,30 +197,79 @@ {/await} {/snippet} -{#snippet renderLine(line: PatchLine, hunkIndex: number, lineIndex: number)} +{#snippet renderLine(line: PatchLine, hunk: DiffViewerPatchHunk, hunkIndex: number, lineIndex: number)} {@const lineType = patchLineTypeProps[line.type]} -
+ {@const lineTypeSelectable = line.type !== PatchLineType.HEADER && line.type !== PatchLineType.SPACER} +
{getDisplayLineNo(line, line.oldLineNo)}
-
-
{getDisplayLineNo(line, line.newLineNo)}
+
+
+ {getDisplayLineNo(line, line.newLineNo)} +
-
+
{ + if (jumpToSelection && selection && selection.hunk === hunkIndex && selectionMidpoint === lineIndex) { + element.scrollIntoView({ block: "center", inline: "center" }); + jumpToSelection = false; + // Need to schedule because otherwise the vlist rendering surrounding elements may shift things + // and cause the element to scroll to the wrong position + // This is not 100% reliable but is good enough for now + //const scheduledJump = setTimeout(() => { + // jumpToSelection = false; + // element.scrollIntoView({ block: "center", inline: "center" }); + //}, 200); + //return () => { + // if (scheduledJump) { + // jumpToSelection = false; + // clearTimeout(scheduledJump); + // } + //}; + } + }} + > {@render lineContentWrapper(line, hunkIndex, lineIndex, lineType, innerPatchLineTypeProps[line.innerPatchLineType])}
{/snippet} {#await Promise.all([view.rootStyle, view.diffViewerPatch])} -
+
+ +
+ +
+
{:then [rootStyle, diffViewerPatch]}
{#each diffViewerPatch.hunks as hunk, hunkIndex (hunkIndex)} {#each hunk.lines as line, lineIndex (lineIndex)} - {@render renderLine(line, hunkIndex, lineIndex)} + {@render renderLine(line, hunk, hunkIndex, lineIndex)} {/each} {/each}
@@ -266,4 +326,19 @@ left: -0.75rem; top: 0; } + + .selected-indicator[data-selected] { + box-shadow: inset -4px 0 0 0 var(--hunk-header-fg); + } + .selected-indicator[data-selection-start] { + box-shadow: inset 0 1px 0 0 var(--hunk-header-fg); + } + .selected-indicator[data-selection-end] { + box-shadow: inset 0 -1px 0 0 var(--hunk-header-fg); + } + .selected-indicator[data-selection-start][data-selection-end] { + box-shadow: + inset 0 1px 0 0 var(--hunk-header-fg), + inset 0 -1px 0 0 var(--hunk-header-fg); + } diff --git a/web/src/lib/components/diff/concise-diff-view.svelte.ts b/web/src/lib/components/diff/concise-diff-view.svelte.ts index c44f2cc..fbf6385 100644 --- a/web/src/lib/components/diff/concise-diff-view.svelte.ts +++ b/web/src/lib/components/diff/concise-diff-view.svelte.ts @@ -16,32 +16,116 @@ import chroma from "chroma-js"; import { getEffectiveGlobalTheme } from "$lib/theme.svelte"; import { onDestroy } from "svelte"; import { DEFAULT_THEME_LIGHT } from "$lib/global-options.svelte"; +import type { WritableBoxedValues } from "svelte-toolbelt"; +import type { Attachment } from "svelte/attachments"; +import { on } from "svelte/events"; + +export interface UnresolvedLineRef { + /** + * line number in the patch data + */ + no: number; + /** + * - true: added or context line + * - false: removed line + */ + new: boolean; +} + +export interface LineRef extends UnresolvedLineRef { + /** + * line index in the diff viewer patch hunk + */ + idx: number; +} + +export interface HunkIndexedLineRef extends LineRef { + /** + * hunk index in the diff viewer patch + */ + hunkIdx: number; +} + +export interface LineSelection { + /** + * hunk index in the diff viewer patch + */ + hunk: number; + start: LineRef; + end: LineRef; +} + +export interface UnresolvedLineSelection { + start: UnresolvedLineRef; + end: UnresolvedLineRef; +} + +export function writeLineRef(ref: UnresolvedLineRef): string { + const prefix = ref.new ? "R" : "L"; + return prefix + ref.no.toString(); +} + +export function parseLineRef(string: string): UnresolvedLineRef | null { + if (string.length < 2) { + return null; + } + const prefix = string.substring(0, 1).toUpperCase(); + if (prefix !== "R" && prefix !== "L") { + return null; + } + const isNew = prefix === "R"; + const numberString = string.substring(1); + const number = Number.parseInt(numberString); + if (!Number.isFinite(number)) { + return null; + } + return { no: number, new: isNew }; +} + +export function resolveLineRef(ref: UnresolvedLineRef, hunks: DiffViewerPatchHunk[]): HunkIndexedLineRef | null { + for (let i = 0; i < hunks.length; i++) { + const hunk = hunks[i]; + for (let j = 0; j < hunk.lines.length; j++) { + const line = hunk.lines[j]; + if (line.type === PatchLineType.HEADER || line.type === PatchLineType.SPACER) { + continue; + } + if (line.oldLineNo === ref.no && !ref.new) { + return { hunkIdx: i, idx: j, no: line.oldLineNo, new: false }; + } + if (line.newLineNo === ref.no && ref.new) { + return { hunkIdx: i, idx: j, no: line.newLineNo, new: true }; + } + } + } + return null; +} -export type DiffViewerPatch = { +export interface DiffViewerPatch { hunks: DiffViewerPatchHunk[]; -}; +} -export type DiffViewerPatchHunk = { +export interface DiffViewerPatchHunk { lines: PatchLine[]; innerPatchHeaderChangesOnly?: boolean; -}; +} -export type PatchLine = { +export interface PatchLine { type: PatchLineType; content: LineSegment[]; lineBreak?: boolean; innerPatchLineType: InnerPatchLineType; oldLineNo?: number; newLineNo?: number; -}; +} -export type LineSegment = { +export interface LineSegment { text?: string | null; iconClass?: string | null; caption?: string | null; classes?: string; style?: string; -}; +} export enum PatchLineType { HEADER, @@ -57,11 +141,11 @@ export enum InnerPatchLineType { NONE, } -export type PatchLineTypeProps = { +export interface PatchLineTypeProps { classes: string; lineNoClasses: string; prefix: string; -}; +} export const patchLineTypeProps: Record = { [PatchLineType.HEADER]: { @@ -91,9 +175,9 @@ export const patchLineTypeProps: Record = { }, }; -export type InnerPatchLineTypeProps = { +export interface InnerPatchLineTypeProps { style: string; -}; +} export const innerPatchLineTypeProps: Record = { [InnerPatchLineType.ADD]: { @@ -1035,12 +1119,19 @@ export interface ConciseDiffViewProps { searchQuery?: string; searchMatchingLines?: () => Promise; activeSearchResult?: number; + jumpToSearchResult?: boolean; cache?: Map; cacheKey?: K; + + unresolvedSelection?: UnresolvedLineSelection; + selection?: LineSelection; + jumpToSelection?: boolean; } -export type ConciseDiffViewStateProps = ReadableBoxedValues<{ +export type ConciseDiffViewStateProps = { + rootElementId: string; +} & ReadableBoxedValues<{ patch: StructuredPatch; syntaxHighlighting: boolean; @@ -1050,7 +1141,12 @@ export type ConciseDiffViewStateProps = ReadableBoxedValues<{ cache: Map | undefined; cacheKey: K | undefined; -}>; + + unresolvedSelection: UnresolvedLineSelection | undefined; +}> & + WritableBoxedValues<{ + selection: LineSelection | undefined; + }>; export class ConciseDiffViewState { diffViewerPatch: Promise = $state(new Promise(() => [])); @@ -1059,6 +1155,10 @@ export class ConciseDiffViewState { private readonly props: ConciseDiffViewStateProps; + private selectionAnchor: { hunkIdx: number; lineIdx: number } | null = null; + private dragSelectionState: { hunk: DiffViewerPatchHunk; didMove: boolean } | null = null; + private suppressNextClick = false; + constructor(props: ConciseDiffViewStateProps) { this.props = props; @@ -1109,9 +1209,10 @@ export class ConciseDiffViewState { ); this.cachedState = new ConciseDiffViewCachedState(promise, this.props); promise.then( - () => { + (patch) => { // Don't replace a potentially completed promise with a pending one, wait until the replacement is ready for smooth transitions this.diffViewerPatch = promise; + this.resolveOrUpdateSelection(patch); }, () => { // Propagate errors @@ -1120,10 +1221,216 @@ export class ConciseDiffViewState { ); } + private resolveOrUpdateSelection(patch: DiffViewerPatch) { + if (this.props.unresolvedSelection.current) { + const unresolved = this.props.unresolvedSelection.current; + const start = resolveLineRef(unresolved.start, patch.hunks); + const end = resolveLineRef(unresolved.end, patch.hunks); + if (start && end && start.hunkIdx === end.hunkIdx) { + this.props.selection.current = { + hunk: start.hunkIdx, + start: start, + end: end, + }; + } + } else if (this.props.selection.current) { + const current = this.props.selection.current; + const start = resolveLineRef(current.start, patch.hunks); + const end = resolveLineRef(current.end, patch.hunks); + if (start && end && start.hunkIdx === end.hunkIdx) { + this.props.selection.current = { + hunk: start.hunkIdx, + start: start, + end: end, + }; + } else { + this.props.selection.current = undefined; + } + } + } + restore(state: ConciseDiffViewCachedState) { this.diffViewerPatch = state.diffViewerPatch; this.cachedState = state; } + + selectable(hunk: DiffViewerPatchHunk, hunkIdx: number, line: PatchLine, lineIdx: number): Attachment { + return (element) => { + if (line.type === PatchLineType.SPACER || line.type === PatchLineType.HEADER) { + return; + } + + const destroyPointerDown = on(element, "pointerdown", (e: PointerEvent) => { + if (e.button !== 0) return; // only handle left click + + if (e.shiftKey) { + // Handle shift+click for adjusting selection + this.updateSelection(hunk, hunkIdx, line, lineIdx, true); + } else { + // Handle regular click with drag support + this.startDrag(element, e.pointerId, hunk, hunkIdx, line, lineIdx); + } + }); + + return () => { + destroyPointerDown(); + }; + }; + } + + private startDrag(element: HTMLElement, pointerId: number, hunk: DiffViewerPatchHunk, hunkIdx: number, line: PatchLine, lineIdx: number) { + this.selectionAnchor = { hunkIdx, lineIdx }; + this.dragSelectionState = { hunk, didMove: false }; + + // Set initial selection + this.props.selection.current = { + hunk: hunkIdx, + start: this.createLineRef(line, lineIdx), + end: this.createLineRef(line, lineIdx), + }; + + // Capture pointer events to this element + element.setPointerCapture(pointerId); + + const abortController = new AbortController(); + const { signal } = abortController; + + on( + element, + "pointermove", + (e: PointerEvent) => { + if (!this.dragSelectionState || !this.selectionAnchor) return; + + // Get the root element for this diff view + const rootElement = document.getElementById(this.props.rootElementId); + if (!rootElement) return; + + // Get the element at the pointer position + const elementAtPoint = document.elementFromPoint(e.clientX, e.clientY); + if (!elementAtPoint) return; + + // Only process if the element is within this diff view's root element + if (!rootElement.contains(elementAtPoint)) return; + + const lineElement = elementAtPoint.closest("[data-hunk-idx][data-line-idx]") as HTMLElement | null; + if (!lineElement) return; + + const currentHunkIdx = Number(lineElement.dataset.hunkIdx); + const currentLineIdx = Number(lineElement.dataset.lineIdx); + + // Only allow dragging within the same hunk + if (currentHunkIdx !== this.selectionAnchor.hunkIdx || !Number.isFinite(currentHunkIdx) || !Number.isFinite(currentLineIdx)) { + return; + } + + if (this.dragSelectionState) { + this.dragSelectionState.didMove = true; + } + this.updateDragSelection(currentLineIdx); + }, + { signal }, + ); + + const onDragEnd = (e: PointerEvent) => { + element.releasePointerCapture(e.pointerId); + abortController.abort(); + + // Suppress the click event only if we actually moved during the drag + if (this.dragSelectionState?.didMove) { + this.suppressNextClick = true; + } + this.dragSelectionState = null; + }; + + on(element, "pointerup", onDragEnd, { signal }); + on(element, "pointercancel", onDragEnd, { signal }); + } + + private createLineRef(line: PatchLine, lineIdx: number): LineRef { + return { + idx: lineIdx, + no: line.newLineNo ?? line.oldLineNo!, + new: line.newLineNo !== undefined, + }; + } + + private updateDragSelection(currentLineIdx: number) { + if (!this.dragSelectionState || !this.selectionAnchor) return; + + const { hunk } = this.dragSelectionState; + const { hunkIdx, lineIdx: anchorIdx } = this.selectionAnchor; + const currentLine = hunk.lines[currentLineIdx]; + + if (currentLine.type === PatchLineType.SPACER || currentLine.type === PatchLineType.HEADER) { + return; + } + + const minIdx = Math.min(anchorIdx, currentLineIdx); + const maxIdx = Math.max(anchorIdx, currentLineIdx); + + this.props.selection.current = { + hunk: hunkIdx, + start: this.createLineRef(hunk.lines[minIdx], minIdx), + end: this.createLineRef(hunk.lines[maxIdx], maxIdx), + }; + } + + updateSelection(hunk: DiffViewerPatchHunk, hunkIdx: number, line: PatchLine, lineIdx: number, shift: boolean) { + const existingSelection = this.props.selection.current; + const clicked = this.createLineRef(line, lineIdx); + + // New selection (no shift or different hunk) + if (!shift || !existingSelection || existingSelection.hunk !== hunkIdx) { + this.props.selection.current = { hunk: hunkIdx, start: clicked, end: clicked }; + this.selectionAnchor = { hunkIdx, lineIdx }; + return; + } + + // Shift click on single-line selection: clear selection + if (existingSelection.start.idx === existingSelection.end.idx && lineIdx === existingSelection.start.idx) { + this.props.selection.current = undefined; + this.selectionAnchor = null; + return; + } + + // Determine anchor point: use existing anchor, or default to start of selection + let anchorIdx: number; + if (this.selectionAnchor && this.selectionAnchor.hunkIdx === hunkIdx) { + anchorIdx = this.selectionAnchor.lineIdx; + } else { + // No anchor or anchor is in different hunk, default to start of selection + anchorIdx = existingSelection.start.idx; + this.selectionAnchor = { hunkIdx, lineIdx: anchorIdx }; + } + + // Shift click: create selection from anchor to clicked line + const minIdx = Math.min(anchorIdx, lineIdx); + const maxIdx = Math.max(anchorIdx, lineIdx); + + this.props.selection.current = { + hunk: hunkIdx, + start: this.createLineRef(hunk.lines[minIdx], minIdx), + end: this.createLineRef(hunk.lines[maxIdx], maxIdx), + }; + } + + isSelected(hunkIdx: number, lineIdx: number): boolean { + const selection = this.props.selection.current; + if (selection === undefined || selection.hunk !== hunkIdx) return false; + return lineIdx >= selection.start.idx && lineIdx <= selection.end.idx; + } + + isSelectionStart(hunkIdx: number, lineIdx: number): boolean { + const selection = this.props.selection.current; + if (selection === undefined || selection.hunk !== hunkIdx) return false; + return lineIdx === selection.start.idx; + } + + isSelectionEnd(hunkIdx: number, lineIdx: number): boolean { + const selection = this.props.selection.current; + if (selection === undefined || selection.hunk !== hunkIdx) return false; + return lineIdx === selection.end.idx; + } } export type SearchSegment = { diff --git a/web/src/lib/components/menu-bar/MenuBar.svelte b/web/src/lib/components/menu-bar/MenuBar.svelte index 924c808..c8be409 100644 --- a/web/src/lib/components/menu-bar/MenuBar.svelte +++ b/web/src/lib/components/menu-bar/MenuBar.svelte @@ -118,5 +118,28 @@ + + Go + + + { + if (viewer.selection) { + viewer.scrollToFile(viewer.selection.file.index, { + focus: !viewer.selection.lines, + }); + if (viewer.selection.lines) { + viewer.jumpToSelection = true; + } + } + }} + > + Go to Selection + + + + diff --git a/web/src/lib/components/settings/ShikiThemeSelector.svelte b/web/src/lib/components/settings/ShikiThemeSelector.svelte index 099b34c..bb0adf0 100644 --- a/web/src/lib/components/settings/ShikiThemeSelector.svelte +++ b/web/src/lib/components/settings/ShikiThemeSelector.svelte @@ -20,17 +20,17 @@ - {capitalizeFirstLetter(mode)} theme + {capitalizeFirstLetter(mode)} theme
(triggerLabelW = e[0].target.scrollWidth)} aria-label="Current {mode} syntax highlighting theme" class="scrolling-text grow text-left text-nowrap" style="--scroll-distance: -{scrollDistance}px;" + {@attach resizeObserver((e) => (triggerLabelW = e[0].target.scrollWidth))} > {value}
diff --git a/web/src/lib/diff-viewer.svelte.ts b/web/src/lib/diff-viewer.svelte.ts index 7dfa5a2..5ad5bf6 100644 --- a/web/src/lib/diff-viewer.svelte.ts +++ b/web/src/lib/diff-viewer.svelte.ts @@ -9,9 +9,18 @@ import { parseMultiFilePatchGithub, } from "./github.svelte"; import { type StructuredPatch } from "diff"; -import { ConciseDiffViewCachedState, isNoNewlineAtEofLine, parseSinglePatch, patchHeaderDiffOnly } from "$lib/components/diff/concise-diff-view.svelte"; +import { + ConciseDiffViewCachedState, + isNoNewlineAtEofLine, + parseSinglePatch, + patchHeaderDiffOnly, + type LineSelection, + writeLineRef, + parseLineRef, + type UnresolvedLineSelection, +} from "$lib/components/diff/concise-diff-view.svelte"; import { countOccurrences, type FileTreeNodeData, makeFileTree, type LazyPromise, lazyPromise, animationFramePromise, yieldToBrowser } from "$lib/util"; -import { onDestroy, tick } from "svelte"; +import { onDestroy, onMount, tick } from "svelte"; import { type TreeNode, TreeState } from "$lib/components/tree/index.svelte"; import { VList } from "virtua/svelte"; import { Context, Debounced, watch } from "runed"; @@ -19,6 +28,9 @@ import { MediaQuery } from "svelte/reactivity"; import { ProgressBarState } from "$lib/components/progress-bar/index.svelte"; import { Keybinds } from "./keybinds.svelte"; import { LayoutState, type PersistentLayoutState } from "./layout.svelte"; +import { page } from "$app/state"; +import { afterNavigate, goto, pushState, replaceState } from "$app/navigation"; +import { type AfterNavigate } from "@sveltejs/kit"; export const GITHUB_URL_PARAM = "github_url"; export const PATCH_URL_PARAM = "patch_url"; @@ -86,6 +98,69 @@ export interface FileState { collapsed: boolean; } +export interface Selection { + file: FileDetails; + lines?: LineSelection; + unresolvedLines?: UnresolvedLineSelection; +} + +/** + * Checks whether two selections refer to the same unresolved lines + * in the same file. + * + * @param a selection a + * @param b selection b + * @returns true if the selections are semantically equal + */ +export function selectionsSemanticallyEqual(a: Selection | undefined, b: Selection | undefined): boolean { + if (!a && !b) return true; + if (!a || !b) return false; + if (a.file !== b.file) return false; + const linesA = a.lines ?? a.unresolvedLines; + const linesB = b.lines ?? b.unresolvedLines; + if (!linesA && !linesB) return true; + if (!linesA || !linesB) return false; + const startEquals = linesA.start.no === linesB.start.no && linesA.start.new === linesB.start.new; + if (!startEquals) return false; + const endEquals = linesA.end.no === linesB.end.no && linesA.end.new === linesB.end.new; + return endEquals; +} + +function makeUrlHashValue(selection: Selection): string { + let hash = encodeURIComponent(selection.file.toFile); + const lines = selection.lines ?? selection.unresolvedLines; + if (lines) { + hash += ":"; + hash += writeLineRef(lines.start); + hash += ":"; + hash += writeLineRef(lines.end); + } + return hash; +} + +interface UnresolvedSelection { + file: string; + lines?: UnresolvedLineSelection; +} + +function parseUrlHashValue(hash: string): UnresolvedSelection | null { + const parts = hash.split(":"); + if (parts.length === 1) { + return { + file: decodeURIComponent(parts[0]), + }; + } + if (parts.length !== 3) return null; + const file = decodeURIComponent(parts[0]); + const start = parseLineRef(parts[1]); + const end = parseLineRef(parts[2]); + if (!start || !end) return null; + return { + file, + lines: { start, end }, + }; +} + export interface ImageDiffDetails { fileA: LazyPromise | null; fileB: LazyPromise | null; @@ -186,18 +261,43 @@ export interface ViewerStatistics { fileRemovedLines: number[]; } -export interface GithubDiffMetadata { +export interface BaseDiffMetadata { + linkable: boolean; +} + +export interface GithubDiffMetadata extends BaseDiffMetadata { type: "github"; details: GithubDiff; } -export interface FileDiffMetadata { +export interface FileDiffMetadata extends BaseDiffMetadata { type: "file"; fileName: string; + url?: string; } export type DiffMetadata = GithubDiffMetadata | FileDiffMetadata; +export function updateSearchParams(params: URLSearchParams, metadata: DiffMetadata) { + if (metadata.type === "github") { + params.set(GITHUB_URL_PARAM, metadata.details.backlink); + } else { + params.delete(GITHUB_URL_PARAM); + } + if (metadata.type === "file" && metadata.url) { + params.set(PATCH_URL_PARAM, metadata.url); + } else { + params.delete(PATCH_URL_PARAM); + } +} + +export interface LoadPatchesOptions { + /** + * default: push + */ + state?: "push" | "replace"; +} + export class MultiFileDiffViewerState { private static readonly context = new Context("MultiFileDiffViewerState"); @@ -213,12 +313,18 @@ export class MultiFileDiffViewerState { diffMetadata: DiffMetadata | null = $state(null); fileDetails: FileDetails[] = $state([]); // Read-only state fileStates: FileState[] = $state([]); // Mutable state - readonly stats: ViewerStatistics = $derived(this.countStats()); + stats: ViewerStatistics = $state({ + addedLines: 0, + removedLines: 0, + fileAddedLines: [], + fileRemovedLines: [], + }); // Content search state searchQuery: string = $state(""); readonly searchQueryDebounced = new Debounced(() => this.searchQuery, 500); readonly searchResults: Promise = $derived(this.findSearchResults()); + jumpToSearchResult: boolean = $state(false); // File tree state tree: TreeState | undefined = $state(); @@ -229,10 +335,15 @@ export class MultiFileDiffViewerState { this.fileTreeFilterDebounced.current ? this.fileDetails.filter((f) => this.filterFile(f)) : this.fileDetails, ); + // Selection state + urlSelection: UnresolvedSelection | undefined = $state(); + selection: Selection | undefined = $state(); + jumpToSelection: boolean = $state(false); + // Misc. component state diffViewCache: Map = new Map(); vlist: VList | undefined = $state(); - readonly loadingState: LoadingState = $state(new LoadingState()); + readonly loadingState = new LoadingState(); readonly layoutState; // Transient state @@ -246,12 +357,64 @@ export class MultiFileDiffViewerState { // Make sure to revoke object URLs when the component is destroyed onDestroy(() => this.clearImages()); + onMount(() => { + let hash = page.url.hash; + if (hash.startsWith("#")) hash = hash.substring(1); + if (hash) { + this.urlSelection = parseUrlHashValue(hash) ?? undefined; + } + }); + + afterNavigate((nav) => this.afterNavigate(nav)); + + this.registerKeybinds(); + } + + private registerKeybinds() { const keybinds = new Keybinds(); keybinds.registerModifierBind("o", () => this.openOpenDiffDialog()); keybinds.registerModifierBind(",", () => this.openSettingsDialog()); keybinds.registerModifierBind("b", () => this.layoutState.toggleSidebar()); } + private afterNavigate(nav: AfterNavigate) { + if (!this.vlist) return; + + if (nav.type === "popstate") { + const selection = page.state.selection; + const file = selection ? this.fileDetails[selection.fileIdx] : undefined; + if (selection && file) { + this.selection = { + file, + lines: selection.lines, + unresolvedLines: selection.unresolvedLines, + }; + } else { + this.selection = undefined; + } + + if (page.state.initialLoad ?? false) { + if (this.selection) { + // TODO: restoring an unresolved selection does not work until something triggers resolveOrUpdateSelection + const hasLines = this.selection.lines || this.selection.unresolvedLines; + this.scrollToFile(this.selection.file.index, { + focus: !hasLines, + }); + if (hasLines) { + this.jumpToSelection = true; + } + } else { + this.vlist.scrollTo(0); + } + } else { + const scrollOffset = page.state.scrollOffset; + if (scrollOffset !== undefined) { + this.vlist.scrollTo(scrollOffset); + } + } + } + } + openOpenDiffDialog() { this.openDiffDialogOpen = true; this.settingsDialogOpen = false; @@ -297,6 +460,51 @@ export class MultiFileDiffViewerState { } } + getSelection(file: FileDetails) { + if (this.selection?.file.index === file.index) { + return this.selection; + } + return null; + } + + private createPageState(opts?: { initialLoad?: boolean }): App.PageState { + return { + initialLoad: opts?.initialLoad ?? false, + scrollOffset: this.vlist?.getScrollOffset(), + selection: this.selection + ? { + fileIdx: this.selection.file.index, + lines: $state.snapshot(this.selection.lines), + unresolvedLines: $state.snapshot(this.selection.unresolvedLines), + } + : undefined, + }; + } + + setSelection(file: FileDetails, lines: LineSelection | undefined) { + const oldSelection = this.selection; + this.selection = { file, lines }; + + if (!selectionsSemanticallyEqual(oldSelection, this.selection)) { + goto(`?${page.url.searchParams}#${makeUrlHashValue(this.selection)}`, { + keepFocus: true, + state: this.createPageState(), + }); + } + } + + clearSelection() { + const oldSelection = this.selection; + this.selection = undefined; + + if (oldSelection !== undefined) { + goto(`?${page.url.searchParams}`, { + keepFocus: true, + state: this.createPageState(), + }); + } + } + scrollToFile(index: number, options: { autoExpand?: boolean; smooth?: boolean; focus?: boolean } = {}) { if (!this.vlist) return; @@ -333,7 +541,10 @@ export class MultiFileDiffViewerState { requestAnimationFrame(() => { const fileElement = document.getElementById(`file-${fileIdx}`); const resultElement = fileElement?.querySelector(`[data-match-id='${idx}']`) as HTMLElement | null | undefined; - if (!resultElement) return; + if (!resultElement) { + this.jumpToSearchResult = true; + return; + } resultElement.scrollIntoView({ block: "center", inline: "center" }); }); } @@ -365,6 +576,8 @@ export class MultiFileDiffViewerState { private clear(clearMeta: boolean = true) { // Reset state + this.selection = undefined; + this.jumpToSelection = false; this.fileStates = []; if (clearMeta) { this.diffMetadata = null; @@ -374,7 +587,7 @@ export class MultiFileDiffViewerState { this.vlist?.scrollToIndex(0, { align: "start" }); } - async loadPatches(meta: () => Promise, patches: () => Promise>) { + async loadPatches(meta: () => Promise, patches: () => Promise>, opts?: LoadPatchesOptions) { if (this.loadingState.loading) { alert("Already loading patches, please wait."); return false; @@ -437,17 +650,52 @@ export class MultiFileDiffViewerState { } tempDetails.sort(compareFileDetails); - this.fileDetails.push(...tempDetails); + const statesArray: FileState[] = []; for (let i = 0; i < tempDetails.length; i++) { const details = tempDetails[i]; details.index = i; const state = tempStates.get(details.fromFile); if (state) { - this.fileStates.push(state); + statesArray.push(state); } } + this.fileDetails = tempDetails; + this.fileStates = statesArray; + this.stats = this.countStats(); + + await tick(); + await animationFramePromise(); + + const urlSelection = this.urlSelection; + this.urlSelection = undefined; + const selectedFile = urlSelection !== undefined ? this.fileDetails.find((f) => f.toFile === urlSelection.file) : undefined; + + if (urlSelection && selectedFile && this.diffMetadata.linkable) { + this.jumpToSelection = urlSelection.lines !== undefined; + this.selection = { + file: selectedFile, + unresolvedLines: urlSelection.lines, + }; + this.scrollToFile(selectedFile.index, { + focus: !urlSelection.lines, + }); + await animationFramePromise(); + } + + const newUrl = new URL(page.url); + updateSearchParams(newUrl.searchParams, this.diffMetadata); + if (this.selection) { + newUrl.hash = makeUrlHashValue(this.selection); + } + const link = `${newUrl.search}${newUrl.hash}`; + if (opts?.state === "replace") { + replaceState(link, this.createPageState({ initialLoad: true })); + } else { + pushState(link, this.createPageState({ initialLoad: true })); + } + return true; } catch (e) { this.clear(); // Clear any partially loaded state @@ -463,30 +711,31 @@ export class MultiFileDiffViewerState { } } - private async loadPatchesGithub(resultOrPromise: Promise | GithubDiffResult) { + private async loadPatchesGithub(resultOrPromise: Promise | GithubDiffResult, opts?: LoadPatchesOptions) { return await this.loadPatches( async () => { const result = resultOrPromise instanceof Promise ? await resultOrPromise : resultOrPromise; - return { type: "github", details: await result.info }; + return { linkable: true, type: "github", details: await result.info }; }, async () => { const result = resultOrPromise instanceof Promise ? await resultOrPromise : resultOrPromise; return parseMultiFilePatchGithub(await result.info, await result.response, this.loadingState); }, + opts, ); } // TODO fails for initial commit? // handle matched github url - async loadFromGithubApi(match: Array): Promise { + async loadFromGithubApi(match: Array, opts?: LoadPatchesOptions): Promise { const [url, owner, repo, type, id] = match; const token = getGithubToken(); try { if (type === "commit") { - return await this.loadPatchesGithub(fetchGithubCommitDiff(token, owner, repo, id.split("/")[0])); + return await this.loadPatchesGithub(fetchGithubCommitDiff(token, owner, repo, id.split("/")[0]), opts); } else if (type === "pull") { - return await this.loadPatchesGithub(fetchGithubPRComparison(token, owner, repo, id.split("/")[0])); + return await this.loadPatchesGithub(fetchGithubPRComparison(token, owner, repo, id.split("/")[0]), opts); } else if (type === "compare") { let refs = id.split("..."); if (refs.length !== 2) { @@ -498,7 +747,7 @@ export class MultiFileDiffViewerState { } const base = refs[0]; const head = refs[1]; - return await this.loadPatchesGithub(fetchGithubComparison(token, owner, repo, base, head)); + return await this.loadPatchesGithub(fetchGithubComparison(token, owner, repo, base, head), opts); } } catch (error) { console.error(error); diff --git a/web/src/lib/open-diff-dialog.svelte.ts b/web/src/lib/open-diff-dialog.svelte.ts index 99043de..b2bdf6b 100644 --- a/web/src/lib/open-diff-dialog.svelte.ts +++ b/web/src/lib/open-diff-dialog.svelte.ts @@ -2,9 +2,7 @@ import type { WritableBoxedValues } from "svelte-toolbelt"; import { DirectoryEntry, FileEntry, MultimodalFileInputState, type MultimodalFileInputValueMetadata } from "./components/files/index.svelte"; import { SvelteSet } from "svelte/reactivity"; import { type FileStatus } from "$lib/github.svelte"; -import { page } from "$app/state"; -import { goto } from "$app/navigation"; -import { GITHUB_URL_PARAM, makeImageDetails, makeTextDetails, MultiFileDiffViewerState, PATCH_URL_PARAM } from "$lib/diff-viewer.svelte"; +import { makeImageDetails, makeTextDetails, MultiFileDiffViewerState, type LoadPatchesOptions } from "$lib/diff-viewer.svelte"; import { binaryFileDummyDetails, bytesEqual, isBinaryFile, isImageFile, parseMultiFilePatch } from "$lib/util"; import { createTwoFilesPatch } from "diff"; @@ -79,7 +77,7 @@ export class OpenDiffDialogState { this.props.open.current = false; const success = await this.viewer.loadPatches( async () => { - return { type: "file", fileName: `${fileAMeta.name}...${fileBMeta.name}.patch` }; + return { linkable: false, type: "file", fileName: `${fileAMeta.name}...${fileBMeta.name}.patch` }; }, async () => { const isImageDiff = isImageFile(fileAMeta.name) && isImageFile(fileBMeta.name); @@ -107,7 +105,6 @@ export class OpenDiffDialogState { this.props.open.current = true; return; } - await this.updateUrlParams(); } async *generateSingleImagePatch(fileAMeta: MultimodalFileInputValueMetadata, fileBMeta: MultimodalFileInputValueMetadata, blobA: Blob, blobB: Blob) { @@ -152,7 +149,7 @@ export class OpenDiffDialogState { this.props.open.current = false; const success = await this.viewer.loadPatches( async () => { - return { type: "file", fileName: `${dirA.fileName}...${dirB.fileName}.patch` }; + return { linkable: false, type: "file", fileName: `${dirA.fileName}...${dirB.fileName}.patch` }; }, async () => { return this.generateDirPatches(dirA, dirB); @@ -162,7 +159,6 @@ export class OpenDiffDialogState { this.props.open.current = true; return; } - await this.updateUrlParams(); } async *generateDirPatches(dirA: DirectoryEntry, dirB: DirectoryEntry) { @@ -258,7 +254,7 @@ export class OpenDiffDialogState { return into; } - async handlePatchFile() { + async handlePatchFile(opts?: LoadPatchesOptions) { if (!this.patchFile || !this.patchFile.metadata) { alert("No patch file selected."); return; @@ -276,24 +272,25 @@ export class OpenDiffDialogState { this.props.open.current = false; const success = await this.viewer.loadPatches( async () => { - return { type: "file", fileName: meta.name }; + return { + linkable: this.patchFile.mode === "url", + type: "file", + fileName: meta.name, + url: this.patchFile.mode === "url" ? this.patchFile.url : undefined, + }; }, async () => { return parseMultiFilePatch(text, this.viewer.loadingState); }, + opts, ); if (!success) { this.props.open.current = true; return; } - let patchUrl: string | undefined; - if (this.patchFile.mode === "url") { - patchUrl = this.patchFile.url; - } - await this.updateUrlParams({ patchUrl }); } - async handleGithubUrl() { + async handleGithubUrl(opts?: LoadPatchesOptions) { const url = new URL(this.githubUrl); // exclude hash + query params const test = url.protocol + "//" + url.hostname + url.pathname; @@ -308,26 +305,10 @@ export class OpenDiffDialogState { this.githubUrl = match[0]; this.props.open.current = false; - const success = await this.viewer.loadFromGithubApi(match); + const success = await this.viewer.loadFromGithubApi(match, opts); if (success) { - await this.updateUrlParams({ githubUrl: this.githubUrl }); return; } this.props.open.current = true; } - - async updateUrlParams(opts: { githubUrl?: string; patchUrl?: string } = {}) { - const newUrl = new URL(page.url); - if (opts.githubUrl) { - newUrl.searchParams.set(GITHUB_URL_PARAM, opts.githubUrl); - } else { - newUrl.searchParams.delete(GITHUB_URL_PARAM); - } - if (opts.patchUrl) { - newUrl.searchParams.set(PATCH_URL_PARAM, opts.patchUrl); - } else { - newUrl.searchParams.delete(PATCH_URL_PARAM); - } - await goto(`?${newUrl.searchParams}`); - } } diff --git a/web/src/lib/util.ts b/web/src/lib/util.ts index ba1fb67..9ee88aa 100644 --- a/web/src/lib/util.ts +++ b/web/src/lib/util.ts @@ -3,9 +3,9 @@ import type { FileStatus } from "./github.svelte"; import type { TreeNode } from "$lib/components/tree/index.svelte"; import type { BundledLanguage, SpecialLanguage } from "shiki"; import { onMount } from "svelte"; -import type { Action } from "svelte/action"; import type { ReadableBox } from "svelte-toolbelt"; import { on } from "svelte/events"; +import { type Attachment } from "svelte/attachments"; export type Getter = () => T; @@ -463,15 +463,15 @@ export function watchLocalStorage(key: string, callback: (newValue: string | nul }); } -export const resizeObserver: Action = (node, callback) => { - const observer = new ResizeObserver(callback); - observer.observe(node); - return { - destroy() { +export function resizeObserver(callback: ResizeObserverCallback): Attachment { + return (element) => { + const observer = new ResizeObserver(callback); + observer.observe(element); + return () => { observer.disconnect(); - }, + }; }; -}; +} export function animationFramePromise() { return new Promise((resolve) => { diff --git a/web/src/routes/DiffWrapper.svelte b/web/src/routes/DiffWrapper.svelte index d6e337b..5bd04d3 100644 --- a/web/src/routes/DiffWrapper.svelte +++ b/web/src/routes/DiffWrapper.svelte @@ -66,8 +66,21 @@ searchQuery={viewer.searchQueryDebounced.current} searchMatchingLines={() => viewer.searchResults.then((r) => r.lines.get(value))} activeSearchResult={viewer.activeSearchResult && viewer.activeSearchResult.file === value ? viewer.activeSearchResult.idx : undefined} + bind:jumpToSearchResult={viewer.jumpToSearchResult} cache={viewer.diffViewCache} cacheKey={value} + unresolvedSelection={viewer.getSelection(value)?.unresolvedLines} + bind:selection={ + () => viewer.getSelection(value)?.lines, + (lines) => { + if (lines === undefined && viewer.selection?.file === value) { + viewer.clearSelection(); + } else { + viewer.setSelection(value, lines); + } + } + } + bind:jumpToSelection={viewer.jumpToSelection} />
{/if} diff --git a/web/src/routes/FileHeader.svelte b/web/src/routes/FileHeader.svelte index e59a3b4..85192bf 100644 --- a/web/src/routes/FileHeader.svelte +++ b/web/src/routes/FileHeader.svelte @@ -4,6 +4,7 @@ import { type FileDetails, MultiFileDiffViewerState } from "$lib/diff-viewer.svelte"; import { GlobalOptions } from "$lib/global-options.svelte"; import { Popover, Button } from "bits-ui"; + import { boolAttr } from "runed"; import { tick } from "svelte"; interface Props { @@ -43,6 +44,16 @@ } return { baseFileUrl: undefined, headFileUrl: undefined }; }); + + function selectHeader() { + viewer.scrollToFile(index, { autoExpand: false, smooth: true }); + viewer.setSelection(value, undefined); + } + + let selected = $derived.by(() => { + const sel = viewer.getSelection(value); + return sel && sel.lines === undefined && sel.unresolvedLines === undefined; + }); {#snippet fileName()} @@ -111,11 +122,15 @@
viewer.scrollToFile(index, { autoExpand: false, smooth: true })} - onkeyup={(event) => event.key === "Enter" && viewer.scrollToFile(index, { autoExpand: false, smooth: true })} + onclick={() => selectHeader()} + onkeyup={(event) => event.key === "Enter" && selectHeader()} + data-selected={boolAttr(selected)} > {#if value.type === "text"} @@ -131,3 +146,24 @@ {/if}
+ + diff --git a/web/src/routes/OpenDiffDialog.svelte b/web/src/routes/OpenDiffDialog.svelte index 3c2c45c..ee20945 100644 --- a/web/src/routes/OpenDiffDialog.svelte +++ b/web/src/routes/OpenDiffDialog.svelte @@ -26,12 +26,12 @@ if (githubUrlParam !== null) { instance.githubUrl = githubUrlParam; - await instance.handleGithubUrl(); + await instance.handleGithubUrl({ state: "replace" }); } else if (patchUrlParam !== null) { instance.patchFile.reset(); instance.patchFile.mode = "url"; instance.patchFile.url = patchUrlParam; - await instance.handlePatchFile(); + await instance.handlePatchFile({ state: "replace" }); } else { open = true; } diff --git a/web/src/routes/Sidebar.svelte b/web/src/routes/Sidebar.svelte index abba6df..f315f2c 100644 --- a/web/src/routes/Sidebar.svelte +++ b/web/src/routes/Sidebar.svelte @@ -3,8 +3,9 @@ import { type FileDetails, getFileStatusProps, MultiFileDiffViewerState, staticSidebar } from "$lib/diff-viewer.svelte"; import Tree from "$lib/components/tree/Tree.svelte"; import { type TreeNode } from "$lib/components/tree/index.svelte"; - import { type Action } from "svelte/action"; import { on } from "svelte/events"; + import { type Attachment } from "svelte/attachments"; + import { boolAttr } from "runed"; const viewer = MultiFileDiffViewerState.get(); @@ -20,30 +21,31 @@ } } - const focusFileDoubleClick: Action = (div, { index }) => { - const destroyDblclick = on(div, "dblclick", (event) => { - const element: HTMLElement = event.target as HTMLElement; - if (element.tagName.toLowerCase() !== "input") { - viewer.scrollToFile(index, { focus: true }); - if (!staticSidebar.current) { - viewer.layoutState.sidebarCollapsed = true; + function focusFileDoubleClick(value: FileDetails): Attachment { + return (div) => { + const destroyDblclick = on(div, "dblclick", (event) => { + const element: HTMLElement = event.target as HTMLElement; + if (element.tagName.toLowerCase() !== "input") { + viewer.scrollToFile(value.index, { focus: true }); + viewer.setSelection(value, undefined); + if (!staticSidebar.current) { + viewer.layoutState.sidebarCollapsed = true; + } } - } - }); - const destoryMousedown = on(div, "mousedown", (event) => { - const element: HTMLElement = event.target as HTMLElement; - if (element.tagName.toLowerCase() !== "input" && event.detail === 2) { - // Don't select text on double click - event.preventDefault(); - } - }); - return { - destroy() { + }); + const destroyMousedown = on(div, "mousedown", (event) => { + const element: HTMLElement = event.target as HTMLElement; + if (element.tagName.toLowerCase() !== "input" && event.detail === 2) { + // Don't select text on double click + event.preventDefault(); + } + }); + return () => { destroyDblclick(); - destoryMousedown(); - }, + destroyMousedown(); + }; }; - }; + }
@@ -75,13 +77,14 @@
{#snippet fileSnippet(value: FileDetails)}
scrollToFileClick(e, value.index)} - use:focusFileDoubleClick={{ index: value.index }} + {@attach focusFileDoubleClick(value)} onkeydown={(e) => e.key === "Enter" && viewer.scrollToFile(value.index)} role="button" tabindex="0" id={"file-tree-file-" + value.index} + data-selected={boolAttr(viewer.selection?.file.index === value.index)} >