From f2fec5eafc5bec93f62a51d4fa865d8a0b26d5c5 Mon Sep 17 00:00:00 2001 From: limina1 Date: Fri, 30 May 2025 10:29:42 -0400 Subject: [PATCH] feat: add display limits and improve visualization reactivity - Add display limits store and utilities - Fix reactivity issues with Svelte 5 - Add max 30040/30041 event controls - Implement fetch if not found functionality - Reorganize settings panel with sections - Add debug logging for troubleshooting --- package-lock.json | 763 +++++++++++++++++- package.json | 4 + src/lib/navigator/EventNetwork/Legend.svelte | 14 +- .../navigator/EventNetwork/Settings.svelte | 130 ++- src/lib/navigator/EventNetwork/index.svelte | 12 +- src/lib/navigator/EventNetwork/types.ts | 74 +- src/lib/stores/displayLimits.ts | 19 + src/lib/stores/index.ts | 2 + src/lib/utils/displayLimits.ts | 137 ++++ src/routes/visualize/+page.svelte | 137 +++- vite.config.ts | 4 +- 11 files changed, 1183 insertions(+), 113 deletions(-) create mode 100644 src/lib/stores/displayLimits.ts create mode 100644 src/lib/stores/index.ts create mode 100644 src/lib/utils/displayLimits.ts diff --git a/package-lock.json b/package-lock.json index b631ab9..1fa57f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,8 @@ "name": "alexandria", "version": "0.0.6", "dependencies": { + "@noble/curves": "^1.9.1", + "@noble/hashes": "^1.8.0", "@nostr-dev-kit/ndk": "2.11.x", "@nostr-dev-kit/ndk-cache-dexie": "2.5.x", "@popperjs/core": "2.11.x", @@ -16,28 +18,32 @@ "asciidoctor": "3.0.x", "bech32": "^2.0.0", "d3": "^7.9.0", - "he": "1.2.x", + "he": "1.2.0", "highlight.js": "^11.11.1", "node-emoji": "^2.2.0", "nostr-tools": "2.10.x", "qrcode": "^1.5.4" }, "devDependencies": { - "@playwright/test": "^1.50.1", - "@sveltejs/adapter-auto": "3.x", + "@playwright/test": "^1.52.0", + "@sveltejs/adapter-auto": "~3.3.1", "@sveltejs/adapter-node": "^5.2.12", - "@sveltejs/adapter-static": "3.x", - "@sveltejs/kit": "^2.16.0", - "@sveltejs/vite-plugin-svelte": "4.x", + "@sveltejs/adapter-static": "~3.0.8", + "@sveltejs/kit": "^2.21.0", + "@sveltejs/vite-plugin-svelte": "~4.0.4", + "@testing-library/dom": "^10.4.0", + "@testing-library/svelte": "^5.2.8", "@types/d3": "^7.4.3", "@types/he": "1.2.x", "@types/node": "22.x", "@types/qrcode": "^1.5.5", + "@vitest/ui": "^3.1.4", "autoprefixer": "10.x", "eslint-plugin-svelte": "2.x", "flowbite": "2.x", "flowbite-svelte": "0.x", "flowbite-svelte-icons": "2.1.x", + "jsdom": "^26.1.0", "playwright": "^1.50.1", "postcss": "8.x", "postcss-load-config": "6.x", @@ -75,6 +81,20 @@ "node": ">=6.0.0" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, "node_modules/@asciidoctor/cli": { "version": "4.0.0", "license": "MIT", @@ -116,6 +136,21 @@ "node": ">=16" } }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "license": "MIT", @@ -143,6 +178,16 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/runtime": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.3.tgz", + "integrity": "sha512-7EYtGezsdiDMyY80+65EzwiGmcJqpmcZCojSXaRgdrBaGtWTgDZKq69cPIVped6MkIM78cTQ2GOiEYjwOlG4xw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/types": { "version": "7.27.1", "license": "MIT", @@ -154,6 +199,121 @@ "node": ">=6.9.0" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.0.2.tgz", + "integrity": "sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.10.tgz", + "integrity": "sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.0.2", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.21.5", "cpu": [ @@ -977,6 +1137,69 @@ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@testing-library/svelte": { + "version": "5.2.8", + "resolved": "https://registry.npmjs.org/@testing-library/svelte/-/svelte-5.2.8.tgz", + "integrity": "sha512-ucQOtGsJhtawOEtUmbR4rRh53e6RbM1KUluJIXRmh6D4UzxR847iIqqjRtg9mHNFmGQ8Vkam9yVcR5d1mhIHKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@testing-library/dom": "9.x.x || 10.x.x" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0", + "vite": "*", + "vitest": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/cookie": { "version": "0.6.0", "dev": true, @@ -1249,12 +1472,14 @@ "license": "MIT" }, "node_modules/@vitest/expect": { - "version": "3.1.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz", + "integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" }, @@ -1263,11 +1488,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "3.1.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz", + "integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "3.1.3", + "@vitest/spy": "3.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.17" }, @@ -1289,6 +1516,8 @@ }, "node_modules/@vitest/mocker/node_modules/estree-walker": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1296,7 +1525,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "3.1.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz", + "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==", "dev": true, "license": "MIT", "dependencies": { @@ -1307,11 +1538,13 @@ } }, "node_modules/@vitest/runner": { - "version": "3.1.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz", + "integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "3.1.3", + "@vitest/utils": "3.1.4", "pathe": "^2.0.3" }, "funding": { @@ -1319,11 +1552,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "3.1.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz", + "integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.3", + "@vitest/pretty-format": "3.1.4", "magic-string": "^0.30.17", "pathe": "^2.0.3" }, @@ -1332,7 +1567,9 @@ } }, "node_modules/@vitest/spy": { - "version": "3.1.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz", + "integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==", "dev": true, "license": "MIT", "dependencies": { @@ -1342,12 +1579,36 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/ui": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-3.1.4.tgz", + "integrity": "sha512-CFc2Bpb3sz4Sdt53kdNGq+qZKLftBwX4qZLC03CBUc0N1LJrOoL0ZeK0oq/708mtnpwccL0BZCY9d1WuiBSr7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.4", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.1", + "tinyglobby": "^0.2.13", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "3.1.4" + } + }, "node_modules/@vitest/utils": { - "version": "3.1.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "3.1.3", + "@vitest/pretty-format": "3.1.4", "loupe": "^3.1.3", "tinyrainbow": "^2.0.0" }, @@ -1383,6 +1644,16 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "dev": true, @@ -1506,6 +1777,8 @@ }, "node_modules/assertion-error": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -1652,6 +1925,8 @@ }, "node_modules/cac": { "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", "dev": true, "license": "MIT", "engines": { @@ -1729,6 +2004,8 @@ }, "node_modules/chai": { "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", "dev": true, "license": "MIT", "dependencies": { @@ -1772,6 +2049,8 @@ }, "node_modules/check-error": { "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", "dev": true, "license": "MIT", "engines": { @@ -1899,6 +2178,20 @@ "node": ">=4" } }, + "node_modules/cssstyle": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.3.1.tgz", + "integrity": "sha512-ZgW+Jgdd7i52AaLYCriF8Mxqft0gD/R9i9wi6RWBhs1pqdPEzPjym7rvRKi397WmQFf3SlyUsszhw+VVCbx79Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.1.2", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/d": { "version": "1.0.2", "license": "ISC", @@ -2249,6 +2542,20 @@ "node": ">=12" } }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/debug": { "version": "4.4.1", "license": "MIT", @@ -2273,8 +2580,17 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "dev": true, + "license": "MIT" + }, "node_modules/deep-eql": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", "dev": true, "license": "MIT", "engines": { @@ -2302,6 +2618,16 @@ "robust-predicates": "^3.0.2" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/devalue": { "version": "5.1.1", "dev": true, @@ -2329,6 +2655,13 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, "node_modules/dunder-proto": { "version": "1.0.1", "license": "MIT", @@ -2371,6 +2704,19 @@ "version": "2.4.0", "license": "MIT" }, + "node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "license": "MIT", @@ -2387,6 +2733,8 @@ }, "node_modules/es-module-lexer": { "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", "dev": true, "license": "MIT" }, @@ -2855,6 +3203,13 @@ } } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "dev": true, @@ -2933,8 +3288,7 @@ "node_modules/flatted": { "version": "3.3.3", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/flowbite": { "version": "2.5.2", @@ -3242,6 +3596,47 @@ "node": ">=12.0.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "license": "MIT", @@ -3391,6 +3786,13 @@ "node": ">=0.12.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-promise": { "version": "2.2.2", "license": "MIT" @@ -3467,6 +3869,13 @@ "version": "1.0.2", "license": "MIT" }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, "node_modules/js-yaml": { "version": "4.1.0", "dev": true, @@ -3479,6 +3888,46 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/json-buffer": { "version": "3.0.1", "dev": true, @@ -3605,6 +4054,8 @@ }, "node_modules/loupe": { "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", "dev": true, "license": "MIT" }, @@ -3612,6 +4063,16 @@ "version": "10.4.3", "license": "ISC" }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, "node_modules/magic-string": { "version": "0.30.17", "dev": true, @@ -3885,6 +4346,13 @@ "node": ">= 6" } }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "dev": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", @@ -3978,6 +4446,19 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "license": "MIT", @@ -4017,6 +4498,8 @@ }, "node_modules/pathval": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", "dev": true, "license": "MIT", "engines": { @@ -4324,6 +4807,34 @@ "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/promise": { "version": "7.3.1", "license": "MIT", @@ -4435,7 +4946,6 @@ "version": "2.3.1", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -4593,6 +5103,13 @@ ], "license": "MIT" }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, "node_modules/read-cache": { "version": "1.0.0", "license": "MIT", @@ -4714,6 +5231,13 @@ "fsevents": "~2.3.2" } }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, "node_modules/run-parallel": { "version": "1.2.0", "funding": [ @@ -4754,6 +5278,19 @@ "version": "2.1.2", "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/semver": { "version": "7.7.2", "dev": true, @@ -5233,6 +5770,13 @@ "node": ">= 0.8.0" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "3.3.0", "dev": true, @@ -5381,12 +5925,34 @@ }, "node_modules/tinyspy": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" } }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/to-regex-range": { "version": "5.0.1", "license": "MIT", @@ -5409,6 +5975,32 @@ "node": ">=6" } }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "license": "Apache-2.0" @@ -5614,7 +6206,9 @@ } }, "node_modules/vite-node": { - "version": "3.1.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz", + "integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==", "dev": true, "license": "MIT", "dependencies": { @@ -5652,17 +6246,19 @@ } }, "node_modules/vitest": { - "version": "3.1.3", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz", + "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "3.1.3", - "@vitest/mocker": "3.1.3", - "@vitest/pretty-format": "^3.1.3", - "@vitest/runner": "3.1.3", - "@vitest/snapshot": "3.1.3", - "@vitest/spy": "3.1.3", - "@vitest/utils": "3.1.3", + "@vitest/expect": "3.1.4", + "@vitest/mocker": "3.1.4", + "@vitest/pretty-format": "^3.1.4", + "@vitest/runner": "3.1.4", + "@vitest/snapshot": "3.1.4", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.2.1", @@ -5675,7 +6271,7 @@ "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "3.1.3", + "vite-node": "3.1.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -5691,8 +6287,8 @@ "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "3.1.3", - "@vitest/ui": "3.1.3", + "@vitest/browser": "3.1.4", + "@vitest/ui": "3.1.4", "happy-dom": "*", "jsdom": "*" }, @@ -5727,6 +6323,29 @@ "node": ">=0.10.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/websocket": { "version": "1.0.35", "license": "Apache-2.0", @@ -5760,6 +6379,43 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "license": "ISC", @@ -5855,6 +6511,45 @@ "version": "1.0.2", "license": "ISC" }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/y18n": { "version": "5.0.8", "license": "ISC", diff --git a/package.json b/package.json index 3f01d92..91dc64b 100644 --- a/package.json +++ b/package.json @@ -37,15 +37,19 @@ "@sveltejs/adapter-static": "~3.0.8", "@sveltejs/kit": "^2.21.0", "@sveltejs/vite-plugin-svelte": "~4.0.4", + "@testing-library/dom": "^10.4.0", + "@testing-library/svelte": "^5.2.8", "@types/d3": "^7.4.3", "@types/he": "1.2.x", "@types/node": "22.x", "@types/qrcode": "^1.5.5", + "@vitest/ui": "^3.1.4", "autoprefixer": "10.x", "eslint-plugin-svelte": "2.x", "flowbite": "2.x", "flowbite-svelte": "0.x", "flowbite-svelte-icons": "2.1.x", + "jsdom": "^26.1.0", "playwright": "^1.50.1", "postcss": "8.x", "postcss-load-config": "6.x", diff --git a/src/lib/navigator/EventNetwork/Legend.svelte b/src/lib/navigator/EventNetwork/Legend.svelte index 46c5acc..7540760 100644 --- a/src/lib/navigator/EventNetwork/Legend.svelte +++ b/src/lib/navigator/EventNetwork/Legend.svelte @@ -1,5 +1,3 @@ - -
@@ -72,22 +106,82 @@ {#if expanded}
- Showing {count} events from {$networkFetchLimit} headers + Showing {count} of {totalCount} events -
- -

- Toggle between star clusters (on) and linear sequence (off) - visualization -

+ +
+

Initial Load

+ +
+ +
+

Display Limits

+ +
+
+ + handleDisplayLimitInput(e, 'max30040')} + placeholder="-1 for unlimited" + class="w-full text-xs bg-primary-0 dark:bg-primary-1000 border border-gray-300 dark:border-gray-700 rounded-md px-2 py-1 dark:text-white" + /> +
+ +
+ + handleDisplayLimitInput(e, 'max30041')} + placeholder="-1 for unlimited" + class="w-full text-xs bg-primary-0 dark:bg-primary-1000 border border-gray-300 dark:border-gray-700 rounded-md px-2 py-1 dark:text-white" + /> +
+ + +

+ Automatically fetch missing referenced events +

+
+
+ + +
+

Visual Settings

+ +
+ +

+ Toggle between star clusters (on) and linear sequence (off) + visualization +

+
+
{/if} +
- - -
{/if}
diff --git a/src/lib/navigator/EventNetwork/index.svelte b/src/lib/navigator/EventNetwork/index.svelte index b09cc25..7e6f60e 100644 --- a/src/lib/navigator/EventNetwork/index.svelte +++ b/src/lib/navigator/EventNetwork/index.svelte @@ -58,10 +58,18 @@ } // Component props - let { events = [], onupdate, onTagExpansionChange } = $props<{ + let { + events = [], + totalCount = 0, + onupdate, + onTagExpansionChange, + onFetchMissing = () => {} + } = $props<{ events?: NDKEvent[]; + totalCount?: number; onupdate: () => void; onTagExpansionChange?: (depth: number, tags: string[]) => void; + onFetchMissing?: (ids: string[]) => void; }>(); // Error state @@ -848,7 +856,9 @@ { - source: NodeType | string | number; // Source node or identifier - target: NodeType | string | number; // Target node or identifier - index?: number; // Link index in the simulation + source: NodeType | string | number; // Source node or identifier + target: NodeType | string | number; // Target node or identifier + index?: number; // Link index in the simulation } /** @@ -36,23 +36,23 @@ export interface SimulationLinkDatum { * Extends the base simulation node with Nostr event-specific properties */ export interface NetworkNode extends SimulationNodeDatum { - id: string; // Unique identifier (event ID) - event?: NDKEvent; // Reference to the original NDK event - level: number; // Hierarchy level in the network - kind: number; // Nostr event kind (30040 for index, 30041/30818 for content) - title: string; // Event title - content: string; // Event content - author: string; // Author's public key - type: "Index" | "Content" | "TagAnchor"; // Node type classification - naddr?: string; // NIP-19 naddr identifier - nevent?: string; // NIP-19 nevent identifier - isContainer?: boolean; // Whether this node is a container (index) - - // Tag anchor specific fields - isTagAnchor?: boolean; // Whether this is a tag anchor node - tagType?: string; // Type of tag (t, p, e, etc.) - tagValue?: string; // The tag value - connectedNodes?: string[]; // IDs of nodes that have this tag + id: string; // Unique identifier (event ID) + event?: NDKEvent; // Reference to the original NDK event + level: number; // Hierarchy level in the network + kind: number; // Nostr event kind (30040 for index, 30041/30818 for content) + title: string; // Event title + content: string; // Event content + author: string; // Author's public key + type: "Index" | "Content" | "TagAnchor"; // Node type classification + naddr?: string; // NIP-19 naddr identifier + nevent?: string; // NIP-19 nevent identifier + isContainer?: boolean; // Whether this node is a container (index) + + // Tag anchor specific fields + isTagAnchor?: boolean; // Whether this is a tag anchor node + tagType?: string; // Type of tag (t, p, e, etc.) + tagValue?: string; // The tag value + connectedNodes?: string[]; // IDs of nodes that have this tag } /** @@ -60,17 +60,17 @@ export interface NetworkNode extends SimulationNodeDatum { * Extends the base simulation link with event-specific properties */ export interface NetworkLink extends SimulationLinkDatum { - source: NetworkNode; // Source node (overridden to be more specific) - target: NetworkNode; // Target node (overridden to be more specific) - isSequential: boolean; // Whether this link represents a sequential relationship + source: NetworkNode; // Source node (overridden to be more specific) + target: NetworkNode; // Target node (overridden to be more specific) + isSequential: boolean; // Whether this link represents a sequential relationship } /** * Represents the complete graph data for visualization */ export interface GraphData { - nodes: NetworkNode[]; // All nodes in the graph - links: NetworkLink[]; // All links in the graph + nodes: NetworkNode[]; // All nodes in the graph + links: NetworkLink[]; // All links in the graph } /** @@ -78,8 +78,8 @@ export interface GraphData { * Used to track relationships and build the final graph */ export interface GraphState { - nodeMap: Map; // Maps event IDs to nodes - links: NetworkLink[]; // All links in the graph - eventMap: Map; // Maps event IDs to original events - referencedIds: Set; // Set of event IDs referenced by other events + nodeMap: Map; // Maps event IDs to nodes + links: NetworkLink[]; // All links in the graph + eventMap: Map; // Maps event IDs to original events + referencedIds: Set; // Set of event IDs referenced by other events } diff --git a/src/lib/stores/displayLimits.ts b/src/lib/stores/displayLimits.ts new file mode 100644 index 0000000..a8103b6 --- /dev/null +++ b/src/lib/stores/displayLimits.ts @@ -0,0 +1,19 @@ +import { writable } from 'svelte/store'; + +export interface DisplayLimits { + max30040: number; // -1 for unlimited + max30041: number; // -1 for unlimited + fetchIfNotFound: boolean; +} + +// Create the store with default values +export const displayLimits = writable({ + max30040: -1, // Show all publication indices by default + max30041: -1, // Show all content by default + fetchIfNotFound: false // Don't fetch missing events by default +}); + +// Helper to check if limits are active +export function hasActiveLimits(limits: DisplayLimits): boolean { + return limits.max30040 !== -1 || limits.max30041 !== -1; +} \ No newline at end of file diff --git a/src/lib/stores/index.ts b/src/lib/stores/index.ts new file mode 100644 index 0000000..467f6e7 --- /dev/null +++ b/src/lib/stores/index.ts @@ -0,0 +1,2 @@ +export * from './relayStore'; +export * from './displayLimits'; \ No newline at end of file diff --git a/src/lib/utils/displayLimits.ts b/src/lib/utils/displayLimits.ts new file mode 100644 index 0000000..811b0e1 --- /dev/null +++ b/src/lib/utils/displayLimits.ts @@ -0,0 +1,137 @@ +import type { NDKEvent } from '@nostr-dev-kit/ndk'; +import type { DisplayLimits } from '$lib/stores/displayLimits'; + +/** + * Filters events based on display limits + * @param events - All available events + * @param limits - Display limit settings + * @returns Filtered events that should be displayed + */ +export function filterByDisplayLimits(events: NDKEvent[], limits: DisplayLimits): NDKEvent[] { + if (limits.max30040 === -1 && limits.max30041 === -1) { + // No limits, return all events + return events; + } + + const result: NDKEvent[] = []; + let count30040 = 0; + let count30041 = 0; + + for (const event of events) { + if (event.kind === 30040) { + if (limits.max30040 === -1 || count30040 < limits.max30040) { + result.push(event); + count30040++; + } + } else if (event.kind === 30041) { + if (limits.max30041 === -1 || count30041 < limits.max30041) { + result.push(event); + count30041++; + } + } else { + // Other event kinds always pass through + result.push(event); + } + + // Early exit optimization if both limits are reached + if (limits.max30040 !== -1 && count30040 >= limits.max30040 && + limits.max30041 !== -1 && count30041 >= limits.max30041) { + // Add remaining non-limited events + const remaining = events.slice(events.indexOf(event) + 1); + for (const e of remaining) { + if (e.kind !== 30040 && e.kind !== 30041) { + result.push(e); + } + } + break; + } + } + + return result; +} + +/** + * Detects events that are referenced but not present in the current set + * @param events - Current events + * @param existingIds - Set of all known event IDs + * @returns Set of missing event identifiers + */ +export function detectMissingEvents(events: NDKEvent[], existingIds: Set): Set { + const missing = new Set(); + + for (const event of events) { + // Check 'a' tags for NIP-33 references (kind:pubkey:d-tag) + const aTags = event.getMatchingTags('a'); + for (const aTag of aTags) { + if (aTag.length < 2) continue; + + const identifier = aTag[1]; + const parts = identifier.split(':'); + + if (parts.length >= 3) { + const [kind, pubkey, dTag] = parts; + // Create a synthetic ID for checking + const syntheticId = `${kind}:${pubkey}:${dTag}`; + + // Check if we have an event matching this reference + const hasEvent = Array.from(existingIds).some(id => { + // This is a simplified check - in practice, you'd need to + // check the actual event's d-tag value + return id === dTag || id === syntheticId; + }); + + if (!hasEvent) { + missing.add(dTag); + } + } + } + + // Check 'e' tags for direct event references + const eTags = event.getMatchingTags('e'); + for (const eTag of eTags) { + if (eTag.length < 2) continue; + + const eventId = eTag[1]; + if (!existingIds.has(eventId)) { + missing.add(eventId); + } + } + } + + return missing; +} + +/** + * Groups events by kind for easier counting and display + */ +export function groupEventsByKind(events: NDKEvent[]): Map { + const groups = new Map(); + + for (const event of events) { + const kind = event.kind; + if (kind !== undefined) { + if (!groups.has(kind)) { + groups.set(kind, []); + } + groups.get(kind)!.push(event); + } + } + + return groups; +} + +/** + * Counts events by kind + */ +export function countEventsByKind(events: NDKEvent[]): Map { + const counts = new Map(); + + for (const event of events) { + const kind = event.kind; + if (kind !== undefined) { + counts.set(kind, (counts.get(kind) || 0) + 1); + } + } + + return counts; +} \ No newline at end of file diff --git a/src/routes/visualize/+page.svelte b/src/routes/visualize/+page.svelte index 767b675..ecb2bb6 100644 --- a/src/routes/visualize/+page.svelte +++ b/src/routes/visualize/+page.svelte @@ -11,6 +11,8 @@ import type { NDKEvent } from "@nostr-dev-kit/ndk"; import { filterValidIndexEvents } from "$lib/utils"; import { networkFetchLimit } from "$lib/state"; + import { displayLimits } from "$lib/stores/displayLimits"; + import { filterByDisplayLimits, detectMissingEvents } from "$lib/utils/displayLimits"; import type { PageData } from './$types'; // Configuration @@ -19,7 +21,7 @@ const CONTENT_EVENT_KINDS = [30041, 30818]; // Props from load function - export let data: PageData; + let { data } = $props<{ data: PageData }>(); /** * Debug logging function that only logs when DEBUG is true @@ -31,12 +33,14 @@ } // State - let events: NDKEvent[] = []; - let loading = true; - let error: string | null = null; - let showSettings = false; - let tagExpansionDepth = 0; - let baseEvents: NDKEvent[] = []; // Store original events before expansion + let allEvents = $state([]); // All fetched events + let events = $state([]); // Events to display (filtered by limits) + let loading = $state(true); + let error = $state(null); + let showSettings = $state(false); + let tagExpansionDepth = $state(0); + let baseEvents = $state([]); // Store original events before expansion + let missingEventIds = $state(new Set()); // Track missing referenced events /** * Fetches events from the Nostr network @@ -119,14 +123,29 @@ debug("Fetched content events:", contentEvents.size); // Step 5: Combine both sets of events - events = [...Array.from(validIndexEvents), ...Array.from(contentEvents)]; - baseEvents = [...events]; // Store base events for tag expansion - debug("Total events for visualization:", events.length); + allEvents = [...Array.from(validIndexEvents), ...Array.from(contentEvents)]; + baseEvents = [...allEvents]; // Store base events for tag expansion + + // Step 6: Apply display limits + events = filterByDisplayLimits(allEvents, $displayLimits); + + // Step 7: Detect missing events + const eventIds = new Set(allEvents.map(e => e.id)); + missingEventIds = detectMissingEvents(events, eventIds); + + debug("Total events fetched:", allEvents.length); + debug("Events displayed:", events.length); + debug("Missing event IDs:", missingEventIds.size); + debug("Display limits:", $displayLimits); + debug("About to set loading to false"); + debug("Current loading state:", loading); } catch (e) { console.error("Error fetching events:", e); error = e instanceof Error ? e.message : String(e); } finally { loading = false; + debug("Loading set to false in fetchEvents"); + debug("Final state check - loading:", loading, "events.length:", events.length, "allEvents.length:", allEvents.length); } } @@ -139,7 +158,8 @@ if (depth === 0 || tags.length === 0) { // Reset to base events only - events = [...baseEvents]; + allEvents = [...baseEvents]; + events = filterByDisplayLimits(allEvents, $displayLimits); return; } @@ -167,7 +187,7 @@ // Extract content event IDs from new publications const contentEventIds = new Set(); const existingContentIds = new Set( - baseEvents.filter(e => CONTENT_EVENT_KINDS.includes(e.kind)).map(e => e.id) + baseEvents.filter(e => e.kind !== undefined && CONTENT_EVENT_KINDS.includes(e.kind)).map(e => e.id) ); newPublications.forEach((event) => { @@ -191,17 +211,26 @@ } // Combine all events: base events + new publications + new content - events = [ + allEvents = [ ...baseEvents, ...newPublications, ...newContentEvents ]; + // Apply display limits + events = filterByDisplayLimits(allEvents, $displayLimits); + + // Update missing events detection + const eventIds = new Set(allEvents.map(e => e.id)); + missingEventIds = detectMissingEvents(events, eventIds); + debug("Events after expansion:", { base: baseEvents.length, newPubs: newPublications.length, newContent: newContentEvents.length, - total: events.length + totalFetched: allEvents.length, + displayed: events.length, + missing: missingEventIds.size }); } catch (e) { @@ -210,6 +239,77 @@ } } + /** + * Dynamically fetches missing events when "fetch if not found" is enabled + */ + async function fetchMissingEvents(missingIds: string[]) { + if (!$displayLimits.fetchIfNotFound || missingIds.length === 0) { + return; + } + + debug("Fetching missing events:", missingIds); + debug("Current loading state:", loading); + + try { + // Fetch by event IDs and d-tags + const fetchedEvents = await $ndkInstance.fetchEvents({ + kinds: [...[INDEX_EVENT_KIND], ...CONTENT_EVENT_KINDS], + "#d": missingIds, // For parameterized replaceable events + }); + + if (fetchedEvents.size === 0) { + // Try fetching by IDs directly + const eventsByIds = await $ndkInstance.fetchEvents({ + ids: missingIds + }); + // Add events from the second fetch to the first set + eventsByIds.forEach(e => fetchedEvents.add(e)); + } + + if (fetchedEvents.size > 0) { + debug(`Fetched ${fetchedEvents.size} missing events`); + + // Add to all events + allEvents = [...allEvents, ...Array.from(fetchedEvents)]; + + // Re-apply display limits + events = filterByDisplayLimits(allEvents, $displayLimits); + + // Update missing events list + const eventIds = new Set(allEvents.map(e => e.id)); + missingEventIds = detectMissingEvents(events, eventIds); + } + } catch (e) { + console.error("Error fetching missing events:", e); + } + } + + // React to display limit changes + $effect(() => { + debug("Effect triggered: allEvents.length =", allEvents.length, "displayLimits =", $displayLimits); + if (allEvents.length > 0) { + const newEvents = filterByDisplayLimits(allEvents, $displayLimits); + + // Only update if actually different to avoid infinite loops + if (newEvents.length !== events.length) { + debug("Updating events due to display limit change:", events.length, "->", newEvents.length); + events = newEvents; + + // Check for missing events when limits change + const eventIds = new Set(allEvents.map(e => e.id)); + missingEventIds = detectMissingEvents(events, eventIds); + + debug("Effect: events filtered to", events.length, "missing:", missingEventIds.size); + } + + // Auto-fetch if enabled (but be conservative to avoid infinite loops) + if ($displayLimits.fetchIfNotFound && missingEventIds.size > 0 && missingEventIds.size < 20) { + debug("Auto-fetching", missingEventIds.size, "missing events"); + fetchMissingEvents(Array.from(missingEventIds)); + } + } + }); + // Fetch events when component mounts onMount(() => { debug("Component mounted"); @@ -227,6 +327,7 @@ {#if loading}
+ {debug("TEMPLATE: Loading is true, events.length =", events.length, "allEvents.length =", allEvents.length)}
diff --git a/vite.config.ts b/vite.config.ts index 61e619b..860038d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -32,7 +32,9 @@ export default defineConfig({ } }, test: { - include: ['./tests/unit/**/*.test.ts', './tests/integration/**/*.test.ts'] + include: ['./tests/unit/**/*.test.ts', './tests/integration/**/*.test.ts'], + environment: 'jsdom', + setupFiles: ['./tests/vitest-setup.ts'] }, define: { // Expose the app version as a global variable