Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 45 additions & 3 deletions app/pages/about.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,59 @@ const { data: contributors, status: contributorsStatus } = useLazyFetch('/api/co
</div>

<div>
<h2 class="text-lg text-fg-subtle uppercase tracking-wider mb-4">
<h2 class="text-lg text-fg-subtle uppercase tracking-wider mb-4">Team</h2>
<p class="text-fg-muted leading-relaxed mb-6">
{{ $t('about.contributors.description') }}
</p>

<!-- TODO: update string and maintainers list -->
<div v-if="contributors?.some(c => c.role !== 'contributor')" class="mb-12">
<h3 class="text-sm text-fg-subtle uppercase tracking-wider mb-4">Governance</h3>
<p class="text-fg-muted leading-relaxed mb-6">
TODO: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
</p>
Comment on lines +150 to +152
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

Remove placeholder TODO text before merging

There are multiple instances of raw placeholder text that will be visible to users:

  • Lines 150-152: "TODO: Lorem ipsum..." in the Governance section
  • Line 178: "TODO: add other maintainers" rendered directly in the grid
  • Lines 192-193: "TODO: Lorem ipsum..." in the Contributors section

These should be replaced with proper i18n keys or removed.

Also applies to: 178-178, 192-193


<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3">
<a
v-for="person in contributors
.filter(c => c.role !== 'contributor')
.concat(contributors.filter(c => c.role !== 'contributor'))
.concat(contributors.filter(c => c.role !== 'contributor'))
.concat(contributors.filter(c => c.role !== 'contributor'))
.concat(contributors.filter(c => c.role !== 'contributor'))"
:key="person.id"
Comment on lines +156 to +162
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 11, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Remove debug .concat() chain — causes duplicate keys and 5× repeated entries

The repeated .concat() calls duplicate governance members five times, which will:

  1. Cause Vue key warnings since :key="person.id" will have duplicates
  2. Render the same people multiple times in the UI
  3. Degrade performance with unnecessary iterations

This appears to be debug/test code that should be removed before merging.

🐛 Proposed fix
               <a
-                v-for="person in contributors
-                  .filter(c => c.role !== 'contributor')
-                  .concat(contributors.filter(c => c.role !== 'contributor'))
-                  .concat(contributors.filter(c => c.role !== 'contributor'))
-                  .concat(contributors.filter(c => c.role !== 'contributor'))
-                  .concat(contributors.filter(c => c.role !== 'contributor'))"
+                v-for="person in contributors.filter(c => c.role !== 'contributor')"
                 :key="person.id"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
v-for="person in contributors
.filter(c => c.role !== 'contributor')
.concat(contributors.filter(c => c.role !== 'contributor'))
.concat(contributors.filter(c => c.role !== 'contributor'))
.concat(contributors.filter(c => c.role !== 'contributor'))
.concat(contributors.filter(c => c.role !== 'contributor'))"
:key="person.id"
v-for="person in contributors.filter(c => c.role !== 'contributor')"
:key="person.id"

Copy link
Member Author

Choose a reason for hiding this comment

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

no problem, this is just a stub for this WIP commit

Copy link
Contributor

Choose a reason for hiding this comment

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

@shuuji3, understood! Thanks for clarifying — I'll let this pass as WIP stub code. Feel free to clean it up before the final merge. 👍

:href="person.html_url"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-3 p-2 border border-border rounded-lg hover:bg-fg/5 transition-colors group"
>
<img
:src="`${person.avatar_url}&s=64`"
alt=""
class="w-10 h-10 rounded-md ring-1 ring-border group-hover:ring-accent"
/>
<div class="min-w-0">
<div class="font-mono text-sm text-fg truncate">@{{ person.login }}</div>
<div class="text-sm text-fg-muted tracking-tight">{{ person.role }}</div>
</div>
</a>
TODO: add other maintainers
</div>
</div>

<h3 class="text-sm text-fg-subtle uppercase tracking-wider mb-4">
{{
$t(
'about.contributors.title',
{ count: $n(contributors?.length ?? 0) },
contributors?.length ?? 0,
)
}}
</h2>
</h3>
<p class="text-fg-muted leading-relaxed mb-6">
{{ $t('about.contributors.description') }}
TODO: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
incididunt ut labore et dolore magna aliqua.
</p>

<!-- Contributors cloud -->
Expand Down
31 changes: 29 additions & 2 deletions server/api/contributors.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@ export interface GitHubContributor {
avatar_url: string
html_url: string
contributions: number
role: Role
}

// TODO: stub - need to fetch list of role members from somewhere to avoid hardcoding (
type Role = 'stewards' | 'core' | 'maintainers' | 'contributor'
const roleMembers: Record<Exclude<Role, 'contributor'>, Set<GitHubContributor['login']>> = {
stewards: new Set(['danielroe', 'patak-dev']),
core: new Set([]),
maintainers: new Set([]),
}

function getRoleInfo(login: string): { role: Role; order: number } {
if (roleMembers.stewards.has(login)) return { role: 'stewards', order: 0 }
if (roleMembers.core.has(login)) return { role: 'core', order: 1 }
if (roleMembers.maintainers.has(login)) return { role: 'maintainers', order: 2 }
return { role: 'contributor', order: 3 }
}

export default defineCachedEventHandler(
Expand Down Expand Up @@ -46,8 +62,19 @@ export default defineCachedEventHandler(
page++
}

// Filter out bots
return allContributors.filter(c => !c.login.includes('[bot]'))
return (
allContributors
// Filter out bots
.filter(c => !c.login.includes('[bot]'))
// Assign role
.map(c => {
const { role, order } = getRoleInfo(c.login)
return Object.assign(c, { role, order })
})
// Sort by role (steward > core > maintainer > contributor)
.sort((a, b) => a.order - b.order)
.map(({ order: _, ...rest }) => rest)
)
},
{
maxAge: 3600, // Cache for 1 hour
Expand Down
Loading