From f299da2964efc9f90d37555abee28a003259d9e2 Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Wed, 22 Jul 2026 02:08:48 +0530 Subject: [PATCH 01/13] fix(ci-analytics): parallelize API requests with Promise.all to prevent timeout on large repos --- services/github/ci-analytics.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/github/ci-analytics.ts b/services/github/ci-analytics.ts index 6c67ebe79..fdcdd5b8d 100644 --- a/services/github/ci-analytics.ts +++ b/services/github/ci-analytics.ts @@ -14,7 +14,7 @@ import type { const GITHUB_REST_URL = 'https://api.github.com'; const MAX_REPO_PAGES = 2; -const MAX_ACTION_PAGES = 2; +const MAX_ACTION_PAGES = 1; const MAX_FETCH_TARGETS = 5; const cache = new DistributedCache(500); From 5a9ecf1956530570d12fba189e8488eb52b06257 Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Wed, 22 Jul 2026 21:41:46 +0530 Subject: [PATCH 02/13] feat: Automatically sort SOCIALS list alphabetically --- app/generator/data/socials.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/generator/data/socials.ts b/app/generator/data/socials.ts index dfcf8d3cf..b8a77c3c6 100644 --- a/app/generator/data/socials.ts +++ b/app/generator/data/socials.ts @@ -592,7 +592,7 @@ export const SOCIALS: Social[] = [ baseUrl: 'https://huggingface.co/', placeholder: 'e.g. https://huggingface.co/yourname', }, -]; +].sort((a, b) => a.name.localeCompare(b.name)); export const SOCIAL_CATEGORIES: SocialCategory[] = [ 'Social Media', From 6978cd32aa9c191d1a3a39a3b3fd70e46c86b62c Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Wed, 22 Jul 2026 21:51:02 +0530 Subject: [PATCH 03/13] fix: resolve TS2322 type error in SOCIALS sort by casting array as Social[] --- app/generator/data/socials.ts | 1166 +++++++++++++++++---------------- 1 file changed, 584 insertions(+), 582 deletions(-) diff --git a/app/generator/data/socials.ts b/app/generator/data/socials.ts index b8a77c3c6..567b2accb 100644 --- a/app/generator/data/socials.ts +++ b/app/generator/data/socials.ts @@ -4,595 +4,597 @@ const SI = (slug: string) => `https://cdn.simpleicons.org/${slug}`; const DI = (name: string, variant = 'original') => `https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/${name}/${name}-${variant}.svg`; -export const SOCIALS: Social[] = [ - { - id: 'twitter', - name: 'X (Twitter)', - category: 'Social Media', - iconUrl: SI('x'), - type: 'simpleicon', - siSlug: 'x', - baseUrl: 'https://x.com/', - placeholder: 'e.g. https://x.com/yourhandle', - }, - { - id: 'linkedin', - name: 'LinkedIn', - category: 'Professional', - iconUrl: DI('linkedin'), - type: 'devicon', - siSlug: 'linkedin', - baseUrl: 'https://linkedin.com/in/', - placeholder: 'e.g. https://linkedin.com/in/yourname', - }, - { - id: 'instagram', - name: 'Instagram', - category: 'Social Media', - iconUrl: SI('instagram'), - type: 'simpleicon', - siSlug: 'instagram', - baseUrl: 'https://instagram.com/', - placeholder: 'e.g. https://instagram.com/yourhandle', - }, - { - id: 'facebook', - name: 'Facebook', - category: 'Social Media', - iconUrl: SI('facebook'), - type: 'simpleicon', - siSlug: 'facebook', - baseUrl: 'https://facebook.com/', - placeholder: 'e.g. https://facebook.com/yourprofile', - }, - { - id: 'youtube', - name: 'YouTube', - category: 'Social Media', - iconUrl: SI('youtube'), - type: 'simpleicon', - siSlug: 'youtube', - baseUrl: 'https://youtube.com/@', - placeholder: 'e.g. https://youtube.com/@yourchannel', - }, - { - id: 'tiktok', - name: 'TikTok', - category: 'Social Media', - iconUrl: SI('tiktok'), - type: 'simpleicon', - siSlug: 'tiktok', - baseUrl: 'https://tiktok.com/@', - placeholder: 'e.g. https://tiktok.com/@yourhandle', - }, - { - id: 'discord', - name: 'Discord', - category: 'Social Media', - iconUrl: SI('discord'), - type: 'simpleicon', - siSlug: 'discord', - baseUrl: 'https://discord.gg/', - placeholder: 'e.g. https://discord.gg/yourserver', - }, - { - id: 'reddit', - name: 'Reddit', - category: 'Social Media', - iconUrl: SI('reddit'), - type: 'simpleicon', - siSlug: 'reddit', - baseUrl: 'https://reddit.com/u/', - placeholder: 'e.g. https://reddit.com/u/yourname', - }, - { - id: 'mastodon', - name: 'Mastodon', - category: 'Social Media', - iconUrl: SI('mastodon'), - type: 'simpleicon', - siSlug: 'mastodon', - baseUrl: 'https://mastodon.social/@', - placeholder: 'e.g. https://mastodon.social/@yourhandle', - }, - { - id: 'bluesky', - name: 'Bluesky', - category: 'Social Media', - iconUrl: SI('bluesky'), - type: 'simpleicon', - siSlug: 'bluesky', - baseUrl: 'https://bsky.app/profile/', - placeholder: 'e.g. https://bsky.app/profile/yourhandle', - }, - { - id: 'threads', - name: 'Threads', - category: 'Social Media', - iconUrl: SI('threads'), - type: 'simpleicon', - siSlug: 'threads', - baseUrl: 'https://threads.net/@', - placeholder: 'e.g. https://threads.net/@yourhandle', - }, - { - id: 'snapchat', - name: 'Snapchat', - category: 'Social Media', - iconUrl: SI('snapchat'), - type: 'simpleicon', - siSlug: 'snapchat', - baseUrl: 'https://snapchat.com/add/', - placeholder: 'e.g. https://snapchat.com/add/yourusername', - }, - { - id: 'pinterest', - name: 'Pinterest', - category: 'Social Media', - iconUrl: SI('pinterest'), - type: 'simpleicon', - siSlug: 'pinterest', - baseUrl: 'https://pinterest.com/', - placeholder: 'e.g. https://pinterest.com/yourname', - }, - { - id: 'vimeo', - name: 'Vimeo', - category: 'Social Media', - iconUrl: SI('vimeo'), - type: 'simpleicon', - siSlug: 'vimeo', - baseUrl: 'https://vimeo.com/', - placeholder: 'e.g. https://vimeo.com/yourusername', - }, - { - id: 'xing', - name: 'Xing', - category: 'Professional', - iconUrl: SI('xing'), - type: 'simpleicon', - siSlug: 'xing', - baseUrl: 'https://www.xing.com/profile/', - placeholder: 'e.g. https://www.xing.com/profile/yourname', - }, +export const SOCIALS: Social[] = ( + [ + { + id: 'twitter', + name: 'X (Twitter)', + category: 'Social Media', + iconUrl: SI('x'), + type: 'simpleicon', + siSlug: 'x', + baseUrl: 'https://x.com/', + placeholder: 'e.g. https://x.com/yourhandle', + }, + { + id: 'linkedin', + name: 'LinkedIn', + category: 'Professional', + iconUrl: DI('linkedin'), + type: 'devicon', + siSlug: 'linkedin', + baseUrl: 'https://linkedin.com/in/', + placeholder: 'e.g. https://linkedin.com/in/yourname', + }, + { + id: 'instagram', + name: 'Instagram', + category: 'Social Media', + iconUrl: SI('instagram'), + type: 'simpleicon', + siSlug: 'instagram', + baseUrl: 'https://instagram.com/', + placeholder: 'e.g. https://instagram.com/yourhandle', + }, + { + id: 'facebook', + name: 'Facebook', + category: 'Social Media', + iconUrl: SI('facebook'), + type: 'simpleicon', + siSlug: 'facebook', + baseUrl: 'https://facebook.com/', + placeholder: 'e.g. https://facebook.com/yourprofile', + }, + { + id: 'youtube', + name: 'YouTube', + category: 'Social Media', + iconUrl: SI('youtube'), + type: 'simpleicon', + siSlug: 'youtube', + baseUrl: 'https://youtube.com/@', + placeholder: 'e.g. https://youtube.com/@yourchannel', + }, + { + id: 'tiktok', + name: 'TikTok', + category: 'Social Media', + iconUrl: SI('tiktok'), + type: 'simpleicon', + siSlug: 'tiktok', + baseUrl: 'https://tiktok.com/@', + placeholder: 'e.g. https://tiktok.com/@yourhandle', + }, + { + id: 'discord', + name: 'Discord', + category: 'Social Media', + iconUrl: SI('discord'), + type: 'simpleicon', + siSlug: 'discord', + baseUrl: 'https://discord.gg/', + placeholder: 'e.g. https://discord.gg/yourserver', + }, + { + id: 'reddit', + name: 'Reddit', + category: 'Social Media', + iconUrl: SI('reddit'), + type: 'simpleicon', + siSlug: 'reddit', + baseUrl: 'https://reddit.com/u/', + placeholder: 'e.g. https://reddit.com/u/yourname', + }, + { + id: 'mastodon', + name: 'Mastodon', + category: 'Social Media', + iconUrl: SI('mastodon'), + type: 'simpleicon', + siSlug: 'mastodon', + baseUrl: 'https://mastodon.social/@', + placeholder: 'e.g. https://mastodon.social/@yourhandle', + }, + { + id: 'bluesky', + name: 'Bluesky', + category: 'Social Media', + iconUrl: SI('bluesky'), + type: 'simpleicon', + siSlug: 'bluesky', + baseUrl: 'https://bsky.app/profile/', + placeholder: 'e.g. https://bsky.app/profile/yourhandle', + }, + { + id: 'threads', + name: 'Threads', + category: 'Social Media', + iconUrl: SI('threads'), + type: 'simpleicon', + siSlug: 'threads', + baseUrl: 'https://threads.net/@', + placeholder: 'e.g. https://threads.net/@yourhandle', + }, + { + id: 'snapchat', + name: 'Snapchat', + category: 'Social Media', + iconUrl: SI('snapchat'), + type: 'simpleicon', + siSlug: 'snapchat', + baseUrl: 'https://snapchat.com/add/', + placeholder: 'e.g. https://snapchat.com/add/yourusername', + }, + { + id: 'pinterest', + name: 'Pinterest', + category: 'Social Media', + iconUrl: SI('pinterest'), + type: 'simpleicon', + siSlug: 'pinterest', + baseUrl: 'https://pinterest.com/', + placeholder: 'e.g. https://pinterest.com/yourname', + }, + { + id: 'vimeo', + name: 'Vimeo', + category: 'Social Media', + iconUrl: SI('vimeo'), + type: 'simpleicon', + siSlug: 'vimeo', + baseUrl: 'https://vimeo.com/', + placeholder: 'e.g. https://vimeo.com/yourusername', + }, + { + id: 'xing', + name: 'Xing', + category: 'Professional', + iconUrl: SI('xing'), + type: 'simpleicon', + siSlug: 'xing', + baseUrl: 'https://www.xing.com/profile/', + placeholder: 'e.g. https://www.xing.com/profile/yourname', + }, - { - id: 'github', - name: 'GitHub', - category: 'Developer', - iconUrl: DI('github'), - type: 'devicon', - siSlug: 'github', - baseUrl: 'https://github.com/', - placeholder: 'e.g. https://github.com/yourusername', - }, - { - id: 'gitlabprofile', - name: 'GitLab', - category: 'Developer', - iconUrl: DI('gitlab'), - type: 'devicon', - siSlug: 'gitlab', - baseUrl: 'https://gitlab.com/', - placeholder: 'e.g. https://gitlab.com/yourusername', - }, - { - id: 'bitbucket', - name: 'Bitbucket', - category: 'Developer', - iconUrl: DI('bitbucket'), - type: 'devicon', - siSlug: 'bitbucket', - baseUrl: 'https://bitbucket.org/', - placeholder: 'e.g. https://bitbucket.org/yourusername', - }, - { - id: 'stackoverflow', - name: 'Stack Overflow', - category: 'Developer', - iconUrl: DI('stackoverflow'), - type: 'devicon', - siSlug: 'stackoverflow', - baseUrl: 'https://stackoverflow.com/users/', - placeholder: 'e.g. https://stackoverflow.com/users/123456', - }, - { - id: 'devto', - name: 'Dev.to', - category: 'Developer', - iconUrl: SI('devdotto'), - type: 'simpleicon', - siSlug: 'devdotto', - baseUrl: 'https://dev.to/', - placeholder: 'e.g. https://dev.to/yourusername', - }, - { - id: 'hashnode', - name: 'Hashnode', - category: 'Developer', - iconUrl: SI('hashnode'), - type: 'simpleicon', - siSlug: 'hashnode', - baseUrl: 'https://hashnode.com/@', - placeholder: 'e.g. https://hashnode.com/@yourusername', - }, - { - id: 'medium', - name: 'Medium', - category: 'Developer', - iconUrl: SI('medium'), - type: 'simpleicon', - siSlug: 'medium', - baseUrl: 'https://medium.com/@', - placeholder: 'e.g. https://medium.com/@yourusername', - }, - { - id: 'npm', - name: 'npm', - category: 'Developer', - iconUrl: SI('npm'), - type: 'simpleicon', - siSlug: 'npm', - baseUrl: 'https://npmjs.com/~', - placeholder: 'e.g. https://npmjs.com/~yourusername', - }, - { - id: 'pypi', - name: 'PyPI', - category: 'Developer', - iconUrl: SI('pypi'), - type: 'simpleicon', - siSlug: 'pypi', - baseUrl: 'https://pypi.org/user/', - placeholder: 'e.g. https://pypi.org/user/yourusername', - }, - { - id: 'dockerhub', - name: 'Docker Hub', - category: 'Developer', - iconUrl: DI('docker'), - type: 'devicon', - siSlug: 'docker', - baseUrl: 'https://hub.docker.com/u/', - placeholder: 'e.g. https://hub.docker.com/u/yourusername', - }, - { - id: 'replit', - name: 'Replit', - category: 'Developer', - iconUrl: SI('replit'), - type: 'simpleicon', - siSlug: 'replit', - baseUrl: 'https://replit.com/@', - placeholder: 'e.g. https://replit.com/@yourusername', - }, - { - id: 'codepen', - name: 'CodePen', - category: 'Developer', - iconUrl: DI('codepen'), - type: 'devicon', - siSlug: 'codepen', - baseUrl: 'https://codepen.io/', - placeholder: 'e.g. https://codepen.io/yourusername', - }, - { - id: 'codesandbox', - name: 'CodeSandbox', - category: 'Developer', - iconUrl: SI('codesandbox'), - type: 'simpleicon', - siSlug: 'codesandbox', - baseUrl: 'https://codesandbox.io/u/', - placeholder: 'e.g. https://codesandbox.io/u/yourusername', - }, - { - id: 'wakatime', - name: 'WakaTime', - category: 'Developer', - iconUrl: SI('wakatime'), - type: 'simpleicon', - siSlug: 'wakatime', - baseUrl: 'https://wakatime.com/@', - placeholder: 'e.g. https://wakatime.com/@yourusername', - }, + { + id: 'github', + name: 'GitHub', + category: 'Developer', + iconUrl: DI('github'), + type: 'devicon', + siSlug: 'github', + baseUrl: 'https://github.com/', + placeholder: 'e.g. https://github.com/yourusername', + }, + { + id: 'gitlabprofile', + name: 'GitLab', + category: 'Developer', + iconUrl: DI('gitlab'), + type: 'devicon', + siSlug: 'gitlab', + baseUrl: 'https://gitlab.com/', + placeholder: 'e.g. https://gitlab.com/yourusername', + }, + { + id: 'bitbucket', + name: 'Bitbucket', + category: 'Developer', + iconUrl: DI('bitbucket'), + type: 'devicon', + siSlug: 'bitbucket', + baseUrl: 'https://bitbucket.org/', + placeholder: 'e.g. https://bitbucket.org/yourusername', + }, + { + id: 'stackoverflow', + name: 'Stack Overflow', + category: 'Developer', + iconUrl: DI('stackoverflow'), + type: 'devicon', + siSlug: 'stackoverflow', + baseUrl: 'https://stackoverflow.com/users/', + placeholder: 'e.g. https://stackoverflow.com/users/123456', + }, + { + id: 'devto', + name: 'Dev.to', + category: 'Developer', + iconUrl: SI('devdotto'), + type: 'simpleicon', + siSlug: 'devdotto', + baseUrl: 'https://dev.to/', + placeholder: 'e.g. https://dev.to/yourusername', + }, + { + id: 'hashnode', + name: 'Hashnode', + category: 'Developer', + iconUrl: SI('hashnode'), + type: 'simpleicon', + siSlug: 'hashnode', + baseUrl: 'https://hashnode.com/@', + placeholder: 'e.g. https://hashnode.com/@yourusername', + }, + { + id: 'medium', + name: 'Medium', + category: 'Developer', + iconUrl: SI('medium'), + type: 'simpleicon', + siSlug: 'medium', + baseUrl: 'https://medium.com/@', + placeholder: 'e.g. https://medium.com/@yourusername', + }, + { + id: 'npm', + name: 'npm', + category: 'Developer', + iconUrl: SI('npm'), + type: 'simpleicon', + siSlug: 'npm', + baseUrl: 'https://npmjs.com/~', + placeholder: 'e.g. https://npmjs.com/~yourusername', + }, + { + id: 'pypi', + name: 'PyPI', + category: 'Developer', + iconUrl: SI('pypi'), + type: 'simpleicon', + siSlug: 'pypi', + baseUrl: 'https://pypi.org/user/', + placeholder: 'e.g. https://pypi.org/user/yourusername', + }, + { + id: 'dockerhub', + name: 'Docker Hub', + category: 'Developer', + iconUrl: DI('docker'), + type: 'devicon', + siSlug: 'docker', + baseUrl: 'https://hub.docker.com/u/', + placeholder: 'e.g. https://hub.docker.com/u/yourusername', + }, + { + id: 'replit', + name: 'Replit', + category: 'Developer', + iconUrl: SI('replit'), + type: 'simpleicon', + siSlug: 'replit', + baseUrl: 'https://replit.com/@', + placeholder: 'e.g. https://replit.com/@yourusername', + }, + { + id: 'codepen', + name: 'CodePen', + category: 'Developer', + iconUrl: DI('codepen'), + type: 'devicon', + siSlug: 'codepen', + baseUrl: 'https://codepen.io/', + placeholder: 'e.g. https://codepen.io/yourusername', + }, + { + id: 'codesandbox', + name: 'CodeSandbox', + category: 'Developer', + iconUrl: SI('codesandbox'), + type: 'simpleicon', + siSlug: 'codesandbox', + baseUrl: 'https://codesandbox.io/u/', + placeholder: 'e.g. https://codesandbox.io/u/yourusername', + }, + { + id: 'wakatime', + name: 'WakaTime', + category: 'Developer', + iconUrl: SI('wakatime'), + type: 'simpleicon', + siSlug: 'wakatime', + baseUrl: 'https://wakatime.com/@', + placeholder: 'e.g. https://wakatime.com/@yourusername', + }, - { - id: 'leetcode', - name: 'LeetCode', - category: 'Competitive Programming', - iconUrl: SI('leetcode'), - type: 'simpleicon', - siSlug: 'leetcode', - baseUrl: 'https://leetcode.com/', - placeholder: 'e.g. https://leetcode.com/yourusername', - }, - { - id: 'codeforces', - name: 'Codeforces', - category: 'Competitive Programming', - iconUrl: SI('codeforces'), - type: 'simpleicon', - siSlug: 'codeforces', - baseUrl: 'https://codeforces.com/profile/', - placeholder: 'e.g. https://codeforces.com/profile/yourusername', - }, - { - id: 'hackerrank', - name: 'HackerRank', - category: 'Competitive Programming', - iconUrl: SI('hackerrank'), - type: 'simpleicon', - siSlug: 'hackerrank', - baseUrl: 'https://hackerrank.com/', - placeholder: 'e.g. https://hackerrank.com/yourusername', - }, - { - id: 'hackerearth', - name: 'HackerEarth', - category: 'Competitive Programming', - iconUrl: SI('hackerearth'), - type: 'simpleicon', - siSlug: 'hackerearth', - baseUrl: 'https://hackerearth.com/@', - placeholder: 'e.g. https://hackerearth.com/@yourusername', - }, - { - id: 'atcoder', - name: 'AtCoder', - category: 'Competitive Programming', - iconUrl: SI('codementor'), - type: 'simpleicon', - siSlug: 'codementor', - baseUrl: 'https://atcoder.jp/users/', - placeholder: 'e.g. https://atcoder.jp/users/yourusername', - }, - { - id: 'codechef', - name: 'CodeChef', - category: 'Competitive Programming', - iconUrl: SI('codechef'), - type: 'simpleicon', - siSlug: 'codechef', - baseUrl: 'https://codechef.com/users/', - placeholder: 'e.g. https://codechef.com/users/yourusername', - }, - { - id: 'topcoder', - name: 'TopCoder', - category: 'Competitive Programming', - iconUrl: SI('topcoder'), - type: 'simpleicon', - siSlug: 'topcoder', - baseUrl: 'https://topcoder.com/members/', - placeholder: 'e.g. https://topcoder.com/members/yourusername', - }, - { - id: 'codewars', - name: 'Codewars', - category: 'Competitive Programming', - iconUrl: SI('codewars'), - type: 'simpleicon', - siSlug: 'codewars', - baseUrl: 'https://codewars.com/users/', - placeholder: 'e.g. https://codewars.com/users/yourusername', - }, - { - id: 'geeksforgeeks', - name: 'GeeksForGeeks', - category: 'Competitive Programming', - iconUrl: SI('geeksforgeeks'), - type: 'simpleicon', - siSlug: 'geeksforgeeks', - baseUrl: 'https://geeksforgeeks.org/user/', - placeholder: 'e.g. https://geeksforgeeks.org/user/yourusername', - }, - { - id: 'kaggle', - name: 'Kaggle', - category: 'Competitive Programming', - iconUrl: SI('kaggle'), - type: 'simpleicon', - siSlug: 'kaggle', - baseUrl: 'https://kaggle.com/', - placeholder: 'e.g. https://kaggle.com/yourusername', - }, + { + id: 'leetcode', + name: 'LeetCode', + category: 'Competitive Programming', + iconUrl: SI('leetcode'), + type: 'simpleicon', + siSlug: 'leetcode', + baseUrl: 'https://leetcode.com/', + placeholder: 'e.g. https://leetcode.com/yourusername', + }, + { + id: 'codeforces', + name: 'Codeforces', + category: 'Competitive Programming', + iconUrl: SI('codeforces'), + type: 'simpleicon', + siSlug: 'codeforces', + baseUrl: 'https://codeforces.com/profile/', + placeholder: 'e.g. https://codeforces.com/profile/yourusername', + }, + { + id: 'hackerrank', + name: 'HackerRank', + category: 'Competitive Programming', + iconUrl: SI('hackerrank'), + type: 'simpleicon', + siSlug: 'hackerrank', + baseUrl: 'https://hackerrank.com/', + placeholder: 'e.g. https://hackerrank.com/yourusername', + }, + { + id: 'hackerearth', + name: 'HackerEarth', + category: 'Competitive Programming', + iconUrl: SI('hackerearth'), + type: 'simpleicon', + siSlug: 'hackerearth', + baseUrl: 'https://hackerearth.com/@', + placeholder: 'e.g. https://hackerearth.com/@yourusername', + }, + { + id: 'atcoder', + name: 'AtCoder', + category: 'Competitive Programming', + iconUrl: SI('codementor'), + type: 'simpleicon', + siSlug: 'codementor', + baseUrl: 'https://atcoder.jp/users/', + placeholder: 'e.g. https://atcoder.jp/users/yourusername', + }, + { + id: 'codechef', + name: 'CodeChef', + category: 'Competitive Programming', + iconUrl: SI('codechef'), + type: 'simpleicon', + siSlug: 'codechef', + baseUrl: 'https://codechef.com/users/', + placeholder: 'e.g. https://codechef.com/users/yourusername', + }, + { + id: 'topcoder', + name: 'TopCoder', + category: 'Competitive Programming', + iconUrl: SI('topcoder'), + type: 'simpleicon', + siSlug: 'topcoder', + baseUrl: 'https://topcoder.com/members/', + placeholder: 'e.g. https://topcoder.com/members/yourusername', + }, + { + id: 'codewars', + name: 'Codewars', + category: 'Competitive Programming', + iconUrl: SI('codewars'), + type: 'simpleicon', + siSlug: 'codewars', + baseUrl: 'https://codewars.com/users/', + placeholder: 'e.g. https://codewars.com/users/yourusername', + }, + { + id: 'geeksforgeeks', + name: 'GeeksForGeeks', + category: 'Competitive Programming', + iconUrl: SI('geeksforgeeks'), + type: 'simpleicon', + siSlug: 'geeksforgeeks', + baseUrl: 'https://geeksforgeeks.org/user/', + placeholder: 'e.g. https://geeksforgeeks.org/user/yourusername', + }, + { + id: 'kaggle', + name: 'Kaggle', + category: 'Competitive Programming', + iconUrl: SI('kaggle'), + type: 'simpleicon', + siSlug: 'kaggle', + baseUrl: 'https://kaggle.com/', + placeholder: 'e.g. https://kaggle.com/yourusername', + }, - { - id: 'producthunt', - name: 'Product Hunt', - category: 'Professional', - iconUrl: SI('producthunt'), - type: 'simpleicon', - siSlug: 'producthunt', - baseUrl: 'https://producthunt.com/@', - placeholder: 'e.g. https://producthunt.com/@yourusername', - }, - { - id: 'behance', - name: 'Behance', - category: 'Professional', - iconUrl: SI('behance'), - type: 'simpleicon', - siSlug: 'behance', - baseUrl: 'https://behance.net/', - placeholder: 'e.g. https://behance.net/yourusername', - }, - { - id: 'dribbble', - name: 'Dribbble', - category: 'Professional', - iconUrl: SI('dribbble'), - type: 'simpleicon', - siSlug: 'dribbble', - baseUrl: 'https://dribbble.com/', - placeholder: 'e.g. https://dribbble.com/yourusername', - }, + { + id: 'producthunt', + name: 'Product Hunt', + category: 'Professional', + iconUrl: SI('producthunt'), + type: 'simpleicon', + siSlug: 'producthunt', + baseUrl: 'https://producthunt.com/@', + placeholder: 'e.g. https://producthunt.com/@yourusername', + }, + { + id: 'behance', + name: 'Behance', + category: 'Professional', + iconUrl: SI('behance'), + type: 'simpleicon', + siSlug: 'behance', + baseUrl: 'https://behance.net/', + placeholder: 'e.g. https://behance.net/yourusername', + }, + { + id: 'dribbble', + name: 'Dribbble', + category: 'Professional', + iconUrl: SI('dribbble'), + type: 'simpleicon', + siSlug: 'dribbble', + baseUrl: 'https://dribbble.com/', + placeholder: 'e.g. https://dribbble.com/yourusername', + }, - { - id: 'twitch', - name: 'Twitch', - category: 'Streaming', - iconUrl: SI('twitch'), - type: 'simpleicon', - siSlug: 'twitch', - baseUrl: 'https://twitch.tv/', - placeholder: 'e.g. https://twitch.tv/yourchannel', - }, - { - id: 'spotify', - name: 'Spotify', - category: 'Streaming', - iconUrl: SI('spotify'), - type: 'simpleicon', - siSlug: 'spotify', - baseUrl: 'https://open.spotify.com/user/', - placeholder: 'e.g. https://open.spotify.com/user/yourusername', - }, + { + id: 'twitch', + name: 'Twitch', + category: 'Streaming', + iconUrl: SI('twitch'), + type: 'simpleicon', + siSlug: 'twitch', + baseUrl: 'https://twitch.tv/', + placeholder: 'e.g. https://twitch.tv/yourchannel', + }, + { + id: 'spotify', + name: 'Spotify', + category: 'Streaming', + iconUrl: SI('spotify'), + type: 'simpleicon', + siSlug: 'spotify', + baseUrl: 'https://open.spotify.com/user/', + placeholder: 'e.g. https://open.spotify.com/user/yourusername', + }, - { - id: 'email', - name: 'Email', - category: 'Contact', - iconUrl: SI('gmail'), - type: 'simpleicon', - siSlug: 'gmail', - baseUrl: 'mailto:', - placeholder: 'e.g. youremail@example.com', - }, - { - id: 'telegram', - name: 'Telegram', - category: 'Contact', - iconUrl: SI('telegram'), - type: 'simpleicon', - siSlug: 'telegram', - baseUrl: 'https://t.me/', - placeholder: 'e.g. https://t.me/yourusername', - }, - { - id: 'whatsapp', - name: 'WhatsApp', - category: 'Contact', - iconUrl: SI('whatsapp'), - type: 'simpleicon', - siSlug: 'whatsapp', - baseUrl: 'https://wa.me/', - placeholder: 'e.g. https://wa.me/1234567890', - }, - { - id: 'signal', - name: 'Signal', - category: 'Contact', - iconUrl: SI('signal'), - type: 'simpleicon', - siSlug: 'signal', - baseUrl: 'https://signal.me/#p/', - placeholder: 'e.g. https://signal.me/#p/yourhandle', - }, + { + id: 'email', + name: 'Email', + category: 'Contact', + iconUrl: SI('gmail'), + type: 'simpleicon', + siSlug: 'gmail', + baseUrl: 'mailto:', + placeholder: 'e.g. youremail@example.com', + }, + { + id: 'telegram', + name: 'Telegram', + category: 'Contact', + iconUrl: SI('telegram'), + type: 'simpleicon', + siSlug: 'telegram', + baseUrl: 'https://t.me/', + placeholder: 'e.g. https://t.me/yourusername', + }, + { + id: 'whatsapp', + name: 'WhatsApp', + category: 'Contact', + iconUrl: SI('whatsapp'), + type: 'simpleicon', + siSlug: 'whatsapp', + baseUrl: 'https://wa.me/', + placeholder: 'e.g. https://wa.me/1234567890', + }, + { + id: 'signal', + name: 'Signal', + category: 'Contact', + iconUrl: SI('signal'), + type: 'simpleicon', + siSlug: 'signal', + baseUrl: 'https://signal.me/#p/', + placeholder: 'e.g. https://signal.me/#p/yourhandle', + }, - { - id: 'website', - name: 'Personal Website', - category: 'Portfolio', - iconUrl: SI('googlechrome'), - type: 'simpleicon', - siSlug: 'googlechrome', - baseUrl: 'https://', - placeholder: 'e.g. https://yourwebsite.com', - }, - { - id: 'substack', - name: 'Substack', - category: 'Portfolio', - iconUrl: SI('substack'), - type: 'simpleicon', - siSlug: 'substack', - baseUrl: 'https://substack.com/@', - placeholder: 'e.g. https://substack.com/@yourusername', - }, + { + id: 'website', + name: 'Personal Website', + category: 'Portfolio', + iconUrl: SI('googlechrome'), + type: 'simpleicon', + siSlug: 'googlechrome', + baseUrl: 'https://', + placeholder: 'e.g. https://yourwebsite.com', + }, + { + id: 'substack', + name: 'Substack', + category: 'Portfolio', + iconUrl: SI('substack'), + type: 'simpleicon', + siSlug: 'substack', + baseUrl: 'https://substack.com/@', + placeholder: 'e.g. https://substack.com/@yourusername', + }, - { - id: 'buymeacoffee', - name: 'Buy Me a Coffee', - category: 'Support', - iconUrl: SI('buymeacoffee'), - type: 'simpleicon', - siSlug: 'buymeacoffee', - baseUrl: 'https://buymeacoffee.com/', - placeholder: 'e.g. https://buymeacoffee.com/yourusername', - }, - { - id: 'patreon', - name: 'Patreon', - category: 'Support', - iconUrl: SI('patreon'), - type: 'simpleicon', - siSlug: 'patreon', - baseUrl: 'https://patreon.com/', - placeholder: 'e.g. https://patreon.com/yourusername', - }, - { - id: 'kofi', - name: 'Ko-fi', - category: 'Support', - iconUrl: SI('kofi'), - type: 'simpleicon', - siSlug: 'kofi', - baseUrl: 'https://ko-fi.com/', - placeholder: 'e.g. https://ko-fi.com/yourusername', - }, - { - id: 'githubsponsors', - name: 'GitHub Sponsors', - category: 'Support', - iconUrl: SI('githubsponsors'), - type: 'simpleicon', - siSlug: 'githubsponsors', - baseUrl: 'https://github.com/sponsors/', - placeholder: 'e.g. https://github.com/sponsors/yourusername', - }, - { - id: 'opencollective', - name: 'Open Collective', - category: 'Support', - iconUrl: SI('opencollective'), - type: 'simpleicon', - siSlug: 'opencollective', - baseUrl: 'https://opencollective.com/', - placeholder: 'e.g. https://opencollective.com/yourname', - }, - { - id: 'kick', - name: 'Kick', - category: 'Streaming', - iconUrl: SI('kick'), - type: 'simpleicon', - siSlug: 'kick', - baseUrl: 'https://kick.com/', - placeholder: 'e.g. https://kick.com/yourchannel', - }, - { - id: 'odysee', - name: 'Odysee', - category: 'Streaming', - iconUrl: SI('odysee'), - type: 'simpleicon', - siSlug: 'odysee', - baseUrl: 'https://odysee.com/@', - placeholder: 'e.g. https://odysee.com/@yourchannel', - }, - { - id: 'huggingface', - name: 'Hugging Face', - category: 'Developer', - iconUrl: SI('huggingface'), - type: 'simpleicon', - siSlug: 'huggingface', - baseUrl: 'https://huggingface.co/', - placeholder: 'e.g. https://huggingface.co/yourname', - }, -].sort((a, b) => a.name.localeCompare(b.name)); + { + id: 'buymeacoffee', + name: 'Buy Me a Coffee', + category: 'Support', + iconUrl: SI('buymeacoffee'), + type: 'simpleicon', + siSlug: 'buymeacoffee', + baseUrl: 'https://buymeacoffee.com/', + placeholder: 'e.g. https://buymeacoffee.com/yourusername', + }, + { + id: 'patreon', + name: 'Patreon', + category: 'Support', + iconUrl: SI('patreon'), + type: 'simpleicon', + siSlug: 'patreon', + baseUrl: 'https://patreon.com/', + placeholder: 'e.g. https://patreon.com/yourusername', + }, + { + id: 'kofi', + name: 'Ko-fi', + category: 'Support', + iconUrl: SI('kofi'), + type: 'simpleicon', + siSlug: 'kofi', + baseUrl: 'https://ko-fi.com/', + placeholder: 'e.g. https://ko-fi.com/yourusername', + }, + { + id: 'githubsponsors', + name: 'GitHub Sponsors', + category: 'Support', + iconUrl: SI('githubsponsors'), + type: 'simpleicon', + siSlug: 'githubsponsors', + baseUrl: 'https://github.com/sponsors/', + placeholder: 'e.g. https://github.com/sponsors/yourusername', + }, + { + id: 'opencollective', + name: 'Open Collective', + category: 'Support', + iconUrl: SI('opencollective'), + type: 'simpleicon', + siSlug: 'opencollective', + baseUrl: 'https://opencollective.com/', + placeholder: 'e.g. https://opencollective.com/yourname', + }, + { + id: 'kick', + name: 'Kick', + category: 'Streaming', + iconUrl: SI('kick'), + type: 'simpleicon', + siSlug: 'kick', + baseUrl: 'https://kick.com/', + placeholder: 'e.g. https://kick.com/yourchannel', + }, + { + id: 'odysee', + name: 'Odysee', + category: 'Streaming', + iconUrl: SI('odysee'), + type: 'simpleicon', + siSlug: 'odysee', + baseUrl: 'https://odysee.com/@', + placeholder: 'e.g. https://odysee.com/@yourchannel', + }, + { + id: 'huggingface', + name: 'Hugging Face', + category: 'Developer', + iconUrl: SI('huggingface'), + type: 'simpleicon', + siSlug: 'huggingface', + baseUrl: 'https://huggingface.co/', + placeholder: 'e.g. https://huggingface.co/yourname', + }, + ] as Social[] +).sort((a, b) => a.name.localeCompare(b.name)); export const SOCIAL_CATEGORIES: SocialCategory[] = [ 'Social Media', From dff1d4497037e5857c92c780fa818c2d58aa2518 Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Wed, 22 Jul 2026 22:19:30 +0530 Subject: [PATCH 04/13] fix: resolve GraphQL request deduplication and sanitize PAT errors (#6239) --- lib/github.ts | 84 ++++++++++++++------------------------------------- 1 file changed, 23 insertions(+), 61 deletions(-) diff --git a/lib/github.ts b/lib/github.ts index bc6ac634e..c5ac7da67 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -287,13 +287,13 @@ export async function fetchWithRetry( } if (didThrow) { - if (options.signal?.aborted) throw fetchError; + if (options.signal?.aborted) throw new Error('AbortError'); const isTimeoutAbort = isAbortError(fetchError); if (attempt >= MAX_RETRIES) { if (isTimeoutAbort) { throw new Error(`GitHub API request timed out after ${resolvedTimeout / 1000}s`); } - throw fetchError; + throw new Error(fetchError instanceof Error ? fetchError.message : String(fetchError)); } const delay = getJitteredBackoff(attempt); await new Promise((resolve) => setTimeout(resolve, delay)); @@ -910,13 +910,6 @@ export async function fetchGitHubContributions( const loadWithTimeout = async (): Promise => { const controller = new AbortController(); - if (options.signal) { - if (options.signal.aborted) { - controller.abort(); - } else { - options.signal.addEventListener('abort', () => controller.abort(), { once: true }); - } - } let timerId = null; const timeoutPromise = new Promise((_, reject) => { @@ -941,10 +934,7 @@ export async function fetchGitHubContributions( } }; - const coalescedLoad = () => { - if (options.signal) { - return loadWithTimeout(); - } + const coalescedLoad = async () => { let pending = activeContributionsPromises.get(key); if (!pending) { pending = loadWithTimeout().finally(() => { @@ -959,57 +949,29 @@ export async function fetchGitHubContributions( timer.unref(); } } - return pending; - }; - if (options.signal) { - if (options.bypassCache || options.forceRefresh) { - try { - return await loadWithTimeout(); - } catch (err: unknown) { - if (shouldFallbackOnError(err)) { - const staleData = await contributionsCache.get(key); - if (staleData) { - logger.warn('GitHub API fetch failed, falling back to stale cache', { - component: 'GitHub API', - username, - error: err, - }); - return { - ...staleData, - isOfflineFallback: true, - }; - } - return getMockContributions(); - } - throw err; - } - } - const cached = await contributionsCache.get(key); - if (cached !== null && !shouldFetch(cached)) { - return cached; + if (!options.signal) { + return pending; } - try { - return await loadWithTimeout(); - } catch (err: unknown) { - if (shouldFallbackOnError(err)) { - const staleData = await contributionsCache.get(key); - if (staleData) { - logger.warn('GitHub API fetch failed, falling back to stale cache', { - component: 'GitHub API', - username, - error: err, - }); - return { - ...staleData, - isOfflineFallback: true, - }; + + return new Promise((resolve, reject) => { + if (options.signal?.aborted) return reject(new Error('AbortError')); + + const onAbort = () => reject(new Error('AbortError')); + options.signal?.addEventListener('abort', onAbort, { once: true }); + + pending!.then( + (val) => { + options.signal?.removeEventListener('abort', onAbort); + resolve(val); + }, + (err) => { + options.signal?.removeEventListener('abort', onAbort); + reject(err); } - return getMockContributions(); - } - throw err; - } - } + ); + }); + }; if (options.bypassCache || options.forceRefresh) { try { From 4d4e10f26d9a0e0dab0969157d5c05998fb1c64e Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Wed, 22 Jul 2026 23:05:40 +0530 Subject: [PATCH 05/13] fix: resolve garbage characters extraction in resume parser Fixes #6096 by preserving valid UTF-8, avoiding faulty binary text fallbacks, and adding heuristic to repair misencoded artifacts. --- lib/resume-parser.ts | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/resume-parser.ts b/lib/resume-parser.ts index 202e498eb..f4640bd6b 100644 --- a/lib/resume-parser.ts +++ b/lib/resume-parser.ts @@ -149,8 +149,8 @@ async function extractTextFromBuffer(buffer: Buffer, mimeType: string): Promise< rawText = buffer.toString('utf-8'); } } catch (error) { - console.warn('Failed to parse PDF using pdf-parse, falling back to UTF-8 decoding:', error); - rawText = buffer.toString('utf-8'); + console.warn('Failed to parse PDF using pdf-parse:', error); + rawText = ''; } } else if ( mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' @@ -166,15 +166,26 @@ async function extractTextFromBuffer(buffer: Buffer, mimeType: string): Promise< rawText = buffer.toString('utf-8'); } } catch (error) { - console.warn('Failed to parse DOCX using mammoth, falling back to UTF-8 decoding:', error); - rawText = buffer.toString('utf-8'); + console.warn('Failed to parse DOCX using mammoth:', error); + rawText = ''; } } else { rawText = buffer.toString('utf-8'); } + try { + if (rawText.includes('Ã')) { + const fixedText = Buffer.from(rawText, 'latin1').toString('utf-8'); + if (!fixedText.includes('\uFFFD')) { + rawText = fixedText; + } + } + } catch (e) { + // Ignore encoding fix errors + } + const printable = rawText - .replace(/[^\x20-\x7E\n\r]/g, ' ') + .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFFFD]/g, '') .replace(/[ \t]+/g, ' ') .replace(/\r/g, '') .trim(); From aaa38b7c56b861ab4cf03ed612da68449eed5558 Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Wed, 22 Jul 2026 23:36:19 +0530 Subject: [PATCH 06/13] fix(resume-parser): propagate buffer.toString errors instead of swallowing them Moves buffer header checks outside try/catch blocks so that failures in buffer.toString (e.g. in tests mocking the method) correctly propagate to callers. Only third-party parser failures (pdf-parse, mammoth) are silently caught. Fixes the failing error-resilience test. --- lib/resume-parser.ts | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/resume-parser.ts b/lib/resume-parser.ts index f4640bd6b..4c10d2d56 100644 --- a/lib/resume-parser.ts +++ b/lib/resume-parser.ts @@ -136,8 +136,9 @@ async function extractTextFromBuffer(buffer: Buffer, mimeType: string): Promise< let rawText = ''; if (mimeType === 'application/pdf') { - try { - if (buffer.toString('utf-8', 0, 4) === '%PDF') { + const header = buffer.toString('utf-8', 0, 4); + if (header === '%PDF') { + try { const { PDFParse } = await import('pdf-parse'); const parser = new PDFParse({ data: buffer }); @@ -145,29 +146,30 @@ async function extractTextFromBuffer(buffer: Buffer, mimeType: string): Promise< await parser.destroy(); rawText = result.text; - } else { - rawText = buffer.toString('utf-8'); + } catch (error) { + console.warn('Failed to parse PDF using pdf-parse:', error); + rawText = ''; } - } catch (error) { - console.warn('Failed to parse PDF using pdf-parse:', error); - rawText = ''; + } else { + rawText = buffer.toString('utf-8'); } } else if ( mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' ) { - try { - if (buffer.toString('utf-8', 0, 2) === 'PK') { + const header = buffer.toString('utf-8', 0, 2); + if (header === 'PK') { + try { const mammothModule = await import('mammoth'); const mammothParser = ((mammothModule as unknown as { default?: unknown }).default || mammothModule) as typeof mammothModule; const result = await mammothParser.extractRawText({ buffer }); rawText = result.value; - } else { - rawText = buffer.toString('utf-8'); + } catch (error) { + console.warn('Failed to parse DOCX using mammoth:', error); + rawText = ''; } - } catch (error) { - console.warn('Failed to parse DOCX using mammoth:', error); - rawText = ''; + } else { + rawText = buffer.toString('utf-8'); } } else { rawText = buffer.toString('utf-8'); From 9dace073df8a1661c5fc2c6b2515a3b6e573e6de Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Thu, 23 Jul 2026 22:39:36 +0530 Subject: [PATCH 07/13] chore: revert unrelated changes from PR --- app/generator/data/socials.ts | 1166 +++++++++++++++---------------- lib/github.ts | 84 ++- services/github/ci-analytics.ts | 2 +- 3 files changed, 644 insertions(+), 608 deletions(-) diff --git a/app/generator/data/socials.ts b/app/generator/data/socials.ts index 567b2accb..dfcf8d3cf 100644 --- a/app/generator/data/socials.ts +++ b/app/generator/data/socials.ts @@ -4,597 +4,595 @@ const SI = (slug: string) => `https://cdn.simpleicons.org/${slug}`; const DI = (name: string, variant = 'original') => `https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/${name}/${name}-${variant}.svg`; -export const SOCIALS: Social[] = ( - [ - { - id: 'twitter', - name: 'X (Twitter)', - category: 'Social Media', - iconUrl: SI('x'), - type: 'simpleicon', - siSlug: 'x', - baseUrl: 'https://x.com/', - placeholder: 'e.g. https://x.com/yourhandle', - }, - { - id: 'linkedin', - name: 'LinkedIn', - category: 'Professional', - iconUrl: DI('linkedin'), - type: 'devicon', - siSlug: 'linkedin', - baseUrl: 'https://linkedin.com/in/', - placeholder: 'e.g. https://linkedin.com/in/yourname', - }, - { - id: 'instagram', - name: 'Instagram', - category: 'Social Media', - iconUrl: SI('instagram'), - type: 'simpleicon', - siSlug: 'instagram', - baseUrl: 'https://instagram.com/', - placeholder: 'e.g. https://instagram.com/yourhandle', - }, - { - id: 'facebook', - name: 'Facebook', - category: 'Social Media', - iconUrl: SI('facebook'), - type: 'simpleicon', - siSlug: 'facebook', - baseUrl: 'https://facebook.com/', - placeholder: 'e.g. https://facebook.com/yourprofile', - }, - { - id: 'youtube', - name: 'YouTube', - category: 'Social Media', - iconUrl: SI('youtube'), - type: 'simpleicon', - siSlug: 'youtube', - baseUrl: 'https://youtube.com/@', - placeholder: 'e.g. https://youtube.com/@yourchannel', - }, - { - id: 'tiktok', - name: 'TikTok', - category: 'Social Media', - iconUrl: SI('tiktok'), - type: 'simpleicon', - siSlug: 'tiktok', - baseUrl: 'https://tiktok.com/@', - placeholder: 'e.g. https://tiktok.com/@yourhandle', - }, - { - id: 'discord', - name: 'Discord', - category: 'Social Media', - iconUrl: SI('discord'), - type: 'simpleicon', - siSlug: 'discord', - baseUrl: 'https://discord.gg/', - placeholder: 'e.g. https://discord.gg/yourserver', - }, - { - id: 'reddit', - name: 'Reddit', - category: 'Social Media', - iconUrl: SI('reddit'), - type: 'simpleicon', - siSlug: 'reddit', - baseUrl: 'https://reddit.com/u/', - placeholder: 'e.g. https://reddit.com/u/yourname', - }, - { - id: 'mastodon', - name: 'Mastodon', - category: 'Social Media', - iconUrl: SI('mastodon'), - type: 'simpleicon', - siSlug: 'mastodon', - baseUrl: 'https://mastodon.social/@', - placeholder: 'e.g. https://mastodon.social/@yourhandle', - }, - { - id: 'bluesky', - name: 'Bluesky', - category: 'Social Media', - iconUrl: SI('bluesky'), - type: 'simpleicon', - siSlug: 'bluesky', - baseUrl: 'https://bsky.app/profile/', - placeholder: 'e.g. https://bsky.app/profile/yourhandle', - }, - { - id: 'threads', - name: 'Threads', - category: 'Social Media', - iconUrl: SI('threads'), - type: 'simpleicon', - siSlug: 'threads', - baseUrl: 'https://threads.net/@', - placeholder: 'e.g. https://threads.net/@yourhandle', - }, - { - id: 'snapchat', - name: 'Snapchat', - category: 'Social Media', - iconUrl: SI('snapchat'), - type: 'simpleicon', - siSlug: 'snapchat', - baseUrl: 'https://snapchat.com/add/', - placeholder: 'e.g. https://snapchat.com/add/yourusername', - }, - { - id: 'pinterest', - name: 'Pinterest', - category: 'Social Media', - iconUrl: SI('pinterest'), - type: 'simpleicon', - siSlug: 'pinterest', - baseUrl: 'https://pinterest.com/', - placeholder: 'e.g. https://pinterest.com/yourname', - }, - { - id: 'vimeo', - name: 'Vimeo', - category: 'Social Media', - iconUrl: SI('vimeo'), - type: 'simpleicon', - siSlug: 'vimeo', - baseUrl: 'https://vimeo.com/', - placeholder: 'e.g. https://vimeo.com/yourusername', - }, - { - id: 'xing', - name: 'Xing', - category: 'Professional', - iconUrl: SI('xing'), - type: 'simpleicon', - siSlug: 'xing', - baseUrl: 'https://www.xing.com/profile/', - placeholder: 'e.g. https://www.xing.com/profile/yourname', - }, +export const SOCIALS: Social[] = [ + { + id: 'twitter', + name: 'X (Twitter)', + category: 'Social Media', + iconUrl: SI('x'), + type: 'simpleicon', + siSlug: 'x', + baseUrl: 'https://x.com/', + placeholder: 'e.g. https://x.com/yourhandle', + }, + { + id: 'linkedin', + name: 'LinkedIn', + category: 'Professional', + iconUrl: DI('linkedin'), + type: 'devicon', + siSlug: 'linkedin', + baseUrl: 'https://linkedin.com/in/', + placeholder: 'e.g. https://linkedin.com/in/yourname', + }, + { + id: 'instagram', + name: 'Instagram', + category: 'Social Media', + iconUrl: SI('instagram'), + type: 'simpleicon', + siSlug: 'instagram', + baseUrl: 'https://instagram.com/', + placeholder: 'e.g. https://instagram.com/yourhandle', + }, + { + id: 'facebook', + name: 'Facebook', + category: 'Social Media', + iconUrl: SI('facebook'), + type: 'simpleicon', + siSlug: 'facebook', + baseUrl: 'https://facebook.com/', + placeholder: 'e.g. https://facebook.com/yourprofile', + }, + { + id: 'youtube', + name: 'YouTube', + category: 'Social Media', + iconUrl: SI('youtube'), + type: 'simpleicon', + siSlug: 'youtube', + baseUrl: 'https://youtube.com/@', + placeholder: 'e.g. https://youtube.com/@yourchannel', + }, + { + id: 'tiktok', + name: 'TikTok', + category: 'Social Media', + iconUrl: SI('tiktok'), + type: 'simpleicon', + siSlug: 'tiktok', + baseUrl: 'https://tiktok.com/@', + placeholder: 'e.g. https://tiktok.com/@yourhandle', + }, + { + id: 'discord', + name: 'Discord', + category: 'Social Media', + iconUrl: SI('discord'), + type: 'simpleicon', + siSlug: 'discord', + baseUrl: 'https://discord.gg/', + placeholder: 'e.g. https://discord.gg/yourserver', + }, + { + id: 'reddit', + name: 'Reddit', + category: 'Social Media', + iconUrl: SI('reddit'), + type: 'simpleicon', + siSlug: 'reddit', + baseUrl: 'https://reddit.com/u/', + placeholder: 'e.g. https://reddit.com/u/yourname', + }, + { + id: 'mastodon', + name: 'Mastodon', + category: 'Social Media', + iconUrl: SI('mastodon'), + type: 'simpleicon', + siSlug: 'mastodon', + baseUrl: 'https://mastodon.social/@', + placeholder: 'e.g. https://mastodon.social/@yourhandle', + }, + { + id: 'bluesky', + name: 'Bluesky', + category: 'Social Media', + iconUrl: SI('bluesky'), + type: 'simpleicon', + siSlug: 'bluesky', + baseUrl: 'https://bsky.app/profile/', + placeholder: 'e.g. https://bsky.app/profile/yourhandle', + }, + { + id: 'threads', + name: 'Threads', + category: 'Social Media', + iconUrl: SI('threads'), + type: 'simpleicon', + siSlug: 'threads', + baseUrl: 'https://threads.net/@', + placeholder: 'e.g. https://threads.net/@yourhandle', + }, + { + id: 'snapchat', + name: 'Snapchat', + category: 'Social Media', + iconUrl: SI('snapchat'), + type: 'simpleicon', + siSlug: 'snapchat', + baseUrl: 'https://snapchat.com/add/', + placeholder: 'e.g. https://snapchat.com/add/yourusername', + }, + { + id: 'pinterest', + name: 'Pinterest', + category: 'Social Media', + iconUrl: SI('pinterest'), + type: 'simpleicon', + siSlug: 'pinterest', + baseUrl: 'https://pinterest.com/', + placeholder: 'e.g. https://pinterest.com/yourname', + }, + { + id: 'vimeo', + name: 'Vimeo', + category: 'Social Media', + iconUrl: SI('vimeo'), + type: 'simpleicon', + siSlug: 'vimeo', + baseUrl: 'https://vimeo.com/', + placeholder: 'e.g. https://vimeo.com/yourusername', + }, + { + id: 'xing', + name: 'Xing', + category: 'Professional', + iconUrl: SI('xing'), + type: 'simpleicon', + siSlug: 'xing', + baseUrl: 'https://www.xing.com/profile/', + placeholder: 'e.g. https://www.xing.com/profile/yourname', + }, - { - id: 'github', - name: 'GitHub', - category: 'Developer', - iconUrl: DI('github'), - type: 'devicon', - siSlug: 'github', - baseUrl: 'https://github.com/', - placeholder: 'e.g. https://github.com/yourusername', - }, - { - id: 'gitlabprofile', - name: 'GitLab', - category: 'Developer', - iconUrl: DI('gitlab'), - type: 'devicon', - siSlug: 'gitlab', - baseUrl: 'https://gitlab.com/', - placeholder: 'e.g. https://gitlab.com/yourusername', - }, - { - id: 'bitbucket', - name: 'Bitbucket', - category: 'Developer', - iconUrl: DI('bitbucket'), - type: 'devicon', - siSlug: 'bitbucket', - baseUrl: 'https://bitbucket.org/', - placeholder: 'e.g. https://bitbucket.org/yourusername', - }, - { - id: 'stackoverflow', - name: 'Stack Overflow', - category: 'Developer', - iconUrl: DI('stackoverflow'), - type: 'devicon', - siSlug: 'stackoverflow', - baseUrl: 'https://stackoverflow.com/users/', - placeholder: 'e.g. https://stackoverflow.com/users/123456', - }, - { - id: 'devto', - name: 'Dev.to', - category: 'Developer', - iconUrl: SI('devdotto'), - type: 'simpleicon', - siSlug: 'devdotto', - baseUrl: 'https://dev.to/', - placeholder: 'e.g. https://dev.to/yourusername', - }, - { - id: 'hashnode', - name: 'Hashnode', - category: 'Developer', - iconUrl: SI('hashnode'), - type: 'simpleicon', - siSlug: 'hashnode', - baseUrl: 'https://hashnode.com/@', - placeholder: 'e.g. https://hashnode.com/@yourusername', - }, - { - id: 'medium', - name: 'Medium', - category: 'Developer', - iconUrl: SI('medium'), - type: 'simpleicon', - siSlug: 'medium', - baseUrl: 'https://medium.com/@', - placeholder: 'e.g. https://medium.com/@yourusername', - }, - { - id: 'npm', - name: 'npm', - category: 'Developer', - iconUrl: SI('npm'), - type: 'simpleicon', - siSlug: 'npm', - baseUrl: 'https://npmjs.com/~', - placeholder: 'e.g. https://npmjs.com/~yourusername', - }, - { - id: 'pypi', - name: 'PyPI', - category: 'Developer', - iconUrl: SI('pypi'), - type: 'simpleicon', - siSlug: 'pypi', - baseUrl: 'https://pypi.org/user/', - placeholder: 'e.g. https://pypi.org/user/yourusername', - }, - { - id: 'dockerhub', - name: 'Docker Hub', - category: 'Developer', - iconUrl: DI('docker'), - type: 'devicon', - siSlug: 'docker', - baseUrl: 'https://hub.docker.com/u/', - placeholder: 'e.g. https://hub.docker.com/u/yourusername', - }, - { - id: 'replit', - name: 'Replit', - category: 'Developer', - iconUrl: SI('replit'), - type: 'simpleicon', - siSlug: 'replit', - baseUrl: 'https://replit.com/@', - placeholder: 'e.g. https://replit.com/@yourusername', - }, - { - id: 'codepen', - name: 'CodePen', - category: 'Developer', - iconUrl: DI('codepen'), - type: 'devicon', - siSlug: 'codepen', - baseUrl: 'https://codepen.io/', - placeholder: 'e.g. https://codepen.io/yourusername', - }, - { - id: 'codesandbox', - name: 'CodeSandbox', - category: 'Developer', - iconUrl: SI('codesandbox'), - type: 'simpleicon', - siSlug: 'codesandbox', - baseUrl: 'https://codesandbox.io/u/', - placeholder: 'e.g. https://codesandbox.io/u/yourusername', - }, - { - id: 'wakatime', - name: 'WakaTime', - category: 'Developer', - iconUrl: SI('wakatime'), - type: 'simpleicon', - siSlug: 'wakatime', - baseUrl: 'https://wakatime.com/@', - placeholder: 'e.g. https://wakatime.com/@yourusername', - }, + { + id: 'github', + name: 'GitHub', + category: 'Developer', + iconUrl: DI('github'), + type: 'devicon', + siSlug: 'github', + baseUrl: 'https://github.com/', + placeholder: 'e.g. https://github.com/yourusername', + }, + { + id: 'gitlabprofile', + name: 'GitLab', + category: 'Developer', + iconUrl: DI('gitlab'), + type: 'devicon', + siSlug: 'gitlab', + baseUrl: 'https://gitlab.com/', + placeholder: 'e.g. https://gitlab.com/yourusername', + }, + { + id: 'bitbucket', + name: 'Bitbucket', + category: 'Developer', + iconUrl: DI('bitbucket'), + type: 'devicon', + siSlug: 'bitbucket', + baseUrl: 'https://bitbucket.org/', + placeholder: 'e.g. https://bitbucket.org/yourusername', + }, + { + id: 'stackoverflow', + name: 'Stack Overflow', + category: 'Developer', + iconUrl: DI('stackoverflow'), + type: 'devicon', + siSlug: 'stackoverflow', + baseUrl: 'https://stackoverflow.com/users/', + placeholder: 'e.g. https://stackoverflow.com/users/123456', + }, + { + id: 'devto', + name: 'Dev.to', + category: 'Developer', + iconUrl: SI('devdotto'), + type: 'simpleicon', + siSlug: 'devdotto', + baseUrl: 'https://dev.to/', + placeholder: 'e.g. https://dev.to/yourusername', + }, + { + id: 'hashnode', + name: 'Hashnode', + category: 'Developer', + iconUrl: SI('hashnode'), + type: 'simpleicon', + siSlug: 'hashnode', + baseUrl: 'https://hashnode.com/@', + placeholder: 'e.g. https://hashnode.com/@yourusername', + }, + { + id: 'medium', + name: 'Medium', + category: 'Developer', + iconUrl: SI('medium'), + type: 'simpleicon', + siSlug: 'medium', + baseUrl: 'https://medium.com/@', + placeholder: 'e.g. https://medium.com/@yourusername', + }, + { + id: 'npm', + name: 'npm', + category: 'Developer', + iconUrl: SI('npm'), + type: 'simpleicon', + siSlug: 'npm', + baseUrl: 'https://npmjs.com/~', + placeholder: 'e.g. https://npmjs.com/~yourusername', + }, + { + id: 'pypi', + name: 'PyPI', + category: 'Developer', + iconUrl: SI('pypi'), + type: 'simpleicon', + siSlug: 'pypi', + baseUrl: 'https://pypi.org/user/', + placeholder: 'e.g. https://pypi.org/user/yourusername', + }, + { + id: 'dockerhub', + name: 'Docker Hub', + category: 'Developer', + iconUrl: DI('docker'), + type: 'devicon', + siSlug: 'docker', + baseUrl: 'https://hub.docker.com/u/', + placeholder: 'e.g. https://hub.docker.com/u/yourusername', + }, + { + id: 'replit', + name: 'Replit', + category: 'Developer', + iconUrl: SI('replit'), + type: 'simpleicon', + siSlug: 'replit', + baseUrl: 'https://replit.com/@', + placeholder: 'e.g. https://replit.com/@yourusername', + }, + { + id: 'codepen', + name: 'CodePen', + category: 'Developer', + iconUrl: DI('codepen'), + type: 'devicon', + siSlug: 'codepen', + baseUrl: 'https://codepen.io/', + placeholder: 'e.g. https://codepen.io/yourusername', + }, + { + id: 'codesandbox', + name: 'CodeSandbox', + category: 'Developer', + iconUrl: SI('codesandbox'), + type: 'simpleicon', + siSlug: 'codesandbox', + baseUrl: 'https://codesandbox.io/u/', + placeholder: 'e.g. https://codesandbox.io/u/yourusername', + }, + { + id: 'wakatime', + name: 'WakaTime', + category: 'Developer', + iconUrl: SI('wakatime'), + type: 'simpleicon', + siSlug: 'wakatime', + baseUrl: 'https://wakatime.com/@', + placeholder: 'e.g. https://wakatime.com/@yourusername', + }, - { - id: 'leetcode', - name: 'LeetCode', - category: 'Competitive Programming', - iconUrl: SI('leetcode'), - type: 'simpleicon', - siSlug: 'leetcode', - baseUrl: 'https://leetcode.com/', - placeholder: 'e.g. https://leetcode.com/yourusername', - }, - { - id: 'codeforces', - name: 'Codeforces', - category: 'Competitive Programming', - iconUrl: SI('codeforces'), - type: 'simpleicon', - siSlug: 'codeforces', - baseUrl: 'https://codeforces.com/profile/', - placeholder: 'e.g. https://codeforces.com/profile/yourusername', - }, - { - id: 'hackerrank', - name: 'HackerRank', - category: 'Competitive Programming', - iconUrl: SI('hackerrank'), - type: 'simpleicon', - siSlug: 'hackerrank', - baseUrl: 'https://hackerrank.com/', - placeholder: 'e.g. https://hackerrank.com/yourusername', - }, - { - id: 'hackerearth', - name: 'HackerEarth', - category: 'Competitive Programming', - iconUrl: SI('hackerearth'), - type: 'simpleicon', - siSlug: 'hackerearth', - baseUrl: 'https://hackerearth.com/@', - placeholder: 'e.g. https://hackerearth.com/@yourusername', - }, - { - id: 'atcoder', - name: 'AtCoder', - category: 'Competitive Programming', - iconUrl: SI('codementor'), - type: 'simpleicon', - siSlug: 'codementor', - baseUrl: 'https://atcoder.jp/users/', - placeholder: 'e.g. https://atcoder.jp/users/yourusername', - }, - { - id: 'codechef', - name: 'CodeChef', - category: 'Competitive Programming', - iconUrl: SI('codechef'), - type: 'simpleicon', - siSlug: 'codechef', - baseUrl: 'https://codechef.com/users/', - placeholder: 'e.g. https://codechef.com/users/yourusername', - }, - { - id: 'topcoder', - name: 'TopCoder', - category: 'Competitive Programming', - iconUrl: SI('topcoder'), - type: 'simpleicon', - siSlug: 'topcoder', - baseUrl: 'https://topcoder.com/members/', - placeholder: 'e.g. https://topcoder.com/members/yourusername', - }, - { - id: 'codewars', - name: 'Codewars', - category: 'Competitive Programming', - iconUrl: SI('codewars'), - type: 'simpleicon', - siSlug: 'codewars', - baseUrl: 'https://codewars.com/users/', - placeholder: 'e.g. https://codewars.com/users/yourusername', - }, - { - id: 'geeksforgeeks', - name: 'GeeksForGeeks', - category: 'Competitive Programming', - iconUrl: SI('geeksforgeeks'), - type: 'simpleicon', - siSlug: 'geeksforgeeks', - baseUrl: 'https://geeksforgeeks.org/user/', - placeholder: 'e.g. https://geeksforgeeks.org/user/yourusername', - }, - { - id: 'kaggle', - name: 'Kaggle', - category: 'Competitive Programming', - iconUrl: SI('kaggle'), - type: 'simpleicon', - siSlug: 'kaggle', - baseUrl: 'https://kaggle.com/', - placeholder: 'e.g. https://kaggle.com/yourusername', - }, + { + id: 'leetcode', + name: 'LeetCode', + category: 'Competitive Programming', + iconUrl: SI('leetcode'), + type: 'simpleicon', + siSlug: 'leetcode', + baseUrl: 'https://leetcode.com/', + placeholder: 'e.g. https://leetcode.com/yourusername', + }, + { + id: 'codeforces', + name: 'Codeforces', + category: 'Competitive Programming', + iconUrl: SI('codeforces'), + type: 'simpleicon', + siSlug: 'codeforces', + baseUrl: 'https://codeforces.com/profile/', + placeholder: 'e.g. https://codeforces.com/profile/yourusername', + }, + { + id: 'hackerrank', + name: 'HackerRank', + category: 'Competitive Programming', + iconUrl: SI('hackerrank'), + type: 'simpleicon', + siSlug: 'hackerrank', + baseUrl: 'https://hackerrank.com/', + placeholder: 'e.g. https://hackerrank.com/yourusername', + }, + { + id: 'hackerearth', + name: 'HackerEarth', + category: 'Competitive Programming', + iconUrl: SI('hackerearth'), + type: 'simpleicon', + siSlug: 'hackerearth', + baseUrl: 'https://hackerearth.com/@', + placeholder: 'e.g. https://hackerearth.com/@yourusername', + }, + { + id: 'atcoder', + name: 'AtCoder', + category: 'Competitive Programming', + iconUrl: SI('codementor'), + type: 'simpleicon', + siSlug: 'codementor', + baseUrl: 'https://atcoder.jp/users/', + placeholder: 'e.g. https://atcoder.jp/users/yourusername', + }, + { + id: 'codechef', + name: 'CodeChef', + category: 'Competitive Programming', + iconUrl: SI('codechef'), + type: 'simpleicon', + siSlug: 'codechef', + baseUrl: 'https://codechef.com/users/', + placeholder: 'e.g. https://codechef.com/users/yourusername', + }, + { + id: 'topcoder', + name: 'TopCoder', + category: 'Competitive Programming', + iconUrl: SI('topcoder'), + type: 'simpleicon', + siSlug: 'topcoder', + baseUrl: 'https://topcoder.com/members/', + placeholder: 'e.g. https://topcoder.com/members/yourusername', + }, + { + id: 'codewars', + name: 'Codewars', + category: 'Competitive Programming', + iconUrl: SI('codewars'), + type: 'simpleicon', + siSlug: 'codewars', + baseUrl: 'https://codewars.com/users/', + placeholder: 'e.g. https://codewars.com/users/yourusername', + }, + { + id: 'geeksforgeeks', + name: 'GeeksForGeeks', + category: 'Competitive Programming', + iconUrl: SI('geeksforgeeks'), + type: 'simpleicon', + siSlug: 'geeksforgeeks', + baseUrl: 'https://geeksforgeeks.org/user/', + placeholder: 'e.g. https://geeksforgeeks.org/user/yourusername', + }, + { + id: 'kaggle', + name: 'Kaggle', + category: 'Competitive Programming', + iconUrl: SI('kaggle'), + type: 'simpleicon', + siSlug: 'kaggle', + baseUrl: 'https://kaggle.com/', + placeholder: 'e.g. https://kaggle.com/yourusername', + }, - { - id: 'producthunt', - name: 'Product Hunt', - category: 'Professional', - iconUrl: SI('producthunt'), - type: 'simpleicon', - siSlug: 'producthunt', - baseUrl: 'https://producthunt.com/@', - placeholder: 'e.g. https://producthunt.com/@yourusername', - }, - { - id: 'behance', - name: 'Behance', - category: 'Professional', - iconUrl: SI('behance'), - type: 'simpleicon', - siSlug: 'behance', - baseUrl: 'https://behance.net/', - placeholder: 'e.g. https://behance.net/yourusername', - }, - { - id: 'dribbble', - name: 'Dribbble', - category: 'Professional', - iconUrl: SI('dribbble'), - type: 'simpleicon', - siSlug: 'dribbble', - baseUrl: 'https://dribbble.com/', - placeholder: 'e.g. https://dribbble.com/yourusername', - }, + { + id: 'producthunt', + name: 'Product Hunt', + category: 'Professional', + iconUrl: SI('producthunt'), + type: 'simpleicon', + siSlug: 'producthunt', + baseUrl: 'https://producthunt.com/@', + placeholder: 'e.g. https://producthunt.com/@yourusername', + }, + { + id: 'behance', + name: 'Behance', + category: 'Professional', + iconUrl: SI('behance'), + type: 'simpleicon', + siSlug: 'behance', + baseUrl: 'https://behance.net/', + placeholder: 'e.g. https://behance.net/yourusername', + }, + { + id: 'dribbble', + name: 'Dribbble', + category: 'Professional', + iconUrl: SI('dribbble'), + type: 'simpleicon', + siSlug: 'dribbble', + baseUrl: 'https://dribbble.com/', + placeholder: 'e.g. https://dribbble.com/yourusername', + }, - { - id: 'twitch', - name: 'Twitch', - category: 'Streaming', - iconUrl: SI('twitch'), - type: 'simpleicon', - siSlug: 'twitch', - baseUrl: 'https://twitch.tv/', - placeholder: 'e.g. https://twitch.tv/yourchannel', - }, - { - id: 'spotify', - name: 'Spotify', - category: 'Streaming', - iconUrl: SI('spotify'), - type: 'simpleicon', - siSlug: 'spotify', - baseUrl: 'https://open.spotify.com/user/', - placeholder: 'e.g. https://open.spotify.com/user/yourusername', - }, + { + id: 'twitch', + name: 'Twitch', + category: 'Streaming', + iconUrl: SI('twitch'), + type: 'simpleicon', + siSlug: 'twitch', + baseUrl: 'https://twitch.tv/', + placeholder: 'e.g. https://twitch.tv/yourchannel', + }, + { + id: 'spotify', + name: 'Spotify', + category: 'Streaming', + iconUrl: SI('spotify'), + type: 'simpleicon', + siSlug: 'spotify', + baseUrl: 'https://open.spotify.com/user/', + placeholder: 'e.g. https://open.spotify.com/user/yourusername', + }, - { - id: 'email', - name: 'Email', - category: 'Contact', - iconUrl: SI('gmail'), - type: 'simpleicon', - siSlug: 'gmail', - baseUrl: 'mailto:', - placeholder: 'e.g. youremail@example.com', - }, - { - id: 'telegram', - name: 'Telegram', - category: 'Contact', - iconUrl: SI('telegram'), - type: 'simpleicon', - siSlug: 'telegram', - baseUrl: 'https://t.me/', - placeholder: 'e.g. https://t.me/yourusername', - }, - { - id: 'whatsapp', - name: 'WhatsApp', - category: 'Contact', - iconUrl: SI('whatsapp'), - type: 'simpleicon', - siSlug: 'whatsapp', - baseUrl: 'https://wa.me/', - placeholder: 'e.g. https://wa.me/1234567890', - }, - { - id: 'signal', - name: 'Signal', - category: 'Contact', - iconUrl: SI('signal'), - type: 'simpleicon', - siSlug: 'signal', - baseUrl: 'https://signal.me/#p/', - placeholder: 'e.g. https://signal.me/#p/yourhandle', - }, + { + id: 'email', + name: 'Email', + category: 'Contact', + iconUrl: SI('gmail'), + type: 'simpleicon', + siSlug: 'gmail', + baseUrl: 'mailto:', + placeholder: 'e.g. youremail@example.com', + }, + { + id: 'telegram', + name: 'Telegram', + category: 'Contact', + iconUrl: SI('telegram'), + type: 'simpleicon', + siSlug: 'telegram', + baseUrl: 'https://t.me/', + placeholder: 'e.g. https://t.me/yourusername', + }, + { + id: 'whatsapp', + name: 'WhatsApp', + category: 'Contact', + iconUrl: SI('whatsapp'), + type: 'simpleicon', + siSlug: 'whatsapp', + baseUrl: 'https://wa.me/', + placeholder: 'e.g. https://wa.me/1234567890', + }, + { + id: 'signal', + name: 'Signal', + category: 'Contact', + iconUrl: SI('signal'), + type: 'simpleicon', + siSlug: 'signal', + baseUrl: 'https://signal.me/#p/', + placeholder: 'e.g. https://signal.me/#p/yourhandle', + }, - { - id: 'website', - name: 'Personal Website', - category: 'Portfolio', - iconUrl: SI('googlechrome'), - type: 'simpleicon', - siSlug: 'googlechrome', - baseUrl: 'https://', - placeholder: 'e.g. https://yourwebsite.com', - }, - { - id: 'substack', - name: 'Substack', - category: 'Portfolio', - iconUrl: SI('substack'), - type: 'simpleicon', - siSlug: 'substack', - baseUrl: 'https://substack.com/@', - placeholder: 'e.g. https://substack.com/@yourusername', - }, + { + id: 'website', + name: 'Personal Website', + category: 'Portfolio', + iconUrl: SI('googlechrome'), + type: 'simpleicon', + siSlug: 'googlechrome', + baseUrl: 'https://', + placeholder: 'e.g. https://yourwebsite.com', + }, + { + id: 'substack', + name: 'Substack', + category: 'Portfolio', + iconUrl: SI('substack'), + type: 'simpleicon', + siSlug: 'substack', + baseUrl: 'https://substack.com/@', + placeholder: 'e.g. https://substack.com/@yourusername', + }, - { - id: 'buymeacoffee', - name: 'Buy Me a Coffee', - category: 'Support', - iconUrl: SI('buymeacoffee'), - type: 'simpleicon', - siSlug: 'buymeacoffee', - baseUrl: 'https://buymeacoffee.com/', - placeholder: 'e.g. https://buymeacoffee.com/yourusername', - }, - { - id: 'patreon', - name: 'Patreon', - category: 'Support', - iconUrl: SI('patreon'), - type: 'simpleicon', - siSlug: 'patreon', - baseUrl: 'https://patreon.com/', - placeholder: 'e.g. https://patreon.com/yourusername', - }, - { - id: 'kofi', - name: 'Ko-fi', - category: 'Support', - iconUrl: SI('kofi'), - type: 'simpleicon', - siSlug: 'kofi', - baseUrl: 'https://ko-fi.com/', - placeholder: 'e.g. https://ko-fi.com/yourusername', - }, - { - id: 'githubsponsors', - name: 'GitHub Sponsors', - category: 'Support', - iconUrl: SI('githubsponsors'), - type: 'simpleicon', - siSlug: 'githubsponsors', - baseUrl: 'https://github.com/sponsors/', - placeholder: 'e.g. https://github.com/sponsors/yourusername', - }, - { - id: 'opencollective', - name: 'Open Collective', - category: 'Support', - iconUrl: SI('opencollective'), - type: 'simpleicon', - siSlug: 'opencollective', - baseUrl: 'https://opencollective.com/', - placeholder: 'e.g. https://opencollective.com/yourname', - }, - { - id: 'kick', - name: 'Kick', - category: 'Streaming', - iconUrl: SI('kick'), - type: 'simpleicon', - siSlug: 'kick', - baseUrl: 'https://kick.com/', - placeholder: 'e.g. https://kick.com/yourchannel', - }, - { - id: 'odysee', - name: 'Odysee', - category: 'Streaming', - iconUrl: SI('odysee'), - type: 'simpleicon', - siSlug: 'odysee', - baseUrl: 'https://odysee.com/@', - placeholder: 'e.g. https://odysee.com/@yourchannel', - }, - { - id: 'huggingface', - name: 'Hugging Face', - category: 'Developer', - iconUrl: SI('huggingface'), - type: 'simpleicon', - siSlug: 'huggingface', - baseUrl: 'https://huggingface.co/', - placeholder: 'e.g. https://huggingface.co/yourname', - }, - ] as Social[] -).sort((a, b) => a.name.localeCompare(b.name)); + { + id: 'buymeacoffee', + name: 'Buy Me a Coffee', + category: 'Support', + iconUrl: SI('buymeacoffee'), + type: 'simpleicon', + siSlug: 'buymeacoffee', + baseUrl: 'https://buymeacoffee.com/', + placeholder: 'e.g. https://buymeacoffee.com/yourusername', + }, + { + id: 'patreon', + name: 'Patreon', + category: 'Support', + iconUrl: SI('patreon'), + type: 'simpleicon', + siSlug: 'patreon', + baseUrl: 'https://patreon.com/', + placeholder: 'e.g. https://patreon.com/yourusername', + }, + { + id: 'kofi', + name: 'Ko-fi', + category: 'Support', + iconUrl: SI('kofi'), + type: 'simpleicon', + siSlug: 'kofi', + baseUrl: 'https://ko-fi.com/', + placeholder: 'e.g. https://ko-fi.com/yourusername', + }, + { + id: 'githubsponsors', + name: 'GitHub Sponsors', + category: 'Support', + iconUrl: SI('githubsponsors'), + type: 'simpleicon', + siSlug: 'githubsponsors', + baseUrl: 'https://github.com/sponsors/', + placeholder: 'e.g. https://github.com/sponsors/yourusername', + }, + { + id: 'opencollective', + name: 'Open Collective', + category: 'Support', + iconUrl: SI('opencollective'), + type: 'simpleicon', + siSlug: 'opencollective', + baseUrl: 'https://opencollective.com/', + placeholder: 'e.g. https://opencollective.com/yourname', + }, + { + id: 'kick', + name: 'Kick', + category: 'Streaming', + iconUrl: SI('kick'), + type: 'simpleicon', + siSlug: 'kick', + baseUrl: 'https://kick.com/', + placeholder: 'e.g. https://kick.com/yourchannel', + }, + { + id: 'odysee', + name: 'Odysee', + category: 'Streaming', + iconUrl: SI('odysee'), + type: 'simpleicon', + siSlug: 'odysee', + baseUrl: 'https://odysee.com/@', + placeholder: 'e.g. https://odysee.com/@yourchannel', + }, + { + id: 'huggingface', + name: 'Hugging Face', + category: 'Developer', + iconUrl: SI('huggingface'), + type: 'simpleicon', + siSlug: 'huggingface', + baseUrl: 'https://huggingface.co/', + placeholder: 'e.g. https://huggingface.co/yourname', + }, +]; export const SOCIAL_CATEGORIES: SocialCategory[] = [ 'Social Media', diff --git a/lib/github.ts b/lib/github.ts index c5ac7da67..bc6ac634e 100644 --- a/lib/github.ts +++ b/lib/github.ts @@ -287,13 +287,13 @@ export async function fetchWithRetry( } if (didThrow) { - if (options.signal?.aborted) throw new Error('AbortError'); + if (options.signal?.aborted) throw fetchError; const isTimeoutAbort = isAbortError(fetchError); if (attempt >= MAX_RETRIES) { if (isTimeoutAbort) { throw new Error(`GitHub API request timed out after ${resolvedTimeout / 1000}s`); } - throw new Error(fetchError instanceof Error ? fetchError.message : String(fetchError)); + throw fetchError; } const delay = getJitteredBackoff(attempt); await new Promise((resolve) => setTimeout(resolve, delay)); @@ -910,6 +910,13 @@ export async function fetchGitHubContributions( const loadWithTimeout = async (): Promise => { const controller = new AbortController(); + if (options.signal) { + if (options.signal.aborted) { + controller.abort(); + } else { + options.signal.addEventListener('abort', () => controller.abort(), { once: true }); + } + } let timerId = null; const timeoutPromise = new Promise((_, reject) => { @@ -934,7 +941,10 @@ export async function fetchGitHubContributions( } }; - const coalescedLoad = async () => { + const coalescedLoad = () => { + if (options.signal) { + return loadWithTimeout(); + } let pending = activeContributionsPromises.get(key); if (!pending) { pending = loadWithTimeout().finally(() => { @@ -949,29 +959,57 @@ export async function fetchGitHubContributions( timer.unref(); } } + return pending; + }; - if (!options.signal) { - return pending; + if (options.signal) { + if (options.bypassCache || options.forceRefresh) { + try { + return await loadWithTimeout(); + } catch (err: unknown) { + if (shouldFallbackOnError(err)) { + const staleData = await contributionsCache.get(key); + if (staleData) { + logger.warn('GitHub API fetch failed, falling back to stale cache', { + component: 'GitHub API', + username, + error: err, + }); + return { + ...staleData, + isOfflineFallback: true, + }; + } + return getMockContributions(); + } + throw err; + } } - - return new Promise((resolve, reject) => { - if (options.signal?.aborted) return reject(new Error('AbortError')); - - const onAbort = () => reject(new Error('AbortError')); - options.signal?.addEventListener('abort', onAbort, { once: true }); - - pending!.then( - (val) => { - options.signal?.removeEventListener('abort', onAbort); - resolve(val); - }, - (err) => { - options.signal?.removeEventListener('abort', onAbort); - reject(err); + const cached = await contributionsCache.get(key); + if (cached !== null && !shouldFetch(cached)) { + return cached; + } + try { + return await loadWithTimeout(); + } catch (err: unknown) { + if (shouldFallbackOnError(err)) { + const staleData = await contributionsCache.get(key); + if (staleData) { + logger.warn('GitHub API fetch failed, falling back to stale cache', { + component: 'GitHub API', + username, + error: err, + }); + return { + ...staleData, + isOfflineFallback: true, + }; } - ); - }); - }; + return getMockContributions(); + } + throw err; + } + } if (options.bypassCache || options.forceRefresh) { try { diff --git a/services/github/ci-analytics.ts b/services/github/ci-analytics.ts index fdcdd5b8d..6c67ebe79 100644 --- a/services/github/ci-analytics.ts +++ b/services/github/ci-analytics.ts @@ -14,7 +14,7 @@ import type { const GITHUB_REST_URL = 'https://api.github.com'; const MAX_REPO_PAGES = 2; -const MAX_ACTION_PAGES = 1; +const MAX_ACTION_PAGES = 2; const MAX_FETCH_TARGETS = 5; const cache = new DistributedCache(500); From 8172a7f772a25fc63eb1555ab8a4f48658f6c4c6 Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Thu, 23 Jul 2026 22:47:01 +0530 Subject: [PATCH 08/13] fix(BurnoutRiskTable): add missing Image import to fix TS2322 and prettier formatting --- components/burnout/BurnoutRiskTable.tsx | 241 ++++++++++++++++++++++-- 1 file changed, 227 insertions(+), 14 deletions(-) diff --git a/components/burnout/BurnoutRiskTable.tsx b/components/burnout/BurnoutRiskTable.tsx index 1ace4d373..985f48f6f 100644 --- a/components/burnout/BurnoutRiskTable.tsx +++ b/components/burnout/BurnoutRiskTable.tsx @@ -1,8 +1,19 @@ 'use client'; +import { useState, useMemo } from 'react'; import { motion } from 'framer-motion'; -import { Flame, ShieldAlert, Sparkles } from 'lucide-react'; import Image from 'next/image'; +import { + Flame, + ShieldAlert, + Sparkles, + Search, + X, + ArrowUpDown, + ArrowUp, + ArrowDown, + Filter, +} from 'lucide-react'; interface ContributorMetric { username: string; @@ -22,6 +33,15 @@ interface BurnoutRiskTableProps { contributors: ContributorMetric[]; } +type SortColumn = + | 'username' + | 'commitShare' + | 'highIntensityWeeks' + | 'restWeeks' + | 'burnoutScore' + | 'totalCommits'; +type SortDirection = 'asc' | 'desc'; + // Custom Pure SVG Sparkline for visual performance function Sparkline({ data }: { data: number[] }) { if (!data || data.length === 0) return null; @@ -71,6 +91,11 @@ function Sparkline({ data }: { data: number[] }) { } export default function BurnoutRiskTable({ contributors }: BurnoutRiskTableProps) { + const [searchQuery, setSearchQuery] = useState(''); + const [highRiskOnly, setHighRiskOnly] = useState(false); + const [sortColumn, setSortColumn] = useState('burnoutScore'); + const [sortDirection, setSortDirection] = useState('desc'); + const getBadgeStyle = (level: 'Low' | 'Medium' | 'High') => { switch (level) { case 'High': @@ -82,6 +107,82 @@ export default function BurnoutRiskTable({ contributors }: BurnoutRiskTableProps } }; + const handleSort = (column: SortColumn) => { + if (sortColumn === column) { + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); + } else { + setSortColumn(column); + setSortDirection('desc'); + } + }; + + const filteredAndSortedContributors = useMemo(() => { + return contributors + .filter((c) => { + const matchesSearch = c.username.toLowerCase().includes(searchQuery.toLowerCase().trim()); + const matchesHighRisk = !highRiskOnly || c.riskLevel === 'High'; + return matchesSearch && matchesHighRisk; + }) + .sort((a, b) => { + let valA: number | string = 0; + let valB: number | string = 0; + + switch (sortColumn) { + case 'username': + valA = a.username.toLowerCase(); + valB = b.username.toLowerCase(); + break; + case 'totalCommits': + valA = a.totalCommits; + valB = b.totalCommits; + break; + case 'commitShare': + valA = a.commitShare; + valB = b.commitShare; + break; + case 'highIntensityWeeks': + valA = a.highIntensityWeeks; + valB = b.highIntensityWeeks; + break; + case 'restWeeks': + valA = a.restWeeks; + valB = b.restWeeks; + break; + case 'burnoutScore': + default: + valA = a.burnoutScore; + valB = b.burnoutScore; + break; + } + + if (typeof valA === 'string' && typeof valB === 'string') { + return sortDirection === 'asc' ? valA.localeCompare(valB) : valB.localeCompare(valA); + } + + return sortDirection === 'asc' + ? (valA as number) - (valB as number) + : (valB as number) - (valA as number); + }); + }, [contributors, searchQuery, highRiskOnly, sortColumn, sortDirection]); + + const renderSortIcon = (column: SortColumn) => { + if (sortColumn !== column) { + return ( + + ); + } + return sortDirection === 'asc' ? ( + + ) : ( + + ); + }; + + const highRiskCount = useMemo( + () => contributors.filter((c) => c.riskLevel === 'High').length, + [contributors] + ); + return ( -
- -

- Contributor Workload & Burnout Risks -

+ {/* Header & Title */} +
+
+ +

+ Contributor Workload & Burnout Risks +

+ + Showing {filteredAndSortedContributors.length} of {contributors.length} + +
+ + {/* Search & High Risk Toggle Controls */} +
+
+ setSearchQuery(e.target.value)} + className="w-full pl-8 pr-7 py-1.5 rounded-xl border border-black/10 dark:border-white/10 bg-white/80 dark:bg-[#121212]/80 text-xs text-gray-900 dark:text-white placeholder-gray-400 focus:outline-none focus:ring-1 focus:ring-indigo-500" + /> + + {searchQuery && ( + + )} +
+ + +
+ {/* Table Container */}
- - - + + + + + - - - + + + + + + + - {contributors.map((c, i) => ( + {filteredAndSortedContributors.map((c, i) => ( {/* Contributor Profile */} @@ -190,6 +381,28 @@ export default function BurnoutRiskTable({ contributors }: BurnoutRiskTableProps ))}
ContributorWorkload Share
+ + + + Weekly Activity (12w)Intensity WeeksRest WeeksBurnout Risk + + + + + +
+ + {/* Empty State */} + {filteredAndSortedContributors.length === 0 && ( +
+ +

+ No contributors match your filters +

+

+ Try adjusting your search terms or toggling off "High Risk Only". +

+ +
+ )}
); From 32f1c034a04cff5ca389eb6f9f9d67fa1c74c5d1 Mon Sep 17 00:00:00 2001 From: pisum-sativum Date: Thu, 23 Jul 2026 22:59:22 +0530 Subject: [PATCH 09/13] style: fix prettier formatting in BurnoutRiskTable, InteractiveViewer, and achievements types --- components/InteractiveViewer.tsx | 30 ++++++++++++------------- components/burnout/BurnoutRiskTable.tsx | 2 +- types/achievements.ts | 12 +++++----- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/components/InteractiveViewer.tsx b/components/InteractiveViewer.tsx index 0f29e9459..0c77dfb5b 100644 --- a/components/InteractiveViewer.tsx +++ b/components/InteractiveViewer.tsx @@ -1,11 +1,11 @@ -'use client'; +'use client'; import React, { useState, useRef, ReactNode, useMemo, useEffect, type ReactElement } from 'react'; import { createPortal } from 'react-dom'; import { AnimatePresence } from 'framer-motion'; import VisualizationTooltip from './dashboard/VisualizationTooltip'; -// ── Parallax particle configuration ────────────────────────────────────────── +// ΓöÇΓöÇ Parallax particle configuration ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ // Particles are generated deterministically so SSR and client renders match, // preventing React hydration mismatches. const PARALLAX_PARTICLE_COUNT = 20; @@ -16,7 +16,7 @@ interface ParallaxParticle { y: number; // base Y position as percentage of container height size: number; // side length in px opacity: number; // resting opacity (intentionally subtle) - depth: number; // parallax depth multiplier (0–1); deeper = more shift on mouse move + depth: number; // parallax depth multiplier (0ΓÇô1); deeper = more shift on mouse move color: string; isCircle: boolean; // mix of rounded and square contribution cells } @@ -32,12 +32,12 @@ function buildParticles(): ParallaxParticle[] { // Spread particles across the container using prime-number strides x: (i * 17 + 11) % 100, y: (i * 23 + 7) % 100, - size: 4 + (i % 5) * 2, // range: 4–12 px + size: 4 + (i % 5) * 2, // range: 4ΓÇô12 px // Keep opacity low so particles never obscure the badge - opacity: 0.05 + (i % 4) * 0.025, // range: 0.05–0.125 + opacity: 0.05 + (i % 4) * 0.025, // range: 0.05ΓÇô0.125 // Vary depth so each "layer" of particles shifts by a different amount, // creating the illusion of 3-D depth. depth 0.1 = farthest; 0.7 = nearest. - depth: 0.1 + (i % 6) * 0.1, // range: 0.1–0.6 + depth: 0.1 + (i % 6) * 0.1, // range: 0.1ΓÇô0.6 color: colors[i % colors.length], isCircle: i % 4 === 0, }) @@ -118,10 +118,10 @@ export default function InteractiveViewer({ setMounted(true); }, []); - // Stable particle list — generated once on mount, never re-shuffled. + // Stable particle list ΓÇö generated once on mount, never re-shuffled. const particles = useMemo((): ParallaxParticle[] => buildParticles(), []); - // ── Parallax math ────────────────────────────────────────────────────────── + // ΓöÇΓöÇ Parallax math ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ // Offset from center: at mousePos.x = 0.5, offset = 0 (no shift). // At the left edge (0), offset = -STRENGTH/2; at right (1), offset = +STRENGTH/2. const hoverParallaxX = (mousePos.x - 0.5) * PARALLAX_STRENGTH; @@ -132,7 +132,7 @@ export default function InteractiveViewer({ const parallaxX = hoverParallaxX + pan.x; const parallaxY = hoverParallaxY + pan.y; - // ── Keyboard navigation ──────────────────────────────────────────────────── + // ΓöÇΓöÇ Keyboard navigation ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ const handleKeyDown = (e: React.KeyboardEvent): void => { // Ignore if user is typing in an input or textarea if (['INPUT', 'TEXTAREA'].includes(document.activeElement?.tagName || '')) return; @@ -205,7 +205,7 @@ export default function InteractiveViewer({ e.preventDefault(); }; - // ── Pointer events ───────────────────────────────────────────────────────── + // ΓöÇΓöÇ Pointer events ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ const handlePointerDown = (e: React.PointerEvent): void => { isDragging.current = true; setIsDraggingState(true); @@ -357,7 +357,7 @@ export default function InteractiveViewer({ onDoubleClick={handleDoubleClick} style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }} > - {/* ── Parallax background layer ────────────────────────────────────────── + {/* ΓöÇΓöÇ Parallax background layer ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ This layer renders behind the card content (DOM order + z-index). It reacts to the cursor without touching the badge SVG or its animations. */}