Duplicate JSX attributes in BountyCard and Avatar break the build and silently revert the #654 and #653 accessibility fixes
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- next.js, react, typescript
- Domain
- accessibility, frontend, testing
Research direction
Read src/components/bounty/BountyCard.tsx and src/components/ui/Avatar.tsx at the listed locations, then inspect BountyCard.test.tsx and Avatar.test.tsx for existing assertions. Run npx tsc --noEmit to confirm the duplicate attribute errors. Done when the duplicates are resolved while preserving the described accessible label, safe avatar source, and supplied alt text, and both regression assertions pass.
Written by the indexing model from the issue text.
Description
Location
src/components/bounty/BountyCard.tsx:37-40—BountyCardsrc/components/ui/Avatar.tsx:57-61—Avatar
Problem
Both components carry the same attribute twice on one JSX element, left behind by an unreconciled merge. React keeps the last occurrence and discards the earlier one, so in each case the newer accessibility fix is the one that gets thrown away.
BountyCard
<Link
href={`/issues/${bounty.id}`}
prefetch={false}
aria-label={accessibleLabel} // line 37 (#654 fix)
className={`... before:left-0 ...`} // line 38
aria-label={`${bounty.title}, ${formatCurrency(bounty.reward, bounty.asset)}`} // line 39 (old)
className={`... before:start-0 ...`} // line 40
>
aria-label and className are each specified twice. The surviving aria-label is the old short one, so accessibleLabel — the full "title, from org/repo, issue N, reward X, status: funded, difficulty: advanced, due in 5 days, claimed by Y" string built at lines 20-31 for closed issue #654 — is computed on every render and then dropped. The card is back to announcing only its title and reward, which is precisely what #654 reported. The variable also becomes unused, so no-unused-vars style lint rules flag it.
The two className values differ in one class: before:left-0 versus the logical before:start-0. The logical property is the one that survives, which is the correct outcome, but it is surviving by accident.
Avatar
<Image
src={src ?? seedToUrl(seed)} // line 58 — bypasses isSafeAvatarUrl
alt={alt ?? seed} // line 59 — honours the `alt` prop
src={resolved} // line 60 — safety-checked + error fallback
alt={seed} // line 61 — ignores the `alt` prop
...
Here the survivors are src={resolved} (good — the host-allowlist and onError fallback logic is the one that applies) and alt={seed} (bad — the alt prop is accepted by the component's signature, documented at lines 40-41 as "Use empty string when the avatar is decorative (next to a visible name)", and then silently ignored).
Every call site that passes alt="" therefore still gets alt={seed}. That reintroduces closed issue #653: src/components/dashboard/ActivityList.tsx:42 renders <Avatar seed={event.handle} /> immediately before a visible {event.handle}, so a screen reader reads the handle twice per activity row.
Both files fail to compile
Duplicate JSX attributes are a TypeScript error, not a warning: TS17001: JSX elements cannot have multiple attributes with the same name. tsc --noEmit and next build both fail on these two files, and react/jsx-no-duplicate-props would have caught them too.
Why it matters
- The app does not build, so no route is reachable.
- Once it does build (by resolving these duplicates the wrong way, i.e. keeping what React keeps today), two closed accessibility issues are quietly back in place: bounty cards on
/issueslose their status/deadline/claimant context for screen-reader users (#654), and decorative avatars double-announce every name in the activity feed (#653). Both regressions are invisible in code review because the "fix" code is still sitting right there in the file.
Reproduction
- Check out the current default branch, run
npm ci, thennpx tsc --noEmit. FourTS17001errors are reported across the two files. - Delete lines 39-40 of
BountyCard.tsxand lines 58-59 ofAvatar.tsx— i.e. resolve each duplicate the way React resolves it at runtime — and build again. - Open
/issueswith VoiceOver or NVDA and move through the links list. Each bounty card announces only "<title>, 500 USDC"; status, difficulty, deadline and claimant are not announced. - Open any dashboard with the activity feed and move through it. Each row announces the contributor handle twice: once as the avatar's alt text, once as the adjacent visible text.
Suggested fix
BountyCard.tsx: delete the oldaria-labelon line 39 and thebefore:left-0classNameon line 38, keepingaria-label={accessibleLabel}(line 37) and thebefore:start-0className (line 40).Avatar.tsx: delete the rawsrc={src ?? seedToUrl(seed)}(line 58) andalt={seed}(line 61), keepingsrc={resolved}(line 60) andalt={alt ?? seed}(line 59).- Enable
react/jsx-no-duplicate-propsas an error ineslint.config.mjsand runtsc --noEmitin CI, so this class of merge damage is caught mechanically. Two other components on this branch have the same kind of unresolved merge. - Re-add a regression assertion to each test file:
BountyCard.test.tsxshould assert the link's accessible name contains the status and deadline, andAvatar.test.tsxshould assertalt=""is honoured when passed.
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 94
- Avg merge
- 1h 2m
- Merged PRs (30d)
- 25
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from MergeFi/frontend
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day
-
bug testing
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
Maintainers usually reply within 1 day
-
accessibility bug
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
Maintainers usually reply within 1 day
All issues in MergeFi/frontend
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
zapcooking/frontend#946 ·
Maintainers usually reply within 1 day
-
enhancement good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
WiliamRosa/cosmos-semantic-platform#40 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 77/100
liberation-data/drivine#733 · 1 comment ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
makecindy/cindy#10062 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day