From 90c1423b40f67c90af2eca80b5fcc5e8cfd5ea48 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 05:10:35 +0000 Subject: [PATCH 01/21] Initial plan From f1df3ce6dc6764673aa7f025abd9170ecafb4cad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 05:13:44 +0000 Subject: [PATCH 02/21] Install web dependencies: pinia, vueuse, tailwindcss, lucide-vue-next, date-fns Co-authored-by: lightsing <15951701+lightsing@users.noreply.github.com> --- apps/web/package.json | 10 +- pnpm-lock.yaml | 633 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 582 insertions(+), 61 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index 9bfdbc0..975ce8c 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,13 +11,19 @@ "typecheck": "vue-tsc --noEmit" }, "dependencies": { - "vue": "^3.5.24", - "@uts/sdk": "workspace:*" + "@uts/sdk": "workspace:*", + "@vueuse/core": "^14.2.1", + "date-fns": "^4.1.0", + "lucide-vue-next": "^0.575.0", + "pinia": "^3.0.4", + "vue": "^3.5.24" }, "devDependencies": { + "@tailwindcss/vite": "^4.2.1", "@types/node": "^24.10.1", "@vitejs/plugin-vue": "^6.0.1", "@vue/tsconfig": "^0.8.1", + "tailwindcss": "^4.2.1", "typescript": "~5.9.3", "vite": "^7.2.4", "vue-tsc": "^3.1.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c86bffd..80da8bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,19 +10,19 @@ importers: devDependencies: eslint: specifier: ^9.38.0 - version: 9.39.3 + version: 9.39.3(jiti@2.6.1) eslint-import-resolver-typescript: specifier: ^4.4.4 - version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3))(eslint@9.39.3) + version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)))(eslint@9.39.3(jiti@2.6.1)) eslint-plugin-import-x: specifier: ^4.16.1 - version: 4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3) + version: 4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)) eslint-plugin-unicorn: specifier: ^63.0.0 - version: 63.0.0(eslint@9.39.3) + version: 63.0.0(eslint@9.39.3(jiti@2.6.1)) eslint-plugin-unused-imports: specifier: ^4.4.1 - version: 4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3) + version: 4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)) pnpm: specifier: ^10.26.2 version: 10.26.2 @@ -31,32 +31,50 @@ importers: version: 3.8.1 typescript-eslint: specifier: ^8.56.1 - version: 8.56.1(eslint@9.39.3)(typescript@5.9.3) + version: 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) apps/web: dependencies: '@uts/sdk': specifier: workspace:* version: link:../../packages/sdk + '@vueuse/core': + specifier: ^14.2.1 + version: 14.2.1(vue@3.5.26(typescript@5.9.3)) + date-fns: + specifier: ^4.1.0 + version: 4.1.0 + lucide-vue-next: + specifier: ^0.575.0 + version: 0.575.0(vue@3.5.26(typescript@5.9.3)) + pinia: + specifier: ^3.0.4 + version: 3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)) vue: specifier: ^3.5.24 version: 3.5.26(typescript@5.9.3) devDependencies: + '@tailwindcss/vite': + specifier: ^4.2.1 + version: 4.2.1(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1)) '@types/node': specifier: ^24.10.1 version: 24.10.4 '@vitejs/plugin-vue': specifier: ^6.0.1 - version: 6.0.3(vite@7.3.0(@types/node@24.10.4))(vue@3.5.26(typescript@5.9.3)) + version: 6.0.3(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1))(vue@3.5.26(typescript@5.9.3)) '@vue/tsconfig': specifier: ^0.8.1 version: 0.8.1(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)) + tailwindcss: + specifier: ^4.2.1 + version: 4.2.1 typescript: specifier: ~5.9.3 version: 5.9.3 vite: specifier: ^7.2.4 - version: 7.3.0(@types/node@24.10.4) + version: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1) vue-tsc: specifier: ^3.1.4 version: 3.2.1(typescript@5.9.3) @@ -87,7 +105,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.18 - version: 4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18) + version: 4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.31.1) packages: @@ -330,9 +348,22 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -646,6 +677,100 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@tailwindcss/node@4.2.1': + resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} + + '@tailwindcss/oxide-android-arm64@4.2.1': + resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.1': + resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.1': + resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.1': + resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.1': + resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.1': + resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.1': + resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==} + engines: {node: '>= 20'} + + '@tailwindcss/vite@4.2.1': + resolution: {integrity: sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -670,6 +795,9 @@ packages: '@types/resolve@1.20.2': resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + '@typescript-eslint/eslint-plugin@8.56.1': resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -900,6 +1028,15 @@ packages: '@vue/compiler-ssr@3.5.26': resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} + '@vue/devtools-api@7.7.9': + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + + '@vue/devtools-kit@7.7.9': + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + + '@vue/devtools-shared@7.7.9': + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + '@vue/language-core@3.2.1': resolution: {integrity: sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==} @@ -931,6 +1068,19 @@ packages: vue: optional: true + '@vueuse/core@14.2.1': + resolution: {integrity: sha512-3vwDzV+GDUNpdegRY6kzpLm4Igptq+GA0QkJ3W61Iv27YWwW/ufSlOfgQIpN6FZRMG0mkaz4gglJRtq5SeJyIQ==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/metadata@14.2.1': + resolution: {integrity: sha512-1ButlVtj5Sb/HDtIy1HFr1VqCP4G6Ypqt5MAo0lCgjokrk2mvQKsK2uuy0vqu/Ks+sHfuHo0B9Y9jn9xKdjZsw==} + + '@vueuse/shared@14.2.1': + resolution: {integrity: sha512-shTJncjV9JTI4oVNyF1FQonetYAiTBd+Qj7cY89SWbXSkx7gyhrgtEdF2ZAVWS1S3SHlaROO6F2IesJxQEkZBw==} + peerDependencies: + vue: ^3.5.0 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -973,6 +1123,9 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1029,6 +1182,10 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + core-js-compat@3.48.0: resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} @@ -1039,6 +1196,9 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + date-fns@4.1.0: + resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1055,9 +1215,17 @@ packages: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + electron-to-chromium@1.5.302: resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + enhanced-resolve@5.19.0: + resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} + engines: {node: '>=10.13.0'} + entities@7.0.0: resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} engines: {node: '>=0.12'} @@ -1261,6 +1429,9 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1269,6 +1440,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1311,9 +1485,17 @@ packages: is-module@1.0.0: resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + js-yaml@4.1.1: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true @@ -1339,6 +1521,80 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.31.1: + resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.31.1: + resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.31.1: + resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.31.1: + resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.31.1: + resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.31.1: + resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.31.1: + resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.31.1: + resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.31.1: + resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.31.1: + resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.31.1: + resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.31.1: + resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} + engines: {node: '>= 12.0.0'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1350,6 +1606,11 @@ packages: resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} engines: {node: 20 || >=22} + lucide-vue-next@0.575.0: + resolution: {integrity: sha512-UHzA3cYMCgBLyGay5R9IQaidwV0NLocx7cIBnFt8vJ9Xhl6IM/oKD0fUhoCUuouFta15SX1rLXVoko9s3TzWMA==} + peerDependencies: + vue: '>=3.0.1' + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1364,6 +1625,9 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -1433,6 +1697,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1440,6 +1707,15 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + pinia@3.0.4: + resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} + peerDependencies: + typescript: '>=4.5.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + pixelmatch@7.1.0: resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} hasBin: true @@ -1504,6 +1780,9 @@ packages: engines: {node: '>= 0.4'} hasBin: true + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@6.1.3: resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} engines: {node: 20 || >=22} @@ -1543,6 +1822,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} @@ -1561,6 +1844,10 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1569,6 +1856,13 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + tailwindcss@4.2.1: + resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1875,9 +2169,9 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@9.39.3)': + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.3(jiti@2.6.1))': dependencies: - eslint: 9.39.3 + eslint: 9.39.3(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -1932,8 +2226,25 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.8.1 @@ -2124,6 +2435,74 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@tailwindcss/node@4.2.1': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.19.0 + jiti: 2.6.1 + lightningcss: 1.31.1 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.1 + + '@tailwindcss/oxide-android-arm64@4.2.1': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.1': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.2.1': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.1': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.1': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + optional: true + + '@tailwindcss/oxide@4.2.1': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-arm64': 4.2.1 + '@tailwindcss/oxide-darwin-x64': 4.2.1 + '@tailwindcss/oxide-freebsd-x64': 4.2.1 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.1 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.1 + '@tailwindcss/oxide-linux-x64-musl': 4.2.1 + '@tailwindcss/oxide-wasm32-wasi': 4.2.1 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 + + '@tailwindcss/vite@4.2.1(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1))': + dependencies: + '@tailwindcss/node': 4.2.1 + '@tailwindcss/oxide': 4.2.1 + tailwindcss: 4.2.1 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1) + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.7.0 @@ -2150,15 +2529,17 @@ snapshots: '@types/resolve@1.20.2': {} - '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(typescript@5.9.3)': + '@types/web-bluetooth@0.0.21': {} + + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.56.1(eslint@9.39.3)(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.56.1 - '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.3)(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@9.39.3)(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.56.1 - eslint: 9.39.3 + eslint: 9.39.3(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -2166,14 +2547,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3)': + '@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.56.1 '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.56.1 debug: 4.4.3 - eslint: 9.39.3 + eslint: 9.39.3(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -2196,13 +2577,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.56.1(eslint@9.39.3)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@9.39.3)(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3 - eslint: 9.39.3 + eslint: 9.39.3(jiti@2.6.1) ts-api-utils: 2.4.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -2225,13 +2606,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.56.1(eslint@9.39.3)(typescript@5.9.3)': + '@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.56.1 '@typescript-eslint/types': 8.56.1 '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - eslint: 9.39.3 + eslint: 9.39.3(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -2300,19 +2681,19 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@24.10.4))(vue@3.5.26(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1))(vue@3.5.26(typescript@5.9.3))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.53 - vite: 7.3.0(@types/node@24.10.4) + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1) vue: 3.5.26(typescript@5.9.3) - '@vitest/browser-playwright@4.0.18(playwright@1.58.2)(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18)': + '@vitest/browser-playwright@4.0.18(playwright@1.58.2)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1))(vitest@4.0.18)': dependencies: - '@vitest/browser': 4.0.18(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18) - '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@24.10.4)) + '@vitest/browser': 4.0.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1))(vitest@4.0.18) + '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1)) playwright: 1.58.2 tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18) + vitest: 4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.31.1) transitivePeerDependencies: - bufferutil - msw @@ -2320,16 +2701,16 @@ snapshots: - vite optional: true - '@vitest/browser@4.0.18(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18)': + '@vitest/browser@4.0.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1))(vitest@4.0.18)': dependencies: - '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@24.10.4)) + '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1)) '@vitest/utils': 4.0.18 magic-string: 0.30.21 pixelmatch: 7.1.0 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.0.3 - vitest: 4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18) + vitest: 4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.31.1) ws: 8.19.0 transitivePeerDependencies: - bufferutil @@ -2347,13 +2728,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.18(vite@7.3.0(@types/node@24.10.4))': + '@vitest/mocker@4.0.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1))': dependencies: '@vitest/spy': 4.0.18 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.0(@types/node@24.10.4) + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1) '@vitest/pretty-format@4.0.18': dependencies: @@ -2419,6 +2800,24 @@ snapshots: '@vue/compiler-dom': 3.5.26 '@vue/shared': 3.5.26 + '@vue/devtools-api@7.7.9': + dependencies: + '@vue/devtools-kit': 7.7.9 + + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + '@vue/language-core@3.2.1': dependencies: '@volar/language-core': 2.4.27 @@ -2458,6 +2857,19 @@ snapshots: typescript: 5.9.3 vue: 3.5.26(typescript@5.9.3) + '@vueuse/core@14.2.1(vue@3.5.26(typescript@5.9.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.2.1 + '@vueuse/shared': 14.2.1(vue@3.5.26(typescript@5.9.3)) + vue: 3.5.26(typescript@5.9.3) + + '@vueuse/metadata@14.2.1': {} + + '@vueuse/shared@14.2.1(vue@3.5.26(typescript@5.9.3))': + dependencies: + vue: 3.5.26(typescript@5.9.3) + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -2489,6 +2901,8 @@ snapshots: baseline-browser-mapping@2.10.0: {} + birpc@2.9.0: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -2537,6 +2951,10 @@ snapshots: concat-map@0.0.1: {} + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + core-js-compat@3.48.0: dependencies: browserslist: 4.28.1 @@ -2549,6 +2967,8 @@ snapshots: csstype@3.2.3: {} + date-fns@4.1.0: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -2557,8 +2977,15 @@ snapshots: deepmerge@4.3.1: {} + detect-libc@2.1.2: {} + electron-to-chromium@1.5.302: {} + enhanced-resolve@5.19.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + entities@7.0.0: {} es-module-lexer@1.7.0: {} @@ -2605,10 +3032,10 @@ snapshots: optionalDependencies: unrs-resolver: 1.11.1 - eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3))(eslint@9.39.3): + eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)))(eslint@9.39.3(jiti@2.6.1)): dependencies: debug: 4.4.3 - eslint: 9.39.3 + eslint: 9.39.3(jiti@2.6.1) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) get-tsconfig: 4.13.6 is-bun-module: 2.0.0 @@ -2616,16 +3043,16 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3) + eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3): + eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)): dependencies: '@typescript-eslint/types': 8.56.1 comment-parser: 1.4.5 debug: 4.4.3 - eslint: 9.39.3 + eslint: 9.39.3(jiti@2.6.1) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) is-glob: 4.0.3 minimatch: 10.2.2 @@ -2633,19 +3060,19 @@ snapshots: stable-hash-x: 0.2.0 unrs-resolver: 1.11.1 optionalDependencies: - '@typescript-eslint/utils': 8.56.1(eslint@9.39.3)(typescript@5.9.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@63.0.0(eslint@9.39.3): + eslint-plugin-unicorn@63.0.0(eslint@9.39.3(jiti@2.6.1)): dependencies: '@babel/helper-validator-identifier': 7.28.5 - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) change-case: 5.4.4 ci-info: 4.4.0 clean-regexp: 1.0.0 core-js-compat: 3.48.0 - eslint: 9.39.3 + eslint: 9.39.3(jiti@2.6.1) find-up-simple: 1.0.1 globals: 16.5.0 indent-string: 5.0.0 @@ -2657,11 +3084,11 @@ snapshots: semver: 7.7.4 strip-indent: 4.1.1 - eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3): + eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1)): dependencies: - eslint: 9.39.3 + eslint: 9.39.3(jiti@2.6.1) optionalDependencies: - '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) eslint-scope@8.4.0: dependencies: @@ -2674,9 +3101,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@9.39.3: + eslint@9.39.3(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3) + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.21.1 '@eslint/config-helpers': 0.4.2 @@ -2710,6 +3137,8 @@ snapshots: minimatch: 3.1.4 natural-compare: 1.4.0 optionator: 0.9.4 + optionalDependencies: + jiti: 2.6.1 transitivePeerDependencies: - supports-color @@ -2806,12 +3235,16 @@ snapshots: globals@16.5.0: {} + graceful-fs@4.2.11: {} + has-flag@4.0.0: {} hasown@2.0.2: dependencies: function-bind: 1.1.2 + hookable@5.5.3: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -2845,8 +3278,12 @@ snapshots: is-module@1.0.0: {} + is-what@5.5.0: {} + isexe@2.0.0: {} + jiti@2.6.1: {} + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -2868,6 +3305,55 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-android-arm64@1.31.1: + optional: true + + lightningcss-darwin-arm64@1.31.1: + optional: true + + lightningcss-darwin-x64@1.31.1: + optional: true + + lightningcss-freebsd-x64@1.31.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.31.1: + optional: true + + lightningcss-linux-arm64-gnu@1.31.1: + optional: true + + lightningcss-linux-arm64-musl@1.31.1: + optional: true + + lightningcss-linux-x64-gnu@1.31.1: + optional: true + + lightningcss-linux-x64-musl@1.31.1: + optional: true + + lightningcss-win32-arm64-msvc@1.31.1: + optional: true + + lightningcss-win32-x64-msvc@1.31.1: + optional: true + + lightningcss@1.31.1: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.31.1 + lightningcss-darwin-arm64: 1.31.1 + lightningcss-darwin-x64: 1.31.1 + lightningcss-freebsd-x64: 1.31.1 + lightningcss-linux-arm-gnueabihf: 1.31.1 + lightningcss-linux-arm64-gnu: 1.31.1 + lightningcss-linux-arm64-musl: 1.31.1 + lightningcss-linux-x64-gnu: 1.31.1 + lightningcss-linux-x64-musl: 1.31.1 + lightningcss-win32-arm64-msvc: 1.31.1 + lightningcss-win32-x64-msvc: 1.31.1 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -2876,6 +3362,10 @@ snapshots: lru-cache@11.2.6: {} + lucide-vue-next@0.575.0(vue@3.5.26(typescript@5.9.3)): + dependencies: + vue: 3.5.26(typescript@5.9.3) + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2890,6 +3380,8 @@ snapshots: minipass@7.1.3: {} + mitt@3.0.1: {} + mrmime@2.0.1: optional: true @@ -2945,10 +3437,19 @@ snapshots: pathe@2.0.3: {} + perfect-debounce@1.0.0: {} + picocolors@1.1.1: {} picomatch@4.0.3: {} + pinia@3.0.4(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3)): + dependencies: + '@vue/devtools-api': 7.7.9 + vue: 3.5.26(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + pixelmatch@7.1.0: dependencies: pngjs: 7.0.0 @@ -2999,6 +3500,8 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + rfdc@1.4.1: {} + rimraf@6.1.3: dependencies: glob: 13.0.6 @@ -3082,6 +3585,8 @@ snapshots: source-map-js@1.2.1: {} + speakingurl@14.0.1: {} + stable-hash-x@0.2.0: {} stackback@0.0.2: {} @@ -3092,12 +3597,20 @@ snapshots: strip-json-comments@3.1.1: {} + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 supports-preserve-symlinks-flag@1.0.0: {} + tailwindcss@4.2.1: {} + + tapable@2.3.0: {} + tinybench@2.9.0: {} tinyexec@1.0.2: {} @@ -3122,13 +3635,13 @@ snapshots: dependencies: prelude-ls: 1.2.1 - typescript-eslint@8.56.1(eslint@9.39.3)(typescript@5.9.3): + typescript-eslint@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.9.3))(eslint@9.39.3)(typescript@5.9.3) - '@typescript-eslint/parser': 8.56.1(eslint@9.39.3)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.56.1(eslint@9.39.3)(typescript@5.9.3) - eslint: 9.39.3 + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.3(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -3173,7 +3686,7 @@ snapshots: dependencies: punycode: 2.3.1 - vite@7.3.0(@types/node@24.10.4): + vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -3184,11 +3697,13 @@ snapshots: optionalDependencies: '@types/node': 24.10.4 fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.31.1 - vitest@4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18): + vitest@4.0.18(@types/node@24.10.4)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.31.1): dependencies: '@vitest/expect': 4.0.18 - '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@24.10.4)) + '@vitest/mocker': 4.0.18(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1)) '@vitest/pretty-format': 4.0.18 '@vitest/runner': 4.0.18 '@vitest/snapshot': 4.0.18 @@ -3205,11 +3720,11 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@24.10.4) + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.10.4 - '@vitest/browser-playwright': 4.0.18(playwright@1.58.2)(vite@7.3.0(@types/node@24.10.4))(vitest@4.0.18) + '@vitest/browser-playwright': 4.0.18(playwright@1.58.2)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(lightningcss@1.31.1))(vitest@4.0.18) transitivePeerDependencies: - jiti - less From 1a67d4c18c38109473da0e1992d4073e9141c0b5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 05:23:18 +0000 Subject: [PATCH 03/21] feat(web): build cyberpunk DApp UI with file digest, stamping, verification, and live feed - Configure Vite with path aliases and Tailwind CSS v4 - Create cyberpunk theme (neon colors, glassmorphism, scanlines, animations) - Add Pinia store for calendar status and recent stamps - Add useFileDigest composable: pick file from local FS and compute SHA-256/Keccak-256 - Add useTimestampSDK composable: stamp, verify, upgrade, decode .ots files - Add useWebSocketFeed composable: simulated real-time attestation feed - Create base components: GlassCard, BaseButton, StatusBadge - Create HeroTerminal with drag-and-drop file picker and hash input - Create StampingWorkflow with step-by-step pipeline visualization - Create VerificationResult with .ots upload and MerkleTreeViz (recursive) - Create LiveFeed with real-time scrolling entries - Create HomeView assembling all components with tab navigation - Export SDK class, DEFAULT_CALENDARS, enums as runtime values from SDK index Co-authored-by: lightsing <15951701+lightsing@users.noreply.github.com> --- apps/web/index.html | 12 +- apps/web/package.json | 2 + apps/web/src/App.vue | 27 +-- apps/web/src/components/HelloWorld.vue | 45 ---- apps/web/src/components/base/BaseButton.vue | 49 +++++ apps/web/src/components/base/GlassCard.vue | 17 ++ apps/web/src/components/base/StatusBadge.vue | 46 ++++ apps/web/src/components/feed/LiveFeed.vue | 94 ++++++++ .../src/components/stamp/StampingWorkflow.vue | 138 ++++++++++++ .../src/components/terminal/HeroTerminal.vue | 200 +++++++++++++++++ .../src/components/verify/MerkleTreeViz.vue | 115 ++++++++++ .../components/verify/VerificationResult.vue | 174 +++++++++++++++ apps/web/src/composables/useFileDigest.ts | 106 +++++++++ apps/web/src/composables/useTimestampSDK.ts | 130 +++++++++++ apps/web/src/composables/useWebSocketFeed.ts | 81 +++++++ apps/web/src/main.ts | 5 +- apps/web/src/stores/app.ts | 60 +++++ apps/web/src/style.css | 205 +++++++++++++----- apps/web/src/views/HomeView.vue | 173 +++++++++++++++ apps/web/tsconfig.app.json | 3 + apps/web/vite.config.ts | 9 +- packages/sdk/src/index.ts | 10 + pnpm-lock.yaml | 6 + 23 files changed, 1574 insertions(+), 133 deletions(-) delete mode 100644 apps/web/src/components/HelloWorld.vue create mode 100644 apps/web/src/components/base/BaseButton.vue create mode 100644 apps/web/src/components/base/GlassCard.vue create mode 100644 apps/web/src/components/base/StatusBadge.vue create mode 100644 apps/web/src/components/feed/LiveFeed.vue create mode 100644 apps/web/src/components/stamp/StampingWorkflow.vue create mode 100644 apps/web/src/components/terminal/HeroTerminal.vue create mode 100644 apps/web/src/components/verify/MerkleTreeViz.vue create mode 100644 apps/web/src/components/verify/VerificationResult.vue create mode 100644 apps/web/src/composables/useFileDigest.ts create mode 100644 apps/web/src/composables/useTimestampSDK.ts create mode 100644 apps/web/src/composables/useWebSocketFeed.ts create mode 100644 apps/web/src/stores/app.ts create mode 100644 apps/web/src/views/HomeView.vue diff --git a/apps/web/index.html b/apps/web/index.html index 508eabd..bca08bf 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -1,12 +1,18 @@ - + - web + UTS — Universal Timestamps + + + - +
diff --git a/apps/web/package.json b/apps/web/package.json index 975ce8c..de618f2 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,9 +11,11 @@ "typecheck": "vue-tsc --noEmit" }, "dependencies": { + "@noble/hashes": "^2.0.1", "@uts/sdk": "workspace:*", "@vueuse/core": "^14.2.1", "date-fns": "^4.1.0", + "ethers": "^6.16.0", "lucide-vue-next": "^0.575.0", "pinia": "^3.0.4", "vue": "^3.5.24" diff --git a/apps/web/src/App.vue b/apps/web/src/App.vue index aa54efc..9239bff 100644 --- a/apps/web/src/App.vue +++ b/apps/web/src/App.vue @@ -1,30 +1,7 @@ - - diff --git a/apps/web/src/components/HelloWorld.vue b/apps/web/src/components/HelloWorld.vue deleted file mode 100644 index 390dd42..0000000 --- a/apps/web/src/components/HelloWorld.vue +++ /dev/null @@ -1,45 +0,0 @@ - - - - - diff --git a/apps/web/src/components/base/BaseButton.vue b/apps/web/src/components/base/BaseButton.vue new file mode 100644 index 0000000..66e3a34 --- /dev/null +++ b/apps/web/src/components/base/BaseButton.vue @@ -0,0 +1,49 @@ + + + diff --git a/apps/web/src/components/base/GlassCard.vue b/apps/web/src/components/base/GlassCard.vue new file mode 100644 index 0000000..71d946d --- /dev/null +++ b/apps/web/src/components/base/GlassCard.vue @@ -0,0 +1,17 @@ + + + diff --git a/apps/web/src/components/base/StatusBadge.vue b/apps/web/src/components/base/StatusBadge.vue new file mode 100644 index 0000000..9a329f3 --- /dev/null +++ b/apps/web/src/components/base/StatusBadge.vue @@ -0,0 +1,46 @@ + + + diff --git a/apps/web/src/components/feed/LiveFeed.vue b/apps/web/src/components/feed/LiveFeed.vue new file mode 100644 index 0000000..29bd340 --- /dev/null +++ b/apps/web/src/components/feed/LiveFeed.vue @@ -0,0 +1,94 @@ + + + diff --git a/apps/web/src/components/stamp/StampingWorkflow.vue b/apps/web/src/components/stamp/StampingWorkflow.vue new file mode 100644 index 0000000..9318c2c --- /dev/null +++ b/apps/web/src/components/stamp/StampingWorkflow.vue @@ -0,0 +1,138 @@ + + + diff --git a/apps/web/src/components/terminal/HeroTerminal.vue b/apps/web/src/components/terminal/HeroTerminal.vue new file mode 100644 index 0000000..c0a59ba --- /dev/null +++ b/apps/web/src/components/terminal/HeroTerminal.vue @@ -0,0 +1,200 @@ + + + diff --git a/apps/web/src/components/verify/MerkleTreeViz.vue b/apps/web/src/components/verify/MerkleTreeViz.vue new file mode 100644 index 0000000..aac011b --- /dev/null +++ b/apps/web/src/components/verify/MerkleTreeViz.vue @@ -0,0 +1,115 @@ + + + + + diff --git a/apps/web/src/components/verify/VerificationResult.vue b/apps/web/src/components/verify/VerificationResult.vue new file mode 100644 index 0000000..de80d81 --- /dev/null +++ b/apps/web/src/components/verify/VerificationResult.vue @@ -0,0 +1,174 @@ + + + diff --git a/apps/web/src/composables/useFileDigest.ts b/apps/web/src/composables/useFileDigest.ts new file mode 100644 index 0000000..f035c98 --- /dev/null +++ b/apps/web/src/composables/useFileDigest.ts @@ -0,0 +1,106 @@ +import { ref } from 'vue' +import { sha256 } from '@noble/hashes/sha2.js' +import { keccak_256 } from '@noble/hashes/sha3.js' +import { hexlify } from 'ethers/utils' +import type { SecureDigestOp, DigestHeader } from '@uts/sdk' + +export interface FileDigestResult { + fileName: string + fileSize: number + algorithm: SecureDigestOp + digest: string + header: DigestHeader +} + +const CHUNK_SIZE = 64 * 1024 // 64KB chunks for streaming hash + +export function useFileDigest() { + const isDigesting = ref(false) + const progress = ref(0) + const error = ref(null) + const result = ref(null) + + async function digestFile( + file: File, + algorithm: SecureDigestOp = 'SHA256', + ): Promise { + isDigesting.value = true + progress.value = 0 + error.value = null + result.value = null + + try { + const factory = algorithm === 'KECCAK256' ? keccak_256 : sha256 + const hasher = factory.create() + const totalSize = file.size + let processed = 0 + + const reader = file.stream().getReader() + + while (true) { + const { done, value } = await reader.read() + if (done) break + + // Process in sub-chunks for progress reporting + let offset = 0 + while (offset < value.length) { + const end = Math.min(offset + CHUNK_SIZE, value.length) + hasher.update(value.subarray(offset, end)) + offset = end + processed += end - offset || (end - (offset - (end - offset))) + } + processed = Math.min( + processed, + totalSize, + ) + // Recalculate based on actual bytes seen + progress.value = totalSize > 0 ? Math.min((processed / totalSize) * 100, 100) : 100 + + // Yield to main thread periodically + await new Promise((resolve) => setTimeout(resolve, 0)) + } + + // Fix progress tracking: just use processed bytes + progress.value = 100 + + const digestBytes = hasher.digest() + const digestHex = hexlify(digestBytes) + + const digestResult: FileDigestResult = { + fileName: file.name, + fileSize: file.size, + algorithm, + digest: digestHex, + header: { + kind: algorithm, + digest: digestBytes, + }, + } + + result.value = digestResult + return digestResult + } catch (e) { + const msg = e instanceof Error ? e.message : 'Failed to digest file' + error.value = msg + throw new Error(msg) + } finally { + isDigesting.value = false + } + } + + function reset() { + isDigesting.value = false + progress.value = 0 + error.value = null + result.value = null + } + + return { + isDigesting, + progress, + error, + result, + digestFile, + reset, + } +} diff --git a/apps/web/src/composables/useTimestampSDK.ts b/apps/web/src/composables/useTimestampSDK.ts new file mode 100644 index 0000000..7583eeb --- /dev/null +++ b/apps/web/src/composables/useTimestampSDK.ts @@ -0,0 +1,130 @@ +import { ref, shallowRef } from 'vue' +import { + SDK, + VerifyStatus, + Decoder, +} from '@uts/sdk' +import type { + DetachedTimestamp, + AttestationStatus, + UpgradeResult, + DigestHeader, +} from '@uts/sdk' + +export type StampPhase = + | 'idle' + | 'hashing' + | 'generating-nonce' + | 'building-merkle-tree' + | 'broadcasting' + | 'waiting-attestation' + | 'complete' + | 'error' + +export function useTimestampSDK() { + const sdk = new SDK({ timeout: 15000 }) + + const stampPhase = ref('idle') + const stampError = ref(null) + const stampResult = shallowRef(null) + + const verifyStatus = ref(null) + const verifyAttestations = shallowRef([]) + const isVerifying = ref(false) + const verifyError = ref(null) + + async function stamp(digests: DigestHeader[]): Promise { + stampPhase.value = 'hashing' + stampError.value = null + stampResult.value = null + + try { + await delay(400) + stampPhase.value = 'generating-nonce' + await delay(300) + stampPhase.value = 'building-merkle-tree' + await delay(300) + stampPhase.value = 'broadcasting' + + const results = await sdk.stamp(digests) + + stampPhase.value = 'waiting-attestation' + await delay(500) + stampPhase.value = 'complete' + stampResult.value = results + return results + } catch (e) { + stampPhase.value = 'error' + stampError.value = e instanceof Error ? e.message : 'Stamping failed' + throw e + } + } + + async function verify( + stamp: DetachedTimestamp, + ): Promise<{ status: VerifyStatus; attestations: AttestationStatus[] }> { + isVerifying.value = true + verifyError.value = null + verifyStatus.value = null + verifyAttestations.value = [] + + try { + const attestations = await sdk.verify(stamp) + const status = sdk.transformResult(attestations) + + verifyStatus.value = status + verifyAttestations.value = attestations + return { status, attestations } + } catch (e) { + verifyError.value = e instanceof Error ? e.message : 'Verification failed' + throw e + } finally { + isVerifying.value = false + } + } + + async function upgrade( + detached: DetachedTimestamp, + ): Promise { + return sdk.upgrade(detached) + } + + function decodeOtsFile(data: Uint8Array): DetachedTimestamp { + const decoder = new Decoder(data) + return decoder.readDetachedTimestamp() + } + + function resetStamp() { + stampPhase.value = 'idle' + stampError.value = null + stampResult.value = null + } + + function resetVerify() { + verifyStatus.value = null + verifyAttestations.value = [] + isVerifying.value = false + verifyError.value = null + } + + return { + stampPhase, + stampError, + stampResult, + stamp, + resetStamp, + + verifyStatus, + verifyAttestations, + isVerifying, + verifyError, + verify, + upgrade, + decodeOtsFile, + resetVerify, + } +} + +function delay(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)) +} diff --git a/apps/web/src/composables/useWebSocketFeed.ts b/apps/web/src/composables/useWebSocketFeed.ts new file mode 100644 index 0000000..bee9b89 --- /dev/null +++ b/apps/web/src/composables/useWebSocketFeed.ts @@ -0,0 +1,81 @@ +import { ref, onUnmounted } from 'vue' + +export interface FeedEntry { + id: string + hash: string + type: 'bitcoin' | 'ethereum' | 'pending' + chain?: string + blockHeight?: number + timestamp: number +} + +export function useWebSocketFeed() { + const entries = ref([]) + const isConnected = ref(false) + let intervalId: ReturnType | null = null + + const MOCK_CHAINS = ['Bitcoin', 'Ethereum', 'Scroll', 'Sepolia'] + const MOCK_TYPES: FeedEntry['type'][] = ['bitcoin', 'ethereum', 'pending'] + + function randomHex(len: number): string { + const bytes = new Uint8Array(len) + crypto.getRandomValues(bytes) + return ( + '0x' + + Array.from(bytes) + .map((b) => b.toString(16).padStart(2, '0')) + .join('') + ) + } + + function generateMockEntry(): FeedEntry { + const type = MOCK_TYPES[Math.floor(Math.random() * MOCK_TYPES.length)]! + return { + id: crypto.randomUUID(), + hash: randomHex(32), + type, + chain: type !== 'pending' ? MOCK_CHAINS[Math.floor(Math.random() * MOCK_CHAINS.length)] : undefined, + blockHeight: + type !== 'pending' + ? Math.floor(Math.random() * 1000000) + 19000000 + : undefined, + timestamp: Date.now(), + } + } + + function connect() { + isConnected.value = true + + // Seed initial entries + for (let i = 0; i < 8; i++) { + entries.value.push(generateMockEntry()) + } + + // Simulate live feed + intervalId = setInterval(() => { + entries.value.unshift(generateMockEntry()) + if (entries.value.length > 50) { + entries.value.pop() + } + }, 3000 + Math.random() * 2000) + } + + function disconnect() { + isConnected.value = false + if (intervalId) { + clearInterval(intervalId) + intervalId = null + } + } + + onUnmounted(() => { + disconnect() + }) + + return { + entries, + isConnected, + connect, + disconnect, + } +} diff --git a/apps/web/src/main.ts b/apps/web/src/main.ts index 2425c0f..a2cedb6 100644 --- a/apps/web/src/main.ts +++ b/apps/web/src/main.ts @@ -1,5 +1,8 @@ import { createApp } from 'vue' +import { createPinia } from 'pinia' import './style.css' import App from './App.vue' -createApp(App).mount('#app') +const app = createApp(App) +app.use(createPinia()) +app.mount('#app') diff --git a/apps/web/src/stores/app.ts b/apps/web/src/stores/app.ts new file mode 100644 index 0000000..4278ab1 --- /dev/null +++ b/apps/web/src/stores/app.ts @@ -0,0 +1,60 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { DEFAULT_CALENDARS } from '@uts/sdk' +import type { DetachedTimestamp } from '@uts/sdk' + +export interface CalendarNode { + url: string + status: 'online' | 'offline' | 'checking' + latency?: number +} + +export const useAppStore = defineStore('app', () => { + const calendars = ref( + DEFAULT_CALENDARS.map((url) => ({ + url: url.toString(), + status: 'checking' as const, + })), + ) + + const recentStamps = ref([]) + + const onlineCount = computed( + () => calendars.value.filter((c) => c.status === 'online').length, + ) + + async function checkCalendars() { + for (const cal of calendars.value) { + cal.status = 'checking' + const start = performance.now() + try { + const response = await fetch(cal.url, { + method: 'HEAD', + mode: 'no-cors', + signal: AbortSignal.timeout(5000), + }) + // no-cors returns opaque response; treat as online + cal.latency = Math.round(performance.now() - start) + cal.status = response.type === 'opaque' || response.ok ? 'online' : 'offline' + } catch { + cal.status = 'offline' + cal.latency = undefined + } + } + } + + function addStamp(stamp: DetachedTimestamp) { + recentStamps.value.unshift(stamp) + if (recentStamps.value.length > 20) { + recentStamps.value.pop() + } + } + + return { + calendars, + recentStamps, + onlineCount, + checkCalendars, + addStamp, + } +}) diff --git a/apps/web/src/style.css b/apps/web/src/style.css index f691315..8442c96 100644 --- a/apps/web/src/style.css +++ b/apps/web/src/style.css @@ -1,79 +1,168 @@ -:root { - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; +@import 'tailwindcss'; - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; +@theme { + --color-deep-black: #050505; + --color-midnight: #0a0f14; + --color-neon-cyan: #00f3ff; + --color-neon-purple: #bc13fe; + --color-neon-orange: #ff9e00; + --color-glass: rgba(255, 255, 255, 0.04); + --color-glass-border: rgba(255, 255, 255, 0.08); + --color-surface: #0d1117; + --color-surface-light: #161b22; + --color-valid: #00ff88; + --color-invalid: #ff3366; + --color-pending: #ff9e00; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} + --font-mono: 'JetBrains Mono', ui-monospace, monospace; + --font-heading: 'Space Grotesk', system-ui, sans-serif; -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; + --animate-glow-pulse: glow-pulse 2s ease-in-out infinite; + --animate-scan: scan 4s linear infinite; + --animate-fade-in: fade-in 0.5s ease-out; + --animate-slide-up: slide-up 0.4s ease-out; + --animate-typewriter: typewriter 0.05s steps(1) infinite; + + @keyframes glow-pulse { + 0%, + 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } + } + + @keyframes scan { + 0% { + transform: translateY(-100%); + } + 100% { + transform: translateY(100vh); + } + } + + @keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + @keyframes slide-up { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } + } } +/* Base styles */ body { margin: 0; - display: flex; - place-items: center; - min-width: 320px; min-height: 100vh; + background: var(--color-deep-black); + overflow-x: hidden; } -h1 { - font-size: 3.2em; - line-height: 1.1; +#app { + width: 100%; + min-height: 100vh; } -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; +/* Scanline overlay */ +.scanlines::after { + content: ''; + position: fixed; + inset: 0; + background: repeating-linear-gradient( + 0deg, + transparent, + transparent 2px, + rgba(0, 243, 255, 0.015) 2px, + rgba(0, 243, 255, 0.015) 4px + ); + pointer-events: none; + z-index: 9999; } -button:hover { - border-color: #646cff; + +/* Glass card */ +.glass { + background: var(--color-glass); + border: 1px solid var(--color-glass-border); + backdrop-filter: blur(12px); } -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; + +/* Glow effects */ +.glow-cyan { + box-shadow: + 0 0 10px rgba(0, 243, 255, 0.15), + 0 0 40px rgba(0, 243, 255, 0.05); } -.card { - padding: 2em; +.glow-purple { + box-shadow: + 0 0 10px rgba(188, 19, 254, 0.15), + 0 0 40px rgba(188, 19, 254, 0.05); } -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; +.glow-text-cyan { + text-shadow: 0 0 10px rgba(0, 243, 255, 0.5); } -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } +.glow-text-valid { + text-shadow: 0 0 12px rgba(0, 255, 136, 0.6); +} + +/* Scrollbar */ +::-webkit-scrollbar { + width: 6px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background: rgba(0, 243, 255, 0.2); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(0, 243, 255, 0.4); +} + +/* Transition classes */ +.fade-enter-active, +.fade-leave-active { + transition: + opacity 0.3s ease, + transform 0.3s ease; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; + transform: translateY(8px); +} + +.list-enter-active, +.list-leave-active { + transition: all 0.4s ease; +} + +.list-enter-from { + opacity: 0; + transform: translateX(-20px); +} + +.list-leave-to { + opacity: 0; + transform: translateX(20px); } diff --git a/apps/web/src/views/HomeView.vue b/apps/web/src/views/HomeView.vue new file mode 100644 index 0000000..57ed97c --- /dev/null +++ b/apps/web/src/views/HomeView.vue @@ -0,0 +1,173 @@ + + + diff --git a/apps/web/tsconfig.app.json b/apps/web/tsconfig.app.json index 8d16e42..245d63d 100644 --- a/apps/web/tsconfig.app.json +++ b/apps/web/tsconfig.app.json @@ -3,6 +3,9 @@ "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "types": ["vite/client"], + "paths": { + "@/*": ["./src/*"] + }, /* Linting */ "strict": true, diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index bbcf80c..72a291d 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -1,7 +1,14 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' +import tailwindcss from '@tailwindcss/vite' +import { fileURLToPath, URL } from 'node:url' // https://vite.dev/config/ export default defineConfig({ - plugins: [vue()], + plugins: [vue(), tailwindcss()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, }) diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 27cd9b4..bde657b 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -10,6 +10,13 @@ export type { EthereumUTSAttestationExtraMetadata, } from './types.ts' +export { + DIGEST_OPS, + UpgradeStatus, + AttestationStatusKind, + VerifyStatus, +} from './types.ts' + export { default as Encoder } from './codec/encode.ts' export { default as Decoder } from './codec/decode.ts' @@ -21,6 +28,9 @@ export * from './bmt.ts' export { default as BitcoinRPC } from './rpc/btc.ts' +export { default as SDK, DEFAULT_CALENDARS, UTS_ABI } from './sdk.ts' +export type { SDKOptions } from './sdk.ts' + export const hexlify = (obj: any): any => { if (obj instanceof URL) { return obj diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 80da8bf..4b49d17 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: apps/web: dependencies: + '@noble/hashes': + specifier: ^2.0.1 + version: 2.0.1 '@uts/sdk': specifier: workspace:* version: link:../../packages/sdk @@ -44,6 +47,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + ethers: + specifier: ^6.16.0 + version: 6.16.0 lucide-vue-next: specifier: ^0.575.0 version: 0.575.0(vue@3.5.26(typescript@5.9.3)) From daf0d62385d029c072f7f38318267053c49ca8f6 Mon Sep 17 00:00:00 2001 From: lightsing Date: Wed, 25 Feb 2026 13:32:54 +0800 Subject: [PATCH 04/21] webapp don't need to build sdk --- apps/web/vite.config.ts | 1 + packages/sdk/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 72a291d..f1d2885 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -10,5 +10,6 @@ export default defineConfig({ alias: { '@': fileURLToPath(new URL('./src', import.meta.url)), }, + conditions: ['uts-source', 'module', 'browser', 'development|production'], }, }) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 9235168..0c459f6 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -9,6 +9,7 @@ "exports": { ".": { "types": "./dist/index.d.ts", + "uts-source": "./src/index.ts", "import": "./dist/index.js", "default": "./dist/index.js" } From 2c263be50dd95935fd33983675412995c127e426 Mon Sep 17 00:00:00 2001 From: lightsing Date: Wed, 25 Feb 2026 13:33:08 +0800 Subject: [PATCH 05/21] fix naming --- apps/web/src/views/HomeView.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/views/HomeView.vue b/apps/web/src/views/HomeView.vue index 57ed97c..032991b 100644 --- a/apps/web/src/views/HomeView.vue +++ b/apps/web/src/views/HomeView.vue @@ -166,7 +166,7 @@ function handleResetWorkflow() {

- UTS Protocol — Powered by OpenTimestamps + UTS Protocol — Powered by Universal Timestamps

From 55fb97a4bb10203491ec07b8599e1455b42390aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 05:41:11 +0000 Subject: [PATCH 06/21] feat(web): ethereum-first UI with real feed data from ethRPCs - Replace mock LiveFeed with real Ethereum Attested event polling via SDK ethRPCs - Show Ethereum chain RPC statuses in header instead of Bitcoin calendar nodes - Update subtitle to "anchored to Ethereum" - Expose singleton SDK instance via getSDK() for shared access - Bitcoin attestation verification still fully supported in Verify tab Co-authored-by: lightsing <15951701+lightsing@users.noreply.github.com> --- apps/web/src/components/feed/LiveFeed.vue | 51 +++------ apps/web/src/composables/useTimestampSDK.ts | 11 +- apps/web/src/composables/useWebSocketFeed.ts | 111 ++++++++++++------- apps/web/src/stores/app.ts | 63 ++++++----- apps/web/src/views/HomeView.vue | 20 ++-- 5 files changed, 145 insertions(+), 111 deletions(-) diff --git a/apps/web/src/components/feed/LiveFeed.vue b/apps/web/src/components/feed/LiveFeed.vue index 29bd340..9370f7b 100644 --- a/apps/web/src/components/feed/LiveFeed.vue +++ b/apps/web/src/components/feed/LiveFeed.vue @@ -1,9 +1,9 @@