feat(ui): add description and identity claims to governance cards#2198
feat(ui): add description and identity claims to governance cards#2198howwohmm wants to merge 2 commits intonpmx-dev:mainfrom
Conversation
Expand governance member cards on the about page to show: - Bio/description line from GitHub profile - Social identity links (X/Twitter, Bluesky, Mastodon, LinkedIn, etc.) The existing `fetchSponsorable` GraphQL query is replaced with a broader `fetchGovernanceProfiles` that fetches bio, twitterUsername, and socialAccounts in the same request. Social icons use the existing UnoCSS icon sets (simple-icons + lucide). Data gracefully degrades when the GitHub token is unavailable. Closes npmx-dev#1564 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
2 Skipped Deployments
|
📝 WalkthroughWalkthroughThe PR extends the governance contributor cards to display biographical information and social identity links. The backend API endpoint now fetches expanded profile data including biography, Twitter username, and social account links for GitHub contributors. The frontend updates the about page to import the new 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (1)
server/api/contributors.get.ts (1)
222-233: Return a fresh object instead of mutatingcand casting it.
Object.assign(c, ...)changes aGitHubAPIContributorinto a wider shape and then hides the mismatch with a cast. Returning a new object keeps this mapper genuinely type-safe and easier to extend.♻️ Proposed refactor
return filtered - .map(c => { + .map((c): GitHubContributor & { order: number } => { const { role, order } = getRoleInfo(c.login, teams) const profile = governanceProfiles.get(c.login) const sponsors_url = profile?.hasSponsorsListing ? `https://github.com/sponsors/${c.login}` : null const bio = profile?.bio ?? null const twitterUsername = profile?.twitterUsername ?? null const socialAccounts = profile?.socialAccounts ?? [] - Object.assign(c, { role, order, sponsors_url, bio, twitterUsername, socialAccounts }) - return c as GitHubContributor & { order: number } + return { + ...c, + role, + order, + sponsors_url, + bio, + twitterUsername, + socialAccounts, + } })As per coding guidelines, "Ensure you write strictly type-safe code".
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1b543fcc-a4bf-42e0-a636-4f3779dfe564
📒 Files selected for processing (2)
app/pages/about.vueserver/api/contributors.get.ts
| const socialIcons: Record<string, string> = { | ||
| TWITTER: 'i-simple-icons:x', | ||
| MASTODON: 'i-simple-icons:mastodon', | ||
| BLUESKY: 'i-simple-icons:bluesky', | ||
| LINKEDIN: 'i-simple-icons:linkedin', | ||
| YOUTUBE: 'i-simple-icons:youtube', | ||
| HOMETOWN: 'i-lucide:globe', | ||
| DISCORD: 'i-simple-icons:discord', |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "uno.config.ts" -o -name "uno.config.js" | head -5Repository: npmx-dev/npmx.dev
Length of output: 76
🏁 Script executed:
wc -l app/pages/about.vueRepository: npmx-dev/npmx.dev
Length of output: 84
🏁 Script executed:
head -100 app/pages/about.vue | tail -80Repository: npmx-dev/npmx.dev
Length of output: 2307
🏁 Script executed:
cat uno.config.tsRepository: npmx-dev/npmx.dev
Length of output: 8651
🏁 Script executed:
sed -n '272,295p' app/pages/about.vueRepository: npmx-dev/npmx.dev
Length of output: 1134
🏁 Script executed:
sed -n '250,290p' app/pages/about.vueRepository: npmx-dev/npmx.dev
Length of output: 1903
Register missing icon collections in UnoCSS configuration.
The socialIcons object uses i-simple-icons:* and i-lucide:* icons, but uno.config.ts only registers the custom collection. Without these collections configured, the icons won't resolve at build time despite being statically discoverable in the source code. Either register these collections in presetIcons or safelist the specific icon classes.
Affected locations
- Lines 40–47: Icon definitions in
socialIconsobject - Line 273: Icon rendering via
:class="[link.icon, 'w-3 h-3']"
| <a | ||
| v-for="link in getSocialLinks(person)" | ||
| :key="link.provider" | ||
| :href="link.url" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| class="text-fg-muted hover:text-fg transition-colors" | ||
| :aria-label="`${person.login} on ${link.provider.toLowerCase()}`" | ||
| > | ||
| <span :class="[link.icon, 'w-3 h-3']" aria-hidden="true" /> |
There was a problem hiding this comment.
Localise the social-link aria-labels.
This is hard-coded English on an otherwise translated page, and link.provider.toLowerCase() yields awkward labels such as hometown. Please route the label through $t(...) and map providers to user-facing names.
| const json = (await response.json()) as { | ||
| data?: Record<string, { login: string; hasSponsorsListing: boolean } | null> | ||
| data?: Record< | ||
| string, | ||
| { | ||
| login: string | ||
| hasSponsorsListing: boolean | ||
| bio: string | null | ||
| twitterUsername: string | null | ||
| socialAccounts: { nodes: { provider: string; url: string }[] } | ||
| } | null | ||
| > | ||
| } | ||
|
|
||
| const sponsorable = new Set<string>() | ||
| const profiles = new Map<string, GovernanceProfile>() | ||
| if (json.data) { | ||
| for (const user of Object.values(json.data)) { | ||
| if (user?.hasSponsorsListing) { | ||
| sponsorable.add(user.login) | ||
| if (user) { | ||
| profiles.set(user.login, { | ||
| hasSponsorsListing: user.hasSponsorsListing, | ||
| bio: user.bio, | ||
| twitterUsername: user.twitterUsername, | ||
| socialAccounts: user.socialAccounts.nodes.map(n => ({ | ||
| provider: n.provider, | ||
| url: n.url, | ||
| })), | ||
| }) | ||
| } | ||
| } | ||
| } | ||
| return sponsorable | ||
| return profiles | ||
| } catch (error) { | ||
| console.warn('Failed to fetch sponsors info:', error) | ||
| return new Set() | ||
| console.warn('Failed to fetch governance profiles:', error) | ||
| return new Map() |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
wc -l server/api/contributors.get.tsRepository: npmx-dev/npmx.dev
Length of output: 95
🏁 Script executed:
cat -n server/api/contributors.get.tsRepository: npmx-dev/npmx.dev
Length of output: 8837
🏁 Script executed:
# Search for any error handling around GraphQL responses
rg -A 5 -B 5 "json\.errors|graphql|GraphQL" server/api/contributors.get.tsRepository: npmx-dev/npmx.dev
Length of output: 653
Handle GraphQL errors before accepting the payload.
GitHub GraphQL can return HTTP 200 with an errors array. Currently, line 118's type assertion omits the errors field entirely, so GraphQL errors are silently ignored. When json.data is absent or null (due to errors), the code returns an empty Map at line 147 with no explicit error logging—causing profile data (bios, social accounts, sponsor status) to vanish silently. This degraded state then gets cached for one hour. Add a check for json.errors and log it explicitly before processing json.data. This aligns with the coding guideline: "Use error handling patterns consistently" and "Ensure you write strictly type-safe code."
| const governanceProfiles = githubToken | ||
| ? await fetchGovernanceProfiles(githubToken, maintainerLogins) | ||
| : new Map<string, GovernanceProfile>() | ||
|
|
||
| return filtered | ||
| .map(c => { | ||
| const { role, order } = getRoleInfo(c.login, teams) | ||
| const sponsors_url = sponsorable.has(c.login) | ||
| const profile = governanceProfiles.get(c.login) | ||
| const sponsors_url = profile?.hasSponsorsListing | ||
| ? `https://github.com/sponsors/${c.login}` | ||
| : null | ||
| Object.assign(c, { role, order, sponsors_url }) | ||
| return c as GitHubContributor & { order: number; sponsors_url: string | null; role: Role } | ||
| const bio = profile?.bio ?? null | ||
| const twitterUsername = profile?.twitterUsername ?? null | ||
| const socialAccounts = profile?.socialAccounts ?? [] |
There was a problem hiding this comment.
The no-token fallback now strips sponsor links from every governance card.
When githubToken is missing, governanceProfiles is always empty, so sponsors_url becomes null for all governance members. That does not match the stated graceful-degradation behaviour for preview/tokenless environments. Please preserve a non-GraphQL fallback for that link if the sponsor CTA should remain available there.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary
Closes #1564
fetchSponsorableGraphQL query with a broaderfetchGovernanceProfilesthat fetchesbio,twitterUsername, andsocialAccountsin a single request — no extra API callsWhat changed
server/api/contributors.get.tsSocialAccountinterface exported alongsideRoleandGitHubContributorGitHubContributornow includesbio,twitterUsername, andsocialAccountsfieldsfetchSponsorable→fetchGovernanceProfiles: same single GraphQL batch query, now also fetchesbio,twitterUsername, andsocialAccounts(first: 10)app/pages/about.vuesocialIcons) andgetSocialLinks()helper to deduplicate Twitter username vs social accountsi-simple-icons:*andi-lucide:*icon setsTest plan
🤖 Generated with Claude Code