fix(changelog): fallback contributor avatars#79
Conversation
Jerry-Xin
left a comment
There was a problem hiding this comment.
The PR is in scope for octo-admin and cleanly addresses changelog contributor avatar fallback behavior.
π¬ Non-blocking
- π΅ Suggestion:
src/pages/Changelog/utils.ts:198seeds DiceBear with the raw contributor name, sooctocatand@octocatproduce different fallback avatars even thoughgithubAvatarUrlnormalizes them to the same GitHub user. Consider reusing the normalized login for the fallback seed if consistent identity rendering matters. - π΅ Suggestion:
src/pages/Changelog/index.tsx:408usesevent.currentTarget.onerror = null, which is a common DOM pattern, but Reactβs synthetic handler may still remain attached. A guard like βonly swap if currentsrcis not already the fallbackβ would make fallback failure handling more robust. - π΅ Suggestion:
src/pages/Changelog/utils.test.ts:5covers URL generation, but not theimgerror behavior. A small component test would protect the actual fallback swap.
β Highlights
- GitHub avatars remain the primary source.
- The fallback URL is generated deterministically and URL-encodes contributor names.
- The new type field is produced and consumed within the same changelog flow.
Verification note: I attempted npm run build, but this checkout lacks tsc (sh: tsc: command not found), so I could not independently verify the declared build command here.
yujiawei
left a comment
There was a problem hiding this comment.
Code Review β PR #79 (octo-admin)
Verdict: APPROVED
A small, well-scoped fix that adds a graceful fallback for changelog contributor avatars. GitHub profile images remain the primary source; if one fails to load, the <img> swaps to a deterministic DiceBear identicon. Unit test and production build both pass locally.
Verification
| Check | Result | Evidence |
|---|---|---|
| Fallback wiring is correct | β | src/pages/Changelog/index.tsx:405-411 β onError nulls currentTarget.onerror first (prevents infinite error loop), then swaps to c.fallbackAvatar. Correct order. |
| Fallback URL generation | β | src/pages/Changelog/utils.ts:198-200 β deterministic per-name seed + indexed color from CONTRIBUTOR_COLORS, with index % length wrap so >8 contributors don't crash. |
| Type/contract update | β | Contributor interface extended with fallbackAvatar (utils.ts:183-187); all producers (utils.ts:213-217) and consumers (index.tsx:406-410) updated consistently. No other usages found (grep of fallbackAvatar/c.avatar across src/). |
| Tests | β | npx vitest run src/pages/Changelog/utils.test.ts β 1 passed. Test asserts both caster-Q and @octocat fallback URLs including URL-encoding of @ (%40octocat). |
| Build | β | npm run build (tsc + vite) β built successfully, no type errors. |
Findings
No P0/P1 issues. Two non-blocking nits below.
Nits (non-blocking)
-
Seed inconsistency for
@-prefixed handles (utils.ts:194vsutils.ts:199). The primary avatar strips the leading@(name.replace(/^@+/, '')βoctocat), but the fallback seed keeps it (encodeURIComponent('@octocat')β%40octocat). This is harmless β the fallback only needs a stable, unique seed β but if you ever want the fallback identicon to be "the same person" conceptually as the GitHub avatar, you'd strip@in both. Purely cosmetic. -
External dependency on api.dicebear.com. The fallback now depends on a third-party service. If both GitHub and DiceBear are unreachable, the image silently stays broken (acceptable β
onerroris nulled so there's no loop). Per the PR description this restores the previously-used generation logic, so no behavior regression. Worth noting only as a future hardening candidate (e.g. a local/inline SVG fallback), not for this PR.
Summary
Clean, minimal, and correct. Tests and build green. Approving.
lml2468
left a comment
There was a problem hiding this comment.
β APPROVED with follow-up suggestions
Reviewed at HEAD b747eb3. Change directly addresses prior round's nits (onError fallback + @-prefix handling via encodeURIComponent). +14/-1, clean and focused.
π‘ P1 β src/pages/Changelog/index.tsx:408 loop guard is incomplete
onError={(event) => {
event.currentTarget.onerror = null
event.currentTarget.src = c.fallbackAvatar
}}event.currentTarget.onerror = null clears the DOM property, but React uses delegated event handlers at the root β the synthetic onError prop is not cleared by this assignment. If the DiceBear fallback URL itself fails to load (CDN outage, ad-blocker, network policy), React will re-fire onError and re-assign the same fallbackAvatar src, creating a request loop.
Suggested guard:
onError={(event) => {
if (event.currentTarget.src === c.fallbackAvatar) return
event.currentTarget.src = c.fallbackAvatar
}}Low real-world probability (DiceBear is reliable), so not blocking β but worth hardening since the cost is one line.
π‘ nit β test only verifies field shape, not fallback behavior
utils.test.ts now asserts fallbackAvatar is present in the parsed output, but there's no test that exercises the actual onError swap, nor edge cases like @-only names, empty entries, weird separators. The data-shape test catches regressions in URL generation but won't catch a regression where the <img onError> handler is removed or broken.
A small component test (RTL + dispatching an error event on the rendered <img>) would cover the behavior this PR is meant to harden.
π‘ nit β fallback color is index-positional, not name-stable
CONTRIBUTOR_COLORS[index % CONTRIBUTOR_COLORS.length]A contributor's fallback color depends on their position in the list, so reordering or inserting names shifts everyone's color. Hashing the name (e.g. simple char-sum mod length) would make the color stable per contributor across releases. Pure cosmetic.
π Summary
No blockers. URL encoding is safe for malformed input, DiceBear fallback is a real improvement, tests assert the data contract. Merge OK β please consider the loop-guard hardening in a follow-up since it's a 1-line change.
RECOMMENDATION: APPROVE
Summary
Tests