Label pool deposit input, announce async results, surface specific wallet errors, fix hero stat drift - #317
Merged
chonilius merged 5 commits intoAug 25, 2026
Conversation
… synchronously
WalletContext#connect already computes a specific, useful error
("Wallet access was not granted.", "Unable to read wallet address.", a
Freighter-SDK message, or the "not installed" message) and stores it in
its own error state — but connect() resolves to null on failure rather
than rejecting, and a caller that awaits it and gets null back has no
reliable way to read that specific reason: destructuring `error` from
useWallet() at render time captures a value from whichever render
created that closure, not necessarily updated yet by the time the
awaited connect() call actually settles inside an async event handler.
Added an errorRef kept in lockstep with every setError() call (via a
new updateError() wrapper) and a getError() accessor that reads it
directly — a plain ref read has no dependency on React's render/commit
timing, so it's guaranteed to reflect connect()'s outcome the instant
its promise resolves, regardless of whether the consuming component has
re-rendered yet. Added a direct test exercising this exact pattern
(MergeFi#235).
… specific wallet errors The notice/error <p> elements IssueActions renders after fund/claim/ refund actions had no role or aria-live attribute — a screen reader user got no announcement when one of these real, often money-moving actions completed, unlike a sighted user who sees the message appear immediately. Added role="status" aria-live="polite" to the success notice and role="alert" (implicitly assertive) to the error message (MergeFi#234). Also, withWallet() fell back to a generic "Connect a Stellar wallet to continue." whenever connect() resolved to null, discarding the specific reason WalletContext's own error state already computed (extension not installed, access denied, address unreadable). Now prefers getWalletError() — read synchronously right after the awaited connect() call, so it's guaranteed current — over the generic fallback (MergeFi#235).
…e specific wallet errors PoolDepositButton's amount <input> had no <label>, aria-label, or aria-labelledby — a screen reader user tabbing to it heard only "number input, value 100" with no indication it's a deposit amount, and the page renders one of these identical, unlabeled input/button pairs per maintenance pool. Added a visually-hidden <label htmlFor> matched to a per-pool id (closes MergeFi#236). Also applied the same two fixes as IssueActions to both MilestoneFundButton and PoolDepositButton: their error <p> elements got role="alert" so screen reader users are notified when a fund/deposit action fails (MergeFi#234), and handleFund()/handleDeposit() now prefer getWalletError() — WalletContext's specific failure reason, read synchronously right after the awaited connect() call — over their generic fallback messages (MergeFi#235).
ConnectPanel's error <p> (shown when useWallet()'s connect() fails) had no role or aria-live attribute, so a screen reader user attempting to connect Freighter from this app's sole onboarding entry point got no announcement when the attempt failed. Added role="alert" to match the same fix applied to IssueActions/MilestoneActions' error messages (MergeFi#234).
…ad of a literal The hero copy hardcoded "341 contributors" as a literal, while a StatCard a few hundred lines later on the same page renders the same fact from platformStats.activeContributors.toLocaleString() — two independent sources of truth for one number that only agreed because 341 happened to match today. Once activeContributors changes (or is wired to a real fetched value), the hero copy has no relationship to it and silently drifts, showing two different contributor counts within a few screens of each other. Interpolated the same platformStats value into the hero sentence so the two can't diverge (closes MergeFi#237).
|
@presidojay1 is attempting to deploy a commit to the chonilius' projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@presidojay1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Four fixes across accessibility, wallet error handling, and a hardcoded stat:
Unlabeled deposit input (#236) —
PoolDepositButton's amount<input>had no<label>,aria-label, oraria-labelledby. A screen reader user tabbing to it heard only "number input, value 100" with no indication of what it represents — and the page renders one of these identical, unlabeled input/button pairs per maintenance pool. Added a visually-hidden<label htmlFor>matched to a per-poolid.No aria-live on async results (#234) —
ConnectPanel,IssueActions, andMilestoneActionsall conditionally render a<p>with the result of an async action (wallet connect, fund/claim/refund, milestone funding, pool deposit), but none hadrole="status"/role="alert"/aria-live. A sighted user sees the message the instant it appears; a screen reader user got no announcement at all. Addedrole="status" aria-live="polite"to success/notice messages androle="alert"to error messages across all three.Generic wallet error swallows the specific reason (#235) —
WalletContext#connectalready computes a specific, useful error (extension not installed, access denied, address unreadable) and stores it in its own state, but resolves tonullon failure rather than rejecting.IssueActions/MilestoneActionsnever read that state, showing a generic fallback instead — and simply destructuringerrorfromuseWallet()wouldn't have worked either, since that value can still reflect a pre-awaitrender by the time the awaitedconnect()call settles inside an async handler. Added agetError()accessor onWalletContext, backed by a ref kept in lockstep with everysetError()call, so it's guaranteed current regardless of React's render/commit timing — and wired both components to prefer it over their generic fallback.Hero stat drift (#237) — the homepage hero hardcoded
"341 contributors"as a literal while aStatCarda few hundred lines later renders the same fact fromplatformStats.activeContributors— two independent sources of truth that only agreed because341happened to match today. Interpolated the sameplatformStatsvalue into the hero sentence.Changes
src/context/WalletContext.tsx/.test.tsx— newgetError()accessor + test.src/app/issues/[id]/IssueActions.tsx— aria-live roles +getError()usage.src/app/milestones/MilestoneActions.tsx— input label + aria-live roles +getError()usage.src/app/connect/ConnectPanel.tsx— aria-live role.src/app/page.tsx— hero copy now derived fromplatformStats.activeContributors.Test plan
npx tsc --noEmit— cleannpx eslinton all changed files — cleannpx jest(full suite) — 195/195 passing, no regressionsCloses #236
Closes #234
Closes #235
Closes #237