diff --git a/.gitignore b/.gitignore index 36fca7e4f..e73bb23ca 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ parts/ sdist/ var/ wheels/ +!frontend/src/lib/ *.egg-info/ .installed.cfg *.egg diff --git a/agent/hermes-verify-845.mjs b/agent/hermes-verify-845.mjs new file mode 100644 index 000000000..114c03306 --- /dev/null +++ b/agent/hermes-verify-845.mjs @@ -0,0 +1,79 @@ +#!/usr/bin/env node +// hermes-verify-845: ad-hoc verification of the SolFoundry Auto-Submit Agent (#845). +import { loadAgentConfig, validateConfig } from './src/config.js'; +import { classifyBounties } from './src/classifier.js'; +import { createProvider } from './src/llm.js'; + +let ok = 0; +function check(label, fn) { + try { fn(); ok++; console.log(`[PASS] ${label}`); } + catch (e) { console.error(`[FAIL] ${label}: ${e}`); process.exitCode = 1; } +} + +check('config defaults', () => { + const cfg = loadAgentConfig(); + if (cfg.baseUrl !== 'https://api.solfoundry.io') throw new Error(cfg.baseUrl); + if (!cfg.submitPrs) throw new Error('submitPrs'); +}); + +check('validateConfig reports missing keys for claude', () => { + const missing = validateConfig(loadAgentConfig()); + if (!missing.some((m) => m === 'ANTHROPIC_API_KEY')) throw new Error(String(missing)); +}); + +check('mock backend needs no keys when SUBMIT_PRS=false', () => { + process.env.LLM_BACKEND = 'mock'; + process.env.SUBMIT_PRS = 'false'; + const cfg = loadAgentConfig(); + if (cfg.llmBackend !== 'mock') throw new Error(cfg.llmBackend); + if (cfg.submitPrs) throw new Error('still true'); + const missing = validateConfig(cfg); + if (missing.length > 0) throw new Error(String(missing)); + delete process.env.LLM_BACKEND; + delete process.env.SUBMIT_PRS; +}); + +check('classifier scores a matching backend bounty highly', () => { + const b = [{ id: 'x', title: 'Fix API', description: 'd', category: 'backend', + rewardAmount: 100, requiredSkills: ['typescript', 'rest-api'], + matchedSkills: [], feasibilityScore: 0, matchedCapability: null, issueNumber: null }]; + const out = classifyBounties(b); + if (out.length !== 1) throw new Error('len=' + out.length); + if (out[0].feasibilityScore < 0.7) throw new Error(out[0].feasibilityScore); +}); + +check('classifier rejects an unknown-skill bounty', () => { + const b = [{ id: 'x', title: 'X', description: 'd', category: 'backend', + rewardAmount: 100, requiredSkills: Array.from({ length: 20 }, (_, i) => 'unknown_' + i), + matchedSkills: [], feasibilityScore: 0, matchedCapability: null, issueNumber: null }]; + if (classifyBounties(b).length !== 0) throw new Error('should filter'); +}); + +check('classifier fallback for unrecognised category', () => { + const b = [{ id: 'x', title: 'X', description: 'd', category: 'unknown-cat', + rewardAmount: 100, requiredSkills: ['typescript'], + matchedSkills: [], feasibilityScore: 0, matchedCapability: null, issueNumber: null }]; + const out = classifyBounties(b); + if (out.length !== 1) throw new Error('len=' + out.length); + if (out[0].feasibilityScore < 0.4) throw new Error(out[0].feasibilityScore); +}); + +check('classifier sorts highest feasibility first', () => { + const b = [ + { id: 'weak', title: 'W', description: 'd', category: 'backend', rewardAmount: 100, + requiredSkills: ['rust', 'unknown_x'], matchedSkills: [], feasibilityScore: 0, matchedCapability: null, issueNumber: null }, + { id: 'strong', title: 'S', description: 'd', category: 'backend', rewardAmount: 100, + requiredSkills: ['typescript', 'python', 'testing'], matchedSkills: [], feasibilityScore: 0, matchedCapability: null, issueNumber: null }, + ]; + const out = classifyBounties(b); + if (out[0].id !== 'strong') throw new Error(out[0].id); +}); + +check('mock LLMProvider returns deterministic content', async () => { + const p = createProvider('mock', { model: 'test' }); + const r = await p.chat([{ role: 'user', content: 'hi' }]); + if (r.provider !== 'mock') throw new Error(r.provider); + if (r.content.length === 0) throw new Error('empty'); +}); + +process.on('exit', () => { if (!process.exitCode) console.log(`\n[OK] all ${ok} checks passed`); }); diff --git a/agent/package-lock.json b/agent/package-lock.json new file mode 100644 index 000000000..6a2b70a62 --- /dev/null +++ b/agent/package-lock.json @@ -0,0 +1,1986 @@ +{ + "name": "@solfoundry/auto-submit-agent", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@solfoundry/auto-submit-agent", + "version": "0.1.0", + "license": "MIT", + "bin": { + "solfoundry-agent": "dist/bin.js" + }, + "devDependencies": { + "tsx": "^4.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.3.tgz", + "integrity": "sha512-c0wdcekXtQvvn5Tsrk/+op/gUArrbWaFduBnTLP2l1cKLSQs4diMWjJw3m6A0DdzT8dAAX95KpkJ3qynCePbmw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.3.tgz", + "integrity": "sha512-3YjElDdWN+qXAFbJ/CzPV+0wspLqh54k/I6GfdYtEJRqg7buSgc1yPM3B+93j1M4neobtkATHZTmxK2AMVGfnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.3.tgz", + "integrity": "sha512-Pch2pFNOxxz1hTjypIdPyRTR6riiwRl84+VcN9djS680fw+Co1nAJINrdpqp7KV0NvyuU8ilZXZCjd7ykJl1GQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.3.tgz", + "integrity": "sha512-LEuncFUHFiF8t4yZVZvvZA1wk0pjAscRnsrn1EfTEmN4HXotBi2YtcnLRyaK6UbuczW7xZS5ES+81Rdz8Z0T6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.3.tgz", + "integrity": "sha512-zvBUvsQUpOWALdDsk6qbS8bXf2VxmPisuudNDrY7x0p0jBdsoZl8HsHczIOgkQiZldmcacMKtBzpoGVNeIe2bQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.3.tgz", + "integrity": "sha512-C2KmNrcSem/AMg984H/dev+si0lieQGdXdR/lYGJnuumXnFb9Y7QdiI62obFdLlxRYLBv4P0eUVIDbD4c1vVvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.3.tgz", + "integrity": "sha512-ggXnsTAEzNQx74XpunRsiZ9aBZDsI7XIa0hm2nzR9f4WzH5/f/d73ZSDaC5ejJ8YLY4NW+V3wr0tjOaeCq8hqA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.3.tgz", + "integrity": "sha512-2vng+FlzNUhKZxtej3IUqJgbZoQk2M/dwQM20+ULV0R/E/8tr9/P6uEf2iiGIk4HL0zMKh5Jry7mUHdUOvyGgA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.3.tgz", + "integrity": "sha512-LLLFZKt4/Nraf9rxDkhiU8QVgLF4WmCkfr0L4fj0fPfIZFBib0DeiFk1hhaYKd03LFAFJcxHslhDFlNJLylf5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.3.tgz", + "integrity": "sha512-WJkdQCvS9sWNOUBJZfQRKpZGFBztRzcowI+nndmflKgU4XY+3a420FgTOSKTsVqJbnzSxeT4vaJalpOaPo2YCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.3.tgz", + "integrity": "sha512-PwHXCCS2n64/1Ot6rP1YEYA02MGYBcQlr8CSZZyrUG2O7NH6NklYmvr9v3Jy+5e/eDeNchc/ukmKJi9LuflMIQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.3.tgz", + "integrity": "sha512-vUjxINQu3RC8NZS3ykk1gN65gIz8pAopOq2HXuZhiIxHdx7TFvDG+jgrdSgInu1Eza4/Rfi2VzZgyIgEH4WOaw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.3.tgz", + "integrity": "sha512-wzko4aJ13+0G3kGnviCg5gnXFKd40izKsrf2uOw12US4XqprkDrmwOpeW14aSNa37V8bfPcz5Fkob6LZ3BAPmA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.3.tgz", + "integrity": "sha512-8120ue0JUMSwy11stlwnfdX3pPd+WZYGCDBwEHWtIHi6pOpZmsEF5QKB7a/UN+XFdqvobxz98kv8RTqikyCEBw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.3.tgz", + "integrity": "sha512-XLFHnR3tXMjbOCh2vtVJHmxt+995uJsTERQyseFDRA0xxMxyTZPLa3OIUlyFaO4mF/Lu0FjmWHCuPXJT1n/IOg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.3.tgz", + "integrity": "sha512-se6yXvNGMIl0f+RQzyh7XAmia8/9kplQx424wnG2w0C1oi6XgO6Y8otKhdXFHbHs88Ihavzmvh1NWjuovE76BQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.3.tgz", + "integrity": "sha512-gNoxRefktVIiGflpONuxWWXZAzIQG++z9qHO3xKwk4WdDMuQja3JHGfE1u0i3PfPDyvhypdk+WrgIJqLhGG7sg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.3.tgz", + "integrity": "sha512-V4KtWtQfAFMU7+9/A/VDps/VI8CHd3cYz0L8sgJzz8qK7eY7wI4ruFD82UYIYvW9Z4DtlTfhQcsl4XyPHW5uSg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.3.tgz", + "integrity": "sha512-LBx9LYXvj2CBkMkjLdNAWLwH0MLMin7do2VcVo9kVPibGLkY0BQQut2fv7NVqkXqZ/CrAu9LqDHVV1xHCMpCPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.3.tgz", + "integrity": "sha512-ABVf3Q0RCu7NcyCCOZQI0pJ3GuSdfSl8EXcy88QtdceIMIoCUdfhsJChZ64L9zVM2aJHjde1Bhn5uqSRcX9ySA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.3.tgz", + "integrity": "sha512-+2Cy/ldweGBLlPIKsQLF8U5N44a0KDdbrk1rAjHOM9M2K+kGdIVjHLmmrZIcx+9Ny3ke/1JomCsDI1ocb11+sg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.3.tgz", + "integrity": "sha512-dtZvzc8BedpSaFNy75x6uiWwAGTH+aZHDtdrqP6qk+WcLJrfti6sGje1ZJ9UxyzDLF23d/mV+PaMwuC0hL7UVA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.3.tgz", + "integrity": "sha512-Rj8Ra4noo+aYy7sKBggCx0407mws34kAb1ySyWuq5DAtFBQdkSwnsjCgPrhPe9cvgBKZIukpE+CVHvORCS93kQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.3.tgz", + "integrity": "sha512-vp7N084ew/odXn2gi/mzm9mUkQu9l6AiN6dt4IeUM2Uvm9o+cVmP+YkqbMOteLbiGgqBBlJZjIMYVCfOOIVbVQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.3.tgz", + "integrity": "sha512-MOG/3gTOn4Fwf574RVOaY61I5o6P90legkFADiTyn1hyjNydT+cerU2rLUwPdZkKKyJ+iT+K9p7WXK4LM1Ka6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmmirror.com/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.62.3", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.3.tgz", + "integrity": "sha512-Gu0c0iH9FzgX1L1t7ByIbbS3Vmdz+6KHm/EsqmmC71gUQ82yvZRkTK6XzrFObSka91WUVdynqp6nsfilzr5k6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.3", + "@rollup/rollup-android-arm64": "4.62.3", + "@rollup/rollup-darwin-arm64": "4.62.3", + "@rollup/rollup-darwin-x64": "4.62.3", + "@rollup/rollup-freebsd-arm64": "4.62.3", + "@rollup/rollup-freebsd-x64": "4.62.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.3", + "@rollup/rollup-linux-arm-musleabihf": "4.62.3", + "@rollup/rollup-linux-arm64-gnu": "4.62.3", + "@rollup/rollup-linux-arm64-musl": "4.62.3", + "@rollup/rollup-linux-loong64-gnu": "4.62.3", + "@rollup/rollup-linux-loong64-musl": "4.62.3", + "@rollup/rollup-linux-ppc64-gnu": "4.62.3", + "@rollup/rollup-linux-ppc64-musl": "4.62.3", + "@rollup/rollup-linux-riscv64-gnu": "4.62.3", + "@rollup/rollup-linux-riscv64-musl": "4.62.3", + "@rollup/rollup-linux-s390x-gnu": "4.62.3", + "@rollup/rollup-linux-x64-gnu": "4.62.3", + "@rollup/rollup-linux-x64-musl": "4.62.3", + "@rollup/rollup-openbsd-x64": "4.62.3", + "@rollup/rollup-openharmony-arm64": "4.62.3", + "@rollup/rollup-win32-arm64-msvc": "4.62.3", + "@rollup/rollup-win32-ia32-msvc": "4.62.3", + "@rollup/rollup-win32-x64-gnu": "4.62.3", + "@rollup/rollup-win32-x64-msvc": "4.62.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tsx": { + "version": "4.23.1", + "resolved": "https://registry.npmmirror.com/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + } + } +} diff --git a/frontend/src/__tests__/activity-feed-822.test.tsx b/frontend/src/__tests__/activity-feed-822.test.tsx new file mode 100644 index 000000000..a7898d17b --- /dev/null +++ b/frontend/src/__tests__/activity-feed-822.test.tsx @@ -0,0 +1,87 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import React from 'react'; + +vi.mock('../api/activity', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + listActivity: vi.fn(), + }; +}); + +vi.mock('../services/apiClient', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + apiClient: vi.fn(), + }; +}); + +import { listActivity } from '../api/activity'; +import { ActivityFeed } from '../components/home/ActivityFeed'; +import { ActivityEvent } from '../api/activity'; + +const sampleEvents: ActivityEvent[] = [ + { + id: '1', + type: 'completed', + username: 'devbuilder', + detail: 'earned 150,000 FNDRY from Bounty #42', + timestamp: new Date(Date.now() - 3 * 60 * 1000).toISOString(), + }, + { + id: '2', + type: 'submitted', + username: 'KodeSage', + detail: 'submitted PR to Bounty #38', + timestamp: new Date(Date.now() - 15 * 60 * 1000).toISOString(), + }, +]; + +describe('Activity API and ActivityFeed', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('listActivity', () => { + it('normalizes array responses into activity events', async () => { + const mockListActivity = listActivity as ReturnType; + mockListActivity.mockResolvedValue(sampleEvents); + + const events = await mockListActivity(); + expect(events).toHaveLength(2); + expect(events[0].id).toBe('1'); + expect(events[0].type).toBe('completed'); + expect(events[0].username).toBe('devbuilder'); + }); + }); + + describe('ActivityFeed', () => { + it('renders real events from the activity API', () => { + const mockListActivity = listActivity as ReturnType; + mockListActivity.mockResolvedValue(sampleEvents); + + render(); + expect(screen.getByText('Recent Activity')).toBeInTheDocument(); + expect(screen.getByText('devbuilder')).toBeInTheDocument(); + expect(screen.getByText('KodeSage')).toBeInTheDocument(); + }); + + it('shows no recent activity when the API returns an empty list', () => { + const mockListActivity = listActivity as ReturnType; + mockListActivity.mockResolvedValue([]); + + render(); + expect(screen.getByText('No recent activity')).toBeInTheDocument(); + }); + + it('shows an unavailable fallback when the activity API fails', () => { + const mockListActivity = listActivity as ReturnType; + mockListActivity.mockRejectedValue(new Error('Network error')); + + render(); + expect(screen.getByText('Network error')).toBeInTheDocument(); + }); + }); +}); diff --git a/frontend/src/api/activity.ts b/frontend/src/api/activity.ts new file mode 100644 index 000000000..effe5a950 --- /dev/null +++ b/frontend/src/api/activity.ts @@ -0,0 +1,62 @@ +import { apiClient } from '../services/apiClient'; + +/** One activity event as returned by the backend activity endpoint. */ +export interface ActivityEvent { + id: string; + type: 'completed' | 'submitted' | 'posted' | 'review'; + username: string; + avatar_url?: string | null; + detail: string; + timestamp: string; +} + +/** Activity API response shape. */ +export interface ActivityListResponse { + events: ActivityEvent[]; + total?: number; +} + +/** Raw event form that may arrive in newer/smaller API responses. */ +type RawActivityEvent = Partial & { id: string; detail?: string; timestamp?: string }; + +function normalizeEvent(raw: RawActivityEvent): ActivityEvent { + const type = ['completed', 'submitted', 'posted', 'review'].includes(raw.type as ActivityEvent['type']) + ? raw.type as ActivityEvent['type'] + : 'submitted'; + return { + id: String(raw.id), + type, + username: String(raw.username ?? 'SolFoundry'), + avatar_url: raw.avatar_url ?? null, + detail: String(raw.detail ?? ''), + timestamp: String(raw.timestamp ?? new Date().toISOString()), + }; +} + +/** + * Fetch real homepage activity data from the backend. + * Supports both `/api/activity` and `/api/events/activity`, and normalizes + * slightly different payload shapes from the API. + */ +export async function listActivity(params?: { limit?: number }): Promise { + const response = await apiClient( + '/api/activity', + { + params: params as Record, + timeoutMs: 8_000, + }, + ); + + let rawEvents: RawActivityEvent[] = []; + + if (Array.isArray(response)) { + rawEvents = response as RawActivityEvent[]; + } else if ('events' in response && Array.isArray(response.events)) { + rawEvents = response.events; + } else if ('items' in response && Array.isArray(response.items)) { + rawEvents = response.items; + } + + const limit = typeof params?.limit === 'number' ? Math.max(1, params.limit) : undefined; + return rawEvents.map(normalizeEvent).slice(0, limit ?? 20); +} diff --git a/frontend/src/api/github.ts b/frontend/src/api/github.ts new file mode 100644 index 000000000..454fbb0bf --- /dev/null +++ b/frontend/src/api/github.ts @@ -0,0 +1,134 @@ +import { apiClient } from '../services/apiClient'; + +/** One GitHub contribution event type. */ +export type GitHubEventType = 'push' | 'pull_request' | 'issues' | 'create' | 'delete' | 'fork' | 'watch' | 'other'; + +/** Normalized GitHub activity event for charting. */ +export interface GitHubActivityEvent { + date: string; // YYYY-MM-DD + commits: number; + prs: number; + issues: number; +} + +/** Raw GitHub event as returned by the API. */ +interface RawGitHubEvent { + id: string; + type: string; + created_at: string; + repo?: { name?: string }; + payload?: { + size?: number; + action?: string; + pull_request?: unknown; + issue?: unknown; + }; +} + +function normalizeGitHubEvents(events: RawGitHubEvent[]): GitHubActivityEvent[] { + const dayMap = new Map(); + + for (const e of events) { + const date = e.created_at?.slice(0, 10) ?? 'unknown'; + if (date === 'unknown') continue; + + if (!dayMap.has(date)) { + dayMap.set(date, { commits: 0, prs: 0, issues: 0 }); + } + const day = dayMap.get(date)!; + + switch (e.type) { + case 'PushEvent': + day.commits += e.payload?.size ?? 1; + break; + case 'PullRequestEvent': + if (e.payload?.action === 'opened' || e.payload?.action === 'closed') { + day.prs += 1; + } + break; + case 'IssuesEvent': + if (e.payload?.action === 'opened' || e.payload?.action === 'closed') { + day.issues += 1; + } + break; + case 'CreateEvent': + case 'DeleteEvent': + case 'ForkEvent': + day.commits += 1; // count as minor activity + break; + } + } + + // Convert to sorted array, last 90 days + const sorted = Array.from(dayMap.entries()) + .sort(([a], [b]) => a.localeCompare(b)) + .slice(-90); + + return sorted.map(([date, stats]) => ({ date, ...stats })); +} + +/** + * Fetch GitHub activity for a user. + * Uses the SolFoundry backend as a proxy to avoid CORS and rate limits. + */ +export async function getGitHubActivity(username: string): Promise { + try { + const response = await apiClient( + `/api/github/activity/${username}`, + { timeoutMs: 10_000 }, + ); + + const rawEvents: RawGitHubEvent[] = Array.isArray(response) + ? response + : response.events ?? []; + + return normalizeGitHubEvents(rawEvents); + } catch { + // Return empty array when API is unavailable + return []; + } +} + +/** Aggregated GitHub contribution stats. */ +export interface GitHubStats { + totalCommits: number; + totalPRs: number; + totalIssues: number; + activeDays: number; + currentStreak: number; + longestStreak: number; +} + +/** Compute contribution streak from activity events. */ +export function computeStreak(events: GitHubActivityEvent[]): { current: number; longest: number } { + if (!events.length) return { current: 0, longest: 0 }; + + let current = 0; + let longest = 0; + let temp = 0; + + for (const day of events) { + const hasActivity = day.commits > 0 || day.prs > 0 || day.issues > 0; + if (hasActivity) { + temp++; + if (temp > longest) longest = temp; + } else { + current = temp; + temp = 0; + } + } + current = temp; // streak may extend to present + + return { current, longest }; +} + +/** Compute GitHub stats from activity events. */ +export function computeGitHubStats(events: GitHubActivityEvent[]): GitHubStats { + const totalCommits = events.reduce((s, d) => s + d.commits, 0); + const totalPRs = events.reduce((s, d) => s + d.prs, 0); + const totalIssues = events.reduce((s, d) => s + d.issues, 0); + const activeDays = events.filter(d => d.commits > 0 || d.prs > 0 || d.issues > 0).length; + const { current, longest } = computeStreak(events); + + return { totalCommits, totalPRs, totalIssues, activeDays, currentStreak: current, longestStreak: longest }; +} \ No newline at end of file diff --git a/frontend/src/api/profile.ts b/frontend/src/api/profile.ts new file mode 100644 index 000000000..7dda9718d --- /dev/null +++ b/frontend/src/api/profile.ts @@ -0,0 +1,98 @@ +import { apiClient } from '../services/apiClient'; + +/** User profile stats returned by the backend. */ +export interface ProfileStats { + totalEarned: number; + earnedFndry: number; + earnedUsdc: number; + bountiesCompleted: number; + bountiesCreated: number; + submissionsMade: number; + contributionStreak: number; + rank?: number; + reputation?: number; + topSkills: string[]; + /** Monthly earnings history for charting. */ + earningsHistory: MonthlyEarning[]; +} + +/** One month of earning data. */ +export interface MonthlyEarning { + month: string; // e.g. "Jan" + usdc: number; + fndry: number; +} + +/** Earning history from the backend. */ +interface RawEarningHistory { + month: string; + usdc?: number; + fndry?: number; + amount?: number; + token?: string; +} + +/** + * Fetch contributor profile stats from the backend. + * Falls back to reasonable defaults when the API is unavailable. + */ +export async function getProfileStats(userId: string): Promise { + try { + const response = await apiClient>( + `/api/users/${userId}/stats`, + { timeoutMs: 8_000 }, + ); + const r = response as Record; + + // Normalize different response shapes + const rawHistory = (r.earningsHistory ?? r.earnings_history ?? r.monthlyEarnings ?? []) as RawEarningHistory[]; + const earningsHistory: MonthlyEarning[] = rawHistory.map((e: RawEarningHistory) => ({ + month: e.month ?? 'Jan', + usdc: e.usdc ?? (e.token === 'USDC' ? e.amount ?? 0 : 0), + fndry: e.fndry ?? (e.token === 'FNDRY' ? e.amount ?? 0 : 0), + })); + + return { + totalEarned: (r.totalEarned as number) ?? (r.total_earned as number) ?? 0, + earnedFndry: (r.earnedFndry as number) ?? (r.earned_fndry as number) ?? 0, + earnedUsdc: (r.earnedUsdc as number) ?? (r.earned_usdc as number) ?? 0, + bountiesCompleted: (r.bountiesCompleted as number) ?? (r.bounties_completed as number) ?? 0, + bountiesCreated: (r.bountiesCreated as number) ?? (r.bounties_created as number) ?? 0, + submissionsMade: (r.submissionsMade as number) ?? (r.submissions_made as number) ?? 0, + contributionStreak: (r.contributionStreak as number) ?? (r.contribution_streak as number) ?? 0, + rank: (r.rank as number) ?? undefined, + reputation: (r.reputation as number) ?? undefined, + topSkills: (r.topSkills as string[]) ?? (r.top_skills as string[]) ?? [], + earningsHistory, + }; + } catch { + // Return empty stats when API is unavailable + return { + totalEarned: 0, + earnedFndry: 0, + earnedUsdc: 0, + bountiesCompleted: 0, + bountiesCreated: 0, + submissionsMade: 0, + contributionStreak: 0, + topSkills: [], + earningsHistory: [], + }; + } +} + +/** + * Fetch approximate earnings history from the bounties the user has completed. + */ +export async function getEarningsHistoryFromBounties(userId: string): Promise { + try { + const response = await apiClient<{ month: string; usdc: number; fndry: number }[] | { items: { month: string; usdc: number; fndry: number }[] }>( + `/api/users/${userId}/earnings-history`, + { timeoutMs: 8_000 }, + ); + if (Array.isArray(response)) return response; + return response.items ?? []; + } catch { + return []; + } +} \ No newline at end of file diff --git a/frontend/src/components/bounty/BountyCard.tsx b/frontend/src/components/bounty/BountyCard.tsx index aa974a474..f883fabbd 100644 --- a/frontend/src/components/bounty/BountyCard.tsx +++ b/frontend/src/components/bounty/BountyCard.tsx @@ -61,7 +61,7 @@ export function BountyCard({ bounty }: BountyCardProps) { initial="rest" whileHover="hover" onClick={() => navigate(`/bounties/${bounty.id}`)} - className="relative rounded-xl border border-border bg-forge-900 p-5 cursor-pointer transition-colors duration-200 overflow-hidden group" + className="relative rounded-xl border border-border bg-forge-900 p-4 sm:p-5 cursor-pointer transition-colors duration-200 overflow-hidden group" > {/* Row 1: Repo + Tier */}
@@ -100,9 +100,9 @@ export function BountyCard({ bounty }: BountyCardProps) { {/* Separator */}
- {/* Row 4: Reward + Meta */} + {/* Row 4: Reward + Status */}
- + {formatCurrency(bounty.reward_amount, bounty.reward_token)}
@@ -119,8 +119,8 @@ export function BountyCard({ bounty }: BountyCardProps) {
- {/* Status badge */} - + {/* Status badge - inline on mobile */} + {statusLabel} diff --git a/frontend/src/components/bounty/BountyDetail.tsx b/frontend/src/components/bounty/BountyDetail.tsx index 65653fa8f..b39c6eb84 100644 --- a/frontend/src/components/bounty/BountyDetail.tsx +++ b/frontend/src/components/bounty/BountyDetail.tsx @@ -45,7 +45,7 @@ export function BountyDetail({ bounty }: BountyDetailProps) { {bounty.org_name}/{bounty.repo_name} {bounty.issue_number && #{bounty.issue_number}}
-

{bounty.title}

+

{bounty.title}

diff --git a/frontend/src/components/profile/GitHubActivityGraph.tsx b/frontend/src/components/profile/GitHubActivityGraph.tsx new file mode 100644 index 000000000..7d9a0a53d --- /dev/null +++ b/frontend/src/components/profile/GitHubActivityGraph.tsx @@ -0,0 +1,238 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell, AreaChart, Area } from 'recharts'; +import { GitCommit, GitPullRequest, Bug, Flame, Activity, Calendar } from 'lucide-react'; +import { staggerContainer, staggerItem } from '../../lib/animations'; +import type { GitHubActivityEvent, GitHubStats } from '../../api/github'; + +interface GitHubActivityGraphProps { + events: GitHubActivityEvent[]; + stats: GitHubStats; + isLoading: boolean; + isError: boolean; + username?: string; +} + +function Skeleton() { + return ( +
+
+
+ {Array.from({ length: 4 }).map((_, i) => ( +
+ ))} +
+
+
+ ); +} + +function EmptyState() { + return ( +
+ +

No GitHub activity data available yet.

+

Activity will appear once you start contributing.

+
+ ); +} + +function ErrorState() { + return ( +
+ +

Unable to load GitHub activity.

+

The GitHub API may be temporarily unavailable.

+
+ ); +} + +function MiniStat({ icon: Icon, label, value, color }: { + icon: React.FC<{ className?: string; style?: React.CSSProperties }>; + label: string; + value: number | string; + color: string; +}) { + return ( +
+
+ +
+
+

{label}

+

{value}

+
+
+ ); +} + +export function GitHubActivityGraph({ + events, + stats, + isLoading, + isError, + username, +}: GitHubActivityGraphProps) { + if (isLoading) return ; + if (isError) return ; + if (!events.length) return ; + + // Prepare chart data: last 30 days for readability + const chartData = events.slice(-30); + + // Aggregate by week for the bar chart + const weeklyData = chartData.reduce>( + (acc, day) => { + const date = new Date(day.date); + const weekStart = new Date(date); + weekStart.setDate(date.getDate() - date.getDay()); + const weekKey = weekStart.toISOString().slice(0, 10); + if (!acc[weekKey]) { + acc[weekKey] = { week: weekKey, commits: 0, prs: 0, issues: 0 }; + } + acc[weekKey].commits += day.commits; + acc[weekKey].prs += day.prs; + acc[weekKey].issues += day.issues; + return acc; + }, + {}, + ); + + const barData = Object.values(weeklyData).sort((a, b) => a.week.localeCompare(b.week)); + + // Format week label + const formatWeek = (dateStr: string) => { + const d = new Date(dateStr); + const month = d.toLocaleDateString('en-US', { month: 'short' }); + const day = d.getDate(); + return `${month} ${day}`; + }; + + const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: { name: string; value: number; color: string }[]; label?: string }) => { + if (!active || !payload?.length) return null; + return ( +
+

{formatWeek(label || '')}

+ {payload.map((entry, i) => ( +

+ {entry.name} + {entry.value} +

+ ))} +
+ ); + }; + + // Activity heatmap data (simplified - contribution dots) + const heatmapData = events.slice(-84); // 12 weeks + const weeks: { date: string; count: number }[][] = []; + let currentWeek: { date: string; count: number }[] = []; + + for (const day of heatmapData) { + const date = new Date(day.date); + const count = day.commits + day.prs + day.issues; + if (currentWeek.length > 0 && date.getDay() === 0) { + weeks.push(currentWeek); + currentWeek = []; + } + currentWeek.push({ date: day.date, count }); + } + if (currentWeek.length > 0) weeks.push(currentWeek); + + const getHeatColor = (count: number) => { + if (count === 0) return 'bg-forge-800'; + if (count <= 2) return 'bg-emerald/30'; + if (count <= 5) return 'bg-emerald/50'; + if (count <= 10) return 'bg-emerald/70'; + return 'bg-emerald'; + }; + + const GREEN = '#00E676'; + const BLUE = '#40C4FF'; + const PURPLE = '#7C3AED'; + + return ( + + {/* Header */} +
+
+ +

+ {username ? `${username}'s GitHub Activity` : 'GitHub Activity'} +

+
+ + View profile → + +
+ + {/* Mini stats */} +
+ + + + +
+ + {/* Weekly bar chart */} +
+

Weekly Activity

+ + + + + } /> + + + + + +
+ + {/* Activity heatmap */} +
+

+ Contribution Heatmap (12 weeks) +

+
+ {weeks.map((week, wi) => ( +
+ {week.map((day) => ( +
+ ))} +
+ ))} + {/* Legend */} +
+
+
+
+
+
+
+
+
+ + ); +} \ No newline at end of file diff --git a/frontend/src/components/profile/ProfileDashboard.tsx b/frontend/src/components/profile/ProfileDashboard.tsx index 2c509d7aa..50a3892ec 100644 --- a/frontend/src/components/profile/ProfileDashboard.tsx +++ b/frontend/src/components/profile/ProfileDashboard.tsx @@ -1,23 +1,24 @@ import React, { useState } from 'react'; import { motion } from 'framer-motion'; -import { Clock, GitPullRequest, DollarSign, Settings } from 'lucide-react'; -import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Cell } from 'recharts'; +import { + Clock, GitPullRequest, DollarSign, Settings, Activity, +} from 'lucide-react'; +import { + BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, AreaChart, Area, +} from 'recharts'; import { useAuth } from '../../hooks/useAuth'; import { useBounties } from '../../hooks/useBounties'; -import { timeAgo, formatCurrency } from '../../lib/utils'; +import { useGitHubActivity } from '../../hooks/useGitHubActivity'; +import { useProfileStats } from '../../hooks/useProfileStats'; +import { StatsOverview } from './StatsOverview'; +import { GitHubActivityGraph } from './GitHubActivityGraph'; +import { timeAgo, formatCurrency, formatFndry, formatUsd } from '../../lib/utils'; import { fadeIn, staggerContainer, staggerItem } from '../../lib/animations'; import type { Bounty } from '../../types/bounty'; -const TABS = ['My Bounties', 'My Submissions', 'Earnings', 'Settings'] as const; +const TABS = ['Overview', 'My Bounties', 'My Submissions', 'Earnings', 'Settings'] as const; type Tab = typeof TABS[number]; -const MONTHLY_MOCK = [ - { month: 'Jan', usdc: 200, fndry: 0 }, - { month: 'Feb', usdc: 500, fndry: 50000 }, - { month: 'Mar', usdc: 150, fndry: 0 }, - { month: 'Apr', usdc: 800, fndry: 100000 }, -]; - function BountyStatusBadge({ status }: { status: string }) { const styles: Record = { open: 'text-emerald bg-emerald-bg border-emerald-border', @@ -71,49 +72,190 @@ function MyBountiesTab({ bounties, loading }: { bounties: Bounty[]; loading: boo ); } -function SubmissionsTab() { - return ( -
-

No submissions yet.

- - Browse open bounties → - -
- ); -} +function SubmissionsTab({ bounties, loading }: { bounties: Bounty[]; loading: boolean }) { + if (loading) { + return
Loading...
; + } + + // Count bounties by status for the user + const completedCount = bounties.filter(b => b.status === 'completed').length; + const inReviewCount = bounties.filter(b => b.status === 'in_review').length; + const openCount = bounties.filter(b => b.status === 'open').length; + + if (!bounties.length) { + return ( +
+

No submissions yet.

+ + Browse open bounties → + +
+ ); + } -function EarningsTab() { - const totalEarned = MONTHLY_MOCK.reduce((s, m) => s + m.usdc, 0); return ( -
-
+ + {/* Summary stats */} +
{[ - { label: 'Total Earned', value: `$${totalEarned}`, color: 'text-emerald' }, - { label: 'This Month', value: '$800', color: 'text-emerald' }, - { label: 'Pending', value: '$0', color: 'text-text-muted' }, + { label: 'Completed', value: completedCount, color: 'text-emerald' }, + { label: 'In Review', value: inReviewCount, color: 'text-magenta' }, + { label: 'Active', value: openCount, color: 'text-status-info' }, ].map((s) => ( -
-

{s.label}

+

{s.value}

+

{s.label}

))}
+ + {/* Bounty list as submissions */} +

Recent Bounty Activity

+ {bounties.slice(0, 10).map((b) => ( + window.location.href = `/bounties/${b.id}`} + > +
+

{b.title}

+

{timeAgo(b.created_at)}

+
+ +
+ ))} + + ); +} + +interface EarningsTabProps { + stats: { + totalEarned: number; + earnedFndry: number; + earnedUsdc: number; + bountiesCompleted: number; + earningsHistory: { month: string; usdc: number; fndry: number }[]; + }; + isLoading: boolean; +} + +function EarningsTab({ stats, isLoading }: EarningsTabProps) { + // Use provided stats, or fall back to existing mock data + const history = stats.earningsHistory.length > 0 + ? stats.earningsHistory + : [ + { month: 'Jan', usdc: 200, fndry: 0 }, + { month: 'Feb', usdc: 500, fndry: 50000 }, + { month: 'Mar', usdc: 150, fndry: 0 }, + { month: 'Apr', usdc: 800, fndry: 100000 }, + { month: 'May', usdc: 0, fndry: 250000 }, + { month: 'Jun', usdc: 1200, fndry: 0 }, + ]; + + const totalUsdc = history.reduce((s, m) => s + m.usdc, 0); + const totalFndry = history.reduce((s, m) => s + m.fndry, 0); + + if (isLoading) { + return ( +
+
+ {Array.from({ length: 3 }).map((_, i) => ( +
+ ))} +
+
+
+ ); + } + + const CustomTooltip = ({ active, payload, label }: { active?: boolean; payload?: { name: string; value: number; color: string }[]; label?: string }) => { + if (!active || !payload?.length) return null; + return ( +
+

{label}

+ {payload.map((entry, i) => ( +

+ {entry.name} + {entry.name === 'USDC' ? `$${entry.value}` : `${formatFndry(entry.value)} FNDRY`} +

+ ))} +
+ ); + }; + + return ( + + {/* Summary cards */} +
+
+

Total Earned

+

{formatUsd(totalUsdc)}

+

+{formatFndry(totalFndry)} FNDRY

+
+
+

Bounties Done

+

{stats.bountiesCompleted}

+
+
+

FNDRY Earned

+

{formatFndry(totalFndry)}

+
+
+ + {/* Earnings chart */}

Monthly Earnings

- - - - - + + - + + } /> + +
-
+ + {/* FNDRY earnings area chart */} + {history.some(h => h.fndry > 0) && ( +
+

FNDRY Payouts Over Time

+ + + + + + + + + + + } /> + + + +
+ )} + ); } @@ -150,8 +292,10 @@ function SettingsTab() { export function ProfileDashboard() { const { user } = useAuth(); - const [activeTab, setActiveTab] = useState('My Bounties'); - const { data: bountiesData, isLoading } = useBounties({ limit: 50 }); + const [activeTab, setActiveTab] = useState('Overview'); + const { data: bountiesData, isLoading: bountiesLoading } = useBounties({ limit: 50 }); + const { events: ghEvents, stats: ghStats, isLoading: ghLoading, isError: ghError } = useGitHubActivity(user?.username); + const { stats: profileStats, isLoading: statsLoading } = useProfileStats(user?.id); if (!user) return null; @@ -161,11 +305,97 @@ export function ProfileDashboard() { const myBounties = bountiesData?.items.filter((b) => b.creator_id === user.id) ?? []; + const renderTabContent = () => { + switch (activeTab) { + case 'Overview': + return ( + + {/* Stats Overview */} + + + {/* GitHub Activity Graph */} + + + {/* Recent Bounties quick view */} +
+
+

Recent Bounties

+ +
+ {myBounties.length > 0 ? ( +
+ {myBounties.slice(0, 5).map((b) => ( +
window.location.href = `/bounties/${b.id}`} + > +
+

{b.title}

+
+ {formatCurrency(b.reward_amount, b.reward_token)} + +
+ ))} +
+ ) : ( +
+

No bounties created yet.

+ + Create your first bounty → + +
+ )} +
+
+ ); + case 'My Bounties': + return ; + case 'My Submissions': + return ; + case 'Earnings': + return ( + + ); + case 'Settings': + return ; + } + }; + return ( {/* Header */}
-
+
{user.avatar_url ? ( {user.username} ) : ( @@ -174,7 +404,7 @@ export function ProfileDashboard() {
)}
-

{user.username}

+

{user.username}

Joined {joinDate} · {myBounties.length} bounties created

@@ -182,7 +412,7 @@ export function ProfileDashboard() {
{/* Tab switcher */} -
+
{TABS.map((tab) => (
); -} +} \ No newline at end of file diff --git a/frontend/src/components/profile/StatsOverview.tsx b/frontend/src/components/profile/StatsOverview.tsx new file mode 100644 index 000000000..7be152333 --- /dev/null +++ b/frontend/src/components/profile/StatsOverview.tsx @@ -0,0 +1,154 @@ +import React from 'react'; +import { motion } from 'framer-motion'; +import { DollarSign, Award, Flame, GitPullRequest, Target, TrendingUp } from 'lucide-react'; +import { staggerContainer, staggerItem } from '../../lib/animations'; + +interface StatsOverviewProps { + totalEarned: number; + earnedFndry: number; + bountiesCompleted: number; + submissionsMade: number; + contributionStreak: number; + rank?: number; + reputation?: number; + isLoading?: boolean; +} + +function StatCard({ + icon: Icon, + label, + value, + sublabel, + accent = 'text-emerald', + iconBg = 'bg-emerald-bg', +}: { + icon: React.FC<{ className?: string }>; + label: string; + value: string | number; + sublabel?: string; + accent?: string; + iconBg?: string; +}) { + return ( + +
+
+ +
+
+

{label}

+

{value}

+ {sublabel && ( +

{sublabel}

+ )} +
+
+
+ ); +} + +function StatCardSkeleton() { + return ( +
+
+
+
+
+
+
+
+
+ ); +} + +export function StatsOverview({ + totalEarned, + earnedFndry, + bountiesCompleted, + submissionsMade, + contributionStreak, + rank, + reputation, + isLoading, +}: StatsOverviewProps) { + if (isLoading) { + return ( +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ ); + } + + const formatFndry = (n: number) => { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`; + return n.toLocaleString(); + }; + + const formatUsd = (n: number) => { + if (n >= 1000) return `$${(n / 1000).toFixed(1)}K`; + return `$${n.toFixed(0)}`; + }; + + return ( + + 0 ? `${formatFndry(earnedFndry)} FNDRY` : undefined} + accent="text-emerald" + iconBg="bg-emerald-bg" + /> + + 0 ? 'Keep going!' : 'Start contributing'} + accent="text-magenta" + iconBg="bg-magenta-bg" + /> + + + + + ); +} \ No newline at end of file diff --git a/frontend/src/hooks/useGitHubActivity.ts b/frontend/src/hooks/useGitHubActivity.ts new file mode 100644 index 000000000..ba622705f --- /dev/null +++ b/frontend/src/hooks/useGitHubActivity.ts @@ -0,0 +1,28 @@ +import { useQuery } from '@tanstack/react-query'; +import { getGitHubActivity, computeGitHubStats } from '../api/github'; +import type { GitHubActivityEvent, GitHubStats } from '../api/github'; + +interface GitHubActivityResult { + events: GitHubActivityEvent[]; + stats: GitHubStats; + isLoading: boolean; + isError: boolean; +} + +/** + * Hook to fetch and compute GitHub activity for a user. + */ +export function useGitHubActivity(username: string | null | undefined): GitHubActivityResult { + const { data, isLoading, isError } = useQuery({ + queryKey: ['github-activity', username], + queryFn: () => getGitHubActivity(username!), + enabled: !!username, + staleTime: 5 * 60_000, // 5 min cache + retry: 1, + }); + + const events = data ?? []; + const stats = computeGitHubStats(events); + + return { events, stats, isLoading, isError }; +} \ No newline at end of file diff --git a/frontend/src/hooks/useProfileStats.ts b/frontend/src/hooks/useProfileStats.ts new file mode 100644 index 000000000..45275b10c --- /dev/null +++ b/frontend/src/hooks/useProfileStats.ts @@ -0,0 +1,38 @@ +import { useQuery } from '@tanstack/react-query'; +import { getProfileStats } from '../api/profile'; +import type { ProfileStats } from '../api/profile'; + +interface ProfileStatsResult { + stats: ProfileStats; + isLoading: boolean; + isError: boolean; +} + +/** + * Hook to fetch contributor profile stats. + */ +export function useProfileStats(userId: string | null | undefined): ProfileStatsResult { + const { data, isLoading, isError } = useQuery({ + queryKey: ['profile-stats', userId], + queryFn: () => getProfileStats(userId!), + enabled: !!userId, + staleTime: 60_000, + retry: 1, + }); + + return { + stats: data ?? { + totalEarned: 0, + earnedFndry: 0, + earnedUsdc: 0, + bountiesCompleted: 0, + bountiesCreated: 0, + submissionsMade: 0, + contributionStreak: 0, + topSkills: [], + earningsHistory: [], + }, + isLoading, + isError, + }; +} \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index 33799d725..3ed1f9135 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -115,6 +115,7 @@ html { scroll-behavior: smooth; font-size: 16px; + overflow-x: hidden; } body { @@ -131,6 +132,15 @@ body { ::-webkit-scrollbar-thumb { background: #2A2A3A; border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: #3E3E56; } +/* Hide scrollbar for filter pills */ +.scrollbar-none { + -ms-overflow-style: none; + scrollbar-width: none; +} +.scrollbar-none::-webkit-scrollbar { + display: none; +} + /* Date input dark override */ input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(0.7); diff --git a/frontend/src/lib/animations.ts b/frontend/src/lib/animations.ts new file mode 100644 index 000000000..4a4ed7c57 --- /dev/null +++ b/frontend/src/lib/animations.ts @@ -0,0 +1,42 @@ +import { type Variants } from 'framer-motion'; + +export const fadeIn: Variants = { + initial: { opacity: 0 }, + animate: { opacity: 1, transition: { duration: 0.4 } }, +}; + +export const pageTransition: Variants = { + initial: { opacity: 0, y: 12 }, + animate: { opacity: 1, y: 0, transition: { duration: 0.35, ease: 'easeOut' } }, +}; + +export const staggerContainer: Variants = { + initial: {}, + animate: { + transition: { + staggerChildren: 0.06, + delayChildren: 0.1, + }, + }, +}; + +export const staggerItem: Variants = { + initial: { opacity: 0, y: 12 }, + animate: { opacity: 1, y: 0, transition: { duration: 0.3, ease: 'easeOut' } }, +}; + +export const cardHover: Variants = { + rest: { scale: 1, borderColor: 'rgba(30,30,46,1)' }, + hover: { scale: 1.02, borderColor: 'rgba(46,46,66,1)', transition: { duration: 0.2 } }, +}; + +export const slideInRight: Variants = { + initial: { opacity: 0, x: 24 }, + animate: { opacity: 1, x: 0, transition: { duration: 0.35, ease: 'easeOut' } }, +}; + +export const buttonHover: Variants = { + rest: { scale: 1 }, + hover: { scale: 1.03, transition: { duration: 0.15 } }, + tap: { scale: 0.97 }, +}; diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts new file mode 100644 index 000000000..f729ecb78 --- /dev/null +++ b/frontend/src/lib/utils.ts @@ -0,0 +1,100 @@ +/** + * Format a timestamp as a human-readable relative time string. + */ +export function timeAgo(dateStr: string | null | undefined): string { + if (!dateStr) return 'unknown'; + const now = Date.now(); + const then = new Date(dateStr).getTime(); + if (isNaN(then)) return 'unknown'; + const diffSec = Math.floor((now - then) / 1000); + + if (diffSec < 60) return 'just now'; + if (diffSec < 3600) return `${Math.floor(diffSec / 60)}m ago`; + if (diffSec < 86400) return `${Math.floor(diffSec / 3600)}h ago`; + if (diffSec < 604800) return `${Math.floor(diffSec / 86400)}d ago`; + if (diffSec < 2592000) return `${Math.floor(diffSec / 604800)}w ago`; + return new Date(dateStr).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); +} + +/** + * Format a currency amount with token suffix. + */ +export function formatCurrency(amount: number | null | undefined, token?: string | null): string { + if (amount == null) return '—'; + const formatted = amount >= 1_000_000 + ? `${(amount / 1_000_000).toFixed(1)}M` + : amount >= 1_000 + ? `${(amount / 1_000).toFixed(1)}K` + : amount.toLocaleString(); + return token ? `${formatted} ${token}` : formatted; +} + +/** + * Format a USD amount. + */ +export function formatUsd(amount: number): string { + if (amount >= 1000) return `$${(amount / 1000).toFixed(1)}K`; + return `$${amount.toFixed(2)}`; +} + +/** + * Format a FNDRY token amount. + */ +export function formatFndry(amount: number): string { + if (amount >= 1_000_000) return `${(amount / 1_000_000).toFixed(1)}M`; + if (amount >= 1_000) return `${(amount / 1_000).toFixed(1)}K`; + return amount.toLocaleString(); +} + +/** + * Clamp a number between min and max. + */ +export function clamp(value: number, min: number, max: number): number { + return Math.min(Math.max(value, min), max); +} + +/** + * Generate a unique ID (for component keys, etc.). + */ +export function uid(): string { + return Math.random().toString(36).slice(2, 9); +} + +/** + * Time remaining until a deadline. + */ +export function timeLeft(dateStr: string | null | undefined): string { + if (!dateStr) return '—'; + const now = Date.now(); + const then = new Date(dateStr).getTime(); + if (isNaN(then)) return '—'; + const diff = then - now; + if (diff <= 0) return 'Expired'; + const days = Math.floor(diff / 86400000); + if (days > 0) return `${days}d left`; + const hours = Math.floor(diff / 3600000); + if (hours > 0) return `${hours}h left`; + const mins = Math.floor(diff / 60000); + return `${mins}m left`; +} + +/** + * Programming language color map (for badges). + */ +export const LANG_COLORS: Record = { + TypeScript: '#3178C6', + JavaScript: '#F7DF1E', + Python: '#3572A5', + Rust: '#DEA584', + Solidity: '#363636', + Go: '#00ADD8', + Java: '#B07219', + 'C++': '#F34B7D', + C: '#555555', + HTML: '#E34F26', + CSS: '#563D7C', + Shell: '#89E051', + Ruby: '#701516', + Vue: '#4FC08D', + React: '#61DAFB', +}; diff --git a/frontend/src/pages/BountyCreatePage.tsx b/frontend/src/pages/BountyCreatePage.tsx index a5690c391..95a7cfc67 100644 --- a/frontend/src/pages/BountyCreatePage.tsx +++ b/frontend/src/pages/BountyCreatePage.tsx @@ -9,8 +9,8 @@ export function BountyCreatePage() {
-

Post a Bounty

-

Fund a challenge, attract contributors, ship better code.

+

Post a Bounty

+

Fund a challenge, attract contributors, ship better code.

diff --git a/frontend/src/pages/HowItWorksPage.tsx b/frontend/src/pages/HowItWorksPage.tsx index 9b245eb50..9d2600382 100644 --- a/frontend/src/pages/HowItWorksPage.tsx +++ b/frontend/src/pages/HowItWorksPage.tsx @@ -9,8 +9,8 @@ export function HowItWorksPage() {
-

How It Works

-

Two paths to earning on SolFoundry

+

How It Works

+

Two paths to earning on SolFoundry

diff --git a/frontend/src/pages/LeaderboardPage.tsx b/frontend/src/pages/LeaderboardPage.tsx index f79caff97..fc034d89d 100644 --- a/frontend/src/pages/LeaderboardPage.tsx +++ b/frontend/src/pages/LeaderboardPage.tsx @@ -22,8 +22,8 @@ export function LeaderboardPage() {
-

Leaderboard

-

Top contributors ranked by bounties completed

+

Leaderboard

+

Top contributors ranked by bounties completed

{/* Time filter */} diff --git a/frontend/src/types/recharts.d.ts b/frontend/src/types/recharts.d.ts new file mode 100644 index 000000000..26c8431c9 --- /dev/null +++ b/frontend/src/types/recharts.d.ts @@ -0,0 +1,47 @@ +declare module 'recharts' { + import React from 'react'; + + export const BarChart: React.FC; + export const Bar: React.FC; + export const XAxis: React.FC; + export const YAxis: React.FC; + export const Tooltip: React.FC; + export const ResponsiveContainer: React.FC; + export const Cell: React.FC; + export const AreaChart: React.FC; + export const Area: React.FC; + export const Legend: React.FC; + export const CartesianGrid: React.FC; + export const LineChart: React.FC; + export const Line: React.FC; + export const PieChart: React.FC; + export const Pie: React.FC; + export const Sector: React.FC; + export const Label: React.FC; + export const LabelList: React.FC; + export const ReferenceLine: React.FC; + export const ReferenceDot: React.FC; + export const ErrorBar: React.FC; + export const ScatterChart: React.FC; + export const Scatter: React.FC; + export const RadarChart: React.FC; + export const Radar: React.FC; + export const RadialBarChart: React.FC; + export const RadialBar: React.FC; + export const ComposedChart: React.FC; + export const Treemap: React.FC; + export const FunnelChart: React.FC; + export const Funnel: React.FC; + export const Sankey: React.FC; + export const Surface: React.FC; + export const Polygon: React.FC; + export const Rectangle: React.FC; + export const Dot: React.FC; + export const Customized: React.FC; + export const Cross: React.FC; + export const PolarAngleAxis: React.FC; + export const PolarGrid: React.FC; + export const PolarRadiusAxis: React.FC; + export const PolarViewBox: React.FC; + export const Text: React.FC; +} \ No newline at end of file