diff --git a/deno.lock b/deno.lock index 0e050e4b..54ed5035 100644 --- a/deno.lock +++ b/deno.lock @@ -8,7 +8,7 @@ "jsr:@cliffy/table@1.0.0-rc.8": "1.0.0-rc.8", "jsr:@oak/commons@0.10": "0.10.2", "jsr:@oak/oak@16.0.0": "16.0.0", - "jsr:@std/assert@*": "1.0.14", + "jsr:@std/assert@*": "1.0.16", "jsr:@std/assert@0.223": "0.223.0", "jsr:@std/assert@0.224": "0.224.0", "jsr:@std/assert@0.226": "0.226.0", @@ -17,7 +17,6 @@ "jsr:@std/cli@~0.224.7": "0.224.7", "jsr:@std/crypto@0.223": "0.223.0", "jsr:@std/crypto@0.224": "0.224.0", - "jsr:@std/datetime@*": "0.225.5", "jsr:@std/datetime@~0.225.3": "0.225.5", "jsr:@std/encoding@0.223": "0.223.0", "jsr:@std/encoding@1.0.0-rc.2": "1.0.0-rc.2", @@ -26,14 +25,13 @@ "jsr:@std/fmt@^1.0.5": "1.0.8", "jsr:@std/fmt@~0.225.4": "0.225.6", "jsr:@std/fmt@~1.0.2": "1.0.8", - "jsr:@std/fs@*": "1.0.19", + "jsr:@std/fs@*": "1.0.20", "jsr:@std/fs@0.229.3": "0.229.3", - "jsr:@std/fs@^1.0.11": "1.0.19", + "jsr:@std/fs@^1.0.11": "1.0.20", "jsr:@std/http@0.223": "0.223.0", "jsr:@std/http@0.224": "0.224.5", "jsr:@std/http@0.224.5": "0.224.5", - "jsr:@std/internal@^1.0.10": "1.0.10", - "jsr:@std/internal@^1.0.9": "1.0.10", + "jsr:@std/internal@^1.0.12": "1.0.12", "jsr:@std/io@0.223": "0.223.0", "jsr:@std/io@~0.225.2": "0.225.2", "jsr:@std/log@~0.224.6": "0.224.14", @@ -47,25 +45,22 @@ "jsr:@std/path@1.0.0-rc.1": "1.0.0-rc.1", "jsr:@std/path@1.0.0-rc.2": "1.0.0-rc.2", "jsr:@std/path@1.0.3": "1.0.3", - "jsr:@std/path@^1.1.1": "1.1.2", + "jsr:@std/path@^1.1.3": "1.1.3", "jsr:@std/streams@~0.224.5": "0.224.5", "jsr:@std/text@~1.0.7": "1.0.16", - "jsr:@std/yaml@*": "1.0.9", - "jsr:@std/yaml@^1.0.5": "1.0.9", + "jsr:@std/yaml@^1.0.5": "1.0.10", "jsr:@torm/sqlite@^1.9.7": "1.9.7", - "npm:@deno/vite-plugin@1.0.6": "1.0.6_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", - "npm:@jsr/andykais__ts-rpc@~0.2.4": "0.2.4_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3__@types+node@24.2.0_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", + "npm:@deno/vite-plugin@1.0.6": "1.0.6_vite@7.1.8__picomatch@4.0.3", + "npm:@jsr/andykais__ts-rpc@~0.2.4": "0.2.4_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___picomatch@4.0.3_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3", "npm:@jsr/torm__sqlite@^1.9.7": "1.9.7", "npm:@opentelemetry/api@1.9.0": "1.9.0", "npm:@opentelemetry/api@^1.9.0": "1.9.0", - "npm:@sveltejs/kit@2.43.7": "2.43.7_@opentelemetry+api@1.9.0_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3__@types+node@24.2.0_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_acorn@8.15.0_@types+node@24.2.0", - "npm:@sveltejs/kit@2.5.24": "2.5.24_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3__@types+node@24.2.0_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", - "npm:@sveltejs/vite-plugin-svelte@6.2.1": "6.2.1_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", - "npm:@tailwindcss/vite@4.1.14": "4.1.14_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0", - "npm:@types/node@*": "24.2.0", - "npm:esbuild@*": "0.25.10", + "npm:@sveltejs/kit@2.43.7": "2.43.7_@opentelemetry+api@1.9.0_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___picomatch@4.0.3_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3_acorn@8.15.0", + "npm:@sveltejs/kit@2.5.24": "2.5.24_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___picomatch@4.0.3_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3", + "npm:@sveltejs/vite-plugin-svelte@6.2.1": "6.2.1_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3", + "npm:@tailwindcss/vite@4.1.14": "4.1.14_vite@7.1.8__picomatch@4.0.3", + "npm:esbuild@*": "0.25.12", "npm:path-to-regexp@6.2.1": "6.2.1", - "npm:sv@*": "0.9.6", "npm:svelte-check@4.3.2": "4.3.2_svelte@5.39.8__acorn@8.15.0_typescript@5.9.3", "npm:svelte@5.39.8": "5.39.8_acorn@8.15.0", "npm:svelte@~5.39.8": "5.39.8_acorn@8.15.0", @@ -74,7 +69,7 @@ "npm:ts-pattern@5.2.0": "5.2.0", "npm:typescript-svelte-plugin@0.3.50": "0.3.50_svelte@5.39.8__acorn@8.15.0_typescript@5.9.3", "npm:typescript@5.9.3": "5.9.3", - "npm:vite@7.1.8": "7.1.8_@types+node@24.2.0_picomatch@4.0.3", + "npm:vite@7.1.8": "7.1.8_picomatch@4.0.3", "npm:zod@4.1.11": "4.1.11", "npm:zod@^3.23.8": "3.25.76" }, @@ -144,10 +139,10 @@ "@std/assert@0.226.0": { "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" }, - "@std/assert@1.0.14": { - "integrity": "68d0d4a43b365abc927f45a9b85c639ea18a9fab96ad92281e493e4ed84abaa4", + "@std/assert@1.0.16": { + "integrity": "6a7272ed1eaa77defe76e5ff63ca705d9c495077e2d5fd0126d2b53fc5bd6532", "dependencies": [ - "jsr:@std/internal@^1.0.10" + "jsr:@std/internal" ] }, "@std/bytes@0.223.0": { @@ -196,11 +191,11 @@ "jsr:@std/path@1.0.0-rc.1" ] }, - "@std/fs@1.0.19": { - "integrity": "051968c2b1eae4d2ea9f79a08a3845740ef6af10356aff43d3e2ef11ed09fb06", + "@std/fs@1.0.20": { + "integrity": "e953206aae48d46ee65e8783ded459f23bec7dd1f3879512911c35e5484ea187", "dependencies": [ - "jsr:@std/internal@^1.0.9", - "jsr:@std/path@^1.1.1" + "jsr:@std/internal", + "jsr:@std/path@^1.1.3" ] }, "@std/http@0.223.0": { @@ -222,8 +217,8 @@ "jsr:@std/streams" ] }, - "@std/internal@1.0.10": { - "integrity": "e3be62ce42cab0e177c27698e5d9800122f67b766a0bea6ca4867886cbde8cf7" + "@std/internal@1.0.12": { + "integrity": "972a634fd5bc34b242024402972cd5143eac68d8dffaca5eaa4dba30ce17b027" }, "@std/io@0.223.0": { "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", @@ -269,10 +264,10 @@ "@std/path@1.0.3": { "integrity": "cd89d014ce7eb3742f2147b990f6753ee51d95276bfc211bc50c860c1bc7df6f" }, - "@std/path@1.1.2": { - "integrity": "c0b13b97dfe06546d5e16bf3966b1cadf92e1cc83e56ba5476ad8b498d9e3038", + "@std/path@1.1.3": { + "integrity": "b015962d82a5e6daea980c32b82d2c40142149639968549c649031a230b1afb3", "dependencies": [ - "jsr:@std/internal@^1.0.10" + "jsr:@std/internal" ] }, "@std/streams@0.224.5": { @@ -281,8 +276,8 @@ "@std/text@1.0.16": { "integrity": "ddb9853b75119a2473857d691cf1ec02ad90793a2e8b4a4ac49d7354281a0cf8" }, - "@std/yaml@1.0.9": { - "integrity": "6bad3dc766dd85b4b37eabcba81b6aa4eac7a392792ae29abcfb0f90602d55bb" + "@std/yaml@1.0.10": { + "integrity": "245706ea3511cc50c8c6d00339c23ea2ffa27bd2c7ea5445338f8feff31fa58e" }, "@torm/sqlite@1.9.7": { "integrity": "e927d867c46e46ae7b4eac9ee6fae7ab46f82abf81b99c6a98d3bbe51e75d3d4", @@ -293,139 +288,139 @@ } }, "npm": { - "@deno/vite-plugin@1.0.6_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "@deno/vite-plugin@1.0.6_vite@7.1.8__picomatch@4.0.3": { "integrity": "sha512-Sh5XqvFuKAwjARTesi0n6xRpEXm1V0UeqKh+SxIrexCofxOaieNDMqXZD02RiZCg0mrJ43V8eCMuVrDfq6mLmg==", "dependencies": [ "vite" ] }, - "@esbuild/aix-ppc64@0.25.10": { - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "@esbuild/aix-ppc64@0.25.12": { + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "os": ["aix"], "cpu": ["ppc64"] }, - "@esbuild/android-arm64@0.25.10": { - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "@esbuild/android-arm64@0.25.12": { + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "os": ["android"], "cpu": ["arm64"] }, - "@esbuild/android-arm@0.25.10": { - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "@esbuild/android-arm@0.25.12": { + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "os": ["android"], "cpu": ["arm"] }, - "@esbuild/android-x64@0.25.10": { - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "@esbuild/android-x64@0.25.12": { + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "os": ["android"], "cpu": ["x64"] }, - "@esbuild/darwin-arm64@0.25.10": { - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "@esbuild/darwin-arm64@0.25.12": { + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "os": ["darwin"], "cpu": ["arm64"] }, - "@esbuild/darwin-x64@0.25.10": { - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "@esbuild/darwin-x64@0.25.12": { + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "os": ["darwin"], "cpu": ["x64"] }, - "@esbuild/freebsd-arm64@0.25.10": { - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "@esbuild/freebsd-arm64@0.25.12": { + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "os": ["freebsd"], "cpu": ["arm64"] }, - "@esbuild/freebsd-x64@0.25.10": { - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "@esbuild/freebsd-x64@0.25.12": { + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "os": ["freebsd"], "cpu": ["x64"] }, - "@esbuild/linux-arm64@0.25.10": { - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "@esbuild/linux-arm64@0.25.12": { + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "os": ["linux"], "cpu": ["arm64"] }, - "@esbuild/linux-arm@0.25.10": { - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "@esbuild/linux-arm@0.25.12": { + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "os": ["linux"], "cpu": ["arm"] }, - "@esbuild/linux-ia32@0.25.10": { - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "@esbuild/linux-ia32@0.25.12": { + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "os": ["linux"], "cpu": ["ia32"] }, - "@esbuild/linux-loong64@0.25.10": { - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "@esbuild/linux-loong64@0.25.12": { + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "os": ["linux"], "cpu": ["loong64"] }, - "@esbuild/linux-mips64el@0.25.10": { - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "@esbuild/linux-mips64el@0.25.12": { + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "os": ["linux"], "cpu": ["mips64el"] }, - "@esbuild/linux-ppc64@0.25.10": { - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "@esbuild/linux-ppc64@0.25.12": { + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "os": ["linux"], "cpu": ["ppc64"] }, - "@esbuild/linux-riscv64@0.25.10": { - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "@esbuild/linux-riscv64@0.25.12": { + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "os": ["linux"], "cpu": ["riscv64"] }, - "@esbuild/linux-s390x@0.25.10": { - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "@esbuild/linux-s390x@0.25.12": { + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "os": ["linux"], "cpu": ["s390x"] }, - "@esbuild/linux-x64@0.25.10": { - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "@esbuild/linux-x64@0.25.12": { + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "os": ["linux"], "cpu": ["x64"] }, - "@esbuild/netbsd-arm64@0.25.10": { - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "@esbuild/netbsd-arm64@0.25.12": { + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", "os": ["netbsd"], "cpu": ["arm64"] }, - "@esbuild/netbsd-x64@0.25.10": { - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "@esbuild/netbsd-x64@0.25.12": { + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "os": ["netbsd"], "cpu": ["x64"] }, - "@esbuild/openbsd-arm64@0.25.10": { - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "@esbuild/openbsd-arm64@0.25.12": { + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", "os": ["openbsd"], "cpu": ["arm64"] }, - "@esbuild/openbsd-x64@0.25.10": { - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "@esbuild/openbsd-x64@0.25.12": { + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "os": ["openbsd"], "cpu": ["x64"] }, - "@esbuild/openharmony-arm64@0.25.10": { - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "@esbuild/openharmony-arm64@0.25.12": { + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@esbuild/sunos-x64@0.25.10": { - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "@esbuild/sunos-x64@0.25.12": { + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "os": ["sunos"], "cpu": ["x64"] }, - "@esbuild/win32-arm64@0.25.10": { - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "@esbuild/win32-arm64@0.25.12": { + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "os": ["win32"], "cpu": ["arm64"] }, - "@esbuild/win32-ia32@0.25.10": { - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "@esbuild/win32-ia32@0.25.12": { + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "os": ["win32"], "cpu": ["ia32"] }, - "@esbuild/win32-x64@0.25.10": { - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "@esbuild/win32-x64@0.25.12": { + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "os": ["win32"], "cpu": ["x64"] }, @@ -462,11 +457,11 @@ "@jridgewell/sourcemap-codec" ] }, - "@jsr/andykais__ts-rpc@0.2.4_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3__@types+node@24.2.0_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "@jsr/andykais__ts-rpc@0.2.4_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___picomatch@4.0.3_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3": { "integrity": "sha512-q8DSNIQLpPw/z+VcVcY+rrbRsmzZmw+bwopW4u95buXRLO/Bet8uYRddroAA8AI5zZkvuHJnxlNppwUmIFU5rA==", "dependencies": [ "@jsr/oak__oak", - "@sveltejs/kit@2.5.24_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3__@types+node@24.2.0_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0" + "@sveltejs/kit@2.5.24_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___picomatch@4.0.3_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3" ], "tarball": "https://npm.jsr.io/~/11/@jsr/andykais__ts-rpc/0.2.4.tgz" }, @@ -514,7 +509,7 @@ "@jsr/std__assert@0.226.0": { "integrity": "sha512-xCuUFDfHkIZd96glKgjZbnYFqu6blu8Y53SyvDMlFDJm1Y/j+/FcW6xq7TzGFIaF5B9QecIlDfamfhzA8ZdVbg==", "dependencies": [ - "@jsr/std__internal@1.0.10" + "@jsr/std__internal@1.0.12" ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__assert/0.226.0.tgz" }, @@ -525,9 +520,9 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__async/0.223.0.tgz" }, - "@jsr/std__async@1.0.14": { - "integrity": "sha512-aIG8W3TOmW+lKdAJA5w56qASu9EiUmBXbhW6eAlSEUBid+KVESGqQygFFg+awt/c8K+qobVM6M/u3SbIy0NyUQ==", - "tarball": "https://npm.jsr.io/~/11/@jsr/std__async/1.0.14.tgz" + "@jsr/std__async@1.0.15": { + "integrity": "sha512-GW6tMWwF79kKKVths4SWcju1cFykhcE+jiV6/k2IOW45611ji+Yomm0MZuW7JR4Oug+GXF0POrsAiSqL8k/9PQ==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__async/1.0.15.tgz" }, "@jsr/std__bytes@0.223.0": { "integrity": "sha512-6bhkazZZPE8iYa3unUKEd0Nk4o0k9pWe4VAjyQVqgx+hbMjLCWQHhdNCW9ySbWUbqBVuI/V+FdWGzxdfAk/HjQ==", @@ -609,7 +604,7 @@ "@jsr/std__http@0.224.5": { "integrity": "sha512-PomDgsxwBoCkqTCG7e1Sz4QCAgNFhEGf71GWNercppQlgKtzp6zxsDgcJ4+vrqHIq5W8L1eqJWiceNkUUkMZjQ==", "dependencies": [ - "@jsr/std__async@1.0.14", + "@jsr/std__async@1.0.15", "@jsr/std__cli@0.224.7", "@jsr/std__encoding@1.0.0-rc.2", "@jsr/std__fmt@0.225.6", @@ -627,9 +622,9 @@ ], "tarball": "https://npm.jsr.io/~/11/@jsr/std__internal/0.224.0.tgz" }, - "@jsr/std__internal@1.0.10": { - "integrity": "sha512-fmD6yKep/sMnB2yPQU/REZG7Z4N9SZwcUBNnceo4QkXk67l3JEfxHoROQ/YHeVSOmq6x55Ra6nuMjz2ib3nj3g==", - "tarball": "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.10.tgz" + "@jsr/std__internal@1.0.12": { + "integrity": "sha512-6xReMW9p+paJgqoFRpOE2nogJFvzPfaLHLIlyADYjKMUcwDyjKZxryIbgcU+gxiTygn8yCjld1HoI0ET4/iZeA==", + "tarball": "https://npm.jsr.io/~/11/@jsr/std__internal/1.0.12.tgz" }, "@jsr/std__io@0.223.0": { "integrity": "sha512-VUPpfHeLvhooQFyujLSj6/KDCqgF+joBpb6eO9dg+JJWvy5wHj1Nl2EMRQe6Pl8nNVUwjt+ZraydkGTdlDS1jw==", @@ -704,126 +699,126 @@ "@polka/url@1.0.0-next.29": { "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==" }, - "@rollup/rollup-android-arm-eabi@4.52.3": { - "integrity": "sha512-h6cqHGZ6VdnwliFG1NXvMPTy/9PS3h8oLh7ImwR+kl+oYnQizgjxsONmmPSb2C66RksfkfIxEVtDSEcJiO0tqw==", + "@rollup/rollup-android-arm-eabi@4.53.5": { + "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==", "os": ["android"], "cpu": ["arm"] }, - "@rollup/rollup-android-arm64@4.52.3": { - "integrity": "sha512-wd+u7SLT/u6knklV/ifG7gr5Qy4GUbH2hMWcDauPFJzmCZUAJ8L2bTkVXC2niOIxp8lk3iH/QX8kSrUxVZrOVw==", + "@rollup/rollup-android-arm64@4.53.5": { + "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==", "os": ["android"], "cpu": ["arm64"] }, - "@rollup/rollup-darwin-arm64@4.52.3": { - "integrity": "sha512-lj9ViATR1SsqycwFkJCtYfQTheBdvlWJqzqxwc9f2qrcVrQaF/gCuBRTiTolkRWS6KvNxSk4KHZWG7tDktLgjg==", + "@rollup/rollup-darwin-arm64@4.53.5": { + "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==", "os": ["darwin"], "cpu": ["arm64"] }, - "@rollup/rollup-darwin-x64@4.52.3": { - "integrity": "sha512-+Dyo7O1KUmIsbzx1l+4V4tvEVnVQqMOIYtrxK7ncLSknl1xnMHLgn7gddJVrYPNZfEB8CIi3hK8gq8bDhb3h5A==", + "@rollup/rollup-darwin-x64@4.53.5": { + "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==", "os": ["darwin"], "cpu": ["x64"] }, - "@rollup/rollup-freebsd-arm64@4.52.3": { - "integrity": "sha512-u9Xg2FavYbD30g3DSfNhxgNrxhi6xVG4Y6i9Ur1C7xUuGDW3banRbXj+qgnIrwRN4KeJ396jchwy9bCIzbyBEQ==", + "@rollup/rollup-freebsd-arm64@4.53.5": { + "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==", "os": ["freebsd"], "cpu": ["arm64"] }, - "@rollup/rollup-freebsd-x64@4.52.3": { - "integrity": "sha512-5M8kyi/OX96wtD5qJR89a/3x5x8x5inXBZO04JWhkQb2JWavOWfjgkdvUqibGJeNNaz1/Z1PPza5/tAPXICI6A==", + "@rollup/rollup-freebsd-x64@4.53.5": { + "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==", "os": ["freebsd"], "cpu": ["x64"] }, - "@rollup/rollup-linux-arm-gnueabihf@4.52.3": { - "integrity": "sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==", + "@rollup/rollup-linux-arm-gnueabihf@4.53.5": { + "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==", "os": ["linux"], "cpu": ["arm"] }, - "@rollup/rollup-linux-arm-musleabihf@4.52.3": { - "integrity": "sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==", + "@rollup/rollup-linux-arm-musleabihf@4.53.5": { + "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==", "os": ["linux"], "cpu": ["arm"] }, - "@rollup/rollup-linux-arm64-gnu@4.52.3": { - "integrity": "sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==", + "@rollup/rollup-linux-arm64-gnu@4.53.5": { + "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==", "os": ["linux"], "cpu": ["arm64"] }, - "@rollup/rollup-linux-arm64-musl@4.52.3": { - "integrity": "sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==", + "@rollup/rollup-linux-arm64-musl@4.53.5": { + "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==", "os": ["linux"], "cpu": ["arm64"] }, - "@rollup/rollup-linux-loong64-gnu@4.52.3": { - "integrity": "sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==", + "@rollup/rollup-linux-loong64-gnu@4.53.5": { + "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==", "os": ["linux"], "cpu": ["loong64"] }, - "@rollup/rollup-linux-ppc64-gnu@4.52.3": { - "integrity": "sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==", + "@rollup/rollup-linux-ppc64-gnu@4.53.5": { + "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==", "os": ["linux"], "cpu": ["ppc64"] }, - "@rollup/rollup-linux-riscv64-gnu@4.52.3": { - "integrity": "sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==", + "@rollup/rollup-linux-riscv64-gnu@4.53.5": { + "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==", "os": ["linux"], "cpu": ["riscv64"] }, - "@rollup/rollup-linux-riscv64-musl@4.52.3": { - "integrity": "sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==", + "@rollup/rollup-linux-riscv64-musl@4.53.5": { + "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==", "os": ["linux"], "cpu": ["riscv64"] }, - "@rollup/rollup-linux-s390x-gnu@4.52.3": { - "integrity": "sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==", + "@rollup/rollup-linux-s390x-gnu@4.53.5": { + "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==", "os": ["linux"], "cpu": ["s390x"] }, - "@rollup/rollup-linux-x64-gnu@4.52.3": { - "integrity": "sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==", + "@rollup/rollup-linux-x64-gnu@4.53.5": { + "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==", "os": ["linux"], "cpu": ["x64"] }, - "@rollup/rollup-linux-x64-musl@4.52.3": { - "integrity": "sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==", + "@rollup/rollup-linux-x64-musl@4.53.5": { + "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==", "os": ["linux"], "cpu": ["x64"] }, - "@rollup/rollup-openharmony-arm64@4.52.3": { - "integrity": "sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==", + "@rollup/rollup-openharmony-arm64@4.53.5": { + "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==", "os": ["openharmony"], "cpu": ["arm64"] }, - "@rollup/rollup-win32-arm64-msvc@4.52.3": { - "integrity": "sha512-+zteHZdoUYLkyYKObGHieibUFLbttX2r+58l27XZauq0tcWYYuKUwY2wjeCN9oK1Um2YgH2ibd6cnX/wFD7DuA==", + "@rollup/rollup-win32-arm64-msvc@4.53.5": { + "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==", "os": ["win32"], "cpu": ["arm64"] }, - "@rollup/rollup-win32-ia32-msvc@4.52.3": { - "integrity": "sha512-of1iHkTQSo3kr6dTIRX6t81uj/c/b15HXVsPcEElN5sS859qHrOepM5p9G41Hah+CTqSh2r8Bm56dL2z9UQQ7g==", + "@rollup/rollup-win32-ia32-msvc@4.53.5": { + "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==", "os": ["win32"], "cpu": ["ia32"] }, - "@rollup/rollup-win32-x64-gnu@4.52.3": { - "integrity": "sha512-s0hybmlHb56mWVZQj8ra9048/WZTPLILKxcvcq+8awSZmyiSUZjjem1AhU3Tf4ZKpYhK4mg36HtHDOe8QJS5PQ==", + "@rollup/rollup-win32-x64-gnu@4.53.5": { + "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==", "os": ["win32"], "cpu": ["x64"] }, - "@rollup/rollup-win32-x64-msvc@4.52.3": { - "integrity": "sha512-zGIbEVVXVtauFgl3MRwGWEN36P5ZGenHRMgNw88X5wEhEBpq0XrMEZwOn07+ICrwM17XO5xfMZqh0OldCH5VTA==", + "@rollup/rollup-win32-x64-msvc@4.53.5": { + "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==", "os": ["win32"], "cpu": ["x64"] }, - "@standard-schema/spec@1.0.0": { - "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==" + "@standard-schema/spec@1.1.0": { + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" }, - "@sveltejs/acorn-typescript@1.0.6_acorn@8.15.0": { - "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", + "@sveltejs/acorn-typescript@1.0.8_acorn@8.15.0": { + "integrity": "sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==", "dependencies": [ "acorn" ] }, - "@sveltejs/kit@2.43.7_@opentelemetry+api@1.9.0_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3__@types+node@24.2.0_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_acorn@8.15.0_@types+node@24.2.0": { + "@sveltejs/kit@2.43.7_@opentelemetry+api@1.9.0_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___picomatch@4.0.3_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3_acorn@8.15.0": { "integrity": "sha512-6trpyltB9XZNkM8cfVHG9U2urAH4NPD7UeO0wiBvZjD8gHj6w9bVeWnBQgnO8LPNpzOhSlwnZDk355OOAa/9Zw==", "dependencies": [ "@opentelemetry/api", @@ -849,7 +844,7 @@ ], "bin": true }, - "@sveltejs/kit@2.5.24_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3__@types+node@24.2.0_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "@sveltejs/kit@2.5.24_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___picomatch@4.0.3_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3": { "integrity": "sha512-Nr2oxsCsDfEkdS/zzQQQbsPYTbu692Qs3/iE3L7VHzCVjG2+WujF9oMUozWI7GuX98KxYSoPMlAsfmDLSg44hQ==", "dependencies": [ "@sveltejs/vite-plugin-svelte", @@ -871,7 +866,7 @@ "scripts": true, "bin": true }, - "@sveltejs/vite-plugin-svelte-inspector@5.0.1_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___@types+node@24.2.0___picomatch@4.0.3__@types+node@24.2.0_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "@sveltejs/vite-plugin-svelte-inspector@5.0.1_@sveltejs+vite-plugin-svelte@6.2.1__svelte@5.39.8___acorn@8.15.0__vite@7.1.8___picomatch@4.0.3_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3": { "integrity": "sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==", "dependencies": [ "@sveltejs/vite-plugin-svelte", @@ -880,7 +875,7 @@ "vite" ] }, - "@sveltejs/vite-plugin-svelte@6.2.1_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "@sveltejs/vite-plugin-svelte@6.2.1_svelte@5.39.8__acorn@8.15.0_vite@7.1.8__picomatch@4.0.3": { "integrity": "sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==", "dependencies": [ "@sveltejs/vite-plugin-svelte-inspector", @@ -985,7 +980,7 @@ ], "scripts": true }, - "@tailwindcss/vite@4.1.14_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "@tailwindcss/vite@4.1.14_vite@7.1.8__picomatch@4.0.3": { "integrity": "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA==", "dependencies": [ "@tailwindcss/node", @@ -1000,12 +995,6 @@ "@types/estree@1.0.8": { "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" }, - "@types/node@24.2.0": { - "integrity": "sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==", - "dependencies": [ - "undici-types" - ] - }, "acorn@8.15.0": { "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "bin": true @@ -1043,21 +1032,21 @@ "deepmerge@4.3.1": { "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, - "detect-libc@2.1.1": { - "integrity": "sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==" + "detect-libc@2.1.2": { + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" }, - "devalue@5.3.2": { - "integrity": "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==" + "devalue@5.6.1": { + "integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==" }, - "enhanced-resolve@5.18.3": { - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", + "enhanced-resolve@5.18.4": { + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "dependencies": [ "graceful-fs", "tapable" ] }, - "esbuild@0.25.10": { - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "esbuild@0.25.12": { + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "optionalDependencies": [ "@esbuild/aix-ppc64", "@esbuild/android-arm", @@ -1092,8 +1081,8 @@ "esm-env@1.2.2": { "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==" }, - "esrap@2.1.0": { - "integrity": "sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==", + "esrap@2.2.1": { + "integrity": "sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==", "dependencies": [ "@jridgewell/sourcemap-codec" ] @@ -1208,8 +1197,8 @@ "locate-character@3.0.0": { "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==" }, - "magic-string@0.30.19": { - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "magic-string@0.30.21": { + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dependencies": [ "@jridgewell/sourcemap-codec" ] @@ -1256,8 +1245,8 @@ "readdirp@4.1.2": { "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==" }, - "rollup@4.52.3": { - "integrity": "sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==", + "rollup@4.53.5": { + "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==", "dependencies": [ "@types/estree" ], @@ -1297,8 +1286,8 @@ "scule@1.3.0": { "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==" }, - "set-cookie-parser@2.7.1": { - "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==" + "set-cookie-parser@2.7.2": { + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==" }, "sirv@2.0.4": { "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", @@ -1319,10 +1308,6 @@ "source-map-js@1.2.1": { "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" }, - "sv@0.9.6": { - "integrity": "sha512-PlulS/0MKK68MXk9fBhIDHDKCI2wFgz7QyaHo9mmNjVOLA7JvTS1KTy9hpxpLj7/EYKeh6tSBFrSnJ/g8tsD+A==", - "bin": true - }, "svelte-check@4.3.2_svelte@5.39.8__acorn@8.15.0_typescript@5.9.3": { "integrity": "sha512-71udP5w2kaSTcX8iV0hn3o2FWlabQHhJTJLIQrCqMsrcOeDUO2VhCQKKCA8AMVHSPwdxLEWkUWh9OKxns5PD9w==", "dependencies": [ @@ -1336,8 +1321,8 @@ ], "bin": true }, - "svelte2tsx@0.7.44_svelte@5.39.8__acorn@8.15.0_typescript@5.9.3": { - "integrity": "sha512-opuH+bCboss0/ncxnfAO+qt0IAprxc8OqwuC7otafWeO5CHjJ6UAAwvQmu/+xjpCSarX8pQKydXQuoJmbCDcTg==", + "svelte2tsx@0.7.45_svelte@5.39.8__acorn@8.15.0_typescript@5.9.3": { + "integrity": "sha512-cSci+mYGygYBHIZLHlm/jYlEc1acjAHqaQaDFHdEBpUueM9kSTnPpvPtSl5VkJOU1qSJ7h1K+6F/LIUYiqC8VA==", "dependencies": [ "dedent-js", "scule", @@ -1370,15 +1355,16 @@ "tapable@2.3.0": { "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==" }, - "tar@7.5.1": { - "integrity": "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==", + "tar@7.5.2": { + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", "dependencies": [ "@isaacs/fs-minipass", "chownr", "minipass", "minizlib", "yallist" - ] + ], + "deprecated": true }, "tiny-glob@0.2.9": { "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", @@ -1414,13 +1400,9 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "bin": true }, - "undici-types@7.10.0": { - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" - }, - "vite@7.1.8_@types+node@24.2.0_picomatch@4.0.3": { + "vite@7.1.8_picomatch@4.0.3": { "integrity": "sha512-oBXvfSHEOL8jF+R9Am7h59Up07kVVGH1NrFGFoEG6bPDZP3tGpQhvkBpy5x7U6+E6wZCu9OihsWgJqDbQIm8LQ==", "dependencies": [ - "@types/node", "esbuild", "fdir", "picomatch", @@ -1431,12 +1413,9 @@ "optionalDependencies": [ "fsevents" ], - "optionalPeers": [ - "@types/node" - ], "bin": true }, - "vitefu@1.1.1_vite@7.1.8__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0": { + "vitefu@1.1.1_vite@7.1.8__picomatch@4.0.3": { "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", "dependencies": [ "vite" diff --git a/packages/core/src/actions/lib/base.ts b/packages/core/src/actions/lib/base.ts index 5d13c333..4c140bd9 100644 --- a/packages/core/src/actions/lib/base.ts +++ b/packages/core/src/actions/lib/base.ts @@ -249,7 +249,8 @@ abstract class Actions extends Emitter extends Emitter) { const group = tag.group ?? '' const color = get_hash_color(group, 'hsl') diff --git a/packages/core/src/actions/series_actions.ts b/packages/core/src/actions/series_actions.ts index 6743d859..c3d896be 100644 --- a/packages/core/src/actions/series_actions.ts +++ b/packages/core/src/actions/series_actions.ts @@ -1,5 +1,5 @@ import { Actions, type MediaSeriesResponse, type SeriesSearchResponse, type UpdateEditor } from '~/actions/lib/base.ts' -import { inputs, outputs, parsers } from '~/inputs/mod.ts' +import { inputs, parsers } from '~/inputs/mod.ts' import type * as result_types from '~/models/lib/result_types.ts' import * as errors from '~/lib/errors.ts' import * as torm from '@torm/sqlite' @@ -55,7 +55,7 @@ class SeriesActions extends Actions { const transaction_result = transaction() // no thumbnails should exist yet, so we give it limit: 0 - return this.#get_media_series_response({series_id: transaction_result.media_reference.id }) + return this.get_media_series_response({series_id: transaction_result.media_reference.id }) } public update = (series_id: number, media_info?: inputs.MediaInfo, tags?: inputs.Tag[], editing?: UpdateEditor): MediaSeriesResponse => { @@ -88,7 +88,7 @@ class SeriesActions extends Actions { }) transaction() - return this.#get_media_series_response({series_id: parsed.series_id}) + return this.get_media_series_response({series_id: parsed.series_id}) } public upsert = (media_info?: inputs.MediaSeriesInfo, tags?: inputs.Tag[]): MediaSeriesResponse => { @@ -149,7 +149,7 @@ class SeriesActions extends Actions { public get = (params: inputs.SeriesGet): MediaSeriesResponse => { const parsed = parsers.SeriesGet.parse(params) - return this.#get_media_series_response(parsed) + return this.get_media_series_response(parsed) } public search = (params: inputs.SeriesSearch): result_types.PaginatedResult => { @@ -194,17 +194,6 @@ class SeriesActions extends Actions { } } - #get_media_series_response(params: outputs.SeriesGet): MediaSeriesResponse { - const media_reference = this.models.MediaReference.select_one_media_series({id: params.series_id, media_series_name: params.series_name}) - const tags = this.models.Tag.select_all({media_reference_id: media_reference.id}) - return { - media_type: 'media_series', - media_reference, - tags, - thumbnails: this.models.MediaThumbnail.select_many({series_id: media_reference.id, limit: 0}), - } - } - #map_series_records_to_responses(records: ReturnType, thumbnail_limit: number, keypoint_tag_id: number | undefined): SeriesSearchResponse[] { const results: SeriesSearchResponse[] = records.results.map(row => { const tags = this.models.Tag.select_all({media_reference_id: row.id}) diff --git a/packages/web/src/routes/browse/components/Footer.svelte b/packages/web/src/lib/components/browse_media/Footer.svelte similarity index 100% rename from packages/web/src/routes/browse/components/Footer.svelte rename to packages/web/src/lib/components/browse_media/Footer.svelte diff --git a/packages/web/src/lib/components/browse_media/MediaBrowserLayout.svelte b/packages/web/src/lib/components/browse_media/MediaBrowserLayout.svelte new file mode 100644 index 00000000..1cd4f8a5 --- /dev/null +++ b/packages/web/src/lib/components/browse_media/MediaBrowserLayout.svelte @@ -0,0 +1,49 @@ + + + + +
+ {@render header({ controller })} +
+ +
+ + {@render media_list({ controller })} +
+
+
+
+ + diff --git a/packages/web/src/routes/browse/components/MediaDetails.svelte b/packages/web/src/lib/components/browse_media/MediaDetails.svelte similarity index 100% rename from packages/web/src/routes/browse/components/MediaDetails.svelte rename to packages/web/src/lib/components/browse_media/MediaDetails.svelte diff --git a/packages/web/src/lib/components/browse_media/MediaTile.svelte b/packages/web/src/lib/components/browse_media/MediaTile.svelte new file mode 100644 index 00000000..baf7d9d0 --- /dev/null +++ b/packages/web/src/lib/components/browse_media/MediaTile.svelte @@ -0,0 +1,124 @@ + + +
+
+
+
{ + if (e.key === 'Enter') { + media_selections.set_current_selection(result, result_index) + } + }} + role="button" + tabindex="0" + use:focusable={!media_selections.current_selection.show && media_selections.current_selection.result_index === result_index} + onclick={e => media_selections.set_current_selection(result, result_index)} + > + {#if settings.ui.media_list.thumbnail_shape === 'original'} + Thumbnail for media reference {result.media_reference?.id} + {:else} + Thumbnail for media reference {result.media_reference?.id} + {/if} +
+ + +
+ {#if result.media_type === 'media_file'} + {#if result.media_file.media_type === 'VIDEO'} + + + {#if result.media_file.audio} + + {/if} + + {human_readable_duration(result.media_file.duration)} + {:else if result.media_file.media_type === 'IMAGE' && result.media_file.content_type === 'image/gif'} + + {:else if result.media_file.media_type === 'IMAGE'} + + {:else if result.media_file.media_type === 'AUDIO'} + + {:else} + unknown media type {result.media_file.media_type} + {/if} + {:else if result.media_type === 'media_series'} + + ({result.media_reference.media_series_length}) + {:else if result.media_type === 'grouped'} + + {result.group_metadata.count} + {:else} + UNEXPECTED MEDIA TYPE {result.media_type} + {/if} +
+ + + {#if metadata} + {@render metadata({ result })} + {/if} +
+
+
diff --git a/packages/web/src/routes/browse/components/MediaView.svelte b/packages/web/src/lib/components/browse_media/MediaView.svelte similarity index 100% rename from packages/web/src/routes/browse/components/MediaView.svelte rename to packages/web/src/lib/components/browse_media/MediaView.svelte diff --git a/packages/web/src/lib/runes/base_queryparams.svelte.ts b/packages/web/src/lib/runes/base_queryparams.svelte.ts new file mode 100644 index 00000000..0d2437a7 --- /dev/null +++ b/packages/web/src/lib/runes/base_queryparams.svelte.ts @@ -0,0 +1,180 @@ +import type { MediaListRune } from '$lib/runes/index.ts' +import type { BaseController } from '$lib/base_controller.ts' +import { onMount } from 'svelte' +import { pushState } from '$app/navigation' +import { Rune } from '$lib/runes/rune.ts' + +/** + * Base QueryParamsManager - handles URL state synchronization without opinions on param structure. + * + * Responsibilities: + * - Two-state model (current/draft params) + * - URL parsing and serialization + * - Browser history integration (back/forward) + * - Generic merge/goto operations + * + * Subclasses define: + * - Param structure (TParams type) + * - Default values (DEFAULTS) + * - URL param mapping (URL_PARAM_MAP) + * - Search execution logic (execute_search) + * - Parse/serialize customization (hooks) + */ +export abstract class BaseQueryParamsManager> extends Rune { + /** Committed params (matches URL, used for pagination/search) */ + public current: TParams = $state() as TParams + + /** Draft params (form staging, can differ from current) */ + public draft: TParams = $state() as TParams + + public current_serialized: string = '?' + + protected media_list: MediaListRune + + constructor(client: BaseController['client'], media_list: MediaListRune) { + super(client) + this.media_list = media_list + + // Initialize with defaults + this.current = { ...this.DEFAULTS } + this.draft = { ...this.DEFAULTS } + + // Initialize from URL on mount + onMount(async () => { + const params = this.parse_url(new URL(window.location.toString())) + this.current = params + this.draft = { ...params } + await this.execute_search(params) + + // Listen for browser back/forward + window.addEventListener('popstate', async () => { + const params = this.parse_url(new URL(window.location.toString())) + this.current = params + this.draft = { ...params } + await this.execute_search(params) + }) + }) + } + + /** Default parameter values - must be implemented by subclass */ + abstract get DEFAULTS(): TParams + + /** URL parameter mapping - must be implemented by subclass */ + abstract get URL_PARAM_MAP(): Partial> + + /** Reversed URL parameter mapping */ + protected get URL_PARAM_MAP_REVERSED(): Record { + return Object.fromEntries( + Object.entries(this.URL_PARAM_MAP).map(([key, val]) => [val, key]) + ) as Record + } + + /** + * Parse URL into params - subclasses should override to implement specific parsing logic + */ + protected parse_url(url: URL): TParams { + const params: TParams = { ...this.DEFAULTS } + const search = url.searchParams + + this.current_serialized = url.search + + if (search) { + for (const [key, val] of search.entries()) { + const params_key: keyof TParams = this.URL_PARAM_MAP_REVERSED[key] ?? key + // Generic assignment - subclass should override for type-specific parsing + // @ts-ignore - dynamic assignment + params[params_key] = val + } + } + + return params + } + + /** + * Serialize params to URL string - subclasses should override for custom serialization logic + */ + public serialize(params: TParams): string { + const url_params = new Map() + + // Only include non-default values + for (const [key, value] of Object.entries(params)) { + if (value !== this.DEFAULTS[key as keyof TParams] && value !== undefined) { + const param_name = this.URL_PARAM_MAP[key as keyof TParams] ?? key + // Generic string conversion - subclass should override for custom encoding + url_params.set(param_name as string, String(value)) + } + } + + const query_string = Array.from(url_params.entries()) + .map(([key, val]) => `${key}=${val}`) + .join('&') + + return query_string ? '?' + query_string : null + } + + /** + * Update URL without executing search + */ + protected write_url(params: TParams): void { + const serialized = this.serialize(params) + + if (this.current_serialized !== serialized) { + this.current_serialized = serialized + this.current = { ...params } + if (serialized) { + pushState(serialized, {}) + } else { + pushState(window.location.pathname, {}) + } + } + } + + /** + * Execute search based on params - must be implemented by subclass + */ + protected abstract execute_search(params: TParams): Promise + + /** + * Submit draft params: update URL and execute search + */ + public async submit(): Promise { + this.write_url(this.draft) + await this.execute_search(this.draft) + } + + /** + * Navigate to new params (updates draft, then submits) + */ + public async goto(params: TParams): Promise { + this.draft = { ...params } + await this.submit() + } + + /** + * Merge partial params with current params - simple replacement by default + * Subclasses can override for more sophisticated merging (e.g., tag accumulation) + */ + public merge(partial_params: Partial>): TParams { + const params = { ...this.current } + + for (const [key, val] of Object.entries(partial_params)) { + const params_key: keyof TParams = this.URL_PARAM_MAP_REVERSED[key] ?? key + // @ts-ignore - dynamic assignment + params[params_key] = val + } + + return params + } + + /** + * Get contextual query for other components - must be implemented by subclass + */ + public abstract get contextual_query(): any + + /** + * Human-readable summary of current search - subclasses should override + */ + public get human_readable_summary(): string { + return 'Search results' + } +} diff --git a/packages/web/src/routes/browse/runes/dimensions.svelte.ts b/packages/web/src/lib/runes/dimensions.svelte.ts similarity index 100% rename from packages/web/src/routes/browse/runes/dimensions.svelte.ts rename to packages/web/src/lib/runes/dimensions.svelte.ts diff --git a/packages/web/src/lib/runes/index.ts b/packages/web/src/lib/runes/index.ts index 480185a4..872b9feb 100644 --- a/packages/web/src/lib/runes/index.ts +++ b/packages/web/src/lib/runes/index.ts @@ -2,3 +2,5 @@ export * from './focus_rune.svelte.ts' export * from './settings_rune.svelte.ts' export * from './media_list_rune.svelte.ts' export * from './media_view_rune.svelte.ts' +export * from './dimensions.svelte.ts' +export * from './media_selections.svelte.ts' diff --git a/packages/web/src/lib/runes/media_list_rune.svelte.ts b/packages/web/src/lib/runes/media_list_rune.svelte.ts index 9388acf7..3b359d4f 100644 --- a/packages/web/src/lib/runes/media_list_rune.svelte.ts +++ b/packages/web/src/lib/runes/media_list_rune.svelte.ts @@ -6,6 +6,7 @@ import { MediaViewRune } from '.' type Result = | ReturnType | ReturnType + | ReturnType interface SearchInput { type: 'media' @@ -15,6 +16,10 @@ interface GroupByInput { type: 'group_by' params: Parameters[0] } +interface SeriesInput { + type: 'series' + params: Parameters[0] +} interface FilesystemInput { type: 'filesystem' params: {} @@ -22,6 +27,7 @@ interface FilesystemInput { export type Input = | SearchInput | GroupByInput + | SeriesInput | FilesystemInput interface MediaListState { @@ -31,7 +37,7 @@ interface MediaListState { } export class MediaListRune extends Rune { - #saved_params_type: 'media' | 'group_by' = 'media' + #saved_params_type: 'media' | 'group_by' | 'series' = 'media' #saved_params: {} | undefined #prev_query_hash: string = '' #fetch_count = 0 @@ -87,6 +93,9 @@ export class MediaListRune extends Rune { else if (params_type === 'group_by') { fetch_params.limit = fetch_params.limit ?? 30 content = await this.client.forager.media.group(fetch_params) + } + else if (params_type === 'series') { + content = await this.client.forager.series.search(fetch_params) } else { throw new Error('unimplemented') } diff --git a/packages/web/src/routes/browse/runes/media_selections.svelte.ts b/packages/web/src/lib/runes/media_selections.svelte.ts similarity index 100% rename from packages/web/src/routes/browse/runes/media_selections.svelte.ts rename to packages/web/src/lib/runes/media_selections.svelte.ts diff --git a/packages/web/src/lib/runes/media_view_rune.svelte.ts b/packages/web/src/lib/runes/media_view_rune.svelte.ts index 3950ea90..ce84d41a 100644 --- a/packages/web/src/lib/runes/media_view_rune.svelte.ts +++ b/packages/web/src/lib/runes/media_view_rune.svelte.ts @@ -96,6 +96,13 @@ export class MediaViewRune extends Rune { export class MediaFileRune extends MediaViewRune { media_type = 'media_file' as const satisfies MediaResponse['media_type'] + series_index?: number + + constructor(client: BaseController['client'], media_response: MediaResponse) { + super(client, media_response) + // @ts-ignore - series_index is only present in SeriesSearchResponse + this.series_index = media_response.series_index + } public override async update(media_info: inputs.MediaInfo, tags: inputs.MediaReferenceUpdateTags) { const updated = await this.client.forager.media.update( @@ -126,12 +133,19 @@ export class MediaFileRune extends MediaViewRune { export class MediaSeriesRune extends MediaViewRune { media_type = 'media_series' as const satisfies MediaResponse['media_type'] + series_index?: number + + constructor(client: BaseController['client'], media_response: MediaResponse) { + super(client, media_response) + // @ts-ignore - series_index is only present in SeriesSearchResponse + this.series_index = media_response.series_index + } public override async load_detailed_view() { if (this.state!.full_thumbnails) return - const series = await forager.series.get({series_id: media_response.media_reference.id }) + const series = await this.client.forager.series.get({series_id: this.state!.media.media_reference.id }) this.state!.full_thumbnails = series.thumbnails - const series_items = await forager.media.search({query: {series_id: media_response.media_reference.id }}) + const series_items = await this.client.forager.media.search({query: {series_id: this.state!.media.media_reference.id }}) // TODO attach series items } diff --git a/packages/web/src/routes/browse/+page.svelte b/packages/web/src/routes/browse/+page.svelte index 011da575..4a082f2b 100644 --- a/packages/web/src/routes/browse/+page.svelte +++ b/packages/web/src/routes/browse/+page.svelte @@ -1,34 +1,23 @@ -
-
-
- -
- - -
-
-
-
+ + {#snippet header({ controller })} +
+ {/snippet} - + {#snippet media_list({ controller })} + + {/snippet} + diff --git a/packages/web/src/routes/browse/components/SearchResults.svelte b/packages/web/src/routes/browse/components/SearchResults.svelte index cc9590e0..0fa7087e 100644 --- a/packages/web/src/routes/browse/components/SearchResults.svelte +++ b/packages/web/src/routes/browse/components/SearchResults.svelte @@ -1,34 +1,14 @@ - - {#if media_list.loading} diff --git a/packages/web/src/routes/browse/controller.ts b/packages/web/src/routes/browse/controller.ts index b505140f..1520cb21 100644 --- a/packages/web/src/routes/browse/controller.ts +++ b/packages/web/src/routes/browse/controller.ts @@ -1,8 +1,6 @@ import type { Config } from '$lib/server/config.ts' import {BaseController} from '$lib/base_controller.ts' -import {create_focuser, SettingsRune, MediaListRune } from '$lib/runes/index.ts' -import { create_dimensional_rune } from './runes/dimensions.svelte.ts' -import { MediaSelectionsRune } from './runes/media_selections.svelte.ts' +import {create_focuser, SettingsRune, MediaListRune, create_dimensional_rune, MediaSelectionsRune } from '$lib/runes/index.ts' import { QueryParamsManager } from './runes/queryparams.svelte.ts' diff --git a/packages/web/src/routes/browse/runes/queryparams.svelte.ts b/packages/web/src/routes/browse/runes/queryparams.svelte.ts index d37953bc..b74526f9 100644 --- a/packages/web/src/routes/browse/runes/queryparams.svelte.ts +++ b/packages/web/src/routes/browse/runes/queryparams.svelte.ts @@ -2,21 +2,12 @@ import type { inputs } from '@forager/core' import type { MediaListRune } from '$lib/runes/index.ts' import type { BaseController } from '$lib/base_controller.ts' import * as parsers from '$lib/parsers.ts' -import { onMount } from 'svelte' -import { pushState } from '$app/navigation' -import { Rune } from '$lib/runes/rune.ts' +import { BaseQueryParamsManager, type BaseSearchParams } from '$lib/runes/base_queryparams.svelte.ts' -interface SearchParams { - search_string: string - filepath: string | undefined +interface SearchParams extends BaseSearchParams { sort: inputs.PaginatedSearch['sort_by'] - unread_only: boolean search_mode: 'media' | 'group_by' | 'filesystem' group_by: string | undefined - stars: number | undefined - stars_equality: 'gte' | 'eq' | undefined - order: 'desc' | 'asc' - media_type: string } const DEFAULTS: SearchParams = { @@ -39,118 +30,32 @@ const URL_PARAM_MAP = { search_mode: 'mode', media_type: 'type', } as const satisfies Partial> -type UrlParamMap = typeof URL_PARAM_MAP - -type SearchParamsReversed = { [K in keyof UrlParamMap as UrlParamMap[K]]: K} -const URL_PARAM_MAP_REVERSED = Object.fromEntries( - Object.entries(URL_PARAM_MAP).map(([key, val]) => [val, key]) -) as SearchParamsReversed /** - * Manages browser URL query parameters and syncs them with search state. - * - * Two-state model: - * - `current`: Committed params (matches URL, used for pagination) - * - `draft`: Staging area for form edits (before submission) + * Browse-specific QueryParamsManager that handles media search and group_by modes. * - * When URL changes externally (back/forward), draft resets to match current. + * Extends BaseQueryParamsManager with browse-specific functionality: + * - search_mode support (media, group_by, filesystem) + * - group_by tag grouping + * - group_by_tag extension for grouped searches */ -export class QueryParamsManager extends Rune { - public DEFAULTS = DEFAULTS - - /** Committed params (matches URL, used for pagination/search) */ - public current: SearchParams = $state({ ...DEFAULTS }) - - /** Draft params (form staging, can differ from current) */ - public draft: SearchParams = $state({ ...DEFAULTS }) - - public current_serialized: string = '?' - - #media_list: MediaListRune - - constructor(client: BaseController['client'], media_list: MediaListRune) { - super(client) - this.#media_list = media_list - - // Initialize from URL on mount - onMount(async () => { - const params = this.#parse_url(new URL(window.location.toString())) - this.current = params - this.draft = { ...params } // Initialize draft - await this.#execute_search(params) - - // Listen for browser back/forward - window.addEventListener('popstate', async () => { - const params = this.#parse_url(new URL(window.location.toString())) - this.current = params - this.draft = { ...params } // Reset draft to match URL - await this.#execute_search(params) - }) - }) +export class QueryParamsManager extends BaseQueryParamsManager { + get DEFAULTS(): SearchParams { + return DEFAULTS } - /** - * Parse URL into SearchParams - */ - #parse_url(url: URL): SearchParams { - const params: SearchParams = { ...DEFAULTS } - const search = url.searchParams - - this.current_serialized = url.search - - // Parse each param with type coercion - if (search) { - for (const [key, val] of search.entries()) { - const params_key: keyof SearchParams = URL_PARAM_MAP_REVERSED[key] ?? key - - if (params_key === 'search_string') { - params.search_string = val.replaceAll(',', ' ') - } else if (params_key === 'stars') { - params.stars = parseInt(val) - } else if (params_key === 'filepath') { - params.filepath = decodeURIComponent(val) - } else { - // @ts-ignore - dynamic assignment - params[params_key] = val - } - } - - // Infer search_mode from group_by presence - if (search.has('group_by')) { - params.search_mode = 'group_by' - } - } - - return params + get URL_PARAM_MAP(): Partial> { + return URL_PARAM_MAP } - /** - * Serialize SearchParams to URL string (for SearchLink components) - */ - public serialize(params: SearchParams): string { - const url_params = new Map() - - // Only include non-default values - for (const [key, value] of Object.entries(params)) { - if (value !== DEFAULTS[key as keyof SearchParams] && value !== undefined) { - const param_name = URL_PARAM_MAP[key as keyof SearchParams] ?? key - - // Special encoding for tags (preserve : and ,) - if (key === 'search_string') { - const encoded = encodeURIComponent(value.replaceAll(/\s/g, ',')) - .replaceAll('%3A', ':') - .replaceAll('%2C', ',') - url_params.set(param_name, encoded) - } else if (key === 'filepath') { - if (value) { - url_params.set(param_name, encodeURIComponent(value)) - } - } else { - url_params.set(param_name, String(value)) - } - } + protected parse_url_custom(params: SearchParams, search: URLSearchParams): void { + // Infer search_mode from group_by presence + if (search.has('group_by')) { + params.search_mode = 'group_by' } + } + protected serialize_custom(url_params: Map, params: SearchParams): void { if (url_params.get('mode') === 'group_by' && !url_params.has('group_by')) { url_params.set('group_by', '') } @@ -159,37 +64,10 @@ export class QueryParamsManager extends Rune { if (['group_by', 'media'].includes(url_params.get('mode') ?? '')) { url_params.delete('mode') } - - const query_string = Array.from(url_params.entries()) - .map(([key, val]) => `${key}=${val}`) - .join('&') - - return query_string ? '?' + query_string : null } - /** - * Update URL without executing search - */ - #write_url(params: SearchParams): void { - const serialized = this.serialize(params) - - if (this.current_serialized !== serialized) { - this.current_serialized = serialized - this.current = { ...params } - if (serialized) { - pushState(serialized, {}) - } else { - // when we have empty query params, we do this to drop the "?" at the end of the url - pushState(window.location.pathname, {}) - } - } - } - - /** - * Execute search based on params - */ - async #execute_search(params: SearchParams): Promise { - this.#media_list.clear() + protected async execute_search(params: SearchParams): Promise { + this.media_list.clear() const tags = params.search_string.split(' ').filter((t) => t.length > 0) const query: inputs.PaginatedSearch['query'] = { @@ -216,7 +94,7 @@ export class QueryParamsManager extends Rune { // Execute appropriate search if (params.search_mode === 'media') { - await this.#media_list.paginate({ + await this.media_list.paginate({ type: 'media', params: { query, @@ -225,7 +103,7 @@ export class QueryParamsManager extends Rune { }, }) } else if (params.search_mode === 'group_by') { - await this.#media_list.paginate({ + await this.media_list.paginate({ type: 'group_by', params: { group_by: { @@ -239,57 +117,26 @@ export class QueryParamsManager extends Rune { } } - /** - * Submit draft params: update URL and execute search - */ - public async submit(): Promise { - this.#write_url(this.draft) - await this.#execute_search(this.draft) - } - - /** - * Navigate to new params (updates draft, then submits) - */ - public async goto(params: SearchParams): Promise { - this.draft = { ...params } - await this.submit() - } - - /** - * Merge partial params with current params - * Supports URL param names (e.g., 'tags') or internal names (e.g., 'search_string') - */ - public merge(partial_params: Partial>): SearchParams { - const params = { ...this.current } + public override merge(partial_params: Partial>): SearchParams { + const params = super.merge(partial_params) + // Handle browse-specific merging for (const [key, val] of Object.entries(partial_params)) { - const params_key: keyof SearchParams = URL_PARAM_MAP_REVERSED[key] ?? key + const params_key = this.URL_PARAM_MAP_REVERSED[key] ?? key - if (params_key === 'search_string') { - // Merge tags instead of replacing - const search_strings = new Set(params.search_string.split(/\s+/)) - search_strings.add(val) - params.search_string = [...search_strings].join(' ').trim() - } else if (params_key === 'search_mode') { + if (params_key === 'search_mode') { params.search_mode = val // Clear group_by if switching away from group_by mode if (val !== 'group_by') { params.group_by = undefined } - } else { - // @ts-ignore - dynamic assignment - params[params_key] = val } } return params } - /** - * Extend current params with a tag - * Supports special 'group_by_tag' key for group-by searches - */ - public extend(key: 'tag' | 'group_by_tag', value: string): SearchParams { + public override extend(key: 'tag' | 'group_by_tag', value: string): SearchParams { const params = { ...this.current } // group_by_tag means we want to do a normal search including the group by tag @@ -313,9 +160,6 @@ export class QueryParamsManager extends Rune { } } - /** - * Get contextual query for other components (e.g., tag autocomplete) - */ public get contextual_query(): inputs.PaginatedSearch['query'] { const tags = this.current.search_string.split(' ').filter((t) => t.length > 0) return { @@ -325,11 +169,4 @@ export class QueryParamsManager extends Rune { animated: this.current.media_type === 'animated' ? true : undefined, } } - - /** - * Human-readable summary of current search - */ - public get human_readable_summary(): string { - return this.current.search_string || 'All media' - } } diff --git a/packages/web/src/routes/series/[series_id]/+page.svelte b/packages/web/src/routes/series/[series_id]/+page.svelte new file mode 100644 index 00000000..585c337d --- /dev/null +++ b/packages/web/src/routes/series/[series_id]/+page.svelte @@ -0,0 +1,24 @@ + + + + {#snippet header({ controller })} +
+ {/snippet} + + {#snippet media_list({ controller })} + + {/snippet} + diff --git a/packages/web/src/routes/series/[series_id]/components/Header.svelte b/packages/web/src/routes/series/[series_id]/components/Header.svelte new file mode 100644 index 00000000..f7ed127d --- /dev/null +++ b/packages/web/src/routes/series/[series_id]/components/Header.svelte @@ -0,0 +1,14 @@ + + +
+ {queryparams.human_readable_summary || 'Forager Series'} + +
diff --git a/packages/web/src/routes/series/[series_id]/components/MediaList.svelte b/packages/web/src/routes/series/[series_id]/components/MediaList.svelte new file mode 100644 index 00000000..6e9d0851 --- /dev/null +++ b/packages/web/src/routes/series/[series_id]/components/MediaList.svelte @@ -0,0 +1,59 @@ + + +
+ controller.runes.media_list.paginate()} + class={[ + "w-full focus:outline-none", + controller.runes.media_selections.current_selection.show ? "overflow-hidden" : "overflow-y-scroll", + ]} + style="height: {controller.runes.dimensions.heights.media_list}px" + > + + +
diff --git a/packages/web/src/routes/series/[series_id]/components/SearchParams.svelte b/packages/web/src/routes/series/[series_id]/components/SearchParams.svelte new file mode 100644 index 00000000..ac09733a --- /dev/null +++ b/packages/web/src/routes/series/[series_id]/components/SearchParams.svelte @@ -0,0 +1,158 @@ + + +
{ + e.preventDefault() + await update_search() + }}> +
+
+ + +
+ +
+
+
+ + +
+ + + + + +
+ + + + + + +
+ +
+ +
+
+ + +
+
+
+
+ +
diff --git a/packages/web/src/routes/series/[series_id]/components/SearchResults.svelte b/packages/web/src/routes/series/[series_id]/components/SearchResults.svelte new file mode 100644 index 00000000..fc0c0f23 --- /dev/null +++ b/packages/web/src/routes/series/[series_id]/components/SearchResults.svelte @@ -0,0 +1,36 @@ + + + + +
+ {#each media_list.results as result, result_index} + + {#snippet metadata({ result })} + {#if result.series_index !== undefined} +
+ #{result.series_index} +
+ {/if} + {/snippet} +
+ {/each} +
+{#if media_list.loading} + Loading... +{/if} diff --git a/packages/web/src/routes/series/[series_id]/controller.ts b/packages/web/src/routes/series/[series_id]/controller.ts new file mode 100644 index 00000000..b3fbb56b --- /dev/null +++ b/packages/web/src/routes/series/[series_id]/controller.ts @@ -0,0 +1,34 @@ +import type { Config } from '$lib/server/config.ts' +import {BaseController} from '$lib/base_controller.ts' +import {create_focuser, SettingsRune, MediaListRune, create_dimensional_rune, MediaSelectionsRune } from '$lib/runes/index.ts' +import { SeriesQueryParamsManager } from './runes/queryparams.svelte.ts' + + +class SeriesController extends BaseController { + series_id: number + runes: { + media_list: MediaListRune + focus: ReturnType + dimensions: ReturnType + settings: SettingsRune + media_selections: MediaSelectionsRune + queryparams: SeriesQueryParamsManager + } + + public constructor(config: Config, series_id: number) { + super(config) + this.series_id = series_id + + const media_list_rune = new MediaListRune(this.client, this.settings) + this.runes = { + media_list: media_list_rune, + focus: create_focuser(), + dimensions: create_dimensional_rune(), + settings: new SettingsRune(this.client, this.config), + media_selections: new MediaSelectionsRune(this.client, media_list_rune), + queryparams: new SeriesQueryParamsManager(this.client, media_list_rune, series_id), + } + } +} + +export { SeriesController } diff --git a/packages/web/src/routes/series/[series_id]/runes/queryparams.svelte.ts b/packages/web/src/routes/series/[series_id]/runes/queryparams.svelte.ts new file mode 100644 index 00000000..17809a7c --- /dev/null +++ b/packages/web/src/routes/series/[series_id]/runes/queryparams.svelte.ts @@ -0,0 +1,169 @@ +import type { inputs } from '@forager/core' +import type { MediaListRune } from '$lib/runes/index.ts' +import type { BaseController } from '$lib/base_controller.ts' +import { BaseQueryParamsManager } from '$lib/runes/base_queryparams.svelte.ts' + +interface SeriesSearchParams { + search_string: string + filepath: string | undefined + sort: inputs.SeriesSearch['sort_by'] + unread_only: boolean + stars: number | undefined + stars_equality: 'gte' | 'eq' | undefined + order: 'desc' | 'asc' + media_type: string +} + +const DEFAULTS: SeriesSearchParams = { + search_string: '', + filepath: undefined, + sort: 'series_index', + order: 'asc', + unread_only: false, + stars: undefined, + stars_equality: undefined, + media_type: 'all', +} + +// Map internal names to URL param names +const URL_PARAM_MAP = { + search_string: 'tags', + unread_only: 'unread', + media_type: 'type', +} as const satisfies Partial> + +/** + * Series-specific QueryParamsManager that handles series.search API. + * + * Extends BaseQueryParamsManager with series-specific functionality: + * - series_id filtering (fixed for the page) + * - series_index sort option (default) + * - Simplified search (no group_by mode) + */ +export class SeriesQueryParamsManager extends BaseQueryParamsManager { + #series_id: number + + constructor(client: BaseController['client'], media_list: MediaListRune, series_id: number) { + super(client, media_list) + this.#series_id = series_id + } + + get DEFAULTS(): SeriesSearchParams { + return DEFAULTS + } + + get URL_PARAM_MAP(): Partial> { + return URL_PARAM_MAP + } + + protected override parse_url(url: URL): SeriesSearchParams { + const params: SeriesSearchParams = { ...DEFAULTS } + const search = url.searchParams + + this.current_serialized = url.search + + if (search) { + for (const [key, val] of search.entries()) { + const params_key: keyof SeriesSearchParams = this.URL_PARAM_MAP_REVERSED[key] ?? key + + // Series-specific parsing + if (params_key === 'search_string') { + params.search_string = val.replaceAll(',', ' ') + } else if (params_key === 'stars') { + params.stars = parseInt(val) + } else if (params_key === 'filepath') { + params.filepath = decodeURIComponent(val) + } else { + // @ts-ignore - dynamic assignment + params[params_key] = val + } + } + } + + return params + } + + public override serialize(params: SeriesSearchParams): string { + const url_params = new Map() + + // Only include non-default values + for (const [key, value] of Object.entries(params)) { + if (value !== DEFAULTS[key as keyof SeriesSearchParams] && value !== undefined) { + const param_name = URL_PARAM_MAP[key as keyof SeriesSearchParams] ?? key + + // Series-specific encoding + if (key === 'search_string') { + const encoded = encodeURIComponent(value.replaceAll(/\s/g, ',')) + .replaceAll('%3A', ':') + .replaceAll('%2C', ',') + url_params.set(param_name, encoded) + } else if (key === 'filepath') { + if (value) { + url_params.set(param_name, encodeURIComponent(value)) + } + } else { + url_params.set(param_name, String(value)) + } + } + } + + const query_string = Array.from(url_params.entries()) + .map(([key, val]) => `${key}=${val}`) + .join('&') + + return query_string ? '?' + query_string : null + } + + protected async execute_search(params: SeriesSearchParams): Promise { + this.media_list.clear() + + const tags = params.search_string.split(' ').filter((t) => t.length > 0) + const query: inputs.SeriesSearch['query'] = { + series_id: this.#series_id, + tags: tags.length > 0 ? tags.map(t => ({ name: t })) : undefined, + filepath: params.filepath, + } + + // Handle boolean and numeric filters + if (params.unread_only) { + if (params.unread_only === 'true' || params.unread_only === true) { + query.unread = true + } + } + + if (params.stars !== undefined) { + query.stars = parseInt(String(params.stars)) + query.stars_equality = params.stars_equality ?? 'gte' + } + + // Handle media type filtering + if (params.media_type === 'animated') { + query.animated = true + } + + // Execute series search + await this.media_list.paginate({ + type: 'series', + params: { + query, + sort_by: params.sort, + order: params.order, + }, + }) + } + + public get contextual_query(): inputs.SeriesSearch['query'] { + const tags = this.current.search_string.split(' ').filter((t) => t.length > 0) + return { + series_id: this.#series_id, + tags: tags.length > 0 ? tags.map(t => ({ name: t })) : undefined, + filepath: this.current.filepath, + unread: this.current.unread_only || undefined, + animated: this.current.media_type === 'animated' ? true : undefined, + } + } + + public override get human_readable_summary(): string { + return this.current.search_string || 'Series items' + } +}