feat(website): lighter docs (GIFs → WebP) + contributors page#44
feat(website): lighter docs (GIFs → WebP) + contributors page#44rubenmarcus merged 2 commits intomainfrom
Conversation
Two improvements to the docs site: 1. **Drop 4.4 MB of animated GIFs.** The introduction and widget pages referenced four animated GIFs totaling 4.4 MB (example.gif alone was 3.5 MB). Replaced each with a representative WebP still frame extracted via ffmpeg. The combined size is now ~30 KB — a ~99% reduction in public/ asset weight, with most of that being LCP savings on the introduction page. 2. **Add /contributors page.** New top-level Astro route at website/src/pages/contributors.astro that fetches the GitHub contributors list at build time and renders a static card grid. No client-side JS, no runtime API calls. Bots are filtered out via `type === 'User'`. On fetch failure (rate-limit, network), a fallback message links to the GitHub contributors page rather than failing the build. Linked from Header.astro nav between "Docs" and "Checker". Local build: 19 pages, contributors page lists current 4 humans. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Docs PreviewPreview URL: https://feat-lighter-docs-and-contri.aeojs.pages.dev This preview was deployed from the latest commit on this PR. |
Greptile SummaryReplaces four heavy GIFs (~4.5 MB) with lightweight WebP still frames (~30 KB total) and adds a new Confidence Score: 5/5Safe to merge — all findings are P2 style/efficiency concerns, no runtime or logic bugs. No P0 or P1 issues found. The GIF-to-WebP migration is correct and the contributors pages build successfully. The only concerns are duplicate API calls and type duplication, both P2. website/src/components/HomepageContributors.astro and website/src/pages/contributors.astro — both should share a single fetch helper to avoid the duplicate API call pattern. Important Files Changed
Sequence DiagramsequenceDiagram
participant Build as Astro Build
participant HP as index.mdx (HomepageContributors)
participant CP as contributors.astro
participant GH as GitHub API
Build->>HP: render homepage
HP->>GH: GET /repos/multivmlabs/aeo.js/contributors
GH-->>HP: JSON (or error → silent skip)
HP->>HP: filter bots, slice top 8, render strip
Build->>CP: render /contributors page
CP->>GH: GET /repos/multivmlabs/aeo.js/contributors
GH-->>CP: JSON (or error → fetchError=true)
CP->>CP: filter bots, sort, render grid or fallback
Note over HP,CP: Two independent API calls per build — share a helper to consume rate limit only once
Prompt To Fix All With AIFix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
website/src/components/HomepageContributors.astro:13-25
**Duplicate GitHub API call doubles rate-limit consumption**
Both `HomepageContributors.astro` and `contributors.astro` independently call the same GitHub API endpoint (`/repos/multivmlabs/aeo.js/contributors`) at build time. Every build fires 2 unauthenticated requests against the 60 req/hr limit. The PR description already flags rate-limit risk and recommends adding a `GITHUB_TOKEN` — having two separate fetches makes that problem arrive twice as fast.
Consider extracting the fetch into a shared Astro content helper (e.g. `src/lib/contributors.ts`) so both components consume the same in-memory result during a single build.
### Issue 2 of 3
website/src/components/HomepageContributors.astro:1-8
**`GhContributor` type duplicated across both components**
The same `GhContributor` type is defined identically in both `HomepageContributors.astro` and `contributors.astro`. Moving it to a shared file (e.g. `src/lib/contributors.ts`) alongside the shared fetch would eliminate this duplication and make future field additions a single-place change.
### Issue 3 of 3
website/src/components/HomepageContributors.astro:44
**Fragile `avatar_url` query-string concatenation**
`${c.avatar_url}&s=80` assumes the URL already contains a `?` query parameter. GitHub's current format (`https://avatars.githubusercontent.com/u/12345?v=4`) always does, but a safer approach is to construct the URL properly:
```ts
const avatarSrc = new URL(c.avatar_url);
avatarSrc.searchParams.set('s', '80');
```
Same pattern applies to the `&s=120` append in `contributors.astro:58`.
Reviews (2): Last reviewed commit: "feat(website): contributors strip on hom..." | Re-trigger Greptile |
| text-decoration: underline; | ||
| text-underline-offset: 0.2em; | ||
| } | ||
| .contrib-grid { | ||
| list-style: none; | ||
| padding: 0; | ||
| margin: 2rem 0; | ||
| display: grid; | ||
| grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); | ||
| gap: 0.75rem; | ||
| } | ||
| .contrib-card { | ||
| margin: 0; | ||
| } | ||
| .contrib-link { | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| gap: 0.5rem; | ||
| padding: 1rem 0.75rem; | ||
| border-radius: 12px; | ||
| background: rgba(255, 255, 255, 0.03); | ||
| border: 1px solid rgba(255, 255, 255, 0.08); | ||
| color: inherit; | ||
| text-decoration: none; | ||
| transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease; | ||
| } | ||
| .contrib-link:hover { | ||
| border-color: rgba(255, 255, 255, 0.2); | ||
| background: rgba(255, 255, 255, 0.06); | ||
| transform: translateY(-1px); | ||
| } | ||
| .contrib-avatar { |
There was a problem hiding this comment.
Hardcoded dark-mode colors break light theme
All color values are hardcoded in dark-mode terms (#fff, rgba(255, 255, 255, 0.6), rgba(255, 255, 255, 0.03/0.08/0.2/0.06)). Starlight ships with a built-in light/dark theme toggle; on the light theme, white text on an off-white card background would be nearly invisible. Prefer CSS custom properties from Starlight's design tokens (e.g. var(--sl-color-text), var(--sl-color-bg-sidebar)) so the cards adapt automatically.
Prompt To Fix With AI
This is a comment left during a code review.
Path: website/src/pages/contributors.astro
Line: 98-130
Comment:
**Hardcoded dark-mode colors break light theme**
All color values are hardcoded in dark-mode terms (`#fff`, `rgba(255, 255, 255, 0.6)`, `rgba(255, 255, 255, 0.03/0.08/0.2/0.06)`). Starlight ships with a built-in light/dark theme toggle; on the light theme, white text on an off-white card background would be nearly invisible. Prefer CSS custom properties from Starlight's design tokens (e.g. `var(--sl-color-text)`, `var(--sl-color-bg-sidebar)`) so the cards adapt automatically.
How can I resolve this? If you propose a fix, please make it concise.| </p> | ||
| <ul class="contrib-grid"> | ||
| {contributors.map((c) => ( | ||
| <li class="contrib-card"> |
There was a problem hiding this comment.
rel="noopener" should also include noreferrer
All target="_blank" anchors use only rel="noopener". Adding noreferrer is the recommended practice — it also suppresses the Referer header and independently implies noopener in all browsers, so it's strictly safer without any downside. The same applies to the contributor card links (href={c.html_url}) and the CTA link.
| </p> | |
| <ul class="contrib-grid"> | |
| {contributors.map((c) => ( | |
| <li class="contrib-card"> | |
| <a href="https://github.com/multivmlabs/aeo.js/graphs/contributors" target="_blank" rel="noopener noreferrer"> |
Prompt To Fix With AI
This is a comment left during a code review.
Path: website/src/pages/contributors.astro
Line: 52-55
Comment:
**`rel="noopener"` should also include `noreferrer`**
All `target="_blank"` anchors use only `rel="noopener"`. Adding `noreferrer` is the recommended practice — it also suppresses the `Referer` header and independently implies `noopener` in all browsers, so it's strictly safer without any downside. The same applies to the contributor card links (`href={c.html_url}`) and the CTA link.
```suggestion
<a href="https://github.com/multivmlabs/aeo.js/graphs/contributors" target="_blank" rel="noopener noreferrer">
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cc8ed64bca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| try { | ||
| const res = await fetch( | ||
| 'https://api.github.com/repos/multivmlabs/aeo.js/contributors?per_page=100', |
There was a problem hiding this comment.
Paginate GitHub contributors API results
The contributors page fetches only a single page (per_page=100) and never follows pagination links, so once the repository exceeds 100 human contributors this page will silently omit everyone after the first page and present an incomplete list. Because this route is meant to represent project contributors, the data becomes inaccurate in normal growth scenarios unless additional pages are fetched.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Looks good — docs-only changes (heavy GIFs swapped for tiny WebPs, plus a build-time-static contributors page with graceful fallback).
Extended reasoning...
Overview
This PR makes two website-only changes: (1) replaces 4 animated GIFs (~4.5 MB) referenced in introduction.mdx and widget.mdx with WebP still frames (~30 KB total), and (2) adds a new /contributors Astro page that fetches the GitHub contributors API at build time, filters bots via type === 'User', sorts by contribution count, and renders a static card grid. A nav link is also added in Header.astro between "Docs" and "Checker".
Security risks
None of significance. The new page issues an unauthenticated GET to api.github.com/repos/multivmlabs/aeo.js/contributors at build time only — no secrets, no user input, no runtime fetch. Output is rendered through Astro's default-escaped JSX ({c.login}, {c.contributions}), and href values come straight from the GitHub API response. External links use target="_blank" rel="noopener". On fetch failure (rate limit, network), the page falls back to a static GitHub link rather than crashing the build.
Level of scrutiny
Low. This is a documentation site under website/ with no impact on the published aeo.js package or any production logic. The asset swap is mechanical, and the contributors page is self-contained with a graceful fallback path.
Other factors
The bug hunting system flagged a nit on contributors.astro (subject-verb disagreement when length === 1, plus an empty-success edge case where a 202 [] response from GitHub would render "0 contributors have shaped aeo.js" with an empty grid). Both are user-facing copy/UX polish on a docs page rather than functional bugs — the existing fallback already handles 4xx/5xx, and the page builds and renders correctly for the current 4-contributor list. Worth a one-line tweak but not blocking.
| <p class="contrib-intro"> | ||
| {contributors.length} contributor{contributors.length === 1 ? '' : 's'} have shaped aeo.js. Thank you. 🙏 | ||
| </p> |
There was a problem hiding this comment.
🟡 Two edge-case rendering issues in the success branch's intro text at website/src/pages/contributors.astro lines 50–52: (1) subject-verb disagreement — contributor{...=== 1 ? '' : 's'} pluralizes the noun but "have" is hardcoded, so a single contributor renders "1 contributor have shaped aeo.js." (should be "has shaped"). (2) Empty-success state — when res.ok is true but the contributor list is empty (e.g. GitHub's /contributors endpoint returns 202 with [] while stats are recomputing, or the type === 'User' filter removes everyone), fetchError stays false and the page renders "0 contributors have shaped aeo.js. Thank you. 🙏" plus an empty grid and a "Want to join them?" CTA instead of the graceful GitHub-link fallback. Trivial fix: branch the verb the same way the noun is, and treat !fetchError && contributors.length === 0 like fetchError.
Extended reasoning...
1. Subject-verb disagreement (line 51)
The success branch renders:
{contributors.length} contributor{contributors.length === 1 ? '' : 's'} have shaped aeo.js. Thank you. 🙏The noun is conditionally pluralized via the ternary, but the verb have is hardcoded. Step-by-step proof for contributors.length === 1:
{contributors.length}→1contributorliteral →contributor{contributors.length === 1 ? '' : 's'}→''(empty)have shaped aeo.js. Thank you. 🙏literal → unchanged
Concatenated output: "1 contributor have shaped aeo.js. Thank you. 🙏" — a real subject-verb agreement error in user-facing copy. Counts of 0 ("0 contributors have") and 2+ render correctly. The fix mirrors the existing pluralization:
{contributors.length} contributor{contributors.length === 1 ? '' : 's'} {contributors.length === 1 ? 'has' : 'have'} shaped aeo.js.For multivmlabs/aeo.js this only triggers if the User-filtered count drops to exactly 1 (currently 4), so the blast radius is small — hence nit.
2. Empty-success state (lines 17–28, 50–82)
fetchError is only set when !res.ok or the try throws. Step-by-step proof for the 202-empty-array case:
fetch(...)resolves withres.status === 202. The Responseokgetter istruefor any status in 200–299, sores.ok === trueandfetchErrorstaysfalse.await res.json()parses an empty array body[]without throwing (this is the documented behavior ofGET /repos/{owner}/{repo}/contributorswhile GitHub recomputes stats — see GitHub REST API docs).data.filter(c => c.type === 'User').sort(...)produces[].contributors.length === 0andfetchError === false, so the success branch renders.- Output: "0 contributors have shaped aeo.js. Thank you. 🙏", an empty
<ul class="contrib-grid">, and "Want to join them? Open a PR." — visually broken even though no error occurred.
The same path is reachable if every entry is filtered out by c.type === 'User' (e.g. only bots committed since the last cache).
Addressing the refutation: the refuting verifier correctly notes the build doesn't crash, the trigger is brief/rare for an established repo, and the next deploy fixes it. That's why this is filed as nit, not normal — the page still renders and the fetchError fallback covers the meaningful 4xx/5xx cases. But the fix is one line and the file is being added in this PR, so it's worth catching now rather than after a confusing user report. The fix is to widen the fallback condition:
{fetchError || contributors.length === 0 ? (
<p class="contrib-fallback">…GitHub fallback…</p>
) : (
…existing success markup…
)}Severity: nit — both issues are low-impact copy/UX polish on a docs page, not functional bugs. They co-locate in the same intro line, so a single edit addresses both.
Adds a small "Built by" avatar strip near the bottom of the homepage that links to the existing /contributors page. Surfaces the community on the most-visited entry point without duplicating the full grid. - New component: website/src/components/HomepageContributors.astro - Reuses the same build-time GitHub API fetch + bot filter as /contributors. On fetch failure, the section silently doesn't render (homepage stays clean). - Top 8 contributors are shown as 40px avatars; if there are more, a "+N" badge links to the full page. - "View all N contributors →" link below the strip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Two improvements to the docs site at website/:
1. Replace heavy GIFs with WebP still frames
The introduction page (
introduction.mdx) and widget feature page (widget.mdx) referenced 4 animated GIFs totaling 4.4 MB, including a 3.5 MBexample.gifthat almost certainly was the LCP element on every first-time visit.example.gifexample.webp9.9 KBwidget-default.gifwidget-default.webp7.8 KBwidget-small.gifwidget-small.webp7.6 KBwidget-icon.gifwidget-icon.webp4.8 KBpublic/Frames extracted via
ffmpeg -vf "select=eq(n,N)"at representative mid-loop indices for each GIF. If a frame ends up looking off, it's a one-line tweak to the index — easy to swap out later.2. Add
/contributorspageNew Astro route at website/src/pages/contributors.astro. Fetches
https://api.github.com/repos/multivmlabs/aeo.js/contributorsat build time, filters bots (type === 'User'), sorts by contribution count, and renders a static card grid.dependabot,claude, etc.) excluded automatically.<StarlightPage>so it inherits site chrome (header, footer, theme).Currently lists 4 contributors:
Test plan
bun run build— site builds, 19 pages, no errorsdist/contributors/index.htmlcontains all 4 contributorsdist/getting-started/introduction/index.htmlreferences/example.webp(not.gif)du -sh website/public/— 208K (down from ~4.5 MB)/contributors/→ verify grid renders, header link works, all avatars loadNotes
GITHUB_TOKENenv var through and add anAuthorizationheader.🤖 Generated with Claude Code