Problem
src/components/ui/Button.tsx is the shared button primitive used everywhere: primary claim/fund/deposit CTAs, wallet connect, GitHub OAuth redirect, tab/filter controls, and presumably comes in several visual variants given the design system's slate/emerald palette. As the single most reused interactive primitive in the codebase, any gap in it multiplies across every feature. Specifically worth scrutinizing given everything else in this batch: does Button.tsx correctly render a disabled and a loading/pending state as genuinely distinct (a disabled-because-invalid-input button versus a disabled-because-request-in-flight button convey different information and should look/announce differently to prevent a user assuming a stuck loading state is just "the button doesn't work")? Does it forward a ref and spread ARIA attributes correctly when used as a submit trigger inside the various claim/fund/deposit flows, or does it swallow/override props in a way that breaks accessibility attributes callers try to pass? Is it correctly implemented as a polymorphic component (rendering as an <a> for navigation-style CTAs like "Connect GitHub" which is fundamentally a redirect/link action, versus a <button> for in-page actions like "Claim bounty") — using a <button onClick={() => location.href = ...}> pattern for what is semantically navigation breaks middle-click-to-open-in-new-tab, right-click-to-copy-link, and screen reader link/button role expectations, all real usability regressions for something as foundational as "how do I start the OAuth flow."
Why this is hard
- Requires reading
Button.tsx's full implementation and then grepping every call site across src/app/** and src/components/** to classify each usage as "genuinely an in-page action" (correct as <button>) versus "actually navigation/redirect" (should be an <a>/Next.js Link under the hood) — this is a full-codebase audit against a real semantic-HTML standard, not a local fix, and the GitHub OAuth connect CTA specifically is a near-certain candidate for being wrongly implemented as a <button> with an imperative redirect given it's described as a "redirect to the backend's OAuth endpoint."
- Loading vs. disabled needs a real accessibility treatment:
aria-busy/aria-disabled (not native disabled, which removes the element from the accessibility tree and tab order, arguably wrong for a "this is processing, please wait" state where the user might want to understand why nothing's happening) versus native disabled (correct for "this action isn't currently valid") are different HTML/ARIA patterns with different keyboard/screen-reader implications, and the current component needs to be audited for which (if either) it actually implements correctly today.
- Making
Button.tsx genuinely polymorphic (as="a"/as={Link} support, or a discriminated prop union) while preserving every existing visual variant, size, and the loading/disabled states designed above is a non-trivial component API redesign with real blast radius across every call site in the app — this needs careful prop-typing (likely a generic/polymorphic TypeScript component pattern) to avoid a sprawling, unsafe any-typed escape hatch.
- Must verify
ref forwarding and full ARIA attribute pass-through work correctly for every variant after the redesign, since this is exactly the kind of regression that's invisible in casual visual QA but breaks real assistive-technology usage.
Scope
- Audit
Button.tsx's current implementation and every call site, classifying each as in-page-action vs. navigation.
- Redesign
Button.tsx to be genuinely polymorphic (correct <a>/Link rendering for navigation CTAs, correct <button> for in-page actions), preserving all current visual variants/sizes.
- Implement a correct, distinct
aria-busy-based loading state separate from native disabled, and audit/fix every call site using the wrong one today.
- Fix the GitHub OAuth "Connect" CTA and any other misclassified navigation CTA to use proper link semantics instead of an imperative
onClick redirect.
- Verify
ref forwarding and ARIA attribute pass-through across all variants.
Acceptance criteria
- The GitHub OAuth connect CTA (and any other navigation-classified CTA found in the audit) is a real anchor/
Link supporting middle-click-open-in-new-tab and right-click-copy-link, verified manually.
- Loading and disabled states are implemented with correct, distinct ARIA semantics, verified with a screen reader.
- All existing visual variants and sizes render unchanged after the polymorphic refactor.
Problem
src/components/ui/Button.tsxis the shared button primitive used everywhere: primary claim/fund/deposit CTAs, wallet connect, GitHub OAuth redirect, tab/filter controls, and presumably comes in several visual variants given the design system'sslate/emeraldpalette. As the single most reused interactive primitive in the codebase, any gap in it multiplies across every feature. Specifically worth scrutinizing given everything else in this batch: doesButton.tsxcorrectly render adisabledand aloading/pending state as genuinely distinct (a disabled-because-invalid-input button versus a disabled-because-request-in-flight button convey different information and should look/announce differently to prevent a user assuming a stuck loading state is just "the button doesn't work")? Does it forward arefand spread ARIA attributes correctly when used as a submit trigger inside the various claim/fund/deposit flows, or does it swallow/override props in a way that breaks accessibility attributes callers try to pass? Is it correctly implemented as a polymorphic component (rendering as an<a>for navigation-style CTAs like "Connect GitHub" which is fundamentally a redirect/link action, versus a<button>for in-page actions like "Claim bounty") — using a<button onClick={() => location.href = ...}>pattern for what is semantically navigation breaks middle-click-to-open-in-new-tab, right-click-to-copy-link, and screen reader link/button role expectations, all real usability regressions for something as foundational as "how do I start the OAuth flow."Why this is hard
Button.tsx's full implementation and then grepping every call site acrosssrc/app/**andsrc/components/**to classify each usage as "genuinely an in-page action" (correct as<button>) versus "actually navigation/redirect" (should be an<a>/Next.jsLinkunder the hood) — this is a full-codebase audit against a real semantic-HTML standard, not a local fix, and the GitHub OAuth connect CTA specifically is a near-certain candidate for being wrongly implemented as a<button>with an imperative redirect given it's described as a "redirect to the backend's OAuth endpoint."aria-busy/aria-disabled(not nativedisabled, which removes the element from the accessibility tree and tab order, arguably wrong for a "this is processing, please wait" state where the user might want to understand why nothing's happening) versus nativedisabled(correct for "this action isn't currently valid") are different HTML/ARIA patterns with different keyboard/screen-reader implications, and the current component needs to be audited for which (if either) it actually implements correctly today.Button.tsxgenuinely polymorphic (as="a"/as={Link}support, or a discriminated prop union) while preserving every existing visual variant, size, and the loading/disabled states designed above is a non-trivial component API redesign with real blast radius across every call site in the app — this needs careful prop-typing (likely a generic/polymorphic TypeScript component pattern) to avoid a sprawling, unsafeany-typed escape hatch.refforwarding and full ARIA attribute pass-through work correctly for every variant after the redesign, since this is exactly the kind of regression that's invisible in casual visual QA but breaks real assistive-technology usage.Scope
Button.tsx's current implementation and every call site, classifying each as in-page-action vs. navigation.Button.tsxto be genuinely polymorphic (correct<a>/Linkrendering for navigation CTAs, correct<button>for in-page actions), preserving all current visual variants/sizes.aria-busy-based loading state separate from nativedisabled, and audit/fix every call site using the wrong one today.onClickredirect.refforwarding and ARIA attribute pass-through across all variants.Acceptance criteria
Linksupporting middle-click-open-in-new-tab and right-click-copy-link, verified manually.