Skip to content
Merged
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
7 changes: 4 additions & 3 deletions src/app/api/maintainers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const CACHE_TTL_MS = 120_000;
interface RosterMaintainer {
githubId: string | null;
login: string;
association: string;
}

interface MinerIndex {
Expand Down Expand Up @@ -58,10 +59,10 @@ async function fetchRoster(owner: string, name: string): Promise<RosterMaintaine
const res = await fetch(url, { cache: 'no-store', signal: AbortSignal.timeout(15_000) });
if (!res.ok) return null;
const body = (await res.json()) as {
maintainers?: Array<{ github_id?: string | number; githubId?: string | number; login?: string }>;
maintainers?: Array<{ github_id?: string | number; githubId?: string | number; login?: string; association?: string }>;
};
return (body.maintainers ?? [])
.map((m) => ({ githubId: normId(m.github_id ?? m.githubId), login: (m.login ?? '').trim() }))
.map((m) => ({ githubId: normId(m.github_id ?? m.githubId), login: (m.login ?? '').trim(), association: (m.association ?? '').trim() }))
.filter((m) => m.login || m.githubId);
} catch {
return null;
Expand Down Expand Up @@ -190,7 +191,7 @@ async function build(): Promise<MaintainersResponse> {

const key = personKey(m);
const login = m.login || (m.githubId ? loginById.get(m.githubId) ?? m.githubId : 'unknown');
repoEntries.push({ login, githubId: m.githubId, registered: isRegistered, rewardShare: reward });
repoEntries.push({ login, githubId: m.githubId, association: m.association, registered: isRegistered, rewardShare: reward });
let p = people.get(key);
if (!p) {
p = {
Expand Down
153 changes: 101 additions & 52 deletions src/app/maintainers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -438,60 +438,81 @@ function MaintainerRow({
}

function RepoBreakdown({ m, minerPoolTAO }: { m: MaintainerSummary; minerPoolTAO: number }) {
// Columns line up with the parent table's GRID, so each repo's figures sit
// directly under Shipping / All-time / Grade / τ-day and visibly sum to the
// maintainer's aggregate row above.
return (
<Box sx={{ bg: 'canvas.inset', px: 3, py: 2, borderTop: '1px solid', borderColor: 'border.muted' }}>
<Box sx={{ bg: 'canvas.inset', borderTop: '1px solid', borderColor: 'border.muted' }}>
{m.repos.map((r, i) => {
const tao = r.rewardShare * minerPoolTAO;
const ship30 = r.mergedPrs30d + r.issuesResolved30d;
const total = r.mergedPrsTotal + r.issuesCompleted;
const modeLabel = r.mode === 'PR' ? 'PR review' : r.mode === 'issue' ? 'issue discovery' : 'mixed';
const cutPct = (r.maintainerCut * 100).toFixed(r.maintainerCut > 0 && r.maintainerCut < 0.1 ? 1 : 0);
return (
<Box
key={`${r.repo}-${i}`}
sx={{
display: 'grid',
gridTemplateColumns: ['1fr', 'minmax(160px,1.4fr) 72px 128px 80px 72px 90px'],
alignItems: 'center', gap: 2, py: '6px', fontSize: 0,
borderBottom: '1px solid', borderColor: 'border.muted',
'&:last-child': { borderBottom: 'none' },
display: 'grid', gridTemplateColumns: GRID, alignItems: 'center', gap: 2,
px: 3, py: '8px', fontSize: 0,
borderBottom: '1px solid', borderColor: 'border.muted', '&:last-child': { borderBottom: 'none' },
}}
>
{/* connector tick (under the caret column) */}
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Box sx={{ width: '1px', height: '16px', bg: 'border.default' }} />
</Box>

{/* repo identity + mode / cut subtext */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, minWidth: 0 }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={`https://github.com/${r.repo.split('/')[0]}.png?size=32`}
src={`https://github.com/${r.repo.split('/')[0]}.png?size=40`}
alt=""
width={16}
height={16}
style={{ borderRadius: 3, flexShrink: 0, background: 'var(--bgColor-muted, #222)' }}
width={18}
height={18}
style={{ borderRadius: 4, flexShrink: 0, background: 'var(--bgColor-muted, #222)' }}
/>
<a
href={`https://github.com/${r.repo}`}
target="_blank"
rel="noreferrer"
onClick={(e) => e.stopPropagation()}
style={{ color: 'inherit', textDecoration: 'none', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
>
<Text sx={{ color: 'fg.muted' }}>{r.repo.split('/')[0]}/</Text>
<Text sx={{ fontWeight: 500 }}>{r.repo.split('/')[1]}</Text>
</a>
<Text sx={{ color: MODE_COLOR[r.mode], textTransform: 'uppercase', letterSpacing: '0.04em', flexShrink: 0, fontSize: '9.5px' }}>
{r.mode === 'PR' ? 'PR' : r.mode === 'issue' ? 'issue' : 'mixed'}
</Text>
<Box sx={{ minWidth: 0 }}>
<a
href={`https://github.com/${r.repo}`}
target="_blank"
rel="noreferrer"
onClick={(e) => e.stopPropagation()}
style={{ color: 'inherit', textDecoration: 'none', display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
>
<Text sx={{ color: 'fg.muted' }}>{r.repo.split('/')[0]}/</Text>
<Text sx={{ fontWeight: 500, color: 'fg.default' }}>{r.repo.split('/')[1]}</Text>
</a>
<Text sx={{ display: 'block', whiteSpace: 'nowrap' }}>
<Text as="span" sx={{ color: MODE_COLOR[r.mode], textTransform: 'uppercase', letterSpacing: '0.04em', fontSize: '9.5px' }}>{modeLabel}</Text>
<Text as="span" sx={{ color: 'fg.subtle' }}> · {cutPct}% cut</Text>
</Text>
</Box>
</Box>
<Text sx={{ textAlign: ['left', 'right'], color: r.gradeScore != null ? LETTER_COLOR[r.gradeLetter] : 'fg.subtle' }}>
{r.gradeLetter}{r.gradeScore != null ? ` ${Math.round(r.gradeScore)}` : ''}{r.provisional ? '*' : ''}
</Text>
<Text className="tnum" sx={{ textAlign: ['left', 'right'], color: 'fg.muted' }}>
{ship30} / 30d · {formatDurationHours(r.speedHours)}
</Text>
<Text className="tnum" sx={{ textAlign: ['left', 'right'], color: 'fg.subtle' }}>
{r.mergedPrsTotal + r.issuesCompleted} total
</Text>
<Text className="tnum" sx={{ textAlign: ['left', 'right'], color: r.maintainerCut > 0 ? 'fg.muted' : 'fg.subtle' }} title="Maintainer cut — share of this repo's emission reserved for maintainers">
{(r.maintainerCut * 100).toFixed(r.maintainerCut > 0 && r.maintainerCut < 0.1 ? 1 : 0)}% cut
</Text>
<Text className="tnum mono" sx={{ textAlign: ['left', 'right'], color: tao > 0 ? 'success.fg' : 'fg.subtle' }}>
{fmtTao(tao)} τ/d
</Text>

{/* Repos column position — left blank for sub-rows */}
<span />

{/* Shipping · 30d (+ median response time) */}
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', lineHeight: 1.25 }}>
<Text className="tnum" sx={{ fontWeight: 500, color: 'fg.default' }}>{ship30}</Text>
{r.speedHours != null ? (
<Text className="tnum" sx={{ color: 'fg.subtle', whiteSpace: 'nowrap' }}>~{formatDurationHours(r.speedHours)}</Text>
) : null}
</Box>

{/* All-time */}
<Text className="tnum" sx={{ textAlign: 'right', color: 'fg.muted' }}>{total}</Text>

{/* Grade */}
<Box sx={{ textAlign: 'right' }}>
<Text sx={{ fontWeight: 600, color: r.gradeScore != null ? LETTER_COLOR[r.gradeLetter] : 'fg.subtle' }}>{r.gradeLetter}</Text>
{r.gradeScore != null ? <Text className="tnum" sx={{ color: 'fg.subtle', ml: 1 }}>{Math.round(r.gradeScore)}{r.provisional ? '*' : ''}</Text> : null}
</Box>

{/* τ / day */}
<Text className="tnum mono" sx={{ textAlign: 'right', color: tao > 0 ? 'success.fg' : 'fg.subtle' }}>{fmtTao(tao)}</Text>
</Box>
);
})}
Expand Down Expand Up @@ -580,50 +601,78 @@ function RepoRow({
);
}

function RolePill({ role }: { role: string }) {
return (
<Box
as="span"
sx={{
flexShrink: 0, px: 1, fontSize: '10px', lineHeight: '16px', borderRadius: 1,
bg: 'neutral.muted', color: 'fg.muted', textTransform: 'lowercase', letterSpacing: '0.02em',
}}
>
{role.toLowerCase()}
</Box>
);
}

function RepoMaintainerList({ r, minerPoolTAO }: { r: RepoMaintainersSummary; minerPoolTAO: number }) {
const registeredCount = r.maintainers.filter((m) => m.registered).length;
const cutPct = (r.maintainerCut * 100).toFixed(r.maintainerCut > 0 && r.maintainerCut < 0.1 ? 1 : 0);
return (
<Box sx={{ bg: 'canvas.inset', px: 3, py: 2, borderTop: '1px solid', borderColor: 'border.muted' }}>
{/* legend — replaces the per-row "registered / not" text */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 1, fontSize: 0, color: 'fg.subtle' }}>
<Box as="span" sx={{ color: 'accent.fg', display: 'inline-flex' }}><VerifiedIcon size={11} /></Box>
<Text>
{registeredCount} / {r.maintainerCount} registered {registeredCount === 1 ? 'miner' : 'miners'}
{r.maintainerCut > 0 ? <> — split this repo&apos;s {cutPct}% maintainer cut</> : ' — no maintainer cut on this repo'}
</Text>
</Box>

{r.maintainers.map((m, i) => {
const tao = m.rewardShare * minerPoolTAO;
return (
<Box
key={`${m.githubId ?? m.login}-${i}`}
sx={{
display: 'grid', gridTemplateColumns: ['1fr', 'minmax(160px,1fr) 140px 90px'],
alignItems: 'center', gap: 2, py: '6px', fontSize: 0,
display: 'grid', gridTemplateColumns: '1fr auto', alignItems: 'center', gap: 2,
py: '7px', fontSize: 1, opacity: m.registered ? 1 : 0.62,
borderBottom: '1px solid', borderColor: 'border.muted', '&:last-child': { borderBottom: 'none' },
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, minWidth: 0 }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={`https://github.com/${m.login}.png?size=32`}
src={`https://github.com/${m.login}.png?size=40`}
alt=""
width={16}
height={16}
width={20}
height={20}
style={{ borderRadius: '50%', flexShrink: 0, background: 'var(--bgColor-muted, #222)' }}
/>
<a
href={`https://github.com/${m.login}`}
target="_blank"
rel="noreferrer"
onClick={(e) => e.stopPropagation()}
style={{ color: 'inherit', textDecoration: 'none', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}
style={{ color: 'inherit', textDecoration: 'none', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', minWidth: 0 }}
>
<Text sx={{ fontWeight: 500 }}>{m.login}</Text>
<Text sx={{ fontWeight: 500, color: m.registered ? 'fg.default' : 'fg.muted' }}>{m.login}</Text>
</a>
{m.registered ? (
<Box sx={{ color: 'accent.fg', display: 'flex', flexShrink: 0 }} title="Registered Gittensor miner">
<Box sx={{ color: 'accent.fg', display: 'flex', flexShrink: 0 }} title="Registered Gittensor miner — earns the maintainer reward">
<VerifiedIcon size={12} />
</Box>
) : null}
{m.association ? <RolePill role={m.association} /> : null}
</Box>
<Text sx={{ textAlign: ['left', 'right'], color: 'fg.subtle' }}>
{m.registered ? 'registered miner' : 'not a registered miner'}
</Text>
<Text className="tnum mono" sx={{ textAlign: ['left', 'right'], color: tao > 0 ? 'success.fg' : 'fg.subtle' }}>
{fmtTao(tao)} τ/d
</Text>

{m.registered ? (
<Text className="tnum mono" sx={{ textAlign: 'right', color: tao > 0 ? 'success.fg' : 'fg.muted', fontSize: 0, whiteSpace: 'nowrap' }}>
{fmtTao(tao)} τ/d
</Text>
) : (
<Text sx={{ textAlign: 'right', color: 'fg.subtle', fontSize: 0 }}>—</Text>
)}
</Box>
);
})}
Expand Down
3 changes: 3 additions & 0 deletions src/lib/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,9 @@ export interface MaintainerSummary {
export interface RepoMaintainerEntry {
login: string;
githubId: string | null;
/** GitHub author association on the repo (OWNER / MEMBER / COLLABORATOR / …),
* '' when the mirror didn't provide one. */
association: string;
/** Registered Gittensor miner — earns the reward split on this repo. */
registered: boolean;
/** This maintainer's τ-fraction from this repo (0 when not registered). */
Expand Down