Skip to content

feat(ui): add description and identity claims to governance cards#2198

Open
howwohmm wants to merge 2 commits intonpmx-dev:mainfrom
howwohmm:feat/governance-card-identity
Open

feat(ui): add description and identity claims to governance cards#2198
howwohmm wants to merge 2 commits intonpmx-dev:mainfrom
howwohmm:feat/governance-card-identity

Conversation

@howwohmm
Copy link
Contributor

Summary

Closes #1564

  • Adds a bio/description line to governance member cards on the about page (truncated with title tooltip for overflow)
  • Adds social identity claim icons (X/Twitter, Bluesky, Mastodon, LinkedIn, Discord, YouTube) linking to each member's profiles
  • Replaces the existing fetchSponsorable GraphQL query with a broader fetchGovernanceProfiles that fetches bio, twitterUsername, and socialAccounts in a single request — no extra API calls
  • Gracefully degrades when no GitHub token is available (cards still show login, role, and sponsor link as before)

What changed

server/api/contributors.get.ts

  • New SocialAccount interface exported alongside Role and GitHubContributor
  • GitHubContributor now includes bio, twitterUsername, and socialAccounts fields
  • fetchSponsorablefetchGovernanceProfiles: same single GraphQL batch query, now also fetches bio, twitterUsername, and socialAccounts(first: 10)

app/pages/about.vue

  • Social icon mapping (socialIcons) and getSocialLinks() helper to deduplicate Twitter username vs social accounts
  • Bio line shown below the role label (truncated, title attribute for full text)
  • Row of social link icons next to the sponsor link, using existing i-simple-icons:* and i-lucide:* icon sets

Test plan

  • Verify governance cards show bio text when available
  • Verify social icons render and link correctly (X, Bluesky, Mastodon, etc.)
  • Verify cards still render correctly when GitHub token is unavailable (fallback path)
  • Verify sponsor link still works alongside social icons
  • Check mobile layout — cards should remain compact

🤖 Generated with Claude Code

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>
@vercel
Copy link

vercel bot commented Mar 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Mar 22, 2026 2:31pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Mar 22, 2026 2:31pm
npmx-lunaria Ignored Ignored Mar 22, 2026 2:31pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

The 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 SocialAccount type, adds logic to map social provider icons to URLs, and renders social links alongside sponsor information on contributor cards. The card layout now conditionally displays the contributor's bio as a tooltip instead of placeholder sponsor text.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The PR description clearly describes the changes related to the changeset, including UI enhancements, API modifications, and file changes that align with the code modifications.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #1564: added bio/description display on governance cards with tooltip, social identity claim icons for multiple platforms, replaced GraphQL query to fetch new fields, and maintained graceful degradation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the requirements: modifications to contributor API structure, governance profile data fetching, and about page card UI enhancements. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
server/api/contributors.get.ts (1)

222-233: Return a fresh object instead of mutating c and casting it.

Object.assign(c, ...) changes a GitHubAPIContributor into 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f2fc1a and e7100cc.

📒 Files selected for processing (2)
  • app/pages/about.vue
  • server/api/contributors.get.ts

Comment on lines +40 to +47
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',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "uno.config.ts" -o -name "uno.config.js" | head -5

Repository: npmx-dev/npmx.dev

Length of output: 76


🏁 Script executed:

wc -l app/pages/about.vue

Repository: npmx-dev/npmx.dev

Length of output: 84


🏁 Script executed:

head -100 app/pages/about.vue | tail -80

Repository: npmx-dev/npmx.dev

Length of output: 2307


🏁 Script executed:

cat uno.config.ts

Repository: npmx-dev/npmx.dev

Length of output: 8651


🏁 Script executed:

sed -n '272,295p' app/pages/about.vue

Repository: npmx-dev/npmx.dev

Length of output: 1134


🏁 Script executed:

sed -n '250,290p' app/pages/about.vue

Repository: 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 socialIcons object
  • Line 273: Icon rendering via :class="[link.icon, 'w-3 h-3']"

Comment on lines +276 to +285
<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" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines 118 to +150
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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

wc -l server/api/contributors.get.ts

Repository: npmx-dev/npmx.dev

Length of output: 95


🏁 Script executed:

cat -n server/api/contributors.get.ts

Repository: 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.ts

Repository: 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."

Comment on lines +218 to +231
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 ?? []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

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
Copy link

codecov bot commented Mar 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Description and identity claims to governance cards

1 participant