diff --git a/docs/security-audit.md b/docs/security-audit.md index 59e7514..a48ddcf 100644 --- a/docs/security-audit.md +++ b/docs/security-audit.md @@ -34,8 +34,89 @@ Solver-supplied strings (`solver.name`, `intent.solver`, `item.solver`) are rend - `src/components/ActivityFeed.tsx:53` — `{item.solver}` - `src/app/explore/[id]/page.tsx:65` — `["Solver", intent.solver]` -**Assessment:** Safe. React automatically escapes all text content rendered via JSX (`{expression}`), preventing XSS. No sanitization change is needed. +**Assessment:** Safe from XSS. React automatically escapes all text content rendered via JSX (`{expression}`), preventing XSS. No sanitization change is needed for XSS. + +**Note:** While safe from XSS, these surfaces are susceptible to Unicode visual-spoofing attacks — see Issue #247 mitigation below. ## Conclusion -No unsafe rendering sinks were found. The codebase does not use `dangerouslySetInnerHTML`, `eval()`, or dynamic URL construction that bypasses React's built-in sanitization. All externally-sourced strings are rendered safely as plain text by React's default behavior. \ No newline at end of file +No unsafe rendering sinks were found. The codebase does not use `dangerouslySetInnerHTML`, `eval()`, or dynamic URL construction that bypasses React's built-in sanitization. All externally-sourced strings are rendered safely as plain text by React's default behavior. + +--- + +## Security Mitigations (Issues #244–#247) + +### #244 — XDR Transaction Review Before Freighter Signing + +**Risk:** Blind signing. The backend relay constructs the transaction XDR and the frontend previously passed it straight to Freighter for signing, with no client-side validation that the encoded transaction matched what the user intended to sign. A compromised or buggy relay could cause a user to sign a transaction with different amounts or destinations. + +**Mitigation implemented in:** `src/lib/xdrReview.ts`, `src/hooks/useSwapSubmission.ts`, `src/hooks/useSolverRegistration.ts` + +**Details:** +- A new `reviewing` status was inserted between `building` and `awaiting-signature` in both hook state machines. +- `decodeXdr()` uses `@stellar/stellar-sdk`'s `TransactionBuilder.fromXDR()` to decode the XDR returned by the relay. Fee-bump transactions are unwrapped to their inner transaction. +- `validateSwapXdr()` cross-checks the decoded destination and amount against the user's submitted params (amount tolerance: ±1% to accommodate stroops rounding). Any mismatch throws `XdrMismatchError`, a hard stop that prevents `freighterApi.signTransaction` from ever being called. +- `validateRegistrationXdr()` applies the same approach to the bond-deposit transaction for solver registration. +- **XDR decode failures are always a hard stop.** We never fall back to signing an XDR that failed to decode. +- Soroban invoke operations cannot be field-validated without full ABI decoding; they are surfaced to the user as a summary for acknowledgement. +- Edge cases handled: fee-bump transactions (unwrapped), network passphrase mismatches (caught by `TransactionBuilder.fromXDR`), multi-operation transactions, null/unknown network (defaults to testnet passphrase). + +**Tests:** `src/lib/xdrReview.test.ts` (decode failure, destination mismatch, amount mismatch, tolerance boundary, multi-op, zero-op), `src/hooks/useSwapSubmission.test.ts` and `src/hooks/useSolverRegistration.test.ts` (mismatch blocking, decode failure, happy path now asserts the review step runs). + +--- + +### #245 — CSV Formula Injection Hardening + +**Risk:** CSV/formula injection (CWE-1236). `buildIntentsCsv` wrote solver-supplied fields directly into CSV cells without neutralising leading formula-trigger characters (`=`, `+`, `-`, `@`, tab, CR). A malicious solver name like `=HYPERLINK("http://evil.example","click")` would execute as a formula when the exported `.csv` is opened in Excel, Google Sheets, or LibreOffice Calc. + +**Mitigation implemented in:** `src/lib/csv.ts` + +**Details:** +- `escapeCsv` now detects leading `=`, `+`, `-`, `@`, tab (U+0009), and carriage-return (U+000D) characters using a compiled regex and prefixes any matching value with a leading apostrophe `'` — the standard OWASP-recommended neutralisation for this class of issue. +- The apostrophe causes spreadsheet apps to treat the cell as inert text rather than a formula. +- Formula-injection neutralisation is applied before the existing CSV quoting logic, so values needing both (e.g. `=HYPERLINK(...)` containing a comma) are handled correctly. +- **Tradeoff:** A leading apostrophe causes Excel to display the apostrophe in the formula bar and sorts the field as text rather than a number. All fields currently exported (`id`, `srcChain`, `srcToken`, `srcAmount`, `dstToken`, `solver`, `status`, `createdAt`) are strings in practice, so no numeric semantics are lost. If a genuinely numeric column is added in future, it should be explicitly excluded from this sanitisation path or pre-formatted to avoid the trigger characters. +- The `-` trigger is intentional: while `-1` is a negative number, no currently-exported field is a bare signed number, and the security benefit of neutralising DDE injection via `-2+3+cmd|'/c calc'!A0`-style payloads outweighs the loss of numeric cell type for hypothetical future numeric fields. + +**Tests:** `src/lib/csv.test.ts` — explicit cases for each trigger character (`=`, `+`, `-`, `@`, tab, CR), combined injection + CSV-quoting, safe values unchanged. + +--- + +### #246 — Content-Security-Policy and Security Headers + +**Risk:** No CSP or security headers were set. The app was embeddable in hostile iframes (clickjacking risk against Freighter wallet-connect/sign flows) and had no defence-in-depth against future introduction of unsafe sinks. + +**Mitigation implemented in:** `next.config.mjs` + +**Details:** +- Added a `headers()` export that applies the following to all routes (`source: "/(.*)"`).: + - `Content-Security-Policy`: `default-src 'self'`; `script-src 'self' 'unsafe-inline'`; `connect-src 'self' ` (plus `ws://localhost:*` in dev for HMR); `frame-ancestors 'none'`; `upgrade-insecure-requests`. + - `X-Frame-Options: DENY` — belt-and-suspenders alongside `frame-ancestors 'none'` for browsers with partial CSP support. + - `X-Content-Type-Options: nosniff` — prevents MIME-type sniffing attacks. + - `Referrer-Policy: strict-origin-when-cross-origin` — limits referrer leakage to cross-origin requests. + - `Permissions-Policy: camera=(), microphone=(), geolocation=()` — disables unnecessary browser features. +- **`'unsafe-inline'` tradeoff:** Next.js 14 injects inline hydration scripts that cannot be removed without a nonce-based CSP requiring custom middleware. `'unsafe-inline'` is accepted for this baseline pass; a nonce-based policy is the recommended follow-up. The XSS risk is low given the absence of `dangerouslySetInnerHTML`, `eval`, or dynamic script insertion in this codebase (confirmed by the audit above). +- API and WebSocket origins are read from `process.env` at server start time (not inlined at build time), so they correctly reflect the runtime configuration. +- **Dev mode:** HMR WebSocket traffic on `ws://localhost:*` is whitelisted only when `NODE_ENV === "development"` so the production policy is not weakened. +- **Freighter wallet communication** happens via `window.postMessage` between the page and the browser extension, which is not a network request and requires no CSP allowance. + +**Verification:** Run the production build (`npm run build && npm start`) and confirm the headers are present in DevTools → Network → Response Headers for any page. Verify no CSP violations appear in the browser console during the full swap/solve/explore flows. + +--- + +### #247 — Unicode Confusable / Bidi-Override / Zero-Width Character Defense + +**Risk:** Unicode visual spoofing. Solver names and address-adjacent strings were displayed without normalisation, allowing bidi-override characters (e.g. U+202E RIGHT-TO-LEFT OVERRIDE) or zero-width characters (U+200B, U+200C, U+200D, U+FEFF) to make a malicious string appear visually identical to a trusted one — a known "address poisoning" attack vector in crypto UIs. + +**Mitigation implemented in:** `src/lib/textSafety.ts` (new), applied to all solver name and truncated-address rendering surfaces. + +**Details:** +- `sanitizeDisplayText(value: string): string` strips the following code point ranges: + - Bidi controls: U+202A–U+202E (LRE, RLE, PDF, LRO, RLO) and U+2066–U+2069 (LRI, RLI, FSI, PDI) + - Zero-width / invisible: U+200B–U+200D (ZWSP, ZWNJ, ZWJ), U+FEFF (BOM / ZWNBS), U+00AD (soft hyphen) +- Stripping (rather than flagging) is the chosen default because there is no legitimate use of these characters in solver names or Stellar addresses. Stripping is transparent to the user and prevents spoofing without requiring UI changes to every consumer. +- Non-Latin scripts (Arabic, CJK, Cyrillic, etc.) are explicitly NOT stripped — this targets control and invisible code points only. +- Applied to: `ActivityFeed.tsx` (solver route display), `CopyButton.tsx` (sanitizes before clipboard write), `ConnectWalletButton.tsx` (truncated wallet address display), `SolvePageClient.tsx` (leaderboard solver names), `solve/[address]/page.tsx` (solver name and truncated address), `explore/[id]/page.tsx` (solver field and truncated addresses), `ExplorePageClient.tsx` (solver via field), `my-intents/page.tsx` (solver via field). +- **Stellar address note:** Stellar public keys are fixed-format 56-character base32 G-strkeys validated structurally by `isValidStellarPublicKey` before any display or form submission. Confusable-character risk is inherently limited there. No address-adjacent free-text field (such as a future memo field) currently bypasses validation, but any future memo or label field must route through `sanitizeDisplayText` before display — this is the expected pattern established by this change. + +**Tests:** `src/lib/textSafety.test.ts` — real Unicode attack fixtures for every bidi control (U+202A–U+202E, U+2066–U+2069), every zero-width character (U+200B–U+200D, U+FEFF, U+00AD), combined payloads, safe ASCII/non-Latin strings asserted unchanged. diff --git a/next.config.mjs b/next.config.mjs index 81359ef..956f34d 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,120 @@ /** @type {import('next').NextConfig} */ + +const isDev = process.env.NODE_ENV === "development"; + +// Read the configured API and WebSocket origins so we can whitelist them in +// the CSP connect-src directive. These values are available here because +// next.config.mjs runs server-side at build/start time and has full access +// to process.env — they are NOT the same as NEXT_PUBLIC_* inlining (which +// happens at compile time inside the browser bundle). +const API_ORIGIN = (() => { + try { + return new URL( + process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:4000" + ).origin; + } catch { + return "http://localhost:4000"; + } +})(); + +const WS_ORIGIN = (() => { + try { + return new URL( + process.env.NEXT_PUBLIC_WS_URL ?? "ws://localhost:4000/ws" + ).origin; + } catch { + return "ws://localhost:4000"; + } +})(); + +/** + * Build the Content-Security-Policy header value. + * + * Tradeoffs documented: + * + * 1. `script-src 'self' 'unsafe-inline'` + * Next.js 14 injects small inline scripts for hydration (the + * __NEXT_DATA__ JSON block and the hydration bootstrap). A fully strict + * nonce-based CSP requires custom server middleware to generate and thread + * the nonce through every render — that is intentionally out of scope for + * this baseline hardening pass. `'unsafe-inline'` is the accepted + * short-term tradeoff; a nonce-based policy is the recommended follow-up + * (tracked separately). The XSS risk is mitigated by the absence of + * dangerouslySetInnerHTML / eval / dynamic script insertion anywhere in + * this codebase (confirmed in the security audit). + * + * 2. `connect-src 'self' ` + * SWR fetches go to API_ORIGIN (REST). The live intent WebSocket connects + * to WS_ORIGIN. Both must be explicitly allowed. + * In dev mode we also allow the Next.js hot-reload WebSocket on + * ws://localhost:* so that HMR keeps working. + * + * 3. `frame-ancestors 'none'` (and X-Frame-Options: DENY) + * Prevents this app from being embedded in a hostile iframe — critical for + * a wallet-integrated dApp (clickjacking against Freighter sign flows). + * + * 4. Freighter wallet communication happens via window.postMessage between + * the page and the browser extension — this is NOT a network request and + * requires no special CSP allowance. + * + * 5. `img-src 'self' data:` + * Next.js Image optimization and inline SVG data URIs both need `data:`. + */ +function buildCsp() { + const connectSrc = [ + "'self'", + API_ORIGIN, + WS_ORIGIN, + ...(isDev ? ["ws://localhost:*", "http://localhost:*"] : []), + ].join(" "); + + const directives = [ + "default-src 'self'", + `script-src 'self' 'unsafe-inline'`, // see tradeoff note 1 above + "style-src 'self' 'unsafe-inline'", // Tailwind injects inline styles via CSS-in-JS in dev + `connect-src ${connectSrc}`, + "img-src 'self' data:", + "font-src 'self'", + "object-src 'none'", + "base-uri 'self'", + "form-action 'self'", + "frame-ancestors 'none'", // clickjacking protection + "upgrade-insecure-requests", + ]; + + return directives.join("; "); +} + +const securityHeaders = [ + { + key: "Content-Security-Policy", + value: buildCsp(), + }, + { + // Belt-and-suspenders: frame-ancestors in CSP is preferred, but + // X-Frame-Options provides coverage for older browsers that don't + // fully support CSP frame-ancestors. + key: "X-Frame-Options", + value: "DENY", + }, + { + // Prevents MIME-type sniffing attacks. + key: "X-Content-Type-Options", + value: "nosniff", + }, + { + // Sends full URL to same origin, only origin to HTTPS cross-origin sites, + // and nothing to HTTP cross-origin sites. + key: "Referrer-Policy", + value: "strict-origin-when-cross-origin", + }, + { + // Disallows the use of browser features not needed by this app. + key: "Permissions-Policy", + value: "camera=(), microphone=(), geolocation=()", + }, +]; + const nextConfig = { reactStrictMode: true, env: { @@ -8,6 +124,15 @@ const nextConfig = { NEXT_PUBLIC_SETTLEMENT_CONTRACT: process.env.NEXT_PUBLIC_SETTLEMENT_CONTRACT ?? "", NEXT_PUBLIC_SOLVER_REGISTRY_CONTRACT: process.env.NEXT_PUBLIC_SOLVER_REGISTRY_CONTRACT ?? "", }, + async headers() { + return [ + { + // Apply security headers to all routes. + source: "/(.*)", + headers: securityHeaders, + }, + ]; + }, }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index aa6f903..ad8e18a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,7 +30,7 @@ "@types/react": "^18", "@types/react-dom": "^18", "@vitejs/plugin-react": "^4.3.4", - "@vitest/coverage-v8": "^4.1.10", + "@vitest/coverage-v8": "^2.1.9", "autoprefixer": "^10", "eslint": "^8", "eslint-config-next": "14.2.3", @@ -398,15 +398,320 @@ } }, "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@commitlint/cli": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.2.tgz", + "integrity": "sha512-a+6hQxIxnpdvSvS2apvttPNbEliYsVC3PqFYDiiB2kjbwIsQsj1urvQ4Tkf70pKYozPalKAuRQmm/GHwndduqA==", + "dev": true, + "dependencies": { + "@commitlint/config-conventional": "^21.2.2", + "@commitlint/format": "^21.2.2", + "@commitlint/lint": "^21.2.2", + "@commitlint/load": "^21.2.2", + "@commitlint/read": "^21.2.1", + "@commitlint/types": "^21.2.0", + "tinyexec": "^1.0.0", + "yargs": "^18.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/cli/node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=18" } }, + "node_modules/@commitlint/config-conventional": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-21.2.2.tgz", + "integrity": "sha512-NxA37SZviusFUEYOQZ5hNnZ1h7O/KiemPkxjOlpzKJNnWxThiwc6/SaZhaPa8fyLvfRBAywhQhJJk8XESHWlpQ==", + "dev": true, + "dependencies": { + "@commitlint/types": "^21.2.0", + "conventional-changelog-conventionalcommits": "^10.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-21.2.0.tgz", + "integrity": "sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==", + "dev": true, + "dependencies": { + "@commitlint/types": "^21.2.0", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/config-validator/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/@commitlint/ensure": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-21.2.0.tgz", + "integrity": "sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==", + "dev": true, + "dependencies": { + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-21.0.1.tgz", + "integrity": "sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==", + "dev": true, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/format": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-21.2.2.tgz", + "integrity": "sha512-v6fvxZSc/AvVMROlr3H34+1766bZSYApRUSCAMjWamStPjKMvZ8GdvVA5YW/VQNgbFTmcMz6OYmSTJEvIjPrfA==", + "dev": true, + "dependencies": { + "@commitlint/types": "^21.2.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-21.2.2.tgz", + "integrity": "sha512-9UoKNgfFE3LU7FrzierCvk3CdDfMDeVGC86qZiT/n0TIjfq/dmZ9MHuXd45OTNRa26ZanmJRxEtmiXk/lEJihg==", + "dev": true, + "dependencies": { + "@commitlint/types": "^21.2.0", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/lint": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-21.2.2.tgz", + "integrity": "sha512-Fy8JxEBzdmsYWFude/61GxXu5O+wEymwiRK2z9GL9R8mCsXphCoGxAFc5iHn5mjlfcSrhiiONE+ksf4KOjnaPg==", + "dev": true, + "dependencies": { + "@commitlint/is-ignored": "^21.2.2", + "@commitlint/parse": "^21.2.2", + "@commitlint/rules": "^21.2.2", + "@commitlint/types": "^21.2.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/load": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-21.2.2.tgz", + "integrity": "sha512-0Tt6wDPX167cjKC5D4zhm0+20wJJG+TN/TKovMOspfSe78rOnKX+MNzlVNiu6HyQPZChPJ8QBH31MVt6Bb8fCg==", + "dev": true, + "dependencies": { + "@commitlint/config-validator": "^21.2.0", + "@commitlint/execute-rule": "^21.0.1", + "@commitlint/resolve-extends": "^21.2.2", + "@commitlint/types": "^21.2.0", + "cosmiconfig": "^9.0.1", + "cosmiconfig-typescript-loader": "^6.1.0", + "es-toolkit": "^1.46.0", + "is-plain-obj": "^4.1.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/message": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-21.2.0.tgz", + "integrity": "sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==", + "dev": true, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/parse": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-21.2.2.tgz", + "integrity": "sha512-MEkobPfvRp+z06Wro8HMG1BDGHzZmj82A1LH1nWeG3ipHpg/x4m6v3wEDvMBIKjRFUnfR3nBeFs3MVCr7UdAmg==", + "dev": true, + "dependencies": { + "@commitlint/types": "^21.2.0", + "conventional-changelog-angular": "^9.0.0", + "conventional-commits-parser": "^7.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/read": { + "version": "21.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-21.2.1.tgz", + "integrity": "sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==", + "dev": true, + "dependencies": { + "@commitlint/top-level": "^21.2.0", + "@commitlint/types": "^21.2.0", + "@conventional-changelog/git-client": "^3.0.0", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/read/node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-21.2.2.tgz", + "integrity": "sha512-RPkJ/IFi7sMUUVbZLqwWFtWw/zRDcfFsmrPSiTMrt5wb7AdxOr86EGQFvmGzef5QKV5IPBWWCujqVTw1RWX44A==", + "dev": true, + "dependencies": { + "@commitlint/config-validator": "^21.2.0", + "@commitlint/types": "^21.2.0", + "es-toolkit": "^1.46.0", + "global-directory": "^5.0.0", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/resolve-extends/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@commitlint/rules": { + "version": "21.2.2", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-21.2.2.tgz", + "integrity": "sha512-eplQzyYkBjYB1HyyRj8hkcK11Y9DU9nuBz7uOKEd6NpE9NGDytLFCAnlRE+OoiK/5sHEJsaz2RGhuWBvYzIbNA==", + "dev": true, + "dependencies": { + "@commitlint/ensure": "^21.2.0", + "@commitlint/message": "^21.2.0", + "@commitlint/to-lines": "^21.0.1", + "@commitlint/types": "^21.2.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-21.0.1.tgz", + "integrity": "sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==", + "dev": true, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/top-level": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-21.2.0.tgz", + "integrity": "sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==", + "dev": true, + "dependencies": { + "escalade": "^3.2.0" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@commitlint/types": { + "version": "21.2.0", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-21.2.0.tgz", + "integrity": "sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==", + "dev": true, + "dependencies": { + "conventional-commits-parser": "^7.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@conventional-changelog/git-client": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-3.1.2.tgz", + "integrity": "sha512-jZqwnJwf7nboIlAcw/mkOjVa6DexCcUOgT2oOQgkoi3z9vR8tGFkcMy2BFcYwjhL9sYcDDXkRQDayiDieCoW7A==", + "dev": true, + "dependencies": { + "@simple-libs/child-process-utils": "^2.0.0", + "@simple-libs/stream-utils": "^2.0.0", + "semver": "^7.5.2" + }, + "engines": { + "node": ">=22" + }, + "peerDependencies": { + "conventional-commits-filter": "^6.0.1", + "conventional-commits-parser": "^7.1.2" + }, + "peerDependenciesMeta": { + "conventional-commits-filter": { + "optional": true + }, + "conventional-commits-parser": { + "optional": true + } + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -1447,9 +1752,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1467,9 +1769,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1487,9 +1786,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1507,9 +1803,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1527,9 +1820,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1547,9 +1837,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1567,9 +1854,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1587,9 +1871,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1825,9 +2106,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1842,9 +2120,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1859,9 +2134,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1876,9 +2148,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1893,9 +2162,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1910,9 +2176,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1927,9 +2190,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1944,9 +2204,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2058,49 +2315,6 @@ "node": ">=14" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", - "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", @@ -2508,301 +2722,21 @@ "urijs": "^1.19.1" } }, - "node_modules/@storybook/addon-a11y": { - "version": "10.5.5", - "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.5.5.tgz", - "integrity": "sha512-nsMnSRe7pzepXIkUUqI/rL7sp8juXOluyypU4Dz0UuYHhw/cKaxfzuO+3WJN5EtEr/gcnKYb4awxH/duPGavUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "axe-core": "^4.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^10.5.5" - } - }, - "node_modules/@storybook/builder-vite": { - "version": "10.5.5", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.5.5.tgz", - "integrity": "sha512-dQoJ7gUl8y0z5rV9cE0mz6qTBNmN9R4GOLIZk98rJ8CwduNJOb9eGZXusDzzvnYcp8TnNkqDtyx4tXQSUDInPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/csf-plugin": "10.5.5", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^10.5.5", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@storybook/csf-plugin": { - "version": "10.5.5", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.5.5.tgz", - "integrity": "sha512-/euibhRFqklYCZqUseokojmfYcQpXshVY2QmA1qCuxMz9SzVFD3iSTw+aFLTxpsJGGdcZJk8fnm/rEthLzZ9jA==", - "dev": true, - "license": "MIT", - "dependencies": { - "unplugin": "^2.3.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "esbuild": "*", - "rollup": "*", - "storybook": "^10.5.5", - "vite": "*", - "webpack": "*" - }, - "peerDependenciesMeta": { - "esbuild": { - "optional": true - }, - "rollup": { - "optional": true - }, - "vite": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/@storybook/global": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", - "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@storybook/icons": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-2.1.0.tgz", - "integrity": "sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@storybook/nextjs-vite": { - "version": "10.5.5", - "resolved": "https://registry.npmjs.org/@storybook/nextjs-vite/-/nextjs-vite-10.5.5.tgz", - "integrity": "sha512-++OLNQBh/32nmhMv1CiFh5wGBFopAcya7SU2PnHlmiZS/d2s6RmJwPe3pEi+HnHrFXgu5hcjgDlTu8EhRtvjig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/builder-vite": "10.5.5", - "@storybook/react": "10.5.5", - "@storybook/react-vite": "10.5.5", - "styled-jsx": "5.1.6", - "vite-plugin-storybook-nextjs": "^3.3.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "next": "^14.1.0 || ^15.0.0 || ^16.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.5.5", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@storybook/nextjs-vite/node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/@storybook/react": { - "version": "10.5.5", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.5.5.tgz", - "integrity": "sha512-T2Xj0ey7a9RHU6coYLC0L5lhjcdyhLCs9wNv15FvHvgmrRobkynEV72kq5vGW8tFkahNWI1X9+GZPQ6r8Nm38w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.5.5", - "react-docgen": "^8.0.2", - "react-docgen-typescript": "^2.2.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.5.5", - "typescript": ">= 4.9.x" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@storybook/react-dom-shim": { - "version": "10.5.5", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.5.5.tgz", - "integrity": "sha512-PIk7N3LLrZIxfNxmkvmQN1d5UQ70XEedT8n0GhBiXnM6XL09xPGB8n8TZXeJBRYluKhDQcAyQeT0/OZmcDVQJg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "@types/react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.5.5" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@storybook/react-vite": { - "version": "10.5.5", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.5.5.tgz", - "integrity": "sha512-Uy7VV72kVSkw6aDTAPQupXUeZX5LF6e4zqNvTZ+36qxsXAkaFgw7HPEm7L1tsaRfiV+s9anU7UvX47tfJpYGuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@joshwooding/vite-plugin-react-docgen-typescript": "^0.7.0", - "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "10.5.5", - "@storybook/react": "10.5.5", - "empathic": "^2.0.0", - "magic-string": "^0.30.0", - "react-docgen": "^8.0.2", - "resolve": "^1.22.8", - "tsconfig-paths": "^4.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.5.5", - "typescript": ">= 4.9.x", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@storybook/react-vite/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@storybook/react-vite/node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/@storybook/react-vite/node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "node_modules/@storybook/global": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", + "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@storybook/icons": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-2.1.0.tgz", + "integrity": "sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==", "dev": true, "license": "MIT", - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/@swc/counter": { @@ -3005,13 +2939,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/doctrine": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", - "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -3058,13 +2985,6 @@ "@types/react": "^18.0.0" } }, - "node_modules/@types/resolve": { - "version": "1.20.6", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", - "integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@typescript-eslint/parser": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz", @@ -3509,29 +3429,30 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", - "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", + "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", "dev": true, - "license": "MIT", "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.10", - "ast-v8-to-istanbul": "^1.0.0", + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", "istanbul-lib-coverage": "^3.2.2", "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "4.1.10", - "vitest": "4.1.10" + "@vitest/browser": "2.1.9", + "vitest": "2.1.9" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -3539,51 +3460,6 @@ } } }, - "node_modules/@vitest/coverage-v8/node_modules/@vitest/pretty-format": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", - "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/coverage-v8/node_modules/@vitest/utils": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", - "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.10", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/coverage-v8/node_modules/std-env": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", - "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vitest/coverage-v8/node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@vitest/expect": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", @@ -4011,25 +3887,6 @@ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true }, - "node_modules/ast-v8-to-istanbul": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", - "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -4685,11 +4542,10 @@ } }, "node_modules/conventional-commits-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.1.tgz", - "integrity": "sha512-B0f42jI++V5Vb7qK+DDw68r0dNxz5hk+RdKUkx2NOi39emc9hsHa3u2M3doF7QQhRFzCrAj7uM90teG+RBTaYQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-7.1.2.tgz", + "integrity": "sha512-O+x4N2yH+ijvqWlIyTHsXTAP+algNWgGbjY2duCe8w2vUMvUB95cLRslCPfTMQyLAKlet3bhZTdu6ozn4M+QJQ==", "dev": true, - "license": "MIT", "dependencies": { "@simple-libs/stream-utils": "^2.0.0", "argue-cli": "^3.1.0" @@ -5091,16 +4947,6 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, - "node_modules/empathic": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", - "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/entities": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", @@ -6377,13 +6223,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true, - "license": "MIT" - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -6598,19 +6437,6 @@ "node": ">= 4" } }, - "node_modules/image-size": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", - "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", - "dev": true, - "license": "MIT", - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -7217,6 +7043,20 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -7606,15 +7446,14 @@ } }, "node_modules/magicast": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", - "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.3", - "@babel/types": "^7.29.0", - "source-map-js": "^1.2.1" + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" } }, "node_modules/make-dir": { @@ -7721,13 +7560,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/module-alias": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.3.4.tgz", - "integrity": "sha512-bOclZt8hkpuGgSSoG07PKmvzTizROilUTvLNyrMqvlC9snhs7y7GzjNWAVbISIOlhCP1T14rH1PDAV9iNyBq/w==", - "dev": true, - "license": "MIT" - }, "node_modules/motion-dom": { "version": "11.18.1", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", @@ -8047,20 +7879,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT", - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -8387,53 +8205,6 @@ "node": ">= 6" } }, - "node_modules/playwright": { - "version": "1.62.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.0.tgz", - "integrity": "sha512-Z14dG305dgaLu6foB1TXQagFiW8JfSUIUaUuPaKQ6NtBPKF1P/qXcqfh6c6K/icPqdy37JmjbiBXf6JNg6Sylw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.62.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=20" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.62.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.0.tgz", - "integrity": "sha512-nsNRyq0r2zsG8AcRHWknc9QRA5XCueC7gWMrs+Gx2tlZn9hcl8zudfh00lhJPY1DE7NmZ6bDsT9g2yey8mXljA==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -8745,73 +8516,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-docgen": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.3.tgz", - "integrity": "sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.2", - "@types/babel__core": "^7.20.5", - "@types/babel__traverse": "^7.20.7", - "@types/doctrine": "^0.0.9", - "@types/resolve": "^1.20.2", - "doctrine": "^3.0.0", - "resolve": "^1.22.1", - "strip-indent": "^4.0.0" - }, - "engines": { - "node": "^20.9.0 || >=22" - } - }, - "node_modules/react-docgen-typescript": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.4.0.tgz", - "integrity": "sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">= 4.3.x" - } - }, - "node_modules/react-docgen/node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/react-docgen/node_modules/strip-indent": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", - "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -10317,44 +10021,12 @@ "typescript": ">=4.2.0" } }, - "node_modules/ts-dedent": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.3.0.tgz", - "integrity": "sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.10" - } - }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, - "node_modules/tsconfck": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", - "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", - "deprecated": "unmaintained", - "dev": true, - "license": "MIT", - "bin": { - "tsconfck": "bin/tsconfck.js" - }, - "engines": { - "node": "^18 || >=20" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -10520,35 +10192,6 @@ "node": ">= 4.0.0" } }, - "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/unrs-resolver": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", @@ -10735,53 +10378,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-plugin-storybook-nextjs": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/vite-plugin-storybook-nextjs/-/vite-plugin-storybook-nextjs-3.3.1.tgz", - "integrity": "sha512-R+3A1qR45CTHc1fe6Lpb2Do4jmT7qDFK+bQnezqW5Q4hKAkT7y+x5fAuoLHcjhm8XCGuqPUl1SoiK+opEuUlCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@next/env": "16.0.0", - "image-size": "^2.0.0", - "magic-string": "^0.30.11", - "module-alias": "^2.2.3", - "ts-dedent": "^2.2.0", - "vite-tsconfig-paths": "^5.1.4" - }, - "peerDependencies": { - "next": "^14.1.0 || ^15.0.0 || ^16.0.0", - "storybook": "^0.0.0-0 || ^9.0.0 || ^10.0.0 || ^10.0.0-0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0 || ^10.6.0-0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/vite-plugin-storybook-nextjs/node_modules/@next/env": { - "version": "16.0.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.0.0.tgz", - "integrity": "sha512-s5j2iFGp38QsG1LWRQaE2iUY3h1jc014/melHFfLdrsMJPqxqDQwWNwyQTcNoUSGZlCVZuM7t7JDMmSyRilsnA==", - "dev": true, - "license": "MIT" - }, - "node_modules/vite-tsconfig-paths": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", - "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.1", - "globrex": "^0.1.2", - "tsconfck": "^3.0.3" - }, - "peerDependencies": { - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } - }, "node_modules/vitest": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", @@ -10868,13 +10464,6 @@ "node": ">=12" } }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", diff --git a/src/app/explore/ExplorePageClient.tsx b/src/app/explore/ExplorePageClient.tsx index 10af3f1..7dae1b9 100644 --- a/src/app/explore/ExplorePageClient.tsx +++ b/src/app/explore/ExplorePageClient.tsx @@ -9,6 +9,7 @@ import { SkeletonCard } from "@/components/Skeleton"; import { useLiveIntents } from "@/hooks/useLiveIntents"; import { timeAgo } from "@/lib/time"; import { CHAINS } from "@/lib/marketData"; +import { sanitizeDisplayText } from "@/lib/textSafety"; import type { IntentStatus } from "@/lib/types"; const STATUS_OPTIONS: Array = ["all", "pending", "accepted", "filled", "failed"]; @@ -145,7 +146,7 @@ export default function ExplorePageClient() { {item.srcAmount} {item.srcToken} → {item.dstToken}
- {item.srcChain} · via {item.solver} + {item.srcChain} · via {sanitizeDisplayText(item.solver)}
diff --git a/src/app/explore/[id]/page.tsx b/src/app/explore/[id]/page.tsx index 88cfb04..3019b14 100644 --- a/src/app/explore/[id]/page.tsx +++ b/src/app/explore/[id]/page.tsx @@ -6,15 +6,18 @@ import { useCopyToClipboard } from "@/hooks/useCopyToClipboard"; import { Nav } from "@/components/Nav"; import { Footer } from "@/components/Footer"; import { IntentStatusBadge } from "@/components/IntentStatusBadge"; +import { CopyButton } from "@/components/CopyButton"; import { SkeletonDetailCard } from "@/components/Skeleton"; import { useIntent } from "@/hooks/useIntent"; import { timeAgo } from "@/lib/time"; +import { sanitizeDisplayText } from "@/lib/textSafety"; const NETWORK = process.env.NEXT_PUBLIC_NETWORK ?? "testnet"; function truncateAddress(address: string) { - if (address.length <= 12) return address; - return `${address.slice(0, 6)}...${address.slice(-6)}`; + const safe = sanitizeDisplayText(address); + if (safe.length <= 12) return safe; + return `${safe.slice(0, 6)}...${safe.slice(-6)}`; } function deadlineLabel(deadline: string) { @@ -73,7 +76,7 @@ export default function IntentDetailPage({ params }: { params: { id: string } })
{[ ["Source chain", intent.srcChain], - ["Solver", intent.solver], + ["Solver", sanitizeDisplayText(intent.solver)], ["Minimum out", `${intent.minOut} ${intent.dstToken}`], ["Submitted", timeAgo(intent.createdAt)], ["Deadline", deadlineLabel(intent.deadline)], diff --git a/src/app/my-intents/page.tsx b/src/app/my-intents/page.tsx index 41f9c75..d1df012 100644 --- a/src/app/my-intents/page.tsx +++ b/src/app/my-intents/page.tsx @@ -10,6 +10,7 @@ import { useWalletStore } from "@/store/wallet"; import { useMyLiveIntents } from "@/hooks/useMyLiveIntents"; import { CHAINS } from "@/lib/marketData"; import { SkeletonCard } from "@/components/Skeleton"; +import { sanitizeDisplayText } from "@/lib/textSafety"; import type { IntentStatus } from "@/lib/types"; const STATUS_OPTIONS: Array = ["all", "pending", "accepted", "filled", "failed"]; @@ -165,7 +166,7 @@ export default function MyIntentsPage() { {item.srcAmount} {item.srcToken} → {item.dstToken}
- {item.srcChain} · via {item.solver} + {item.srcChain} · via {sanitizeDisplayText(item.solver)}
diff --git a/src/app/solve/SolvePageClient.tsx b/src/app/solve/SolvePageClient.tsx index 890745c..5b7dbac 100644 --- a/src/app/solve/SolvePageClient.tsx +++ b/src/app/solve/SolvePageClient.tsx @@ -11,6 +11,7 @@ import { timeRemaining } from "@/lib/time"; import { isValidStellarPublicKey } from "@/lib/stellarAddress"; import { getMessage } from "@/i18n/messages"; import { formatCurrency } from "@/lib/format"; +import { sanitizeDisplayText } from "@/lib/textSafety"; import Link from "next/link"; const usdCompact = (value: number) => @@ -170,7 +171,7 @@ export default function SolvePageClient() { {String(i + 1).padStart(2, "0")}
-
{s.name}
+
{sanitizeDisplayText(s.name)}
{s.address}
{s.chains.map((c) => ( diff --git a/src/app/solve/[address]/page.tsx b/src/app/solve/[address]/page.tsx index 0fb501b..e1a9970 100644 --- a/src/app/solve/[address]/page.tsx +++ b/src/app/solve/[address]/page.tsx @@ -4,14 +4,19 @@ import Link from "next/link"; import { Nav } from "@/components/Nav"; import { Footer } from "@/components/Footer"; import { IntentStatusBadge } from "@/components/IntentStatusBadge"; +import { CopyButton } from "@/components/CopyButton"; +import { SkeletonCard } from "@/components/Skeleton"; import { useSolver } from "@/hooks/useSolver"; import { useIntentFeed } from "@/hooks/useIntentFeed"; import { timeAgo } from "@/lib/time"; import { CHAINS } from "@/lib/marketData"; +import { isValidStellarPublicKey } from "@/lib/stellarAddress"; +import { sanitizeDisplayText } from "@/lib/textSafety"; function truncateAddress(address: string) { - if (address.length <= 12) return address; - return `${address.slice(0, 6)}...${address.slice(-6)}`; + const safe = sanitizeDisplayText(address); + if (safe.length <= 12) return safe; + return `${safe.slice(0, 6)}...${safe.slice(-6)}`; } const usdCompact = new Intl.NumberFormat("en-US", { @@ -63,7 +68,7 @@ export default function SolverDetailPage({ params }: { params: { address: string
Solver

- {solver.name} + {sanitizeDisplayText(solver.name)}

= { @@ -59,7 +60,7 @@ export function ActivityFeed() {
{t("activityFeed.item.route", { chain: item.srcChain, - solver: item.solver, + solver: sanitizeDisplayText(item.solver), })}
diff --git a/src/components/ConnectWalletButton.tsx b/src/components/ConnectWalletButton.tsx index a0f7150..781eb93 100644 --- a/src/components/ConnectWalletButton.tsx +++ b/src/components/ConnectWalletButton.tsx @@ -3,11 +3,13 @@ import { useWalletStore } from "@/store/wallet"; import { useToastStore } from "@/store/toast"; import { useTranslation } from "@/lib/i18n/I18nProvider"; +import { sanitizeDisplayText } from "@/lib/textSafety"; const FREIGHTER_INSTALL_URL = "https://www.freighter.app/"; function truncateAddress(address: string) { - return `${address.slice(0, 4)}...${address.slice(-4)}`; + const safe = sanitizeDisplayText(address); + return `${safe.slice(0, 4)}...${safe.slice(-4)}`; } export function ConnectWalletButton({ compact = false }: { compact?: boolean }) { diff --git a/src/components/CopyButton.tsx b/src/components/CopyButton.tsx index 56aa993..a6e6b11 100644 --- a/src/components/CopyButton.tsx +++ b/src/components/CopyButton.tsx @@ -1,6 +1,7 @@ "use client"; import { useCopyToClipboard } from "@/hooks/useCopyToClipboard"; +import { sanitizeDisplayText } from "@/lib/textSafety"; export function CopyButton({ value, label = "Copy to clipboard" }: { value: string; label?: string }) { const { copy } = useCopyToClipboard(); @@ -8,7 +9,9 @@ export function CopyButton({ value, label = "Copy to clipboard" }: { value: stri return (