Discontinued for privacy reasons: the published data and all screenshots were deleted, the site only says the board is discontinued, and the repository is archived. The code stays for reference.
A self-refreshing wall display for JabCon, the JabRef developer meetup: what participants are working on, what got merged, who did what, plus a gource video of the JabRef repository during the meetup.
Requirements: docs/requirements.md, traced with OpenFastTrace (scripts/trace.sh).
Live: https://jabref.github.io/jabcon-board/ (only carries data while JabCon runs). The layout scales with the
screen width (Full HD and UHD look the same); Ctrl+mouse wheel scales the page (remembered in the browser, Ctrl+0 resets); ?scale=0.8 / ?scale=1.2 sets it via the URL; ?still=1 to switch off the slow background animation that keeps TVs from dimming a "static" image, ?still=0 to keep the board moving even when the machine asks for reduced motion (Windows does that in battery saver, and a still board loses the newsticker and the dwarf too).
scripts/collect.pyqueries the GitHub search and events APIs for the participants' public activity in any repository and writesdata.json. Cards from the configured org are shown normally, other repos dimmed. The ring next to the logo fills up over the typical gap between data runs (10 min; GitHub runs the 5-minute schedule only best-effort), turns gray when a run is late and amber when data is older than 30 min. "In progress" only lists open PRs touched since the start; "Backlog" lists the org'sready-for-reviewPRs and everything assigned to a participant.- Posts by participants to the
mailing_listsare read from the OpenJDK HyperKitty archive and join the ticker. Senders are matched by the public name and e-mail of their GitHub profile; mail from an@openjdk.orgaddress is the Skara bot mirroring GitHub pull request activity and is skipped, so JavaFX reviews are not counted twice. - A new release of the
pr_goalrepository (prereleases included) sets off a party: the bell, a minute of confetti and a chorus line of dancing dwarfs that keeps going, dimmed and slower, for three hours, plus a mark on the header timeline and a headline on the newsticker. The dance is timed from the release itself, so a board opened during those hours joins in. Releases are cut by a bot, so the collector asks the repository rather than the participants' event feeds. site/is plain HTML/CSS/JS. It fetchesdata.jsonevery minute, the video every 15 minutes, and reloads itself when a new version of the site was deployed..github/workflows/board.ymlruns every 5 minutes (and on push tomain), copiessite/and a freshdata.jsonto thegh-pagesbranch. Outside the JabCon window it publishes the site but skips data collection.gh-pagesis always a single commit (scripts/gh-pages.shforce-pushes a fresh one), so it never piles up old data or videos..github/workflows/screenshot.ymlstores a screenshot underscreenshots/every three hours during the day of a JabCon day; the night hours are left out. (The screenshots were removed from the history when the board was discontinued.)- The video is rendered by
gource-jabcon.ymlin JabRef/jabref tohttps://files.jabref.org/gource/jabcon-2026.mp4; the URL is set at the top ofsite/app.js. scripts/highlights.py data.json jabcon-2026.mp4 highlights.mp4 [count]cuts a highlights reel from that video: a Star-Wars crawl per biggest merged PR over the gource footage leading up to it, then a BOOM at the merge moment. Only seconds in which gource moves are used, so the reel never shows a still. Needsffmpegandgh(the merge commits onmainlocate the moments; PRs the video does not contain yet are left out). With a fifth argument it also writes the full run with a sports commentator's subtitles, one line per merge, phrased from the board's data (no language model), at half speed for reading..github/workflows/highlights.ymlrenders both hourly to https://jabref.github.io/jabcon-board/highlights.mp4 andcommentary.mp4; the wall plays the three videos in turn.
config.json: jabcon_start / jabcon_end (ISO timestamps with offset), phases (label + end time, drawn on the header progress bar), timezone (for the clock),
participants (GitHub logins; their order sets the colours), org (highlighting and the ready-for-review backlog), focus_label (a label in the org, e.g. project: jabcon: its open items are always in the Backlog, labeled cards form the first group of every column, and a progress bar for it sits above the milestones),
exclude_repos (owner/name entries to hide, e.g. when unrelated activity dominates the board),
mailing_lists (archive address -> the repo whose factor and link its posts use, e.g. openjfx-dev@openjdk.org -> openjdk/jfx),
milestones (owner/repo/number; progress bars in the stats panel, green = closed since the board first saw the milestone),
repo_factors (multipliers keyed by repo or whole org; a repo entry beats its org entry, which is how JabRef/jabcon-board stays at 1 while the rest of the org counts fivefold),
dependency_repos (JabRef's own dependency stack, for the 🔧 bonus; forks of them count too),
record_bonus_exclude (logins that keep their nerd corner records but pass the +100 to the runner-up),
nerd_prs (explicit owner/repo#number PRs whose refactorings appear in Nerd corner without becoming participant cards),
honorary_awards (login, title, text, emoji, optional url: +100 for what the API cannot see).
private_repos lists private repos whose issue activity is counted for the leaderboard and shown as counts only (no titles) under the milestones. Milestones and private_repos need a repository secret BOARD_TOKEN (fine-grained PAT with read access to issues of that repo); otherwise they are skipped with a warning.
The optional BONUS_CAP environment variable ({"login": n}, a repository secret) limits how many bonus awards a
contributor holds; it is deliberately not part of config.json, which the board publishes.
Bot activity is excluded, and so is automation running under a human account: exclude_titles (item titles, e.g.
New Crowdin updates) and exclude_branches (e.g. l10n_main) drop those items, their events and their points.
"Nerd corner" lists the five most interesting merged changes, detected by regexes on the diffs (sealed types, records, pattern matching, moved or deleted classes, net-negative PRs, ...), plus one record holder per funny category (longest identifier, longest, shortest and most sophisticated method, most modern Java, most code written or deleted, most tangled diff, wordiest changelog entry). Each record carries an icon and pays its author +100 bonus points.
Recomputed at every data run from activity since jabcon_start, in any public repository (bots excluded):
| Activity | Points | Credited to |
|---|---|---|
| Pull request merged | 3 | the PR author (not the person who pressed merge) |
| Review submitted (approve, request changes, comment) | 1, 2 or 3 by the complexity of the reviewed diff (branch points in added code: trivial, normal, 20+) | the reviewer |
| Issue comment or review comment | 1 | the commenter |
| Issue opened or closed, PR closed without merge | 1 | the actor |
| Push | 1 per push event (not per commit) | the pusher |
Issue opened / closed / commented in a private_repos repo |
1 | the actor |
| Mail to a configured mailing list | 1 | the sender |
Each of these is multiplied: ten-fold on a JabCon item (focus label or a configured milestone), otherwise by
repo_factors (repo before org). A merged PR whose commits credit an AI assistant scores a quarter — writing it
by hand is the harder craft. A comment or review whose body carries 🤖 or names Claude was written by the assistant
and scores nothing at all, and counts for no bonus either.
Labeling, assigning, starring, forking and merging someone else's PR score nothing. Hover a leaderboard entry to see
the breakdown; the activity ticker shows the points of each entry. A bell rings when position 1 changes (browsers may
need one click on the page after load before they allow sound). Weights live in leaderboard() in scripts/collect.py
and, for the ticker badge, in eventPoints() in site/app.js.
A second evaluation, like the bonus round in a game: +100 for every superlative a contributor holds, shared by everyone tied for it. Only what somebody contributed can win one: no award ranks when a person works or whether they used an assistant. The icons sit under the login on the leaderboard and in the contributor detail view, newest first; each links to what earned it (the record's PR, or the GitHub search behind the number). Self-reviews, fork syncs and AI-written comments count for none of them.
| Icon | Award | Who gets it |
|---|---|---|
| 💬 | Chatterbox | most comments |
| 🐝 | Busy bee | most PRs and issues touched |
| 🌍 | Globetrotter | most repositories worked on |
| 💡 | Idea machine | most PRs opened |
| 🛡️ | Gatekeeper | most reviews |
| 🏁 | Closer | most merged PRs |
| ☕ | Ambassador | most activity outside the org (own forks do not count) |
| 🔧 | Dependency whisperer | most activity in dependency_repos and their forks |
| 🎨 | Jack of all trades | widest set of components written or reviewed (by changed files) |
| 🏷️ | Component collector | most distinct component: labels written or reviewed |
| 🛸 | Exotic explorer | most foreign repositories that are not dependencies |
| 🔍 | Reviewer's reviewer | largest share of own activity spent reviewing (from 10 reviews on) |
| 🤐 | Actions over words | most reviews per comment written (from 10 reviews on) |
| ✅ | Rubber stamp | most approvals given |
| 💥 | Force of nature | most force pushes (the pushed branch's previous head is no ancestor of the new one) |
| 🧹 | Janitor | most branches deleted |
| 🤝 | Widest reach | reviewed the PRs of the most different authors |
| ❓ | Socratic | most questions asked |
| 🙏 | Most gracious | most thanks said |
| 🧲 | Magnet | own PRs pulled in the most reviews |
| ⚡ | First responder | first to review the most PRs |
| 🚀 | All killer, no filler | largest share of own activity that is landing a PR (from 5 events on) |
| 📝 | Essayist | longest comments on average (from 5 comments on) |
| 🚚 | Freight train | most commits per push (from 5 pushes on) |
| 🐛 | Reporter | most issues opened |
| 🪶 | Featherweight | most merged PRs of at most 50 changed lines |
| ⚖️ | Middleweight | most merged PRs between 50 and 500 changed lines |
| 🐘 | Heavyweight | most merged PRs above 500 changed lines |
| 🗣️ | Sounding board | largest share of own activity spent commenting (from 5 events on) |
| 🔭 | Big picture | largest share of own activity spent on issues rather than code (from 5 events on) |
| 🚧 | Hard to please | largest share of own reviews that asked for changes (from 3 reviews on) |
| 🏎️ | Speedrun | shortest time from opening a PR to its merge |
| 🧟 | Necromancer | activity on the oldest item somebody else opened |
| 🐣 | Newcomer | most recent first issue or PR in the org |
| 📏🐍🤏🧠☕✍️🔥🍝📜 | Nerd corner records | longest identifier, longest, shortest and most sophisticated method, most modern Java, most code written, most deleted, most tangled diff, wordiest changelog entry |
| 🍸 … | honorary_awards |
whatever the jury decides |
Categories, thresholds and the 100 itself live in BONUS_KINDS and BONUS in scripts/collect.py.
The board names people, so site/privacy.html (linked from the footer) is the GDPR Art. 13/14 notice: what is
collected, from where, why, for how long, and how to be taken off the board — which is removing the login from
participants, after which the next run drops that person's cards, events and points. Keep it honest when the data
changes. Profile e-mail addresses are used in memory to match mailing list posts and never published. The page loads
nothing from third parties (confetti.browser.min.js and the logo are vendored) and sets no cookies.
All workflows have workflow_dispatch (Actions tab → workflow → "Run workflow"). "Board" takes a force
flag to collect data outside the JabCon window.
Locally:
GITHUB_TOKEN=$(gh auth token) python3 scripts/collect.py --force --out site/data.json
python3 -m http.server -d site 8000 # open http://localhost:8000/Delete the gh-pages branch, switch or remove the gource workflow in JabRef/jabref.