feat: nightly contributors.svg — squircle mosaic for the credits pages#5724
Conversation
…t capture Adds a contributors mode to tools/sponsorkit that renders a squircle mosaic of all repo contributors, graded by contribution count. Besides the REST contributors API it scans the v2/v3 changelogs for @login credits so people whose PRs were squashed or hand-applied still appear. Both docs credits pages now embed the nightly-generated SVG instead of the stale 2024 all-contributors table.
WalkthroughAdds a Go-based contributors SVG generator to ChangesContributors Image Generation
Docs and Website Credits Pages Migration
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Workflow as GitHub Actions workflow
participant CLI as tools/sponsorkit/main.go
participant Fetcher as FetchContributors
participant GitHub as GitHub API
participant Renderer as RenderContributors
Workflow->>CLI: run -mode contributors -repo ... -changelogs ...
CLI->>Fetcher: FetchContributors(client, token, repo, changelogs)
Fetcher->>GitHub: REST contributors + GraphQL merged PRs
GitHub-->>Fetcher: contributor data
Fetcher-->>CLI: sorted []Contributor
CLI->>Renderer: RenderContributors(contributors, opts)
Renderer-->>CLI: contributors.svg
CLI->>Workflow: write contributors.svg
Workflow->>Workflow: commit sponsors.svg and contributors.svg if changed
Possibly related PRs
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Pull request overview
This PR replaces the stale, manually-committed contributors HTML tables on the v2 + v3 docs credits pages with a nightly-generated contributors.svg, produced by the existing Go tools/sponsorkit generator via a new -mode contributors.
Changes:
- Add contributors fetching + changelog credit scanning, and render a contributors “squircle mosaic” SVG (
tools/sponsorkitcontributors mode). - Update v2/v3 credits pages (and locale copies) to embed
contributors.svgvia<object>(with<img>fallback) so in-SVG links/hover effects work. - Update the scheduled GitHub Actions workflow to generate + commit both
sponsors.svgandcontributors.svg.
Reviewed changes
Copilot reviewed 30 out of 31 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| website/src/pages/credits.mdx | Replaces inline contributors table with <object> embed of /img/contributors.svg. |
| tools/sponsorkit/render_contributors.go | New contributors SVG renderer (bands, squircles, hover chip/glare effects). |
| tools/sponsorkit/README.md | Documents new contributors mode and where both images are used. |
| tools/sponsorkit/main.go | Adds -mode contributors, -repo, and -changelogs and routes generation accordingly. |
| tools/sponsorkit/contributors.go | New REST + changelog scanning contributor aggregation and deterministic sorting. |
| tools/sponsorkit/config.go | Adds contributor “bands” configuration (sizes, rings, hover behavior). |
| tools/sponsorkit/avatar.go | Keeps original encoded avatar bytes when smaller than re-encoded JPEG. |
| docs/src/content/docs/credits.mdx | Switches contributors section from imported HTML to <object> embed of deployed contributors SVG. |
| docs/src/content/docs/de/credits.mdx | Same contributors embed update for German locale. |
| docs/src/content/docs/fr/credits.mdx | Same contributors embed update for French locale. |
| docs/src/content/docs/id/credits.mdx | Same contributors embed update for Indonesian locale. |
| docs/src/content/docs/ja/credits.mdx | Same contributors embed update for Japanese locale. |
| docs/src/content/docs/ko/credits.mdx | Same contributors embed update for Korean locale. |
| docs/src/content/docs/pt/credits.mdx | Same contributors embed update for Portuguese locale. |
| docs/src/content/docs/ru/credits.mdx | Same contributors embed update for Russian locale. |
| docs/src/content/docs/zh-cn/credits.mdx | Same contributors embed update for Simplified Chinese locale. |
| docs/src/content/docs/zh-tw/credits.mdx | Same contributors embed update for Traditional Chinese locale. |
| docs/src/assets/contributors.html | Removes the old committed contributors snapshot asset. |
| .github/workflows/generate-sponsor-image.yml | Nightly workflow now regenerates + commits both SVGs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Commit counts reward granular unsquashed histories (tmclane's 231 commits vs 26 merged PRs) while squash-merged work collapses to one commit per PR. -metric prs counts merged pull requests per author via GraphQL instead, treating one PR as one unit of work regardless of merge style. Band thresholds have a PR-tuned set; changelog mentions still act as the fallback credit. Default remains commits until the metric choice is settled.
Lea's call after comparing both metrics: merged-PR counts match the project's sense of who contributed what far better than raw commit counts, which are skewed by merge style. -metric defaults to prs; the workflow passes it explicitly; commits remains available as a flag.
- clipPath ids are now sequence-numbered instead of sanitized logins, which could collide (foo-bar vs foobar) and clip the hover glare to the wrong avatar; applies to both renderers - remove stale 'import the auto-generated contributors file' comments left in the id/ja/ko/zh-cn locale credits pages - regenerate both SVGs
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tools/sponsorkit/avatar.go (1)
82-97: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winSize-based raw-bytes fallback can reintroduce transparency the JPEG flattening was meant to remove.
This picks whichever encoding is smaller by byte size alone, with no opacity check. The comment scopes the intent to "flat-colour identicons" (which are typically fully opaque), but the condition applies to any avatar — a user-uploaded PNG/GIF with transparent regions that happens to compress smaller than the JPEG re-encode would bypass the
cardBackgroundflattening done above and ship with its original alpha intact, showing through as whatever's behind the SVG document rather than the intended dark card colour.🐛 Proposed fix: only take the raw-bytes shortcut when the source is already opaque
+ if op, ok := img.(interface{ Opaque() bool }); !ok || !op.Opaque() { + return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(buf.Bytes()), nil + } // Flat-colour identicons compress far better in their original PNG than // as JPEG, so keep whichever encoding is smaller. if mime := resp.Header.Get("Content-Type"); mime != "" && len(data) < buf.Len() { return "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(data), nil }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tools/sponsorkit/avatar.go` around lines 82 - 97, The raw-bytes fallback in avatar.go’s encoding path should not be chosen purely by size, because it can bypass the `cardBackground` flattening done before `jpeg.Encode`. Update the logic around the `resp.Header.Get("Content-Type")` check and the `len(data) < buf.Len()` comparison so the original data is only returned when the source image is known to be opaque; otherwise always return the flattened JPEG. Use the existing `img`/`flat` processing in `EncodeAvatar` (or the surrounding helper) to gate the fallback with an opacity check before emitting the data URI.
🧹 Nitpick comments (5)
docs/src/content/docs/credits.mdx (1)
43-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider extracting the SVG embed into a shared component.
This identical
object/imgmarkup is duplicated verbatim across all 10 locale credits pages (docs + website i18n). A small shared Astro/React component (e.g.,<ContributorsEmbed />) would remove this duplication and centralize future changes (URL, styling, fallback text) in one place.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/src/content/docs/credits.mdx` around lines 43 - 51, The contributors SVG embed markup is duplicated across multiple credits pages, so extract the repeated object/img block into a shared component such as ContributorsEmbed and reuse it everywhere. Update the credits pages that currently inline this markup to render the shared component, and keep the existing SVG URL, fallback image, accessibility text, and styling centralized so future changes only need to be made in one place.tools/sponsorkit/render_contributors.go (1)
212-240: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value"animated" ring's bloom/fast sweep aren't gated by
b.Hover, unlike the "static" case.In the
"static"branch, the extra.bloomelement is only emittedif b.Hover. In"animated",.bloomand.fastare always emitted regardless ofb.Hover. This is harmless today because bothRing: "animated"bands also setHover: trueinconfig.go, so the.sp:hoverCSS selector never has a chance to fire without thespclass being present anyway — but it's an implicit assumption that isn't enforced, and a future animated band withHover: falsewould silently ship dead hover markup.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tools/sponsorkit/render_contributors.go` around lines 212 - 240, The animated ring branch in renderContributors currently emits the hover-only .bloom and .fast SVG elements unconditionally, unlike the static branch. Update the logic around the ring switch so the animated overlay markup is only written when b.Hover is true, using the existing ring/b.Hover flow in renderContributors and keeping behavior consistent with the static case. This makes the intent explicit and avoids shipping dead hover-only markup for future animated bands.tools/sponsorkit/contributors.go (2)
169-216: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider unit tests for the regex-based credit parsing.
changelogMentions,Credit(), and the profile-link/mention regexes encode a fair amount of subtle logic (trailing-hyphen trimming, scope/underscore exclusion, markdown-link precedence, PR/commit/mention max-taking). These are pure functions and cheap to test, and regressions here would silently mis-credit or drop contributors without any compile-time signal.Also applies to: 320-324
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tools/sponsorkit/contributors.go` around lines 169 - 216, Add focused unit tests around the pure credit-parsing logic so the regex and aggregation rules don’t regress silently. Cover changelogMentions, Credit(), mentionRe, and profileLinkRe with cases for trailing hyphens, underscore/scope exclusion, markdown profile-link precedence over raw mentions, and max-taking behavior across PR/commit/mention sources. Keep the tests table-driven and assert the exact credited login counts/selection outcomes for representative inputs.
105-154: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winNo throttling on per-login
lookupUsercalls for PR-only / changelog-only contributors.The paginated contributor and merged-PR fetch loops sleep 100ms between pages, but the fallback
lookupUsercalls issued here (once per unmatched PR author, and again per unmatched changelog mention) fire back-to-back with no delay. With enough changelog-only mentions or unlinked PR authors, this could burst into GitHub's secondary rate limits and fail the nightly generation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tools/sponsorkit/contributors.go` around lines 105 - 154, Add throttling to the fallback lookupUser calls in contributors.go so PR-only and changelog-only contributor lookups don’t hammer GitHub back-to-back. In the loops that process prCounts and changelogMentions, pause between successive lookupUser(client, token, key) calls using the same pacing approach already used in the paginated fetch flow, and keep the existing skip/continue behavior in the byLogin/order handling intact.tools/sponsorkit/config.go (1)
134-181: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win
prBandMinsandbandsmust stay index-parallel — fragile coupling risks a panic.
main.godoesfor i := range bands { bands[i].MinCredit = prBandMins[i] }when-metric prsis selected. Both slices currently have 7 entries, but nothing enforces that invariant — adding/removing aBandentry here without updatingprBandMins(or vice versa) causes an index-out-of-range panic at runtime inmain.go.Consider folding the PR-metric threshold into
Banditself so the two numbers can't drift apart:♻️ Proposed refactor
type Band struct { MinCredit int + // PRMinCredit overrides MinCredit when ranking by merged PRs + // (-metric prs); one merged PR is a much rarer unit than one commit. + PRMinCredit int Avatar float64 ... } -var prBandMins = []int{300, 100, 40, 15, 6, 2, 0} - var bands = []Band{ - {MinCredit: 1000, ...}, + {MinCredit: 1000, PRMinCredit: 300, ...}, ... }Then in
main.go:for i := range bands { bands[i].MinCredit = bands[i].PRMinCredit }.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tools/sponsorkit/config.go` around lines 134 - 181, The `prBandMins` slice and `bands` array in `config.go` are brittle because `main.go` assumes they stay index-parallel when applying the PR metric thresholds. Refactor so each `Band` carries its own PR-specific threshold (for example as a `PRMinCredit` field) and update the `bands` initialization accordingly, then change the `main.go` loop to read the threshold from each `Band` instead of indexing a separate slice. This removes the hidden coupling and prevents index-out-of-range panics if the band list changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@tools/sponsorkit/avatar.go`:
- Around line 82-97: The raw-bytes fallback in avatar.go’s encoding path should
not be chosen purely by size, because it can bypass the `cardBackground`
flattening done before `jpeg.Encode`. Update the logic around the
`resp.Header.Get("Content-Type")` check and the `len(data) < buf.Len()`
comparison so the original data is only returned when the source image is known
to be opaque; otherwise always return the flattened JPEG. Use the existing
`img`/`flat` processing in `EncodeAvatar` (or the surrounding helper) to gate
the fallback with an opacity check before emitting the data URI.
---
Nitpick comments:
In `@docs/src/content/docs/credits.mdx`:
- Around line 43-51: The contributors SVG embed markup is duplicated across
multiple credits pages, so extract the repeated object/img block into a shared
component such as ContributorsEmbed and reuse it everywhere. Update the credits
pages that currently inline this markup to render the shared component, and keep
the existing SVG URL, fallback image, accessibility text, and styling
centralized so future changes only need to be made in one place.
In `@tools/sponsorkit/config.go`:
- Around line 134-181: The `prBandMins` slice and `bands` array in `config.go`
are brittle because `main.go` assumes they stay index-parallel when applying the
PR metric thresholds. Refactor so each `Band` carries its own PR-specific
threshold (for example as a `PRMinCredit` field) and update the `bands`
initialization accordingly, then change the `main.go` loop to read the threshold
from each `Band` instead of indexing a separate slice. This removes the hidden
coupling and prevents index-out-of-range panics if the band list changes.
In `@tools/sponsorkit/contributors.go`:
- Around line 169-216: Add focused unit tests around the pure credit-parsing
logic so the regex and aggregation rules don’t regress silently. Cover
changelogMentions, Credit(), mentionRe, and profileLinkRe with cases for
trailing hyphens, underscore/scope exclusion, markdown profile-link precedence
over raw mentions, and max-taking behavior across PR/commit/mention sources.
Keep the tests table-driven and assert the exact credited login counts/selection
outcomes for representative inputs.
- Around line 105-154: Add throttling to the fallback lookupUser calls in
contributors.go so PR-only and changelog-only contributor lookups don’t hammer
GitHub back-to-back. In the loops that process prCounts and changelogMentions,
pause between successive lookupUser(client, token, key) calls using the same
pacing approach already used in the paginated fetch flow, and keep the existing
skip/continue behavior in the byLogin/order handling intact.
In `@tools/sponsorkit/render_contributors.go`:
- Around line 212-240: The animated ring branch in renderContributors currently
emits the hover-only .bloom and .fast SVG elements unconditionally, unlike the
static branch. Update the logic around the ring switch so the animated overlay
markup is only written when b.Hover is true, using the existing ring/b.Hover
flow in renderContributors and keeping behavior consistent with the static case.
This makes the intent explicit and avoids shipping dead hover-only markup for
future animated bands.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b9cb0d47-947c-48d5-9d88-6d73e1bac745
⛔ Files ignored due to path filters (1)
website/static/img/contributors.svgis excluded by!**/*.svg
📒 Files selected for processing (30)
.github/workflows/generate-sponsor-image.ymldocs/src/assets/contributors.htmldocs/src/content/docs/credits.mdxdocs/src/content/docs/de/credits.mdxdocs/src/content/docs/fr/credits.mdxdocs/src/content/docs/id/credits.mdxdocs/src/content/docs/ja/credits.mdxdocs/src/content/docs/ko/credits.mdxdocs/src/content/docs/pt/credits.mdxdocs/src/content/docs/ru/credits.mdxdocs/src/content/docs/zh-cn/credits.mdxdocs/src/content/docs/zh-tw/credits.mdxtools/sponsorkit/README.mdtools/sponsorkit/avatar.gotools/sponsorkit/config.gotools/sponsorkit/contributors.gotools/sponsorkit/main.gotools/sponsorkit/render_contributors.gowebsite/i18n/ar/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/de/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/fr/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/ja/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/ko/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/pt/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/ru/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/tr/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/uk/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/vi/docusaurus-plugin-content-pages/credits.mdxwebsite/i18n/zh-Hans/docusaurus-plugin-content-pages/credits.mdxwebsite/src/pages/credits.mdx
💤 Files with no reviewable changes (1)
- docs/src/assets/contributors.html
…utors.svg — squircle mosaic for the credits pages
|
@coderabbitai Thanks for the review — it landed just after the PR merged, so the fixes are in follow-up PR #5731. Point-by-point: avatar.go — raw-bytes fallback can reintroduce transparency (skipped): The premise doesn't hold for this SVG. The credits.mdx — shared embed component (skipped): The duplication spans two unrelated doc systems (Astro Starlight in config.go — prBandMins/bands index-parallel fragility (fixed in #5731): render_contributors.go — animated bloom/fast not gated on Hover (fixed in #5731): gated, matching the static branch. contributors.go — lookupUser throttling (fixed in #5731): the changelog-only and PR-only lookups now sleep 100ms per call, same cadence as the paginated fetches. contributors.go — unit tests for credit parsing (fixed in #5731): table-driven tests for |
|
Tip For best results, initiate chat on the files or code changes.
Thanks again for closing the loop on all of these. (っ˘̩╭╮˘̩)っ |
Follow-up to #5719. The contributors section on both docs credits pages was a stale all-contributors HTML table committed once in 2024 and never regenerated. This replaces it with a nightly-generated
contributors.svg, rendered by the same Go tool as the sponsors image.What's in here
tools/sponsorkitgrows a-mode contributors:@logincredits, because squash-merged or hand-applied patches are often credited only there. Changelog-only contributors still get their squircle. Markdown profile links ([@x](https://github.com/y)) trust the URL over the (occasionally typo'd) link text, and every changelog-only login is validated against the API so typos, renamed accounts and organisations are dropped rather than mis-credited.max(commits, changelog mentions)to avoid double counting.The card: a mosaic of superellipse squircles on the same dark card as the sponsors image, graded by credit into 7 bands — the most prolific contributors get large named squircles with animated gradient rings and travelling light arcs; the long tail gets small plain squircles. Every squircle deep-links to the profile. Hovering the bigger squircles lifts them, blooms the ring and pops up a chip with the commit count (hover requires the
objectembed, like the sponsors image).Size control: with 420 embedded avatars the naïve output was 2.4 MB; shared
defs/usesquircle geometry, per-band JPEG quality and keeping original PNGs for flat-colour identicons bring it to ~1 MB.Wiring:
generate-sponsor-image.ymlnow generates and commits both SVGs nightly.contributors.htmlimport replaced with anobjectembed ofhttps://wails.io/img/contributors.svg; the 2024 snapshot asset is deleted.objectembed of/img/contributors.svg.Verification
objectembed, hover effects fire, XML validates, animations run.go vet/go buildclean; tool remains stdlib-only.Summary by CodeRabbit