From 57e855c7baf4d2bc1dbfbec3b19e03d905848b65 Mon Sep 17 00:00:00 2001
From: shravanithouta108
Date: Sat, 13 Jun 2026 09:27:44 +0530
Subject: [PATCH 01/45] feat: add Community Activity Score (Issue #138)
Closes #138
Signed-off-by: shravanithouta108
---
.github/workflows/compute-activity-scores.yml | 32 ++
agent/scripts/compute-activity-scores.js | 155 +++++
data/community_activity.json | 1 +
index.html | 447 +++++---------
src/js/app.js | 544 +++++++-----------
src/styles.css | 195 ++-----
6 files changed, 571 insertions(+), 803 deletions(-)
create mode 100644 .github/workflows/compute-activity-scores.yml
create mode 100644 agent/scripts/compute-activity-scores.js
create mode 100644 data/community_activity.json
diff --git a/.github/workflows/compute-activity-scores.yml b/.github/workflows/compute-activity-scores.yml
new file mode 100644
index 0000000000..c531b2cfb1
--- /dev/null
+++ b/.github/workflows/compute-activity-scores.yml
@@ -0,0 +1,32 @@
+name: Compute Community Activity Scores
+
+on:
+ schedule:
+ - cron: '0 0 * * *' # Daily at midnight
+ workflow_dispatch: # Manual trigger
+
+jobs:
+ compute-scores:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+
+ - name: Setup Node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.2.0
+ with:
+ node-version: '18'
+
+ - name: Run activity score computation
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: node agent/scripts/compute-activity-scores.js
+
+ - name: Commit updated scores
+ run: |
+ git config --local user.email "action@github.com"
+ git config --local user.name "GitHub Action"
+ git add data/community_activity.json
+ git diff --staged --quiet || git commit -m "chore: update community activity scores"
+ git push
\ No newline at end of file
diff --git a/agent/scripts/compute-activity-scores.js b/agent/scripts/compute-activity-scores.js
new file mode 100644
index 0000000000..52bd86d0e3
--- /dev/null
+++ b/agent/scripts/compute-activity-scores.js
@@ -0,0 +1,155 @@
+// @ts-check
+/* eslint-env node */
+// Computes community activity scores for all GSoC orgs
+// Run via GitHub Actions daily
+
+const fs = require('node:fs');
+const path = require('node:path');
+const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
+const OUTPUT_FILE = path.join(__dirname, '../../data/community_activity.json');
+
+if (!GITHUB_TOKEN) {
+ console.error('โ GITHUB_TOKEN is required');
+ process.exit(1);
+}
+
+const headers = {
+ 'Authorization': `token ${GITHUB_TOKEN}`,
+ 'Accept': 'application/vnd.github.v3+json',
+ 'User-Agent': 'gsoc-org-finder'
+};
+
+// Load orgs from org.js
+const orgDataRaw = fs.readFileSync(
+ path.join(__dirname, '../../src/js/org.js'), 'utf8'
+);
+
+// Extract github field from each org
+const githubRepos = [];
+const orgMatches = orgDataRaw.matchAll(/name:\s*"([^"]+)"[^}]+github:\s*"([^"]+)"/g);
+for (const m of orgMatches) {
+ const name = m[1];
+ const repo = m[2];
+ if (repo?.includes('/')) {
+ githubRepos.push({ name, repo });
+ }
+}
+
+async function fetchJSON(url) {
+ const res = await fetch(url, { headers });
+ if (!res.ok) return null;
+ return res.json();
+}
+
+function computeScore({ issueResponseDays, commitFrequency, prMergeRate, ideasFreshnessDays, starsGrowth }) {
+ const issueScore = Math.max(0, 100 - (issueResponseDays * 5));
+ const commitScore = Math.min(100, commitFrequency * 10);
+ const prScore = Math.min(100, prMergeRate * 100);
+ const ideasScore = Math.max(0, 100 - (ideasFreshnessDays * 0.5));
+ const growthScore = Math.min(100, starsGrowth * 2);
+
+ return Math.round(
+ issueScore * 0.3 +
+ commitScore * 0.2 +
+ prScore * 0.15 +
+ ideasScore * 0.2 +
+ growthScore * 0.15
+ );
+}
+
+function getTier(score) {
+ if (score >= 80) return { tier: 'very-active', label: '๐ข Very Active' };
+ if (score >= 60) return { tier: 'active', label: '๐ต Active' };
+ if (score >= 40) return { tier: 'moderate', label: '๐ก Moderate' };
+ return { tier: 'low', label: '๐ด Low Activity' };
+}
+
+async function analyzeOrg({ name, repo }) {
+ try {
+ const since = new Date(Date.now() - 90 * 86400000).toISOString();
+
+ const commits = await fetchJSON(
+ `https://api.github.com/repos/${repo}/commits?since=${since}&per_page=100`
+ );
+ const commitFrequency = Array.isArray(commits) ? commits.length / 90 : 0;
+
+ const issues = await fetchJSON(
+ `https://api.github.com/repos/${repo}/issues?state=closed&since=${since}&per_page=100`
+ );
+ let issueResponseDays = null;
+ if (Array.isArray(issues) && issues.length > 0) {
+ const responseTimes = issues
+ .filter(i => i.created_at && i.closed_at && !i.pull_request)
+ .map(i => (+new Date(i.closed_at) - +new Date(i.created_at)) / 86400000);
+ if (responseTimes.length > 0) {
+ issueResponseDays = responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length;
+ }
+ }
+
+ // Fetch PRs
+ const prs = await fetchJSON(
+ `https://api.github.com/repos/${repo}/pulls?state=closed&per_page=100`
+ );
+ let prMergeRate = null;
+ if (Array.isArray(prs) && prs.length > 0) {
+ const recentPrs = prs.filter(p => p.closed_at && new Date(p.closed_at) >= new Date(since));
+ if (recentPrs.length > 0) {
+ const merged = recentPrs.filter(p => p.merged_at).length;
+ prMergeRate = merged / recentPrs.length;
+ }
+ }
+
+ const repoData = await fetchJSON(`https://api.github.com/repos/${repo}`);
+ const starsGrowth = repoData ? Math.min(50, repoData.stargazers_count / 1000) : 0;
+
+ const ideasFreshnessDays = repoData?.pushed_at
+ ? (Date.now() - new Date(repoData.pushed_at)) / 86400000
+ : 60;
+
+ const score = computeScore({
+ issueResponseDays: issueResponseDays ?? 14,
+ commitFrequency,
+ prMergeRate: prMergeRate ?? 0.5,
+ ideasFreshnessDays,
+ starsGrowth
+ });
+ const { tier, label } = getTier(score);
+
+ return {
+ score,
+ tier,
+ label,
+ signals: {
+ issueResponseDays: Math.round(issueResponseDays),
+ commitFrequency: Math.round(commitFrequency * 10) / 10,
+ prMergeRate: Math.round(prMergeRate * 100),
+ ideasFreshnessDays: Math.round(ideasFreshnessDays),
+ starsGrowth: Math.round(starsGrowth * 10) / 10
+ },
+ lastUpdated: new Date().toISOString()
+ };
+ } catch (e) {
+ console.error(`Error analyzing ${name}:`, e.message);
+ return null;
+ }
+}
+
+async function main() {
+ console.log(`Analyzing ${githubRepos.length} orgs...`);
+ const results = {};
+
+ for (const org of githubRepos) {
+ console.log(`Processing: ${org.name}`);
+ const data = await analyzeOrg(org);
+ if (data) results[org.name] = data;
+ await new Promise(r => setTimeout(r, 500));
+ }
+
+ fs.writeFileSync(OUTPUT_FILE, JSON.stringify(results, null, 2));
+ console.log(`โ
Done! Wrote ${Object.keys(results).length} orgs to community_activity.json`);
+}
+
+main().catch((err) => {
+ console.error(err);
+ process.exit(1);
+});
diff --git a/data/community_activity.json b/data/community_activity.json
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/data/community_activity.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/index.html b/index.html
index 4a79ce1cc8..ef81fb4022 100644
--- a/index.html
+++ b/index.html
@@ -307,11 +307,7 @@
.timeline-grid { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.timeline-grid span { font-size: 0.75rem; font-weight: 500; color: #666; background: #fff; border: 1px solid #eee; padding: 0.3rem 0.7rem; border-radius: 0.5rem; }
- .timeline-grid span.current-year { background: #fffbeb; color: #92400e; border-color: #fbbf24; font-weight: 700; }
- .timeline-grid a.timeline-year-link { font-size: 0.75rem; font-weight: 500; color: #666; background: #fff; border: 1px solid #eee; padding: 0.3rem 0.7rem; border-radius: 0.5rem; text-decoration: none; display: inline-block; cursor: pointer; transition: border-color 0.15s, color 0.15s, background 0.15s; }
- .timeline-grid a.timeline-year-link:hover { border-color: var(--orange); color: var(--orange); background: var(--orange-pale); }
- .timeline-grid a.timeline-year-link.current-year { background: #fffbeb; color: #92400e; border-color: #fbbf24; font-weight: 700; }
- .timeline-grid a.timeline-year-link.current-year:hover { border-color: #92400e; background: #fef3c7; color: #78350f; }
+ .timeline-grid span.current-year { background: #fffbeb; color: #d97706; border-color: #fef3c7; font-weight: 700; }
.modal-footer { padding: 2rem 3rem 3rem; background: #f9f9f9; display: flex; gap: 1rem; border-top: 1px solid #eee; flex-shrink: 0; }
.modal-cta { flex: 1; padding: 1rem; border-radius: 1rem; font-size: 0.8rem; font-weight: 800; text-align: center; text-transform: uppercase; letter-spacing: 0.05em; transition: all 0.2s; }
@@ -445,6 +441,26 @@
.complexity-badge.beginner { background: #dcfce7; color: #166534; }
.complexity-badge.intermediate { background: #fef9c3; color: #854d0e; }
.complexity-badge.advanced { background: #fee2e2; color: #991b1b; }
+
+ /* Activity Badges */
+.activity-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 2px 8px;
+ border-radius: 99px;
+ font-size: 0.65rem;
+ font-weight: 700;
+ letter-spacing: 0.03em;
+}
+.tier-very-active { background: #dcfce7; color: #166534; }
+.tier-active { background: #dbeafe; color: #1d4ed8; }
+.tier-moderate { background: #fef9c3; color: #854d0e; }
+.tier-low { background: #fee2e2; color: #991b1b; }
+.dark .tier-very-active { background: #14532d; color: #86efac; }
+.dark .tier-active { background: #1e3a5f; color: #93c5fd; }
+.dark .tier-moderate { background: #422006; color: #fcd34d; }
+.dark .tier-low { background: #450a0a; color: #fca5a5; }
/* Bookmark Button */
.bookmark-btn { transition: all 0.2s; font-size: 1.25rem; }
@@ -912,13 +928,6 @@
stroke-dasharray: none;
stroke-dashoffset: 0;
}
- .section-scroll-btn {
- transition: none !important;
- }
- .section-scroll-btn:hover,
- .section-scroll-btn:active {
- transform: none !important;
- }
}
/* Smooth fading for search bar active animation on focus */
@@ -952,18 +961,9 @@
align-items: center;
justify-content: center;
box-shadow: 0 8px 24px rgba(157, 67, 0, 0.35);
- transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
- overflow: hidden;
-}
-.section-scroll-btn:hover {
- transform: translateY(-2px);
- box-shadow: 0 12px 32px rgba(157, 67, 0, 0.45);
-}
-.section-scroll-btn:active {
- transform: translateY(1px) scale(0.92);
- box-shadow: 0 4px 12px rgba(157, 67, 0, 0.2);
- filter: brightness(0.95);
+ transition: all 0.25s ease;
}
+.section-scroll-btn:hover { transform: translateY(-2px); }
#nextSectionBtn { bottom: calc(var(--section-fab-bottom) + env(safe-area-inset-bottom)); }
#prevSectionBtn {
bottom: calc(var(--section-fab-bottom) + var(--section-fab-size) + var(--section-fab-gap) + env(safe-area-inset-bottom));
@@ -977,25 +977,6 @@
.dark .section-scroll-btn:hover {
box-shadow: 0 12px 32px rgba(194, 65, 12, 0.45);
}
-.dark .section-scroll-btn:active {
- transform: translateY(1px) scale(0.92);
- box-shadow: 0 4px 12px rgba(194, 65, 12, 0.2);
- filter: brightness(0.95);
-}
-
-/* Dark mode - Section scroll indicator */
-.dark .section-scroll-indicator {
- background: rgba(24, 24, 27, 0.88);
- border-color: rgba(249, 115, 22, 0.25);
- color: #fb923c;
-}
-.dark .section-scroll-indicator-dot {
- background: linear-gradient(135deg, #fb923c, #f97316);
- box-shadow: 0 0 0 4px rgba(249, 115, 22, 0.12);
-}
-.dark .section-scroll-indicator-value {
- color: #e4e4e7;
-}
.section-scroll-btn:disabled {
opacity: 0.3;
@@ -1003,44 +984,6 @@
pointer-events: none;
}
-/* Custom ripple effect inside scroll buttons */
-.section-scroll-btn .click-ripple {
- position: absolute;
- border-radius: 50%;
- transform: scale(0);
- animation: ripple-animation 0.5s cubic-bezier(0.1, 0.8, 0.3, 1);
- background-color: rgba(255, 255, 255, 0.45);
- pointer-events: none;
-}
-
-@keyframes ripple-animation {
- to {
- transform: scale(2.5);
- opacity: 0;
- }
-}
-
-/* Icon animations inside scroll buttons on click */
-@keyframes bounce-down {
- 0% { transform: translateY(0); }
- 30% { transform: translateY(4px); }
- 70% { transform: translateY(-2px); }
- 100% { transform: translateY(0); }
-}
-@keyframes bounce-up {
- 0% { transform: translateY(0); }
- 30% { transform: translateY(-4px); }
- 70% { transform: translateY(2px); }
- 100% { transform: translateY(0); }
-}
-
-.section-scroll-btn.bounce-down-active .material-symbols-outlined {
- animation: bounce-down 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
-}
-.section-scroll-btn.bounce-up-active .material-symbols-outlined {
- animation: bounce-up 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
-}
-
.section-scroll-indicator {
position: fixed;
right: calc(var(--section-fab-right) + env(safe-area-inset-right));
@@ -1170,13 +1113,12 @@
@@ -1315,9 +1257,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- No languages selected
-
-
-
-
-
-
-
Showing 184 of 184 organizations
@@ -1435,6 +1380,7 @@
-
+
Last updated: pending refresh
- โธ GSoC selection complete. Data won't update frequently.
+ โธ GSoC selection complete. Data won't update frequently.
@@ -1914,10 +1860,25 @@ Join Discord
-
-
-
-
+
+
@@ -1953,8 +1914,9 @@
Join Discord
- Participation Timeline ยท click a year to explore its projects
+ Participation Timeline
+
Mentors & Contact
@@ -2110,9 +2072,18 @@
@@ -4965,6 +4878,7 @@ Keyboard Shortcuts
});
+
@@ -5093,9 +5007,15 @@ Keyboard Shortcuts
function scrollToSectionIndex(index) {
const targetSection = sections[index];
+
if (!targetSection) return;
- // Use scrollIntoView to respect scroll-margin-top defined in CSS
- targetSection.scrollIntoView({ behavior: "smooth", block: "start" });
+
+ const targetTop = Math.max(0, targetSection.offsetTop - getSectionDetectionOffset());
+
+ window.scrollTo({
+ top: targetTop,
+ behavior: "smooth"
+ });
}
function updateSectionIndicator() {
@@ -5155,32 +5075,15 @@ Keyboard Shortcuts
});
}
- let isNavClicking = false;
- let navClickTimer = null;
-
document.querySelectorAll(".desktop-nav-link").forEach(link => {
- link.addEventListener("click", (e) => {
- e.preventDefault(); // stop browser anchor scroll
- isNavClicking = true;
- clearTimeout(navClickTimer);
+ link.addEventListener("click", () => {
setCurrentSectionById(link.dataset.section);
- scrollToSectionIndex(currentSection);
- navClickTimer = setTimeout(() => {
- isNavClicking = false;
- }, 1500);
});
});
document.querySelectorAll(".mobile-menu-link").forEach(link => {
- link.addEventListener("click", (e) => {
- e.preventDefault(); // stop browser anchor scroll
- isNavClicking = true;
- clearTimeout(navClickTimer);
+ link.addEventListener("click", () => {
setCurrentSectionById(link.dataset.section);
- scrollToSectionIndex(currentSection);
- navClickTimer = setTimeout(() => {
- isNavClicking = false;
- }, 1500);
if (typeof globalThis.toggleMenu === "function") {
globalThis.toggleMenu();
@@ -5189,104 +5092,26 @@ Keyboard Shortcuts
});
function updateCurrentSection() {
- if (isNavClicking) return;
- const offset = getSectionDetectionOffset() + 10;
- let found = 0;
- let closestDistance = Infinity;
- sections.forEach((section, index) => {
- const rect = section.getBoundingClientRect();
- if (rect.top <= offset) {
- const distance = offset - rect.top;
- if (distance < closestDistance) {
- closestDistance = distance;
- found = index;
- }
- }
- });
- currentSection = found;
- updateNavState();
- }
-
- const visibleSections = new Set();
+ const scrollPos = window.scrollY + getSectionDetectionOffset();
- const observer = new IntersectionObserver((entries) => {
- if (isNavClicking) return;
- entries.forEach(entry => {
- const index = sections.indexOf(entry.target);
- if (index === -1) return;
- if (entry.isIntersecting) {
- visibleSections.add(index);
- } else {
- visibleSections.delete(index);
- }
- });
- if (visibleSections.size === 0) {
- return;
- }
- currentSection = Math.min(...visibleSections);
- updateNavState();
- }, {
- rootMargin: `-${(mainHeader?.clientHeight || 0) + 10}px 0px -60% 0px`,
- threshold: 0
+ sections.forEach((section, index) => {
+ if (scrollPos >= section.offsetTop) currentSection = index;
});
- sections.forEach(section => observer.observe(section));
- window.addEventListener("resize", updateCurrentSection);
- updateCurrentSection();
-
- const nextBtn = document.getElementById("nextSectionBtn");
- const prevBtn = document.getElementById("prevSectionBtn");
-
- function addClickFeedback(btn, direction) {
- if (!btn) return;
- btn.addEventListener("click", (e) => {
- const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
-
- // Icon bounce animation
- const animClass = direction === "up" ? "bounce-up-active" : "bounce-down-active";
- if (!prefersReducedMotion) {
- btn.classList.add(animClass);
- }
-
- // Create ripple effect
- if (!prefersReducedMotion) {
- const ripple = document.createElement("span");
- ripple.classList.add("click-ripple");
- const rect = btn.getBoundingClientRect();
- const size = Math.max(rect.width, rect.height);
- ripple.style.width = ripple.style.height = `${size}px`;
-
- const isKeyboardClick = e.detail === 0;
- const x = (!isKeyboardClick && e.clientX !== undefined && e.clientX !== null) ? (e.clientX - rect.left - size / 2) : (rect.width / 2 - size / 2);
- const y = (!isKeyboardClick && e.clientY !== undefined && e.clientY !== null) ? (e.clientY - rect.top - size / 2) : (rect.height / 2 - size / 2);
- ripple.style.left = `${x}px`;
- ripple.style.top = `${y}px`;
-
- btn.appendChild(ripple);
-
- ripple.addEventListener("animationend", () => {
- ripple.remove();
- });
-
- btn.addEventListener("animationend", (event) => {
- if (event.animationName === "bounce-up" || event.animationName === "bounce-down") {
- btn.classList.remove(animClass);
- }
- }, { once: true });
- }
- });
+ updateNavState();
}
- addClickFeedback(nextBtn, "down");
- addClickFeedback(prevBtn, "up");
+ window.addEventListener("scroll", updateCurrentSection, { passive: true });
+ window.addEventListener("resize", updateCurrentSection);
+ updateCurrentSection();
- nextBtn?.addEventListener("click", () => {
+ document.getElementById("nextSectionBtn")?.addEventListener("click", () => {
if (currentSection < sections.length - 1) {
scrollToSectionIndex(currentSection + 1);
}
});
- prevBtn?.addEventListener("click", () => {
+ document.getElementById("prevSectionBtn")?.addEventListener("click", () => {
if (currentSection > 0) {
scrollToSectionIndex(currentSection - 1);
} else {
diff --git a/src/js/app.js b/src/js/app.js
index 8a51a0ce45..7eed97572c 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -466,21 +466,59 @@ function cleanCache() {
}
cleanCache();
-async function checkAPI() {
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+// COMMUNITY ACTIVITY DATA
+// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+let communityActivity = {}; // will hold the parsed community_activity.json
+
+async function loadCommunityActivity() {
try {
- const r = await fetch(`${API}?repo=django/django`);
- const banner = document.getElementById('apiBanner');
- if (!banner) return;
- if (r.ok) {
- banner.className = 'api-banner api-ok';
- document.getElementById('apiStrong').textContent = 'โ GitHub API Connected';
- document.getElementById('apiText').textContent = 'Live stats (stars, forks, good first issues) available for all visitors.';
- const fetchBtn = document.getElementById('fetchBtn');
- if (fetchBtn) fetchBtn.style.display = 'flex';
- } else {
- banner.className = 'api-banner api-warn';
- document.getElementById('apiStrong').textContent = 'โ API Error';
- document.getElementById('apiText').textContent = 'Add GITHUB_TOKEN in Vercel dashboard and redeploy.';
+ const bust = new Date().toISOString().slice(0, 10);
+ const res = await fetch('/data/community_activity.json?v=' + bust);
+ if (!res.ok) return;
+ communityActivity = await res.json();
+ applyFilters();
+ } catch (e) {
+ console.warn('community_activity.json not yet available:', e);
+ }
+}
+
+function getCommunityBadge(org) {
+ if (!org.github) return '';
+ const d = communityActivity[org.name];
+ if (!d || d.score === undefined) return ''; // no data โ hide badge entirely
+ const map = {
+ 'very-active': { dot: '๐ข', label: 'Very Active', cls: 'bca-very' },
+ 'active': { dot: '๐ต', label: 'Active', cls: 'bca-active' },
+ 'moderate': { dot: '๐ก', label: 'Moderate', cls: 'bca-mod' },
+ 'low': { dot: '๐ด', label: 'Low Activity', cls: 'bca-low' },
+ };
+ const tier = map[d.tier] || map['low'];
+ return `${tier.dot} ${tier.label}`;
+}
+
+function getCommunityData(org) {
+ if (!org.github) return null;
+ return communityActivity[org.name] || null;
+}
+
+// Expose to global scope for HTML onclick handlers and debugging
+globalThis.matchAllLanguages = matchAllLanguages;
+
+async function checkAPI(){
+ try{
+ const r=await fetch(`${API}?repo=django/django`);
+ const banner=document.getElementById('apiBanner');
+ if(!banner) return;
+ if(r.ok){
+ banner.className='api-banner api-ok';
+ document.getElementById('apiStrong').textContent='โ GitHub API Connected';
+ document.getElementById('apiText').textContent='Live stats (stars, forks, good first issues) available for all visitors.';
+ document.getElementById('fetchBtn').style.display='flex';
+ }else{
+ banner.className='api-banner api-warn';
+ document.getElementById('apiStrong').textContent='โ API Error';
+ document.getElementById('apiText').textContent='Add GITHUB_TOKEN in Vercel dashboard and redeploy.';
}
} catch {
const banner = document.getElementById('apiBanner');
@@ -713,6 +751,20 @@ function handleNavigationUp(e) {
updateCardFocus();
}
+function syncBookmark(name, shouldAdd) {
+ if (!name) return;
+ if (shouldAdd) bookmarkedSet.add(name);
+ else bookmarkedSet.delete(name);
+ localStorage.setItem('bookmarks', JSON.stringify([...bookmarkedSet]));
+ refreshOrgGridAfterBookmarkChange();
+ renderWatchlist();
+ updateAIInsights();
+}
+
+function renderMentorFinder() {
+ // Mentor finder is rendered by index.html's inline script
+}
+
function handleGlobalKeydown(e) {
if (e.key === 'Escape' && handleEscapeKey(e)) return;
@@ -776,15 +828,18 @@ function parseStoredBookmarks() {
}
}
-function syncBookmark(name, shouldAdd) {
- if (!name) return;
- if (shouldAdd) bookmarkedSet.add(name);
- else bookmarkedSet.delete(name);
- localStorage.setItem('bookmarks', JSON.stringify([...bookmarkedSet]));
-
- refreshOrgGridAfterBookmarkChange();
- renderWatchlist();
- updateAIInsights();
+function applySecondarySort(a, b, sortType) {
+ if(sortType==='years-desc') return b.years - a.years;
+ if(sortType==='years-asc') return a.years - b.years;
+ if(sortType==='comp-low') return ['chill','moderate','hot'].indexOf(a.competition) - ['chill','moderate','hot'].indexOf(b.competition);
+ if(sortType==='stars') return (b._gh?.stars||0) - (a._gh?.stars||0);
+ if(sortType==='gfi') return (b._gh?.gfi||0) - (a._gh?.gfi||0);
+ if(sortType==='activity-score') {
+ const sa = getCommunityData(a)?.score ?? -1;
+ const sb = getCommunityData(b)?.score ?? -1;
+ return sb - sa;
+ }
+ return a.name.localeCompare(b.name);
}
globalThis.toggleBookmark = function (e, name) {
@@ -872,7 +927,7 @@ function renderWatchlist() {
? safeHTML`
`
: safeHTML`corporate_fare`;
- item.innerHTML = safeHTML`
+ item.innerHTML = safeHTML`
${logoHtml}
@@ -888,20 +943,10 @@ function renderWatchlist() {
${topTags}
-
- calendar_today
- ${String(org.years)}y in GSoC
-
-
- bar_chart
- ${org.competition || 'โ'}
-
+ ${String(org.years)}y in GSoC
+ ${org.competition || 'โ'}
-
+ ${rawHTML(getCommunityBadge(org))}
`;
@@ -1105,15 +1150,6 @@ function applyFilters() {
}
}
-function applySecondarySort(a, b, sortType) {
- if (sortType === 'years-desc') return b.years - a.years;
- if (sortType === 'years-asc') return a.years - b.years;
- if (sortType === 'comp-low') return ['chill', 'moderate', 'hot'].indexOf(a.competition) - ['chill', 'moderate', 'hot'].indexOf(b.competition);
- if (sortType === 'stars') return (b._gh?.stars || 0) - (a._gh?.stars || 0);
- if (sortType === 'gfi') return (b._gh?.gfi || 0) - (a._gh?.gfi || 0);
- return a.name.localeCompare(b.name);
-}
-
function renderOrgs(reset = true) {
const grid = document.getElementById('orgGrid');
const emptyState = document.getElementById('emptyState');
@@ -1196,6 +1232,16 @@ function renderOrgs(reset = true) {
document.getElementById('loadMoreContainer').style.display = (visibleCount < filteredOrgs.length) ? 'flex' : 'none';
}
+function handleImgError(img, orgName) {
+ if (!img.dataset.triedClearbit) {
+ img.dataset.triedClearbit = 'true';
+ const domain = orgName.toLowerCase().replace(/[^a-z0-9]/g, '') + '.org';
+ img.src = `https://logo.clearbit.com/${domain}`;
+ } else {
+ img.style.display = 'none';
+ }
+}
+
function attachOrgCardListeners(root) {
// Image error fallbacks
root.querySelectorAll('img[data-org-name]').forEach(img => {
@@ -1246,19 +1292,27 @@ function attachOrgCardListeners(root) {
}
}
-function handleImgError(img, orgName) {
- if (img.dataset.triedClearbit) {
- img.style.display = 'none';
- const placeholder = img.parentElement.querySelector('.logo-placeholder');
- if (placeholder) {
- placeholder.style.display = 'flex';
- placeholder.textContent = orgName.split(' ').map(n => n[0]).join('').substring(0, 2).toUpperCase();
- }
+function toggleChip(k){
+ const el=document.getElementById('chip-'+k);
+ if(!el) return;
+
+ if(activeChip===k){
+ activeChip=null;
+ el.classList.remove('bg-orange-600','text-white');
+ el.classList.add('bg-surface-container-highest');
} else {
- img.dataset.triedClearbit = 'true';
- const domain = orgName.toLowerCase().replace(/[^a-z0-9]/g, '') + '.org';
- img.src = `https://logo.clearbit.com/${domain}`;
+ if(activeChip){
+ const prev=document.getElementById('chip-'+activeChip);
+ if(prev){
+ prev.classList.remove('bg-orange-600','text-white');
+ prev.classList.add('bg-surface-container-highest');
+ }
+ }
+ activeChip=k;
+ el.classList.add('bg-orange-600','text-white');
+ el.classList.remove('bg-surface-container-highest');
}
+ applyFilters();
}
// Global capturing image error event listener to replace inline onerror attributes
@@ -1472,61 +1526,34 @@ globalThis.openModal = function (name, triggerElement = null) {
ideasBtn.style.display = 'none';
}
}
-
- if (repoBtn) {
- if (repoHref) {
- repoBtn.href = repoHref;
- repoBtn.style.display = 'inline-flex';
- } else {
- repoBtn.removeAttribute('href');
- repoBtn.style.display = 'none';
+
+ // Community Activity section in modal
+ const mCommunity = document.getElementById('mCommunity');
+ if (mCommunity) {
+ const cd = getCommunityData(org);
+ if (cd) {
+ const tierMap = {
+ 'very-active': '๐ข Very Active',
+ 'active': '๐ต Active',
+ 'moderate': '๐ก Moderate',
+ 'low': '๐ด Low Activity',
+ };
+ mCommunity.style.display = 'block';
+ mCommunity.innerHTML = `
+ Community Activity
+
+
Overall Score${cd.score} / 100
+
Tier${tierMap[cd.tier] || cd.tier}
+
Avg Issue Response${cd.signals?.issueResponseDays !== null ? escapeHtml(String(cd.signals.issueResponseDays)) + ' days' : 'โ'}
+
Commit Frequency (90d)${cd.signals?.commitFrequency !== null ? escapeHtml(String(cd.signals.commitFrequency)) + '/day' : 'โ'}
+
PR Merge Rate${cd.signals?.prMergeRate !== null ? escapeHtml(String(cd.signals.prMergeRate)) + '%' : 'โ'}
+
Last updated${cd.lastUpdated ? escapeHtml(String(cd.lastUpdated)) : 'โ'}
+
`;
+ } else {
+ mCommunity.style.display = 'none';
}
}
-
- // Copy Ideas Button
- const oldCopy = document.getElementById('mIdeasCopyBtn');
- if (oldCopy) oldCopy.remove();
- if (org.ideas && ideasBtn) {
- const copyBtn = document.createElement('button');
- copyBtn.id = 'mIdeasCopyBtn';
- copyBtn.innerHTML = 'content_copy Copy Link';
- copyBtn.className = 'modal-cta modal-repo-link flex items-center justify-center gap-2';
- copyBtn.addEventListener('click', () => {
- navigator.clipboard.writeText(org.ideas).then(() => {
- copyBtn.innerHTML = 'check_circle Copied!';
- copyBtn.style.background = '#dcfce7';
- copyBtn.style.color = '#166534';
- setTimeout(() => {
- copyBtn.innerHTML = 'content_copy Copy Link';
- copyBtn.style.background = '';
- copyBtn.style.color = '';
- }, 2000);
- });
- });
- ideasBtn.after(copyBtn);
- }
-
- renderMentorContactSection(org);
- openModalElement('orgModal', triggerElement);
-
- // Lazily retrieve GFIs if missing
- if (org.github && (org._gh?.gfi === null || org._gh?.gfi === undefined)) {
- const placeholder = document.getElementById('mGfiPlaceholder');
- if (placeholder) placeholder.textContent = 'โฆ';
- fetchGFI(org.github).then(gfi => {
- if (gfi !== null) {
- if (!org._gh) org._gh = {};
- org._gh.gfi = gfi;
- const placeholders = document.querySelectorAll('.metric-card #mGfiPlaceholder');
- placeholders.forEach(p => p.textContent = fmt(gfi));
- renderOrgs(false);
- renderCompareModal();
- } else {
- const placeholders = document.querySelectorAll('.metric-card #mGfiPlaceholder');
- placeholders.forEach(p => p.textContent = 'โ');
- }
- });
- }
+ openModalElement('orgModal');
};
function closeModal() {
@@ -1968,32 +1995,32 @@ globalThis.fetchAllIssues = async function () {
updateStats();
};
-async function loadCachedIssues() {
- if (allIssues.length || issuesFetching) return;
- try {
+async function loadCachedIssues(){
+ if(allIssues.length||issuesFetching) return;
+ try{
const res = await fetch('/data/issues.json');
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
- const data = await res.json();
- if (!Array.isArray(data.issues)) return;
-
- const orgByGithub = new Map(ORGS.map(o => [o.github?.toLowerCase(), o]));
- const orgByName = new Map(ORGS.map(o => [o.name?.toLowerCase(), o]));
-
- allIssues = data.issues.map(issue => {
- const key = issue.github?.toLowerCase() || issue.repo?.toLowerCase() || issue.org?.toLowerCase();
- const orgMeta = orgByGithub.get(key) || orgByName.get(issue.org?.toLowerCase());
- const owner = githubOwnerFromValue(issue.github || issue.repo);
- return {
- title: issue.title || '',
- url: issue.url || '',
- org: issue.org || '',
- orgCat: orgMeta?.cat || '',
- orgTags: orgMeta?.tags || [],
- logo: owner ? `https://github.com/${owner}.png?size=64` : '',
- repo: issue.repo || issue.github || '',
- created_at: issue.created_at || '',
- labels: Array.isArray(issue.labels) ? issue.labels.map(l => typeof l === 'string' ? l : (l.name || '')) : [],
- comments: typeof issue.comments === 'number' ? issue.comments : Number(issue.comments || 0),
+ if(!res.ok) throw new Error(`HTTP ${res.status}`);
+ const data=await res.json();
+ if(!Array.isArray(data.issues)) return;
+
+ const orgByGithub=new Map(ORGS.map(o=>[o.github?.toLowerCase(),o]));
+ const orgByName=new Map(ORGS.map(o=>[o.name?.toLowerCase(),o]));
+
+ allIssues=data.issues.map(issue=>{
+ const key=issue.github?.toLowerCase()||issue.repo?.toLowerCase()||issue.org?.toLowerCase();
+ const orgMeta=orgByGithub.get(key)||orgByName.get(issue.org?.toLowerCase());
+ const owner=githubOwnerFromValue(issue.github||issue.repo);
+ return{
+ title:issue.title||'',
+ url:issue.url||'',
+ org:issue.org||'',
+ orgCat:orgMeta?.cat||'',
+ orgTags:orgMeta?.tags||[],
+ logo:owner?`https://github.com/${owner}.png?size=64`:'',
+ repo:issue.repo||issue.github||'',
+ created_at:issue.created_at||'',
+ labels:Array.isArray(issue.labels)?issue.labels.map(l=>typeof l==='string'?l:(l.name||'')):[],
+ comments:typeof issue.comments==='number'?issue.comments:Number(issue.comments||0),
};
});
@@ -2223,196 +2250,54 @@ async function loadMentorData() {
}
}
-function renderMentorFinder() {
- const container = document.getElementById('mentorsContainer');
- if (!container || mentorDataState !== 'loaded') return;
-
- const search = (document.getElementById('mentorSearchInput')?.value || '').toLowerCase().trim();
- const channel = document.getElementById('mentorChannelFilter')?.value || '';
-
- const matched = [];
- Object.entries(MENTOR_DATA).forEach(([orgName, mentors]) => {
- if (!Array.isArray(mentors)) return;
- mentors.forEach(m => {
- const matchSearch = !search ||
- m.name.toLowerCase().includes(search) ||
- orgName.toLowerCase().includes(search) ||
- (m.channels || []).some(c => String(c.value).toLowerCase().includes(search));
-
- const matchChannel = !channel ||
- (m.channels || []).some(c => c.type === channel);
-
- if (matchSearch && matchChannel) {
- matched.push({ orgName, ...m });
- }
- });
- });
-
- container.innerHTML = '';
-
- if (matched.length === 0) {
- container.innerHTML = `
-
-
person_search
-
No Mentors Match Your Query
-
Try adjusting your keyword filter or changing communication channels.
-
`;
- return;
- }
-
- matched.slice(0, 30).forEach(m => {
- const card = document.createElement('div');
- card.className = 'mentor-card p-6 bg-white dark:bg-zinc-950 border border-zinc-200 dark:border-zinc-800 rounded-2xl flex flex-col justify-between hover:shadow-lg transition-all animate-fade-up';
-
- const channelsHtml = (m.channels || []).map(c => {
- const icon = CHANNEL_ICONS[c.type] || '๐ฌ';
- const isUrl = sanitizeHrefUrl(c.value);
- if (isUrl) {
- return safeHTML`
- ${icon} ${c.type}
- `;
- } else {
- return safeHTML`
- ${icon} ${c.value}
- `;
- }
- });
-
- card.innerHTML = safeHTML`
-
-
-
${m.name}
- ${m.role || 'Mentor'}
-
-
${m.orgName}
-
-
- ${channelsHtml}
-
`;
- container.appendChild(card);
- });
-}
-
-function renderMentorContactSection(org) {
- const container = document.getElementById('mMentorsSection');
- if (!container) return;
-
- container.innerHTML = '';
- const mentors = MENTOR_DATA[org.name];
-
- if (!mentors || !mentors.length) {
- container.innerHTML = safeHTML`
-
-
Contact details unavailable for ${org.name}. Browse their GSoC Ideas Page for mentor details.
-
`;
- return;
- }
-
- mentors.forEach(m => {
- const card = document.createElement('div');
- card.className = 'mentor-card p-4 bg-zinc-50 dark:bg-zinc-900/40 border border-zinc-100 dark:border-zinc-800 rounded-xl mb-3 last:mb-0';
-
- const channelsHtml = (m.channels || []).map(c => {
- const icon = CHANNEL_ICONS[c.type] || '๐ฌ';
- const isUrl = sanitizeHrefUrl(c.value);
- if (isUrl) {
- return safeHTML`
- ${icon} ${c.type}: Connect
- `;
- } else {
- return safeHTML`
- ${icon} ${c.type}: ${c.value}
- `;
- }
- });
-
- card.innerHTML = safeHTML`
-
-
${m.name}
- ${m.role || 'Mentor'}
-
-
- ${channelsHtml}
-
`;
- container.appendChild(card);
- });
-}
-
-// Stale Data Banner Notice Check
function applyStaleDataNotice() {
- const now = new Date();
- if (now > GSOC_SELECTION_DATE) {
- const alertBanner = document.getElementById('selectionStaleAlert');
- if (alertBanner) alertBanner.classList.remove('hidden');
-
- const mentorStaleNote = document.getElementById('mentorStaleNote');
- const mentorBannerDot = document.getElementById('mentorBannerDot');
- if (mentorStaleNote) mentorStaleNote.classList.remove('hidden');
- if (mentorBannerDot) {
- mentorBannerDot.classList.remove('pulse-dot');
- mentorBannerDot.style.backgroundColor = '#a1a1aa'; // Zinc-400
- }
- }
-}
-
-// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-// INITIALIZATION
-// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-document.addEventListener('DOMContentLoaded', () => {
- // Sync bookmarks from storage initially
- ORGS.forEach(o => {
- if (o.github && ghCache[o.github]) {
- o._gh = ghCache[o.github];
- }
- });
-
- // Restore filter state from URL parameters
- (function restoreFiltersFromURL() {
- const params = new URLSearchParams(location.search);
- const setElValue = (id, val) => {
- const el = document.getElementById(id);
- if (el && val) el.value = val;
- };
-
- setElValue('searchInput', params.get('q'));
- setElValue('hero-search', params.get('q'));
- setElValue('categoryFilter', params.get('cat'));
- setElValue('complexityFilter', params.get('comp'));
- setElValue('sortSelect', params.get('sort'));
-
- const lang = params.get('lang');
- if (lang) {
- lang.split(',').map(s => s.trim()).filter(Boolean).forEach(l => {
- selectedLanguages.add(l);
- const pillBtn = document.querySelector(`.pill[data-lang="${l}"]`);
- if (pillBtn) {
- pillBtn.classList.add('active');
- pillBtn.setAttribute('aria-pressed', 'true');
- }
+ const SELECTION_DATE = new Date('2026-05-08T18:00:00Z');
+ if (Date.now() <= SELECTION_DATE.getTime()) return;
+ const issuesStaleNote = document.getElementById('issuesStaleNote');
+ if (issuesStaleNote) issuesStaleNote.classList.remove('hidden');
+ const mentorStaleNote = document.getElementById('mentorStaleNote');
+ if (mentorStaleNote) mentorStaleNote.classList.remove('hidden');
+}
+
+const _initFromURL = () => {
+ loadCommunityActivity();
+ const params = new URLSearchParams(location.search);
+ if (params.get('q')) document.getElementById('searchInput').value = params.get('q');
+ if (params.get('cat')) document.getElementById('categoryFilter').value = params.get('cat');
+ const langParam = params.get('lang');
+ if (langParam) {
+ const langs = langParam.split(',').map(s => s.trim()).filter(Boolean);
+ if (langs.length > 1) {
+ selectedLanguages.clear();
+ document.querySelectorAll('.pill').forEach(btn => {
+ const active = langs.includes(btn.dataset.lang);
+ btn.classList.toggle('active', active);
+ btn.setAttribute('aria-pressed', active ? 'true' : 'false');
+ if (active) selectedLanguages.add(btn.dataset.lang);
});
renderSelectedLanguages();
}
+ }
- const chip = params.get('chip');
- if (chip) {
- activeChip = chip;
- document.querySelectorAll('.filter-chip').forEach(el => {
- const text = el.textContent.trim().toLowerCase();
- let key = null;
- if (text.includes('bookmarked')) key = 'bookmarked';
- else if (text.includes('veteran')) key = 'veterans';
- else if (text.includes('newcomer')) key = 'newcomers';
- else if (text.includes('low competition')) key = 'low-competition';
- else if (text.includes('high competition')) key = 'high-competition';
- else if (text.includes('actively')) key = 'active';
-
- if (key === chip) {
- el.classList.add('bg-orange-600', 'text-white');
- el.classList.remove('bg-surface-container-highest');
- }
- });
- }
- })();
+ const chip = params.get('chip');
+ if (chip) {
+ activeChip = chip;
+ document.querySelectorAll('.filter-chip').forEach(el => {
+ const text = el.textContent.trim().toLowerCase();
+ let key = null;
+ if (text.includes('bookmarked')) key = 'bookmarked';
+ else if (text.includes('veteran')) key = 'veterans';
+ else if (text.includes('newcomer')) key = 'newcomers';
+ else if (text.includes('low competition')) key = 'low-competition';
+ else if (text.includes('high competition')) key = 'high-competition';
+ else if (text.includes('actively')) key = 'active';
+
+ if (key === chip) {
+ el.classList.add('bg-orange-600', 'text-white');
+ el.classList.remove('bg-surface-container-highest');
+ }
+ });
+ }
updateCountdown();
const countdownTimer = setInterval(updateCountdown, 60000);
@@ -2420,7 +2305,6 @@ document.addEventListener('DOMContentLoaded', () => {
renderTimeline();
applyStaleDataNotice();
- // Watchlist, comparison, analytics
applyFilters();
renderWatchlist();
renderCompare();
@@ -2430,7 +2314,6 @@ document.addEventListener('DOMContentLoaded', () => {
loadMentorData();
renderGoodFirstIssues();
- // Wire up filter event listeners
document.getElementById('searchInput')?.addEventListener('input', applyFilters);
document.getElementById('categoryFilter')?.addEventListener('change', applyFilters);
document.getElementById('complexityFilter')?.addEventListener('change', applyFilters);
@@ -2450,7 +2333,6 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('surpriseBtn')?.addEventListener('click', openRandomOrg);
- // Programmatic event listeners replacing inline HTML handlers for close and action buttons
document.getElementById('closeOrgModalBtn')?.addEventListener('click', closeModal);
document.getElementById('closeCompareModalBtn')?.addEventListener('click', closeCompareModal);
document.getElementById('closeHelpModalBtn')?.addEventListener('click', closeHelpModal);
@@ -2476,7 +2358,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
- // Sync hero-search
const heroSearch = document.getElementById('hero-search');
if (heroSearch) {
heroSearch.value = document.getElementById('searchInput')?.value || new URLSearchParams(location.search).get('q') || '';
@@ -2491,7 +2372,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
- // Quick chips listeners
document.querySelectorAll('.filter-chip').forEach(chip => {
chip.addEventListener('click', () => {
const text = chip.textContent.trim().toLowerCase();
@@ -2520,7 +2400,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
});
- // Phase 2: Add programmatic event listeners to pills, empty state clear button, and compare modal button
document.querySelectorAll('.pill[data-lang]').forEach(pill => {
pill.addEventListener('click', () => {
if (typeof globalThis.togglePill === 'function') {
@@ -2531,11 +2410,8 @@ document.addEventListener('DOMContentLoaded', () => {
document.getElementById('emptyStateClearBtn')?.addEventListener('click', clearAllFilters);
document.getElementById('openCompareModalBtn')?.addEventListener('click', openCompareModal);
-
- // Wire up live stats fetch button
document.getElementById('mFetchBtn')?.addEventListener('click', fetchModalGH);
- // Event delegation for trending cards scroll list
const trendingScroll = document.getElementById('trendingScroll');
if (trendingScroll) {
trendingScroll.addEventListener('click', (e) => {
@@ -2546,7 +2422,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
- // Event delegation for selected languages strip
const selectedStrip = document.getElementById('selectedLangsStrip');
if (selectedStrip) {
selectedStrip.addEventListener('click', (e) => {
@@ -2565,7 +2440,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
- // Event delegation for mentors container
const mentorsContainer = document.getElementById('mentorsContainer');
if (mentorsContainer) {
mentorsContainer.addEventListener('click', (e) => {
@@ -2575,7 +2449,13 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
}
-});
+};
+
+if (typeof document !== 'undefined' && typeof document.addEventListener === 'function') {
+ document.addEventListener('DOMContentLoaded', _initFromURL);
+} else if (typeof requestAnimationFrame !== 'undefined') {
+ requestAnimationFrame(_initFromURL);
+}
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// EXPORT FOR NODE ENVIRONMENT TESTING COMPATIBILITY
diff --git a/src/styles.css b/src/styles.css
index aa003ad049..10fc14fb33 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -444,126 +444,10 @@ body{background:var(--bg);color:var(--ink);font-family:'Plus Jakarta Sans',sans-
.an-note{font-size:11px;color:var(--muted);text-align:center;padding-top:13px;border-top:1px solid var(--border2);margin-top:4px;line-height:1.6}
/* โโ FOOTER โโ */
-.premium-footer {
- background: var(--nav-bg);
- border-top: 1.5px solid var(--border);
- position: relative;
- transition: background 0.3s, border-color 0.3s;
-}
-
-.premium-footer::before {
- content: '';
- position: absolute;
- top: -1.5px;
- left: 0;
- right: 0;
- height: 2px;
- background: linear-gradient(90deg, var(--orange) 0%, var(--blue) 50%, var(--purple) 100%);
- z-index: 1;
-}
-
-.premium-footer a {
- transition: all 0.2s ease-in-out;
-}
-
-.footer-stat-card {
- background: rgba(255, 255, 255, 0.03);
- backdrop-filter: blur(8px);
- border: 1px solid var(--border2);
- transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
-}
-
-.premium-footer[data-theme="light"] .footer-stat-card,
-:root:not(.dark) .footer-stat-card {
- background: rgba(0, 0, 0, 0.02);
-}
-
-.footer-stat-card:hover {
- transform: translateY(-2px);
- border-color: var(--orange);
- box-shadow: var(--shadow);
-}
-
-.social-link {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- width: 36px;
- height: 36px;
- border-radius: 50%;
- border: 1px solid var(--border);
- background: var(--card-bg);
- color: var(--ink3);
- transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.social-link:hover {
- transform: translateY(-3px);
- color: var(--white);
-}
-
-.social-link.github:hover {
- background: #24292e;
- border-color: #24292e;
- box-shadow: 0 0 12px rgba(36, 41, 46, 0.5);
-}
-
-.social-link.discord:hover {
- background: #5865F2;
- border-color: #5865F2;
- box-shadow: 0 0 12px rgba(88, 101, 242, 0.5);
-}
-
-.social-link.linkedin:hover {
- background: #0A66C2;
- border-color: #0A66C2;
- box-shadow: 0 0 12px rgba(10, 102, 194, 0.5);
-}
-
-.social-link.twitter:hover {
- background: #1DA1F2;
- border-color: #1DA1F2;
- box-shadow: 0 0 12px rgba(29, 161, 242, 0.5);
-}
-
-#back-to-top-btn {
- position: fixed;
- bottom: 24px;
- right: 24px;
- width: 44px;
- height: 44px;
- border-radius: 50%;
- background: var(--orange);
- color: white;
- border: none;
- box-shadow: 0 4px 14px rgba(244, 107, 14, 0.4);
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- z-index: 90;
- opacity: 0;
- visibility: hidden;
- transform: translateY(10px) scale(0.9);
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-#back-to-top-btn.show {
- opacity: 1;
- visibility: visible;
- transform: translateY(0) scale(1);
-}
-
-#back-to-top-btn:hover {
- background: var(--orange-deep);
- transform: translateY(-3px) scale(1.05);
- box-shadow: 0 6px 20px rgba(244, 107, 14, 0.6);
-}
-
-#back-to-top-btn:focus-visible {
- outline: 2px solid var(--orange);
- outline-offset: 2px;
-}
+footer{border-top:1.5px solid var(--border);padding:24px;text-align:center;transition:border-color .3s}
+.footer-inner{max-width:1340px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:10px;font-size:12px;color:var(--muted)}
+footer a{color:var(--orange);text-decoration:none;font-weight:600}
+footer a:hover{text-decoration:underline}
[data-theme="dark"] select option{background:#1A1108;color:var(--ink)}
/* โโ SKELETON CARDS โโ */
@@ -872,45 +756,36 @@ body{background:var(--bg);color:var(--ink);font-family:'Plus Jakarta Sans',sans-
background: var(--orange-pale);
}
-/* Privacy Page specific overrides to match exact styling */
-.privacy-page .premium-footer {
- background: #f4f4f5;
- border-top-color: #e4e4e7;
-}
-.dark .privacy-page .premium-footer,
-.privacy-page.dark .premium-footer {
- background: #09090b;
- border-top-color: #27272a;
-}
-.privacy-page .footer-stat-card {
- background: rgba(0, 0, 0, 0.02);
- border-color: #e4e4e7;
-}
-.dark .privacy-page .footer-stat-card,
-.privacy-page.dark .footer-stat-card {
- background: rgba(255, 255, 255, 0.03);
- border-color: #27272a;
-}
-.privacy-page .social-link {
- border-color: #e4e4e7;
- background: #ffffff;
- color: #71717a;
-}
-.dark .privacy-page .social-link,
-.privacy-page.dark .social-link {
- border-color: #27272a;
- background: #18181b;
- color: #a1a1aa;
-}
-.privacy-page #back-to-top-btn {
- background: #f97316;
- box-shadow: 0 4px 14px rgba(249, 115, 22, 0.4);
-}
-.privacy-page #back-to-top-btn:hover {
- background: #ea580c;
- box-shadow: 0 6px 20px rgba(249, 115, 22, 0.6);
-}
-.privacy-page #back-to-top-btn:focus-visible {
- outline-color: #f97316;
+/* โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+ COMMUNITY ACTIVITY BADGES & MODAL SECTION
+โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */
+.bca {
+ /* base โ shares existing .b sizing */
}
+.bca-very { background:rgba(0,135,90,.09); color:var(--green); border-color:rgba(0,135,90,.25); }
+.bca-active { background:rgba(26,86,219,.08); color:var(--blue); border-color:rgba(26,86,219,.25); }
+.bca-mod { background:rgba(251,191,36,.10); color:#92600A; border-color:rgba(251,191,36,.35); }
+.bca-low { background:rgba(197,48,48,.07); color:var(--red); border-color:rgba(197,48,48,.2); }
+[data-theme="dark"] .bca-mod { background:rgba(251,191,36,.15); color:#FBBC04; }
+
+/* Community activity rows inside the modal */
+.ca-grid {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ background: var(--bg);
+ border: 1.5px solid var(--border2);
+ border-radius: 11px;
+ padding: 12px 14px;
+ margin-bottom: 16px;
+ transition: background .3s, border-color .3s;
+}
+.ca-row {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 12px;
+}
+.ca-label { color: var(--muted); font-weight: 500; }
+.ca-value { color: var(--ink); font-family: 'Fira Code', monospace; font-size: 11px; }
From c90740cee60c5366dc92bb0fbbb0013db451f90c Mon Sep 17 00:00:00 2001
From: shravanithouta108
Date: Sat, 20 Jun 2026 17:15:25 +0530
Subject: [PATCH 02/45] fix: resolve ORGS undefined error and duplicate
filteredOrgs declaration
Signed-off-by: shravanithouta108
---
index.html | 3 ++-
src/js/app.js | 7 ++++---
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/index.html b/index.html
index ef81fb4022..616bfd1096 100644
--- a/index.html
+++ b/index.html
@@ -2279,7 +2279,7 @@
diff --git a/src/js/app.js b/src/js/app.js
index 7eed97572c..a32610d517 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -4,7 +4,7 @@
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// GLOBAL STATE & COMPATIBILITY LAYER
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-let filteredOrgs = [];
+// filteredOrgs declared in index.html inline script
let MENTOR_DATA = {};
let mentorDataState = 'idle';
const compareList = []; // list of org names
@@ -1204,6 +1204,7 @@ function renderOrgs(reset = true) {
${String(org.years)}y Veteran
${org.codebase}
+ ${rawHTML(getCommunityBadge(org))}
@@ -2259,8 +2260,8 @@ function applyStaleDataNotice() {
if (mentorStaleNote) mentorStaleNote.classList.remove('hidden');
}
-const _initFromURL = () => {
- loadCommunityActivity();
+const _initFromURL = async () => {
+ await loadCommunityActivity();
const params = new URLSearchParams(location.search);
if (params.get('q')) document.getElementById('searchInput').value = params.get('q');
if (params.get('cat')) document.getElementById('categoryFilter').value = params.get('cat');
From 5661fcd36567b74a7f774c8954124352d3411c60 Mon Sep 17 00:00:00 2001
From: shravanithouta108
Date: Fri, 10 Jul 2026 10:45:41 +0530
Subject: [PATCH 03/45] chore: reset community_activity.json to empty object
Signed-off-by: shravanithouta108
---
data/community_activity.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/data/community_activity.json b/data/community_activity.json
index 9e26dfeeb6..0967ef424b 100644
--- a/data/community_activity.json
+++ b/data/community_activity.json
@@ -1 +1 @@
-{}
\ No newline at end of file
+{}
From b71c0661eb6f7c0ca4369243964d0be07dc40dcd Mon Sep 17 00:00:00 2001
From: shravanithouta108
Date: Fri, 10 Jul 2026 10:55:45 +0530
Subject: [PATCH 04/45] fix: include bare GitHub owners and preserve null
metrics
Signed-off-by: shravanithouta108
---
agent/scripts/compute-activity-scores.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/agent/scripts/compute-activity-scores.js b/agent/scripts/compute-activity-scores.js
index 52bd86d0e3..353ff2b441 100644
--- a/agent/scripts/compute-activity-scores.js
+++ b/agent/scripts/compute-activity-scores.js
@@ -30,7 +30,7 @@ const orgMatches = orgDataRaw.matchAll(/name:\s*"([^"]+)"[^}]+github:\s*"([^"]+)
for (const m of orgMatches) {
const name = m[1];
const repo = m[2];
- if (repo?.includes('/')) {
+ if (repo) {
githubRepos.push({ name, repo });
}
}
@@ -120,9 +120,9 @@ async function analyzeOrg({ name, repo }) {
tier,
label,
signals: {
- issueResponseDays: Math.round(issueResponseDays),
+ issueResponseDays: issueResponseDays === null ? null : Math.round(issueResponseDays),
commitFrequency: Math.round(commitFrequency * 10) / 10,
- prMergeRate: Math.round(prMergeRate * 100),
+ prMergeRate: prMergeRate === null ? null : Math.round(prMergeRate * 100),
ideasFreshnessDays: Math.round(ideasFreshnessDays),
starsGrowth: Math.round(starsGrowth * 10) / 10
},
From 3b2fea3ee0b29a6f1a7e32e855c990483a1c91c8 Mon Sep 17 00:00:00 2001
From: shravanithouta108
Date: Sat, 11 Jul 2026 11:02:59 +0530
Subject: [PATCH 05/45] fix: resolve safeHTML/rawHTML ReferenceError, duplicate
communityActivity load, and bare owner API calls
- index.html: remove duplicate 'let communityActivity' declaration/fetch that
caused a SyntaxError (two classic scripts sharing global scope both
declared it), which aborted app.js parsing and broke safeHTML/rawHTML.
- src/js/app.js: loadCommunityActivity() remains the single fetch point,
now mirrored onto window.communityActivity.
- index.html: getActivityBadge(), the active-communities chip filter, and
the activity-score sort now read window.communityActivity.
- agent/scripts/compute-activity-scores.js: restore repo.includes('/')
check so bare GitHub owners (c2siorg, ML4SCI, etc.) are skipped instead
of hitting an invalid API endpoint.
Signed-off-by: shravanithouta108
---
agent/scripts/compute-activity-scores.js | 2 +-
index.html | 18 +++++++++---------
src/js/app.js | 5 +++++
3 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/agent/scripts/compute-activity-scores.js b/agent/scripts/compute-activity-scores.js
index 353ff2b441..cbd32ca3cd 100644
--- a/agent/scripts/compute-activity-scores.js
+++ b/agent/scripts/compute-activity-scores.js
@@ -30,7 +30,7 @@ const orgMatches = orgDataRaw.matchAll(/name:\s*"([^"]+)"[^}]+github:\s*"([^"]+)
for (const m of orgMatches) {
const name = m[1];
const repo = m[2];
- if (repo) {
+ if (repo && repo.includes('/')) {
githubRepos.push({ name, repo });
}
}
diff --git a/index.html b/index.html
index 616bfd1096..173a43af31 100644
--- a/index.html
+++ b/index.html
@@ -2073,14 +2073,14 @@ ${escapeHtml(a.label)}`;
}
@@ -3975,7 +3975,7 @@ ${escapeHtml(org.name)}
else if (activeChip === 'newcomers') matchChip = o.years <= 3;
else if (activeChip === 'low-competition') matchChip = o.competition === 'chill';
else if (activeChip === 'high-competition') matchChip = o.competition === 'hot';
- else if (activeChip === 'active-communities') matchChip = (communityActivity[o.name]?.score || 0) >= 60;
+ else if (activeChip === 'active-communities') matchChip = ((window.communityActivity || {})[o.name]?.score || 0) >= 60;
return matchSearch && matchCat && matchComp && matchLang && matchChip;
});
@@ -4013,7 +4013,7 @@ ${escapeHtml(org.name)}
if(sortType==='comp-low') return ['chill','moderate','hot'].indexOf(a.competition) - ['chill','moderate','hot'].indexOf(b.competition);
if(sortType==='stars') return (b._gh?.stars||0) - (a._gh?.stars||0);
if(sortType==='gfi') return (b._gh?.gfi||0) - (a._gh?.gfi||0);
- if(sortType==='most-active'||sortType==='activity-score') return ((communityActivity[b.name]?.score)||0) - ((communityActivity[a.name]?.score)||0);
+ if(sortType==='most-active'||sortType==='activity-score') return (((window.communityActivity || {})[b.name]?.score)||0) - (((window.communityActivity || {})[a.name]?.score)||0);
return a.name.localeCompare(b.name);
}
diff --git a/src/js/app.js b/src/js/app.js
index a32610d517..cb9b0061f4 100644
--- a/src/js/app.js
+++ b/src/js/app.js
@@ -470,6 +470,10 @@ cleanCache();
// COMMUNITY ACTIVITY DATA
// โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
let communityActivity = {}; // will hold the parsed community_activity.json
+// Single source of truth: this is the ONLY place communityActivity is fetched.
+// Mirrored onto window.communityActivity so index.html's inline script can
+// read it without redeclaring/refetching and racing this assignment.
+window.communityActivity = communityActivity;
async function loadCommunityActivity() {
try {
@@ -477,6 +481,7 @@ async function loadCommunityActivity() {
const res = await fetch('/data/community_activity.json?v=' + bust);
if (!res.ok) return;
communityActivity = await res.json();
+ window.communityActivity = communityActivity;
applyFilters();
} catch (e) {
console.warn('community_activity.json not yet available:', e);
From d6f20537d107128c516292f31511b6ded848dd0b Mon Sep 17 00:00:00 2001
From: Ayush
Date: Wed, 17 Jun 2026 08:02:05 +0530
Subject: [PATCH 06/45] fix: updated github-action bot issue assignment
workflow (#1792)
* fix: updated github-action bot issue assignment workflow
Signed-off-by: imayuss
* fix: Admin Fallback & Unapprovable State issue fixed
Signed-off-by: imayuss
* fix: fixed unique marker issue
Signed-off-by: imayuss
* fix(workflow): prevent comment spoofing and secure state lookups
Signed-off-by: imayuss
* fix(workflow): Implemented more specific error handling or logging
Signed-off-by: imayuss
* fix(workflow): Removed inline comments,duplicate var declaration & simplified fallback text handling
Signed-off-by: imayuss
---------
Signed-off-by: imayuss
Signed-off-by: shravanithouta108
---
.../workflows/issue-context-assignment.yml | 423 ++++++++----------
1 file changed, 189 insertions(+), 234 deletions(-)
diff --git a/.github/workflows/issue-context-assignment.yml b/.github/workflows/issue-context-assignment.yml
index 1094e2a24f..339a50d30e 100644
--- a/.github/workflows/issue-context-assignment.yml
+++ b/.github/workflows/issue-context-assignment.yml
@@ -34,36 +34,14 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #v9.0.0
with:
script: |
- const body =
- (
- context.payload.comment?.body ||
- ''
- ).trim();
-
- const assignMatch =
- /^\/assign$/i.test(body);
-
- const approveMatch =
- body.match(
- /^\/approve-assignment\s+@?([\w-]+)/i
- );
+ const body = (context.payload.comment?.body || '').trim();
- core.setOutput(
- 'is_assign',
- String(assignMatch)
- );
+ const assignMatch = /^\/assign$/i.test(body);
+ const approveMatch = body.match(/^\/approve-assignment\s+@?([\w-]+)/i);
- core.setOutput(
- 'is_approve',
- String(!!approveMatch)
- );
-
- core.setOutput(
- 'target_user',
- approveMatch
- ? approveMatch[1].toLowerCase()
- : ''
- );
+ core.setOutput('is_assign', String(assignMatch));
+ core.setOutput('is_approve', String(!!approveMatch));
+ core.setOutput('target_user', approveMatch ? approveMatch[1].toLowerCase() : '');
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
# ACTIVE MENTOR CONTEXT REVIEW
@@ -80,7 +58,6 @@ jobs:
RANDOM_ROTATION: "true"
run: |
RESULT=$(node .github/scripts/select-issue-mentors.js)
-
echo "result=$RESULT" >> "$GITHUB_OUTPUT"
- name: Post mentor review request
@@ -92,47 +69,38 @@ jobs:
SELECTED_MENTORS_JSON: ${{ steps.mentor_select.outputs.result }}
with:
script: |
- const { owner, repo } =
- context.repo;
+ const { owner, repo } = context.repo;
+ const issue_number = context.issue.number;
- const issue_number =
- context.issue.number;
+ function findBotComment(comments, marker) {
+ return comments.find(
+ c =>
+ c.user?.login === 'github-actions[bot]' &&
+ c.body?.includes(marker)
+ );
+ }
let selected = [];
-
try {
-
- selected =
- JSON.parse(
- process.env.SELECTED_MENTORS_JSON || '{}'
- ).selected || [];
-
+ selected = JSON.parse(process.env.SELECTED_MENTORS_JSON || '{}').selected || [];
} catch {
-
selected = [];
-
}
- const marker =
- '';
+ const MENTOR_CONTEXT_MARKER = '';
- const mentionList =
- selected.length
- ? selected
- .map(m => `@${m}`)
- .join(' โข ')
- : '_No active mentors available._';
+ const mentionList = selected.length
+ ? selected.map(m => `@${m}`).join(' โข ')
+ : '_No active mentors available._';
- const avatars =
- selected
- .map(
- mentor =>
- `
`
- )
- .join('\n');
+ const avatars = selected
+ .map(mentor =>
+ `
`
+ )
+ .join('\n');
const body = [
- marker,
+ MENTOR_CONTEXT_MARKER,
'',
'# ๐ Active Mentor Review Requested',
'',
@@ -158,42 +126,27 @@ jobs:
'_Mentor rotation updates automatically across issues._'
].join('\n');
- const comments =
- await github.paginate(
- github.rest.issues.listComments,
- {
- owner,
- repo,
- issue_number,
- per_page: 100
- }
- );
+ const comments = await github.paginate(
+ github.rest.issues.listComments,
+ { owner, repo, issue_number, per_page: 100 }
+ );
- const existing =
- comments.find(
- c =>
- c.body &&
- c.body.includes(marker)
- );
+ const existing = findBotComment(comments, MENTOR_CONTEXT_MARKER);
if (existing) {
-
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body
});
-
} else {
-
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body
});
-
}
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
@@ -206,66 +159,53 @@ jobs:
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 #v9.0.0
with:
script: |
- const { owner, repo } =
- context.repo;
-
- const issue_number =
- context.payload.issue.number;
-
- const claimant =
- context.payload.comment.user.login;
-
- const issue =
- (
- await github.rest.issues.get({
- owner,
- repo,
- issue_number
- })
- ).data;
-
- if (
- issue.state !== 'open' ||
- issue.locked
- ) {
-
- core.setOutput(
- 'eligible',
- 'false'
- );
+ const { owner, repo } = context.repo;
+ const issue_number = context.payload.issue.number;
+ const claimant = context.payload.comment.user.login;
- return;
+ const issue = (
+ await github.rest.issues.get({ owner, repo, issue_number })
+ ).data;
+ if (issue.state !== 'open' || issue.locked) {
+ core.setOutput('eligible', 'false');
+ return;
}
- if (
- (issue.assignees || []).length > 0
- ) {
-
- core.setOutput(
- 'eligible',
- 'false'
- );
-
+ if ((issue.assignees || []).length > 0) {
+ core.setOutput('eligible', 'false');
return;
-
}
- core.setOutput(
- 'eligible',
- 'true'
- );
+ const PENDING_MARKER = '';
- core.setOutput(
- 'claimant',
- claimant
+ const comments = await github.paginate(
+ github.rest.issues.listComments,
+ { owner, repo, issue_number, per_page: 100 }
);
- core.setOutput(
- 'issue_number',
- String(issue_number)
+ const pendingComment = comments.find(
+ c =>
+ c.user?.login === 'github-actions[bot]' &&
+ c.body?.includes(PENDING_MARKER) &&
+ !c.body?.includes('โ
Issue Assigned')
);
+ if (pendingComment) {
+ core.setOutput('eligible', 'false');
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number,
+ body: `โ ๏ธ @${claimant} - an assignment request is already pending mentor approval for this issue. Please wait for the current request to be resolved.`
+ });
+ return;
+ }
+
+ core.setOutput('eligible', 'true');
+ core.setOutput('claimant', claimant);
+ core.setOutput('issue_number', String(issue_number));
+
- name: Select rotating mentors
id: assign_mentors
if: |
@@ -277,7 +217,6 @@ jobs:
RANDOM_ROTATION: "true"
run: |
RESULT=$(node .github/scripts/select-issue-mentors.js)
-
echo "result=$RESULT" >> "$GITHUB_OUTPUT"
- name: Post assignment request
@@ -290,48 +229,59 @@ jobs:
SELECTED_MENTORS_JSON: ${{ steps.assign_mentors.outputs.result }}
with:
script: |
- const { owner, repo } =
- context.repo;
+ const { owner, repo } = context.repo;
+ const issue_number = context.issue.number;
+ const claimant = process.env.CLAIMANT;
- const issue_number =
- context.issue.number;
-
- const claimant =
- process.env.CLAIMANT;
+ function findBotComment(comments, marker) {
+ return comments.find(
+ c =>
+ c.user?.login === 'github-actions[bot]' &&
+ c.body?.includes(marker)
+ );
+ }
let selected = [];
-
try {
-
- selected =
- JSON.parse(
- process.env.SELECTED_MENTORS_JSON || '{}'
- ).selected || [];
-
+ selected = JSON.parse(process.env.SELECTED_MENTORS_JSON || '{}').selected || [];
} catch {
-
selected = [];
+ }
+ if (selected.length === 0) {
+ try {
+ const collabs = await github.paginate(
+ github.rest.repos.listCollaborators,
+ { owner, repo, permission: 'admin', per_page: 100 }
+ );
+ selected = collabs
+ .map(c => c.login)
+ .filter(l => l !== 'github-actions[bot]')
+ .slice(0, 2);
+ } catch (err) {
+ core.warning(
+ `Admin fallback failed โ could not list collaborators: ${err.message || err}. No mentors will be tagged.`
+ );
+ selected = [];
+ }
}
- const marker =
- '';
+ const PENDING_MARKER = '';
- const mentions =
- selected
- .map(m => `@${m}`)
- .join(' โข ');
+ const mentions = selected.length
+ ? selected.map(m => `@${m}`).join(' โข ')
+ : '_No active mentors available._';
- const avatars =
- selected
- .map(
- mentor =>
+ const avatars = selected.length
+ ? selected
+ .map(mentor =>
`
`
- )
- .join('\n');
+ )
+ .join('\n')
+ : '';
const body = [
- marker,
+ PENDING_MARKER,
'',
'# โณ Assignment Pending Mentor Approval',
'',
@@ -349,46 +299,30 @@ jobs:
`/approve-assignment @${claimant}`,
'```',
'',
- '> Mentors rotate dynamically across issues.',
- '> If no response occurs within 24h, PA escalation may happen.'
+ '> Mentors rotate dynamically across issues.'
].join('\n');
- const comments =
- await github.paginate(
- github.rest.issues.listComments,
- {
- owner,
- repo,
- issue_number,
- per_page: 100
- }
- );
+ const comments = await github.paginate(
+ github.rest.issues.listComments,
+ { owner, repo, issue_number, per_page: 100 }
+ );
- const existing =
- comments.find(
- c =>
- c.body &&
- c.body.includes(marker)
- );
+ const existing = findBotComment(comments, PENDING_MARKER);
if (existing) {
-
await github.rest.issues.updateComment({
owner,
repo,
comment_id: existing.id,
body
});
-
} else {
-
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body
});
-
}
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
@@ -402,87 +336,108 @@ jobs:
TARGET_USER: ${{ steps.cmd.outputs.target_user }}
with:
script: |
- const { owner, repo } =
- context.repo;
-
- const issue_number =
- context.issue.number;
-
- const approver =
- context.payload.comment.user.login;
-
- const claimant =
- process.env.TARGET_USER;
-
- const comments =
- await github.paginate(
- github.rest.issues.listComments,
- {
- owner,
- repo,
- issue_number,
- per_page: 100
- }
- );
+ const { owner, repo } = context.repo;
+ const issue_number = context.issue.number;
+ const approver = context.payload.comment.user.login;
+ const claimant = process.env.TARGET_USER;
- const assignmentComment =
- comments.find(
+ function findBotComment(comments, marker) {
+ return comments.find(
c =>
- c.body &&
- c.body.includes(
- ''
- )
+ c.user?.login === 'github-actions[bot]' &&
+ c.body?.includes(marker)
);
+ }
- if (!assignmentComment) {
+ const PENDING_MARKER = '';
- core.info(
- 'No assignment request found.'
- );
+ const comments = await github.paginate(
+ github.rest.issues.listComments,
+ { owner, repo, issue_number, per_page: 100 }
+ );
- return;
+ const assignmentComment = findBotComment(comments, PENDING_MARKER);
+ if (!assignmentComment) {
+ core.info('No assignment request found.');
+ return;
}
- const body =
- assignmentComment.body || '';
+ const pendingBody = assignmentComment.body || '';
- const mentorMentioned =
- body.includes(`@${approver}`);
+ const requestedByMatch = pendingBody.match(/Requested by @([\w-]+)/i);
+ const originalClaimant = requestedByMatch ? requestedByMatch[1].toLowerCase() : null;
- if (!mentorMentioned) {
+ if (!originalClaimant) {
+ core.warning('Could not determine original claimant from pending comment.');
+ return;
+ }
+ if (claimant !== originalClaimant) {
await github.rest.issues.createComment({
owner,
repo,
issue_number,
- body:
- `โ @${approver} is not one of the selected mentors for this issue.`
+ body: `โ @${approver} โ the username \`@${claimant}\` does not match the original requester \`@${originalClaimant}\` for this issue. Please use \`/approve-assignment @${originalClaimant}\`.`
});
+ return;
+ }
+ const noMentorsAvailable = pendingBody.includes('_No active mentors available._');
+ const isMentorMentioned = pendingBody.includes(`@${approver}`);
+
+ if (!isMentorMentioned && !noMentorsAvailable) {
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number,
+ body: `โ @${approver} is not one of the selected mentors for this issue.`
+ });
return;
+ }
+ let permissionLevel = 'none';
+ try {
+ const { data: permData } = await github.rest.repos.getCollaboratorPermissionLevel({
+ owner,
+ repo,
+ username: approver
+ });
+ permissionLevel = permData.permission;
+ } catch (err) {
+ core.warning(`Permission check failed for @${approver}: ${err.message || err}`);
+ permissionLevel = 'none';
}
- const issue =
- (
- await github.rest.issues.get({
- owner,
- repo,
- issue_number
- })
- ).data;
-
- if (
- (issue.assignees || []).length > 0
- ) {
-
- core.info(
- 'Issue already assigned.'
- );
+ const allowedRoles = ['admin', 'maintain', 'write'];
+ if (!allowedRoles.includes(permissionLevel)) {
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number,
+ body: `โ @${approver} does not have sufficient repository permissions to approve assignments (requires write, maintain, or admin).`
+ });
+ return;
+ }
+
+ const issue = (
+ await github.rest.issues.get({ owner, repo, issue_number })
+ ).data;
+ if (issue.state !== 'open') {
+ core.info('Issue is no longer open โ skipping assignment.');
+ await github.rest.issues.createComment({
+ owner,
+ repo,
+ issue_number,
+ body: `โ ๏ธ @${approver} โ this issue is no longer open, so the assignment cannot be processed.`
+ });
return;
+ }
+ if ((issue.assignees || []).length > 0) {
+ core.info('Issue already assigned.');
+ return;
}
await github.rest.issues.addAssignees({
@@ -493,7 +448,7 @@ jobs:
});
const updatedBody = [
- '',
+ '',
'',
'# โ
Issue Assigned',
'',
From 592beccd7c36712b0d9882264b17663483f82570 Mon Sep 17 00:00:00 2001
From: Aastha Khatri
Date: Wed, 17 Jun 2026 08:06:02 +0530
Subject: [PATCH 07/45] Fix: mariadb mentor contact issue 346 clean (#898)
* fix: update MariaDB mentor contact info and status
Signed-off-by: Aastha Khatri
* fix: update MariaDB mailing list URL to active location
Signed-off-by: Aastha Khatri
* fix: add MariaDB GSoC 2026 mentors
Signed-off-by: Aastha Khatri
* fix: correct indentation in MariaDB mentors and mailingLists
Signed-off-by: Aastha Khatri
* fix: remove blank line in Fossology entry and set null for empty MariaDB github fields
Signed-off-by: Aastha Khatri
---------
Signed-off-by: Aastha Khatri
Co-authored-by: Savio Dsouza
Signed-off-by: shravanithouta108
---
data/mentors.json | 53 ++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 48 insertions(+), 5 deletions(-)
diff --git a/data/mentors.json b/data/mentors.json
index ac7e1e498d..2830564e39 100644
--- a/data/mentors.json
+++ b/data/mentors.json
@@ -2737,11 +2737,54 @@
"MariaDB": {
"org": "MariaDB",
"ideasUrl": "https://mariadb.org/get-involved/gsoc/",
- "channels": [],
- "mentors": [],
- "mailingLists": [],
- "tip": "",
- "status": "no-contact-found",
+ "channels": [
+ {
+ "type": "Zulip",
+ "url": "https://mariadb.zulipchat.com/",
+ "label": "MariaDB Zulip"
+ }
+ ],
+ "mentors": [
+ {
+ "name": "Sergei Golubchik",
+ "github": "vuvova",
+ "githubUrl": "https://github.com/vuvova"
+ },
+ {
+ "name": "Brandon Nesterenko",
+ "github": null,
+ "githubUrl": null
+ },
+ {
+ "name": "Oleksandr Byelkin",
+ "github": "sanja-byelkin",
+ "githubUrl": "https://github.com/sanja-byelkin"
+ },
+ {
+ "name": "Alexander Barkov",
+ "github": null,
+ "githubUrl": null
+ },
+ {
+ "name": "Nikita Malyavin",
+ "github": null,
+ "githubUrl": null
+ },
+ {
+ "name": "Rucha Deodhar",
+ "github": null,
+ "githubUrl": null
+ }
+ ],
+ "mailingLists": [
+ {
+ "type": "Mailing list",
+ "url": "https://lists.mariadb.org/postorius/lists/developers.lists.mariadb.org/",
+ "label": "maria-developers mailing list"
+ }
+ ],
+ "tip": "Join the Zulip community and subscribe to the maria-developers mailing list before asking project-specific questions.",
+ "status": "ok",
"lastFetched": "2026-05-09T16:09:12.548Z"
},
"MDAnalysis": {
From ea9d77a8bac2a2804131c05af3a8492b5e400957 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 17 Jun 2026 08:07:00 +0530
Subject: [PATCH 08/45] chore: update mentor leaderboard (#1839)
Co-authored-by: S3DFX-CYBER <213652949+S3DFX-CYBER@users.noreply.github.com>
Co-authored-by: Savio Dsouza
Signed-off-by: shravanithouta108
---
.github/reviewers/mentor-leaderboard.md | 78 ++++++++++++-------------
.github/reviewers/mentor-stats.json | 20 +++----
2 files changed, 49 insertions(+), 49 deletions(-)
diff --git a/.github/reviewers/mentor-leaderboard.md b/.github/reviewers/mentor-leaderboard.md
index dcd61b4fa5..3409f65d73 100644
--- a/.github/reviewers/mentor-leaderboard.md
+++ b/.github/reviewers/mentor-leaderboard.md
@@ -4,48 +4,48 @@
| Rank | Mentor | Reviews | Score |
|------|--------|---------|-------|
-| ๐ฅ | @TarunyaProgrammer | 19 | 75 |
-| ๐ฅ | @KUMARNiru007 | 18 | 73 |
-| ๐ฅ | @nitinog10 | 13 | 54 |
-| 4 | @4f4d | 7 | 37 |
-| 5 | @Anushreebasics | 11 | 29 |
+| ๐ฅ | @KUMARNiru007 | 25 | 108 |
+| ๐ฅ | @TarunyaProgrammer | 19 | 75 |
+| ๐ฅ | @nitinog10 | 15 | 64 |
+| 4 | @Anushreebasics | 17 | 45 |
+| 5 | @4f4d | 7 | 37 |
| 6 | @deepak0x | 7 | 26 |
| 7 | @itsdakshjain | 6 | 19 |
-| 8 | @saurabh24thakur | 3 | 17 |
-| 9 | @Balaji91221 | 8 | 16 |
-| 10 | @knoxiboy | 5 | 14 |
+| 8 | @knoxiboy | 6 | 19 |
+| 9 | @saurabh24thakur | 3 | 17 |
+| 10 | @Balaji91221 | 8 | 16 |
| 11 | @12fahed | 3 | 11 |
| 12 | @Mrigakshi-Rathore | 5 | 10 |
-| 13 | @MUKUL-PRASAD-SIGH | 4 | 9 |
-| 14 | @CoderOggy78 | 3 | 7 |
-| 15 | @nihalawasthi | 3 | 7 |
-| 16 | @AnirudhPhophalia | 3 | 6 |
-| 17 | @BandhiyaHardik | 3 | 6 |
-| 18 | @sabeenaviklar | 2 | 6 |
-| 19 | @stealthwhizz | 2 | 6 |
-| 20 | @lourduradjou | 2 | 5 |
-| 21 | @SparshM8 | 2 | 4 |
-| 22 | @AnirbansarkarS | 1 | 2 |
-| 23 | @Ayush-Patel-56 | 1 | 2 |
-| 24 | @piyushdotcomm | 1 | 2 |
-| 25 | @Sagar-Datkhile | 1 | 2 |
-| 26 | @aanjalii01 | 0 | 0 |
-| 27 | @adithyan-css | 0 | 0 |
-| 28 | @AditthyaSS | 0 | 0 |
-| 29 | @anubhavxdev | 0 | 0 |
-| 30 | @aryanbhutani26 | 0 | 0 |
-| 31 | @ayu-yishu13 | 0 | 0 |
-| 32 | @Ayushh-Sharmaa | 0 | 0 |
-| 33 | @coder-zs-cse | 0 | 0 |
-| 34 | @deepaksinghh12 | 0 | 0 |
-| 35 | @DevROHIT11 | 0 | 0 |
-| 36 | @Haile-12 | 0 | 0 |
-| 37 | @JoeCelaster | 0 | 0 |
-| 38 | @kallal79 | 0 | 0 |
-| 39 | @KaranGupta2005 | 0 | 0 |
-| 40 | @lovestaco | 0 | 0 |
-| 41 | @magic-peach | 0 | 0 |
-| 42 | @Maxd646 | 0 | 0 |
+| 13 | @Maxd646 | 3 | 10 |
+| 14 | @MUKUL-PRASAD-SIGH | 4 | 9 |
+| 15 | @CoderOggy78 | 3 | 7 |
+| 16 | @nihalawasthi | 3 | 7 |
+| 17 | @AnirudhPhophalia | 3 | 6 |
+| 18 | @BandhiyaHardik | 3 | 6 |
+| 19 | @sabeenaviklar | 2 | 6 |
+| 20 | @stealthwhizz | 2 | 6 |
+| 21 | @lourduradjou | 2 | 5 |
+| 22 | @SparshM8 | 2 | 4 |
+| 23 | @AnirbansarkarS | 1 | 2 |
+| 24 | @Ayush-Patel-56 | 1 | 2 |
+| 25 | @piyushdotcomm | 1 | 2 |
+| 26 | @Sagar-Datkhile | 1 | 2 |
+| 27 | @aanjalii01 | 0 | 0 |
+| 28 | @adithyan-css | 0 | 0 |
+| 29 | @AditthyaSS | 0 | 0 |
+| 30 | @anubhavxdev | 0 | 0 |
+| 31 | @aryanbhutani26 | 0 | 0 |
+| 32 | @ayu-yishu13 | 0 | 0 |
+| 33 | @Ayushh-Sharmaa | 0 | 0 |
+| 34 | @coder-zs-cse | 0 | 0 |
+| 35 | @deepaksinghh12 | 0 | 0 |
+| 36 | @DevROHIT11 | 0 | 0 |
+| 37 | @Haile-12 | 0 | 0 |
+| 38 | @JoeCelaster | 0 | 0 |
+| 39 | @kallal79 | 0 | 0 |
+| 40 | @KaranGupta2005 | 0 | 0 |
+| 41 | @lovestaco | 0 | 0 |
+| 42 | @magic-peach | 0 | 0 |
| 43 | @MAYANKSHARMA01010 | 0 | 0 |
| 44 | @Mohit-368 | 0 | 0 |
| 45 | @morningstarxcdcode | 0 | 0 |
@@ -63,4 +63,4 @@
| 57 | @uddalak2005 | 0 | 0 |
| 58 | @vanshaggarwal07 | 0 | 0 |
-Last updated: Thu, 11 Jun 2026 07:49:18 GMT
+Last updated: Wed, 17 Jun 2026 02:34:31 GMT
diff --git a/.github/reviewers/mentor-stats.json b/.github/reviewers/mentor-stats.json
index 79c69eaa52..06ddbd1bc1 100644
--- a/.github/reviewers/mentor-stats.json
+++ b/.github/reviewers/mentor-stats.json
@@ -32,8 +32,8 @@
"score": 0
},
"Anushreebasics": {
- "reviews": 11,
- "score": 29
+ "reviews": 17,
+ "score": 45
},
"aryanbhutani26": {
"reviews": 0,
@@ -100,12 +100,12 @@
"score": 0
},
"knoxiboy": {
- "reviews": 5,
- "score": 14
+ "reviews": 6,
+ "score": 19
},
"KUMARNiru007": {
- "reviews": 18,
- "score": 73
+ "reviews": 25,
+ "score": 108
},
"lourduradjou": {
"reviews": 2,
@@ -120,8 +120,8 @@
"score": 0
},
"Maxd646": {
- "reviews": 0,
- "score": 0
+ "reviews": 3,
+ "score": 10
},
"MAYANKSHARMA01010": {
"reviews": 0,
@@ -148,8 +148,8 @@
"score": 7
},
"nitinog10": {
- "reviews": 13,
- "score": 54
+ "reviews": 15,
+ "score": 64
},
"oasis-parzival": {
"reviews": 0,
From b9ac12eb7aee5262b0529faf9f7c2380d525af45 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 17 Jun 2026 08:08:50 +0530
Subject: [PATCH 09/45] docs: backfill contributors (#1896)
Co-authored-by: S3DFX-CYBER <213652949+S3DFX-CYBER@users.noreply.github.com>
Signed-off-by: shravanithouta108
---
README.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/README.md b/README.md
index 7e92934dbc..c187312a9b 100644
--- a/README.md
+++ b/README.md
@@ -605,6 +605,7 @@ These mentors help guide and review contributions for the GSSoC program:
+
@@ -615,6 +616,7 @@ These mentors help guide and review contributions for the GSSoC program:
+
@@ -630,6 +632,7 @@ These mentors help guide and review contributions for the GSSoC program:
+
@@ -648,9 +651,12 @@ These mentors help guide and review contributions for the GSSoC program:
+
+
+
@@ -676,10 +682,12 @@ These mentors help guide and review contributions for the GSSoC program:
+
+
@@ -696,12 +704,15 @@ These mentors help guide and review contributions for the GSSoC program:
+
+
+
From a5bf07b7bee6858c78fbd5715c53f450681afe71 Mon Sep 17 00:00:00 2001
From: shreyjoshi73
Date: Wed, 17 Jun 2026 08:16:24 +0530
Subject: [PATCH 10/45] fix: add OpenWISP mentors, channels and mailing lists
(#1061)
* fix: add OpenWISP mentors, channels & mailing lists (closes #381)
Signed-off-by: Shrey
* fix: fix OpenWISP schema - add githubUrl to mentors and fix status
Signed-off-by: Shrey
* fix: update OpenWISP channels with correct matrix URL and schema
Signed-off-by: Shrey
---------
Signed-off-by: Shrey
Co-authored-by: nitin10
Co-authored-by: Savio Dsouza
Signed-off-by: shravanithouta108
---
data/mentors.json | 40 ++++++++++++++++++++++++++++++----------
1 file changed, 30 insertions(+), 10 deletions(-)
diff --git a/data/mentors.json b/data/mentors.json
index 2830564e39..aa49e4771f 100644
--- a/data/mentors.json
+++ b/data/mentors.json
@@ -3549,16 +3549,36 @@
"status": "ok",
"lastFetched": "2026-05-09T16:09:12.548Z"
},
- "OpenWISP": {
- "org": "OpenWISP",
- "ideasUrl": "https://github.com/openwisp/gsoc-ideas",
- "channels": [],
- "mentors": [],
- "mailingLists": [],
- "tip": "",
- "status": "fetch-failed",
- "lastFetched": "2026-05-09T16:09:12.548Z"
- },
+"OpenWISP": {
+ "org": "OpenWISP",
+ "ideasUrl": "https://openwisp.io/docs/dev/developer/gsoc-ideas-2026.html",
+ "channels": [
+ {
+ "type": "matrix",
+ "url": "https://matrix.to/#/#openwisp_development:gitter.im",
+ "label": "OpenWISP Dev Chat"
+ }
+ ],
+ "mentors": [
+ {
+ "name": "Federico Capoano",
+ "github": "nemesifier",
+ "githubUrl": "https://github.com/nemesifier"
+ },
+ {
+ "name": "Gagan Deep",
+ "github": "pandafy",
+ "githubUrl": "https://github.com/pandafy"
+ }
+ ],
+ "mailingLists": [
+ "https://groups.google.com/d/forum/openwisp",
+ "https://groups.google.com/g/openwisp-gsoc"
+ ],
+ "tip": "",
+ "status": "ok",
+ "lastFetched": "2026-05-09T16:09:12.548Z"
+},
"Oppia Foundation": {
"org": "Oppia Foundation",
"ideasUrl": "https://github.com/oppia/oppia/wiki/Google-Summer-of-Code-2026",
From a98546be220eab9a5e43c97ac306a7222891fa32 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 17 Jun 2026 09:22:40 +0530
Subject: [PATCH 11/45] chore(deps-dev): bump js-yaml from 4.1.1 to 4.2.0
(#1898)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/commits)
---
updated-dependencies:
- dependency-name: js-yaml
dependency-version: 4.2.0
dependency-type: indirect
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Signed-off-by: shravanithouta108
---
package-lock.json | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 3e820a6678..b4f0a5038e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1528,10 +1528,20 @@
"license": "MIT"
},
"node_modules/js-yaml": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
- "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
"dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
From 857d10589e6dd9cc7596f3cd181aaec22d40adba Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 17 Jun 2026 09:23:45 +0530
Subject: [PATCH 12/45] chore: update mentor leaderboard (#1897)
Co-authored-by: S3DFX-CYBER <213652949+S3DFX-CYBER@users.noreply.github.com>
Co-authored-by: Savio Dsouza
Signed-off-by: shravanithouta108
---
.github/reviewers/mentor-leaderboard.md | 4 ++--
.github/reviewers/mentor-stats.json | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/.github/reviewers/mentor-leaderboard.md b/.github/reviewers/mentor-leaderboard.md
index 3409f65d73..3e7128fe5d 100644
--- a/.github/reviewers/mentor-leaderboard.md
+++ b/.github/reviewers/mentor-leaderboard.md
@@ -6,7 +6,7 @@
|------|--------|---------|-------|
| ๐ฅ | @KUMARNiru007 | 25 | 108 |
| ๐ฅ | @TarunyaProgrammer | 19 | 75 |
-| ๐ฅ | @nitinog10 | 15 | 64 |
+| ๐ฅ | @nitinog10 | 16 | 69 |
| 4 | @Anushreebasics | 17 | 45 |
| 5 | @4f4d | 7 | 37 |
| 6 | @deepak0x | 7 | 26 |
@@ -63,4 +63,4 @@
| 57 | @uddalak2005 | 0 | 0 |
| 58 | @vanshaggarwal07 | 0 | 0 |
-Last updated: Wed, 17 Jun 2026 02:34:31 GMT
+Last updated: Wed, 17 Jun 2026 02:49:00 GMT
diff --git a/.github/reviewers/mentor-stats.json b/.github/reviewers/mentor-stats.json
index 06ddbd1bc1..667cefaa4b 100644
--- a/.github/reviewers/mentor-stats.json
+++ b/.github/reviewers/mentor-stats.json
@@ -148,8 +148,8 @@
"score": 7
},
"nitinog10": {
- "reviews": 15,
- "score": 64
+ "reviews": 16,
+ "score": 69
},
"oasis-parzival": {
"reviews": 0,
From 830325495c8c7836b76deb57d4fba223d600d368 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 17 Jun 2026 09:55:08 +0530
Subject: [PATCH 13/45] chore: update mentor leaderboard (#1899)
Co-authored-by: S3DFX-CYBER <213652949+S3DFX-CYBER@users.noreply.github.com>
Signed-off-by: shravanithouta108
---
.github/reviewers/mentor-leaderboard.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/reviewers/mentor-leaderboard.md b/.github/reviewers/mentor-leaderboard.md
index 3e7128fe5d..108f83cce7 100644
--- a/.github/reviewers/mentor-leaderboard.md
+++ b/.github/reviewers/mentor-leaderboard.md
@@ -63,4 +63,4 @@
| 57 | @uddalak2005 | 0 | 0 |
| 58 | @vanshaggarwal07 | 0 | 0 |
-Last updated: Wed, 17 Jun 2026 02:49:00 GMT
+Last updated: Wed, 17 Jun 2026 03:57:34 GMT
From 86260a17ffbe52e279f89924b1e66f49118e8a02 Mon Sep 17 00:00:00 2001
From: LEELA SOWMYA
Date: Fri, 19 Jun 2026 10:20:23 +0530
Subject: [PATCH 14/45] feat: add live word and character counter to proposal
builder (#1851)
* feat: add live word and character counter to proposal builder
Signed-off-by: Soquixx
* fix: move proposal stats outside autosave label
Signed-off-by: Soquixx
* fix:add address accesibility review feedback
Signed-off-by: Soquixx
* fix: resolve review feedback for proposal builder
Signed-off-by: Soquixx
* fix: resolve review feedback for proposal builder
Signed-off-by: Soquixx
* fix: update proposal counter
Signed-off-by: Soquixx
---------
Signed-off-by: Soquixx
Co-authored-by: Savio Dsouza
Signed-off-by: shravanithouta108
---
index.html | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/index.html b/index.html
index 173a43af31..4656323730 100644
--- a/index.html
+++ b/index.html
@@ -1948,6 +1948,7 @@ Organization Proposal
Enable autosave
Drafts are stored only on this device when autosave is enabled.
+