From 0e55b285386bd26b659334fae58278dce89b960a Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Wed, 23 Sep 2026 11:54:31 +0700 Subject: [PATCH 1/5] feat(queen): the leaderboard shows the GitHub people behind the lanes The connection to GitHub is direct - the issues, the branches and the pull requests a bee opens all live there - so a lane signed `@login` in TRIOS_KEY_OWNERS now draws that person's avatar and links their profile. A leaderboard of faces is an invitation; a leaderboard of `key #7` is a log. The avatar is `github.com/.png`, a public redirect: no API call, no token, no rate limit, and a 404 leaves the row readable. The login is checked AGAIN here rather than trusted from the wire, because it ends up in an href and an img src, and a page that trusts a remote string to build a profile link can be pointed at somebody else's account by whoever writes that string. Also fixes the row on a phone: the three numbers now ride in one box, so "Accepted" and "Bee hours" can no longer land on top of each other when the row wraps (seen at 440 px on the live board). Co-Authored-By: Claude Opus 5 --- .../src/components/QueenLeaderboard.css | 48 +++++++++- .../src/components/QueenLeaderboard.tsx | 96 ++++++++++++++----- 2 files changed, 117 insertions(+), 27 deletions(-) diff --git a/apps/website/src/components/QueenLeaderboard.css b/apps/website/src/components/QueenLeaderboard.css index 1b6a61f61c..145cfd774a 100644 --- a/apps/website/src/components/QueenLeaderboard.css +++ b/apps/website/src/components/QueenLeaderboard.css @@ -46,7 +46,7 @@ } .ql-row { display: grid; - grid-template-columns: 28px minmax(140px, 1.4fr) minmax(80px, 2fr) auto auto auto; + grid-template-columns: 28px 36px minmax(140px, 1.4fr) minmax(80px, 1.6fr) auto; align-items: center; gap: 12px; padding: 10px 12px; @@ -60,6 +60,37 @@ border-style: dashed; opacity: 0.85; } +.ql-face { + width: 36px; + height: 36px; + border-radius: 50%; + object-fit: cover; + border: 1px solid var(--hud-line, rgba(0, 255, 136, 0.22)); + background: rgba(255, 255, 255, 0.04); +} +.ql-face.is-anon { + display: grid; + place-items: center; + font-size: 14px; + color: var(--hud-muted, rgba(255, 255, 255, 0.78)); +} +.ql-who a { + color: var(--hud-green, #00ff88); + text-decoration: none; + border-bottom: 1px solid rgba(0, 255, 136, 0.35); +} +.ql-who a:hover, +.ql-who a:focus-visible { + border-bottom-color: var(--hud-green, #00ff88); +} +/* The three numbers ride together so a wrap can never stack one label on + another: at width they sit in a row, on a phone they spread across it. */ +.ql-nums { + display: flex; + align-items: baseline; + gap: 16px; + white-space: nowrap; +} .ql-rank { font-variant-numeric: tabular-nums; color: var(--hud-gold, #ffd700); @@ -108,10 +139,19 @@ @media (max-width: 760px) { .ql-row { - grid-template-columns: 24px 1fr auto; - row-gap: 6px; + grid-template-columns: 22px 32px 1fr; + row-gap: 8px; + } + .ql-face { + width: 32px; + height: 32px; } - .ql-bar { + .ql-bar, + .ql-nums { grid-column: 1 / -1; } + .ql-nums { + justify-content: space-between; + gap: 10px; + } } diff --git a/apps/website/src/components/QueenLeaderboard.tsx b/apps/website/src/components/QueenLeaderboard.tsx index f5a6dc1bf6..c2ed01cd83 100644 --- a/apps/website/src/components/QueenLeaderboard.tsx +++ b/apps/website/src/components/QueenLeaderboard.tsx @@ -18,6 +18,8 @@ import './QueenLeaderboard.css' interface Contributor { name: string claimed: boolean + /** Their GitHub login, when the operator signed the lane as `@login`. */ + github?: string keys: number[] accepted: number finished: number @@ -25,6 +27,18 @@ interface Contributor { xp: number } +/** + * A GitHub login, checked again here rather than trusted from the wire. + * + * The server already refuses anything that is not one, and this is the second + * lock on the same door: the value ends up in an `href` and an ``, and + * a page that trusts a remote string to build a profile link is a page that can + * be pointed at somebody else's account by whoever can write that string. + */ +const GITHUB_LOGIN = /^[a-zA-Z\d](?:[a-zA-Z\d]|-(?=[a-zA-Z\d])){0,38}$/ +const loginOf = (row: Contributor) => + row.github && GITHUB_LOGIN.test(row.github) ? row.github : null + interface Board { days: number measuredAt: string @@ -123,29 +137,65 @@ export default function QueenLeaderboard({ lang }: { lang: 'en' | 'ru' }) {

{c.empty}

) : (
    - {board.contributors.map((row, i) => ( -
  1. - {i + 1} - - {row.name} - - {c.lanes}: {row.keys.map((k) => `#${k}`).join(' ')} - - - {/* The bar is the share of the leader's XP: a rank tells you the - order, and this tells you the distance. */} - - - {fmt(row.accepted)} {c.accepted} - - - {row.hours.toFixed(1)} {c.hours} - - {fmt(row.xp)} {c.xp} -
  2. - ))} + {board.contributors.map((row, i) => { + const login = loginOf(row) + return ( +
  3. + {i + 1} + {/* The avatar comes from github.com/.png, a public + redirect: no API call, no token, and a leaderboard that does + not wait on GitHub. If it 404s the row still reads. */} + {login ? ( + + ) : ( + + )} + + {login ? ( + + {row.name} + + ) : ( + row.name + )} + + {c.lanes}: {row.keys.map((k) => `#${k}`).join(' ')} + + + {/* The bar is the share of the leader's XP: a rank tells you the + order, and this tells you the distance. */} + + {/* The three numbers ride in one box so they cannot land on top + of each other when the row wraps on a phone. */} + + + {fmt(row.accepted)} {c.accepted} + + + {row.hours.toFixed(1)} {c.hours} + + + {fmt(row.xp)} {c.xp} + + +
  4. + ) + })}
)} From 3d8a1ce2b69a519ab5909f6eecb4b048ec164066 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Wed, 23 Sep 2026 11:58:03 +0700 Subject: [PATCH 2/5] feat(queen): the roadmap asks people in, not just counts The tab said how far the rewrite has to go and never said that anyone could push it. A measurement is not a reason for a stranger to stay. So the count of OPEN port issues is now a button that opens the real GitHub search - not a page about the project, the issues themselves - and the text says the two things a newcomer does not know: that the work is cut into one file per issue, and that they need neither permission nor prior .t27 to take one. Underneath, the other way in for someone with no time to write code: lend the swarm a lane and its work earns XP. The number costs no extra request. It is the same stage-progress search the bars already make, read as `all - done`. Co-Authored-By: Claude Opus 5 --- apps/website/src/components/QueenRoadmap.tsx | 64 ++++++++++++++++++++ apps/website/src/components/queenRoadmap.css | 60 ++++++++++++++++++ 2 files changed, 124 insertions(+) diff --git a/apps/website/src/components/QueenRoadmap.tsx b/apps/website/src/components/QueenRoadmap.tsx index 4359c12de0..a505a696de 100644 --- a/apps/website/src/components/QueenRoadmap.tsx +++ b/apps/website/src/components/QueenRoadmap.tsx @@ -98,6 +98,15 @@ const COPY = { target: 'target', loading: 'Reading the count…', failed: 'The count could not be read.', + joinTitle: 'The rewrite is one file at a time. Take one.', + joinBody: + 'Nobody rewrites a stack in one commit. Each stage is cut into issues of one file each, and every closed one moves the number above. You do not need permission and you do not need to know .t27 first: the issue names the file, the boundary says what it may touch, and the Queen reviews what comes back.', + joinOpen: 'port issues open right now', + joinBrowse: 'Browse the open issues', + joinLend: 'Or lend the swarm a lane', + joinLendBody: + 'No time to write code? A bee runs on somebody’s provider API key. Lend one and its work earns you XP on the leaderboard — several providers give a key away for nothing.', + joinLearn: 'How to join, step by step', }, ru: { title: 'ДОРОЖНАЯ КАРТА', @@ -121,6 +130,15 @@ const COPY = { target: 'цель', loading: 'Читаю подсчёт…', failed: 'Подсчёт прочитать не удалось.', + joinTitle: 'Переписывание идёт по одному файлу. Возьмите один.', + joinBody: + 'Никто не переписывает стек одним коммитом. Каждый этап нарезан на задачи по одному файлу, и каждая закрытая двигает число выше. Разрешения не нужно, и знать .t27 заранее тоже: в задаче назван файл, границы говорят, что можно трогать, а Королева проверяет то, что вернулось.', + joinOpen: 'задач переноса открыто прямо сейчас', + joinBrowse: 'Посмотреть открытые задачи', + joinLend: 'Или дайте рою полосу', + joinLendBody: + 'Нет времени писать код? Пчела работает на чьём-то API-ключе провайдера. Одолжите свой — и его работа принесёт вам XP в лидерборде; несколько провайдеров выдают ключ бесплатно.', + joinLearn: 'Как присоединиться, по шагам', }, } as const @@ -283,6 +301,52 @@ export default function QueenRoadmap({ lang }: { lang: 'en' | 'ru' }) {
{summary.code.length}{lang === 'ru' ? 'языков' : 'languages'}
+ {/* THE INVITATION. A measurement is not a reason for a stranger to stay: + this tab said how far the rewrite has to go and never said that + anyone could push it. The count of open issues is the one already + fetched for the stage bars, so this costs no extra request, and it + links the real GitHub search rather than a page about the project. */} + {goals && ( + + )} +

{c.languages}

[l, v.bytes])} total={summary.total} height={22} />