From 44a3baf2104475bf6dfd06c0e2d9da08a575b48f Mon Sep 17 00:00:00 2001 From: shreya Date: Wed, 27 May 2026 17:21:51 +0530 Subject: [PATCH 01/63] feat: add toast feedback for copy actions --- app/layout.tsx | 4 +++ app/page.tsx | 34 +++++++++++++----- package-lock.json | 90 +++++++++++++++++++++++++++++------------------ package.json | 1 + 4 files changed, 85 insertions(+), 44 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 22f52d0b..72a681ec 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -4,6 +4,7 @@ import { Analytics } from '@vercel/analytics/next'; import Navbar from './components/navbar'; import BrandParticles from '@/components/BrandParticles'; import type { Metadata } from 'next'; +import { Toaster } from 'react-hot-toast'; const inter = Inter({ subsets: ['latin'] }); @@ -70,6 +71,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
{children}
+ + {children} + ); diff --git a/app/page.tsx b/app/page.tsx index 68a72cb9..1e7028c2 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -10,6 +10,8 @@ import { CommitPulseLogo } from '@/components/commitpulse-logo'; import { CustomizeCTA } from './components/CustomizeCTA'; import { useRecentSearches } from '@/hooks/useRecentSearches'; +import toast from 'react-hot-toast'; + const Icons = { Github: () => ( @@ -108,18 +110,32 @@ export default function LandingPage() { return () => controller.abort(); }, [badgeUrl, hasUsername]); - const copyToClipboard = () => { + const copyToClipboard = async () => { if (!hasUsername) return; - trackUser(trimmedUsername); - addSearch(trimmedUsername); + try { + trackUser(trimmedUsername); + addSearch(trimmedUsername); + + await navigator.clipboard.writeText(markdown); + + setCopied(true); + + toast.success('Markdown copied successfully!', { + id: 'copy-success', + }); - navigator.clipboard.writeText(markdown); - setCopied(true); - setTimeout(() => { - guideRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }); - }, 80); - setTimeout(() => setCopied(false), 50000); + setTimeout(() => { + guideRef.current?.scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + }, 80); + + setTimeout(() => setCopied(false), 2000); + } catch (error) { + toast.error('Failed to copy markdown'); + } }; return ( diff --git a/package-lock.json b/package-lock.json index 0703cd8b..b715d5f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "next": "^16.2.3", "react": "19.2.4", "react-dom": "19.2.4", + "react-hot-toast": "^2.6.0", "sonner": "^2.0.7", "zod": "^4.4.3" }, @@ -138,6 +139,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -468,6 +470,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" }, @@ -516,31 +519,11 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } }, - "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -2575,8 +2558,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/chai": { "version": "5.2.3", @@ -2623,6 +2605,7 @@ "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -2633,6 +2616,7 @@ "integrity": "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2643,6 +2627,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2707,6 +2692,7 @@ "integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.59.4", "@typescript-eslint/types": "8.59.4", @@ -3314,6 +3300,7 @@ "integrity": "sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.7", @@ -3443,6 +3430,7 @@ "integrity": "sha512-TP6utB2yX6rsJNVRo2qAlsi48i1YwFTrLV2tnTtWqJaYX7m4lRCCLirZBjU6xC5m0RsPHr+L2+N+eIPhgEzFfw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "4.1.7", "fflate": "^0.8.2", @@ -3494,6 +3482,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3550,7 +3539,6 @@ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -3584,7 +3572,6 @@ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "dequal": "^2.0.3" } @@ -3941,6 +3928,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -4207,8 +4195,8 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -4369,7 +4357,6 @@ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -4401,8 +4388,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", @@ -4727,6 +4713,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4928,6 +4915,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5618,6 +5606,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.19", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.19.tgz", + "integrity": "sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -7015,7 +7012,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -7458,6 +7454,7 @@ "resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz", "integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "16.2.6", "@swc/helpers": "0.5.15", @@ -7975,6 +7972,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -8023,7 +8021,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -8039,7 +8036,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -8052,8 +8048,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/prop-types": { "version": "15.8.1", @@ -8119,6 +8114,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -8128,6 +8124,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -8135,6 +8132,23 @@ "react": "^19.2.4" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "19.2.6", "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", @@ -9140,6 +9154,7 @@ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9277,6 +9292,7 @@ "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.28.0" }, @@ -9401,6 +9417,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9554,6 +9571,7 @@ "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -9645,6 +9663,7 @@ "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/expect": "4.1.7", "@vitest/mocker": "4.1.7", @@ -10011,6 +10030,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index a0c6dfab..3ba32f48 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "next": "^16.2.3", "react": "19.2.4", "react-dom": "19.2.4", + "react-hot-toast": "^2.6.0", "sonner": "^2.0.7", "zod": "^4.4.3" }, From 36217fef1f9e611bb1bf8f73d8fd359cdf797ce6 Mon Sep 17 00:00:00 2001 From: shreya Date: Wed, 27 May 2026 17:45:57 +0530 Subject: [PATCH 02/63] feat: add toast feedback for copy actions --- app/page.tsx | 2 +- hooks/useRecentSearches.ts | 24 ------------------------ 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 1e7028c2..6d1afcaf 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -133,7 +133,7 @@ export default function LandingPage() { }, 80); setTimeout(() => setCopied(false), 2000); - } catch (error) { + } catch { toast.error('Failed to copy markdown'); } }; diff --git a/hooks/useRecentSearches.ts b/hooks/useRecentSearches.ts index d09492de..b2a26581 100644 --- a/hooks/useRecentSearches.ts +++ b/hooks/useRecentSearches.ts @@ -7,30 +7,6 @@ export const MAX_SEARCHES = 5; type State = { searches: string[]; mounted: boolean }; -function loadFromStorage(): string[] { - let saved: string[] = []; - try { - const stored = localStorage.getItem(STORAGE_KEY); - if (stored) saved = JSON.parse(stored) as string[]; - } catch { - // ignore malformed storage - } - return saved; -} - -function writeStorage(searches: string[] | null): void { - try { - if (searches === null) { - localStorage.removeItem(STORAGE_KEY); - return; - } - - localStorage.setItem(STORAGE_KEY, JSON.stringify(searches)); - } catch { - // ignore storage write failures - } -} - export function useRecentSearches() { // Always start with [] and mounted:false on both server and client so the // initial render matches (SSR-safe). A single setState in the mount effect From 68a21a2bab2ab1aa940466617c3fc6d910dc6aaf Mon Sep 17 00:00:00 2001 From: shreya Date: Wed, 27 May 2026 18:32:37 +0530 Subject: [PATCH 03/63] fix: regenerate lockfile and resolve dependency sync --- package-lock.json | 361 ++++++++++++++++++++++++---------------------- 1 file changed, 191 insertions(+), 170 deletions(-) diff --git a/package-lock.json b/package-lock.json index b715d5f6..7a897de5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,13 +109,13 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -124,9 +124,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -134,22 +134,22 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -166,14 +166,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -183,14 +183,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -200,9 +200,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -210,29 +210,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -242,9 +242,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -252,9 +252,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -262,9 +262,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -272,27 +272,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -302,9 +302,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "dev": true, "license": "MIT", "engines": { @@ -312,33 +312,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -346,14 +346,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -524,6 +524,29 @@ "node": ">=20.19.0" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -2648,17 +2671,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.4.tgz", - "integrity": "sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", + "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.4", - "@typescript-eslint/type-utils": "8.59.4", - "@typescript-eslint/utils": "8.59.4", - "@typescript-eslint/visitor-keys": "8.59.4", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/type-utils": "8.60.0", + "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2671,7 +2694,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.4", + "@typescript-eslint/parser": "^8.60.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -2687,17 +2710,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.4.tgz", - "integrity": "sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", + "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.59.4", - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/typescript-estree": "8.59.4", - "@typescript-eslint/visitor-keys": "8.59.4", + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3" }, "engines": { @@ -2713,14 +2736,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.4.tgz", - "integrity": "sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", + "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.4", - "@typescript-eslint/types": "^8.59.4", + "@typescript-eslint/tsconfig-utils": "^8.60.0", + "@typescript-eslint/types": "^8.60.0", "debug": "^4.4.3" }, "engines": { @@ -2735,14 +2758,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.4.tgz", - "integrity": "sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", + "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/visitor-keys": "8.59.4" + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2753,9 +2776,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.4.tgz", - "integrity": "sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", + "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", "dev": true, "license": "MIT", "engines": { @@ -2770,15 +2793,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.4.tgz", - "integrity": "sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", + "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/typescript-estree": "8.59.4", - "@typescript-eslint/utils": "8.59.4", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2795,9 +2818,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.4.tgz", - "integrity": "sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", + "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", "dev": true, "license": "MIT", "engines": { @@ -2809,16 +2832,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.4.tgz", - "integrity": "sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", + "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.4", - "@typescript-eslint/tsconfig-utils": "8.59.4", - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/visitor-keys": "8.59.4", + "@typescript-eslint/project-service": "8.60.0", + "@typescript-eslint/tsconfig-utils": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2889,16 +2912,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.4.tgz", - "integrity": "sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", + "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.4", - "@typescript-eslint/types": "8.59.4", - "@typescript-eslint/typescript-estree": "8.59.4" + "@typescript-eslint/scope-manager": "8.60.0", + "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2913,13 +2936,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.4.tgz", - "integrity": "sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", + "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.4", + "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3754,9 +3777,9 @@ "license": "MIT" }, "node_modules/ast-v8-to-istanbul": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", - "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.2.tgz", + "integrity": "sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3885,9 +3908,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", "dev": true, "license": "MIT", "dependencies": { @@ -4406,9 +4429,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.361", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.361.tgz", - "integrity": "sha512-Q6Hts7N9FnJc5LeGRINFvLhCI9xZmNtTDe5ZbcVezQz7cU4a8Aua3GH1b8J2XY8Al9PF+OCwYqhgsOOheMdvkA==", + "version": "1.5.362", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.362.tgz", + "integrity": "sha512-PUY2DrLvkjkUuWqq+KPL2iWshrJsZOcIojzRQ7eXFacc9dWga7MGMJAa15VbiejSZB1PAXaRLAiKgruHP8LB1w==", "dev": true, "license": "ISC" }, @@ -4915,7 +4938,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -9173,22 +9195,22 @@ } }, "node_modules/tldts": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.1.2.tgz", - "integrity": "sha512-RUX5sQm8bl03ycwQ6nSgJiKw9FZWhA8GBzxX6X1lsG3xKzFIW+lYLLpM30FQbDD6XjTWa/VkF15VxjnAMWYJAQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.0.tgz", + "integrity": "sha512-yHBe+zVfzNZ3QfTPW/Z6KK1G2t340gFjMHqI/4KKSt/abzYydzuCnpqdaF5gCCABby+9Yfbj59oR5F2Fd5CBzg==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.1.2" + "tldts-core": "^7.4.0" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.1.2.tgz", - "integrity": "sha512-26EbERPLf5wkNFKW+7egxArSPN1Nqipe/PIe1nvhpNu8HqQeY2pYdOeQk4sAiADv/e03VzHkLY0PPohKv1JMAQ==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.0.tgz", + "integrity": "sha512-/mb9kRld+x1sIMXxWNOAp5m6C+D4GrAORWlJkOJ5dElvxdN1eutz/o7qHLp9gFvDF4Y3/L2xeScoxz6AbEo8rQ==", "dev": true, "license": "MIT" }, @@ -9427,16 +9449,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.59.4", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.4.tgz", - "integrity": "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ==", + "version": "8.60.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", + "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.4", - "@typescript-eslint/parser": "8.59.4", - "@typescript-eslint/typescript-estree": "8.59.4", - "@typescript-eslint/utils": "8.59.4" + "@typescript-eslint/eslint-plugin": "8.60.0", + "@typescript-eslint/parser": "8.60.0", + "@typescript-eslint/typescript-estree": "8.60.0", + "@typescript-eslint/utils": "8.60.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9470,9 +9492,9 @@ } }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.26.0.tgz", + "integrity": "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==", "dev": true, "license": "MIT", "engines": { @@ -9571,7 +9593,6 @@ "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -9893,14 +9914,14 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", + "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", + "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", From 7c1d5a97d901d0abfb63063349a3d70995bdcf6f Mon Sep 17 00:00:00 2001 From: shreya Date: Wed, 27 May 2026 20:24:01 +0530 Subject: [PATCH 04/63] feat: add toast feedback for copy actions --- app/layout.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/app/layout.tsx b/app/layout.tsx index 72a681ec..162769af 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -72,7 +72,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
{children}
- {children} From 60ff1bd661a3672e4bb67f99f045c21c822dc24a Mon Sep 17 00:00:00 2001 From: shreya Date: Wed, 27 May 2026 20:47:44 +0530 Subject: [PATCH 05/63] feat: add toast feedback for copy actions --- package-lock.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package-lock.json b/package-lock.json index 7a897de5..d9c042e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -528,6 +528,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "devOptional": true, "license": "MIT", "optional": true, "peer": true, @@ -540,6 +541,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "devOptional": true, "license": "MIT", "optional": true, "peer": true, From 1a5ff06915995063f8e19ef6a663fdb8551f870d Mon Sep 17 00:00:00 2001 From: sandesh-861 Date: Wed, 27 May 2026 18:27:17 +0530 Subject: [PATCH 06/63] test(api): verify no-cache header for ?theme=random (#489) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description Fixes #397 ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview N/A - Test only, no visual changes ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [x] I have tested these changes locally. - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors. - [x] My commits follow the Conventional Commits format. - [ ] I have updated `README.md` if I added a new theme or URL parameter. - [x] I have starred the repo. - [x] I have made sure that i have only one commit to merge in this PR. - [ ] The SVG output matches the CommitPulse "premium quality" aesthetic standard. - [ ] (Recommended) I joined the CommitPulse Discord server. --- app/api/streak/route.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/api/streak/route.test.ts b/app/api/streak/route.test.ts index cd30832a..68677068 100644 --- a/app/api/streak/route.test.ts +++ b/app/api/streak/route.test.ts @@ -432,4 +432,12 @@ describe('GET /api/streak', () => { expect(body).toContain('CURRENT_STREAK'); }); }); + + describe('theme=random cache header', () => { + it('returns no-cache header when ?theme=random is given', async () => { + const response = await GET(makeRequest({ user: 'octocat', theme: 'random' })); + + expect(response.headers.get('Cache-Control')).toBe('no-cache, no-store, must-revalidate'); + }); + }); }); From 6df6b32293ba4d68e77ba1572a1acf1093ec3f2a Mon Sep 17 00:00:00 2001 From: mahek Date: Wed, 27 May 2026 18:33:13 +0530 Subject: [PATCH 07/63] Refactor/add portuguese locale (#570) * refactor(i18n): add Portuguese locale to badge labels * style(readme): format Portuguese locale docs --- README.md | 2 +- lib/i18n/badgeLabels.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 35402995..0522079e 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ URL Parameter > Theme Default > System Fallback | `hide_background` | `boolean` | No | `false` | Remove the background rect, letting the monolith float on the page | | `hide_stats` | `boolean` | No | `false` | Hides the bottom row displaying Current Streak, Annual Sync Total, and Peak Streak stats when set to `true` or `1`. | | `tz` | `string` | No | Omitted = UTC | IANA timezone (e.g. `Asia/Kolkata`, `America/New_York`) — aligns "today" with the user local midnight. Note: `?tz=UTC` is valid but cached separately from omitting `tz`. | -| `lang` | `string` | No | `en` | Language code for labels (`en`, `es`, `hi`, `fr`) | +| `lang` | `string` | No | `en` | Language code for labels (`en`, `es`, `hi`, `fr`, `pt`) | | `view` | `string` | No | `default` | Rendering mode: `default` (3D Monolith) or `monthly` (Compact monthly stats) | | `delta_format` | `string` | No | `percent` | Format for month-over-month delta in monthly view: `percent` (e.g. +12%), `absolute` (e.g. +15 commits), or `both` | | `width` | `number` | No | `300` | Custom width for the SVG canvas (currently only applies to `view=monthly`) | diff --git a/lib/i18n/badgeLabels.ts b/lib/i18n/badgeLabels.ts index 242a5809..35038ebc 100644 --- a/lib/i18n/badgeLabels.ts +++ b/lib/i18n/badgeLabels.ts @@ -28,6 +28,13 @@ export const labels: Record = { COMMITS_THIS_MONTH: 'इस महीने के कमिट्स', VS_LAST_MONTH: 'पिछले महीने की तुलना में', }, + pt: { + CURRENT_STREAK: 'SÉRIE_ATUAL', + ANNUAL_SYNC_TOTAL: 'TOTAL_ANUAL', + PEAK_STREAK: 'SÉRIE_MÁXIMA', + COMMITS_THIS_MONTH: 'COMMITS ESTE MÊS', + VS_LAST_MONTH: 'vs mês passado', + }, fr: { CURRENT_STREAK: 'SÉRIE_ACTUELLE', ANNUAL_SYNC_TOTAL: 'TOTAL_ANNUEL', From 4c4e68c98db8608cfc877bc0b522146c92a14132 Mon Sep 17 00:00:00 2001 From: mahek888 Date: Wed, 27 May 2026 12:00:08 +0530 Subject: [PATCH 08/63] refactor(github): extract language color map --- lib/github.ts | 32 ++------------------------------ lib/svg/languageColors.ts | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 30 deletions(-) create mode 100644 lib/svg/languageColors.ts diff --git a/lib/github.ts b/lib/github.ts index 43624272..c8f18257 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -3,6 +3,7 @@ import type { ContributionCalendar } from '../types'; import { calculateStreak } from './calculate'; import { TTLCache } from './cache'; +import { LANGUAGE_COLORS } from './svg/languageColors'; interface GitHubRepo { stargazers_count: number; @@ -459,41 +460,12 @@ export async function getFullDashboardData(username: string, options: FetchOptio } }); - // Fixed color mapping for common languages to avoid random colors - const languageColors: Record = { - TypeScript: '#3178c6', - JavaScript: '#f1e05a', - Python: '#3572A5', - Java: '#b07219', - 'C++': '#f34b7d', - HTML: '#e34c26', - CSS: '#563d7c', - Go: '#00ADD8', - Rust: '#dea584', - - C: '#555555', - 'C#': '#178600', - PHP: '#4F5D95', - Ruby: '#701516', - Swift: '#F05138', - Kotlin: '#A97BFF', - Dart: '#00B4AB', - Lua: '#000080', - R: '#198CE7', - Scala: '#c22d40', - Perl: '#0298c3', - Haskell: '#5e5086', - Elixir: '#6e4a7e', - Vue: '#41b883', - Svelte: '#ff3e00', - }; - const totalLangs = Object.values(langCounts).reduce((a, b) => a + b, 0); const languages = Object.entries(langCounts) .map(([name, count]) => ({ name, percentage: Math.round((count / totalLangs) * 100), - color: languageColors[name] || '#a855f7', // fallback purple + color: LANGUAGE_COLORS[name] || '#a855f7', // fallback purple })) .sort((a, b) => b.percentage - a.percentage) .slice(0, 5); // top 5 diff --git a/lib/svg/languageColors.ts b/lib/svg/languageColors.ts new file mode 100644 index 00000000..6214c99f --- /dev/null +++ b/lib/svg/languageColors.ts @@ -0,0 +1,26 @@ +export const LANGUAGE_COLORS: Record = { + TypeScript: '#3178c6', + JavaScript: '#f1e05a', + Python: '#3572A5', + Java: '#b07219', + 'C++': '#f34b7d', + HTML: '#e34c26', + CSS: '#563d7c', + Go: '#00ADD8', + Rust: '#dea584', + C: '#555555', + 'C#': '#178600', + PHP: '#4F5D95', + Ruby: '#701516', + Swift: '#F05138', + Kotlin: '#A97BFF', + Dart: '#00B4AB', + Lua: '#000080', + R: '#198CE7', + Scala: '#c22d40', + Perl: '#0298c3', + Haskell: '#5e5086', + Elixir: '#6e4a7e', + Vue: '#41b883', + Svelte: '#ff3e00', +}; From e8780b071f2ce50eaad92ae524b371f1ff94b375 Mon Sep 17 00:00:00 2001 From: SANCHI GOYAL Date: Wed, 27 May 2026 09:56:03 +0000 Subject: [PATCH 09/63] test(dashboard): add LanguageChart gradient tests --- components/dashboard/LanguageChart.test.tsx | 49 +++++++++++++++++++++ components/dashboard/LanguageChart.tsx | 28 +++++++----- 2 files changed, 67 insertions(+), 10 deletions(-) diff --git a/components/dashboard/LanguageChart.test.tsx b/components/dashboard/LanguageChart.test.tsx index 9c37a6f6..5091c5d0 100644 --- a/components/dashboard/LanguageChart.test.tsx +++ b/components/dashboard/LanguageChart.test.tsx @@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import LanguageChart from './LanguageChart'; +import { buildGradientStops } from './LanguageChart'; vi.mock('framer-motion', () => ({ motion: { @@ -44,3 +45,51 @@ describe('LanguageChart', () => { expect(screen.getByText('JavaScript')).toBeDefined(); }); }); + +describe('buildGradientStops', () => { + it('builds gradient for one language', () => { + const result = buildGradientStops([ + { + name: 'TypeScript', + percentage: 100, + color: '#3178c6', + }, + ]); + + expect(result).toBe('#3178c6 0% 100%'); + }); + + it('builds gradient for two languages', () => { + const result = buildGradientStops([ + { + name: 'TypeScript', + percentage: 60, + color: '#3178c6', + }, + { + name: 'JavaScript', + percentage: 40, + color: '#f7df1e', + }, + ]); + + expect(result).toBe('#3178c6 0% 60%, #f7df1e 60% 100%'); + }); + + it('handles decimal percentages correctly', () => { + const result = buildGradientStops([ + { + name: 'TS', + percentage: 33.3, + color: '#3178c6', + }, + { + name: 'JS', + percentage: 66.7, + color: '#f7df1e', + }, + ]); + + expect(result).toBe('#3178c6 0% 33.3%, #f7df1e 33.3% 100%'); + }); +}); diff --git a/components/dashboard/LanguageChart.tsx b/components/dashboard/LanguageChart.tsx index 07bcde9c..c2229e2b 100644 --- a/components/dashboard/LanguageChart.tsx +++ b/components/dashboard/LanguageChart.tsx @@ -3,6 +3,23 @@ import { motion } from 'framer-motion'; import { LanguageData } from '@/types/dashboard'; +export function buildGradientStops(languages: LanguageData[]): string { + return languages + .reduce<{ stops: string[]; current: number }>( + (acc, lang) => { + const next = acc.current + lang.percentage; + + acc.stops.push(`${lang.color} ${acc.current}% ${next}%`); + + return { + stops: acc.stops, + current: next, + }; + }, + { stops: [], current: 0 } + ) + .stops.join(', '); +} export default function LanguageChart({ languages }: { languages: LanguageData[] }) { if (languages.length === 0) { return ( @@ -24,16 +41,7 @@ export default function LanguageChart({ languages }: { languages: LanguageData[] ); } - const gradientStops = languages - .reduce<{ stops: string[]; current: number }>( - (acc, lang) => { - const next = acc.current + lang.percentage; - acc.stops.push(`${lang.color} ${acc.current}% ${next}%`); - return { stops: acc.stops, current: next }; - }, - { stops: [], current: 0 } - ) - .stops.join(', '); + const gradientStops = buildGradientStops(languages); return ( Date: Wed, 27 May 2026 09:45:48 +0000 Subject: [PATCH 10/63] style(test): format sanitizeFont tests --- lib/svg/sanitizer.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lib/svg/sanitizer.test.ts b/lib/svg/sanitizer.test.ts index 87df0096..f031e5f9 100644 --- a/lib/svg/sanitizer.test.ts +++ b/lib/svg/sanitizer.test.ts @@ -97,6 +97,30 @@ describe('SVG Sanitizer Utilities', () => { it('returns null for completely invalid font', () => { expect(sanitizeFont('!!!')).toBe(null); }); + + it('returns null for null input', () => { + expect(sanitizeFont(null)).toBe(null); + }); + + it('returns null for whitespace-only input', () => { + expect(sanitizeFont(' ')).toBe(null); + }); + + it('preserves valid font names with spaces', () => { + expect(sanitizeFont('Fira Code')).toBe('Fira Code'); + }); + + it('allows numeric font names', () => { + expect(sanitizeFont('123')).toBe('123'); + }); + + it('sanitizes script injection attempts', () => { + expect(sanitizeFont('')).toBe('scriptalert1script'); + }); + + it('returns null when sanitization removes all characters', () => { + expect(sanitizeFont('@@@')).toBe(null); + }); }); describe('sanitizeGoogleFontUrl', () => { From 6ee8249c7fa0b76d194a62676bc8a5d8e6e649b9 Mon Sep 17 00:00:00 2001 From: SANCHI GOYAL Date: Wed, 27 May 2026 18:39:40 +0530 Subject: [PATCH 11/63] test(api): add integration tests for og route (#607) * test(api): add integration tests for og route * fix(test): resolve og route typecheck issues --- app/api/og/route.test.ts | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app/api/og/route.test.ts diff --git a/app/api/og/route.test.ts b/app/api/og/route.test.ts new file mode 100644 index 00000000..69a73030 --- /dev/null +++ b/app/api/og/route.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { GET } from './route'; +import { NextRequest } from 'next/server'; +vi.mock('../../../lib/github', () => ({ + fetchGitHubContributions: vi.fn(), +})); + +vi.mock('../../../lib/calculate', () => ({ + calculateStreak: vi.fn(), +})); + +import { fetchGitHubContributions } from '../../../lib/github'; +import { calculateStreak } from '../../../lib/calculate'; + +describe('OG Route', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns 200 successfully', async () => { + vi.mocked(fetchGitHubContributions).mockResolvedValue({} as never); + + vi.mocked(calculateStreak).mockReturnValue({ + totalContributions: 120, + longestStreak: 20, + currentStreak: 5, + todayDate: '2026-05-27', + }); + + const req = new NextRequest('http://localhost:3000/api/og?user=testuser'); + + const res = await GET(req); + + expect(res).toBeDefined(); + expect(res.status).toBe(200); + }); + + it('falls back to zeros when github fetch fails', async () => { + vi.mocked(fetchGitHubContributions).mockRejectedValue(new Error('GitHub API failed')); + + const req = new NextRequest('http://localhost:3000/api/og?user=testuser'); + + const res = await GET(req as never); + + expect(res.status).toBe(200); + }); + + it('handles missing user query param', async () => { + const req = new NextRequest('http://localhost:3000/api/og'); + + const res = await GET(req as never); + + expect(res.status).toBe(200); + }); +}); From 1080d16e7902d95a1521a13e1cf1a699f7f955d9 Mon Sep 17 00:00:00 2001 From: anishakujur367 -alt Date: Wed, 27 May 2026 15:20:26 +0530 Subject: [PATCH 12/63] docs(time): add JSDoc for UTC and timezone midnight functions --- utils/time.ts | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/utils/time.ts b/utils/time.ts index d28c19bc..81c573bb 100644 --- a/utils/time.ts +++ b/utils/time.ts @@ -1,5 +1,15 @@ // utils/time.ts +/** + * Calculates the number of seconds remaining until the next UTC midnight. + * + * @returns Number of seconds until the upcoming UTC midnight + * + * @example + * const seconds = getSecondsUntilUTCMidnight(); + * console.log(seconds); // e.g., 3600 + */ + export function getSecondsUntilUTCMidnight(): number { const now = new Date(); @@ -12,11 +22,21 @@ export function getSecondsUntilUTCMidnight(): number { return Math.floor((midnight.getTime() - now.getTime()) / 1000); } -// Returns seconds until midnight in the given IANA timezone (e.g. 'America/New_York'). -// Used to set CDN cache TTLs that reset at the user's local midnight rather than UTC midnight. -// Note: on DST transition days (spring-forward/fall-back) the day is 23 or 25 hours, -// so the returned TTL can be off by up to one hour on those two days per year — acceptable -// for a cache TTL. +/** + * Calculates the number of seconds remaining until midnight in a given timezone. + * + * @param tz - IANA timezone string (e.g., "America/New_York", "Asia/Kolkata") + * @returns Number of seconds until the next midnight in the specified timezone + * + * @remarks + * ⚠️ On Daylight Saving Time (DST) transition days (spring-forward/fall-back), + * the day length may be 23 or 25 hours. As a result, the returned value can be + * off by up to one hour. This is acceptable for use cases like cache TTL. + * + * @example + * const seconds = getSecondsUntilMidnightInTimezone("Asia/Kolkata"); + * console.log(seconds); + */ export function getSecondsUntilMidnightInTimezone(tz: string): number { const now = new Date(); From 3431942a81f2d55c853374ca0774e93f312ccb9c Mon Sep 17 00:00:00 2001 From: Shambavi Date: Wed, 27 May 2026 15:29:31 +0530 Subject: [PATCH 13/63] test(svg): add year parameter route test --- app/api/streak/route.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/api/streak/route.test.ts b/app/api/streak/route.test.ts index 68677068..05f72e1f 100644 --- a/app/api/streak/route.test.ts +++ b/app/api/streak/route.test.ts @@ -249,6 +249,16 @@ describe('GET /api/streak', () => { expect(response.status).toBe(200); }); + it('passes correct from/to range when ?year=2023 is provided', async () => { + await GET(makeRequest({ user: 'octocat', year: '2023' })); + + expect(fetchGitHubContributions).toHaveBeenCalledWith('octocat', { + bypassCache: false, + from: '2023-01-01T00:00:00Z', + to: '2023-12-31T23:59:59Z', + }); + }); + it('functions normally when the year parameter is missing', async () => { const response = await GET(makeRequest({ user: 'octocat' })); From eaedab77df45ed31c6ddf32a8217537aa19cf417 Mon Sep 17 00:00:00 2001 From: tamilr0727-ux Date: Wed, 27 May 2026 16:24:31 +0530 Subject: [PATCH 14/63] docs(cache): add JSDoc comments for TTLCache --- lib/cache.ts | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/lib/cache.ts b/lib/cache.ts index dc2c881b..b16fdd87 100644 --- a/lib/cache.ts +++ b/lib/cache.ts @@ -1,13 +1,30 @@ +/** + * Represents a cached item with its expiration timestamp. + */ type CacheItem = { value: T; expiresAt: number; }; +/** + * A Simple in-memory TTL(Time To Live) cache. + * + * Stores values in-process only and automatically removes expired entries. + * This cache is not shared accross multiple server instances or severless invocations. + * + * @typeParam T - Type of values stored in the cache. + */ export class TTLCache { private store = new Map>(); private cleanupInterval: ReturnType | null = null; private readonly maxSize?: number; + /** + * Creates a new TTL cache instance. + * + * @param maxSize - Maximum number of items allowed in the cache. + * @param cleanupIntervalMs - Interval in milliseconds for cleaning expired entries. + */ constructor(maxSize?: number, cleanupIntervalMs: number = 60000) { this.maxSize = maxSize === undefined ? undefined : Math.max(1, maxSize); const interval = Math.max(1000, cleanupIntervalMs); @@ -35,6 +52,17 @@ export class TTLCache { } } + /** + * Retrieves a value from the cache. + * + * Returns 'null' if the key does not exist or if the entry has expired. + * + * @param key - Cache key. + * @returns The cached value or 'null'. + * + * @example + * const user = cache.get("user:1"); + */ get(key: string): T | null { const hit = this.store.get(key); if (!hit) return null; @@ -47,6 +75,20 @@ export class TTLCache { return hit.value; } + /** + * Stores a value in the cache with a TTL. + * + * If the cache reaches its maximum capacity, the oldest item + * may be removed to make room for new entries. + * + * @param key - Cache key. + * @param value - Value to cache. + * @param ttlMs - Time to live in milliseconds. + * @returns void + * + * @example + * cache.set("user:1",userData,5000); + */ set(key: string, value: T, ttlMs: number): void { // Capacity eviction (FIFO / LRU-lite) const maxSize = this.maxSize; @@ -64,10 +106,23 @@ export class TTLCache { this.store.set(key, { value, expiresAt: Date.now() + ttlMs }); } + /** + * Removes all entries from the cache. + * + * @returns void + * + * @example + * cache.clear(); + */ clear(): void { this.store.clear(); } + /** + * Stops the cleanup interval and clears the cache. + * + * @returns void + */ destroy(): void { if (this.cleanupInterval) { clearInterval(this.cleanupInterval); From 844c15aafc1b9bdf6cbe38dfe3a5e2482a92c013 Mon Sep 17 00:00:00 2001 From: VirenSumbly Date: Wed, 27 May 2026 17:19:41 +0530 Subject: [PATCH 15/63] refactor(svg): reuse renderHeader in auto theme renderer --- lib/svg/generator.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/lib/svg/generator.ts b/lib/svg/generator.ts index c2194906..505ea3cf 100644 --- a/lib/svg/generator.ts +++ b/lib/svg/generator.ts @@ -294,13 +294,7 @@ function generateAutoThemeSVG( fill="none" role="img" > - CommitPulse Stats for ${safeUser} - - ${params.user || 'This user'} has ${stats.totalContributions} total contributions and a longest streak of ${stats.longestStreak} days. - - - - + ${renderHeader(safeUser, stats, sf)}