Conversation
…d banner, payment schedule, contrast, account gov-activity signpost Closes the UX/QA gaps flagged in the SYSHUB001v3 audit. No business-logic changes — all edits are presentational or surface-level state wiring. - ProposalStatus: fix failReason -> copy mapping to match dispatcher codes (submit_rejected, collateral_not_found, duplicate_governance_hash) and add a dedicated terminal panel for `Object creation rate limit exceeded` via classifyCoreRejection(). - ProposalVoteModal: track failedCount / retryableCount / dedupCount explicitly so the retry CTA reads "Retry X of Y failed" and a hint explains which rows are benign dupes vs real unrecoverable failures. - AuthContext + new SessionExpiredBanner: on a 401 received while the user WAS authenticated, flag the session as expired and render a non-blocking global banner that preserves the return-path into /login. Does not fire on initial boot. - NewProposal Review step: approximate payment-schedule breakdown + total budget when paymentCount>=2, with a truncation warning when the voting window can't fit every payment. - App.css: footer copyright/social wrap fix, schedule styles, session-expired banner styles, vote-modal hint styles. - index.css: darken --muted (#63748d -> #525f76) and strengthen --panel-outline to clear WCAG AA contrast on every panel/surface. - Account page: new GovernanceActivityLink signpost card deep-linking to /governance (no second data fetch — receipts list stays canonical on the governance page). Made-with: Cursor
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 61ac831bb5
ℹ️ 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".
| const paymentAmountNum = Number(form.paymentAmount); | ||
| const totalBudget = | ||
| Number.isFinite(paymentCountNum) && | ||
| Number.isFinite(paymentAmountNum) && | ||
| paymentCountNum > 0 && |
There was a problem hiding this comment.
Use exact arithmetic for review total budget
The review total is derived from Number(form.paymentAmount)/Number(form.paymentCount), which introduces floating-point and integer-precision loss for valid large values; for example, amounts above 2^53 can display a different SYS total than the submitted proposal data. Because this block is the user’s final budget confirmation before collateral burn, showing rounded/incorrect totals is an accuracy regression. Consider computing totals in sats with BigInt (the same exact-math approach already used elsewhere in proposal form helpers).
Useful? React with 👍 / 👎.
| <Link | ||
| to={loginTo} | ||
| className="button button--primary button--small" | ||
| onClick={dismissSessionExpired} | ||
| data-testid="session-expired-signin" |
There was a problem hiding this comment.
Don't clear session-expired state on sign-in click
The sign-in CTA clears sessionExpired immediately via onClick, so if navigation is canceled or login fails, the user returns to anonymous/public pages without the expiry banner even though re-authentication never succeeded. This removes the only persistent context for why protected actions stopped working; the flag should remain until successful login (or explicit dismiss) rather than being cleared preemptively.
Useful? React with 👍 / 👎.
P2: Use exact BigInt sats arithmetic for the review-step total budget (src/pages/NewProposal.js). `Number * Number` loses precision past 2^53 sats, which would show the user a rounded total right before they burn 150 SYS on collateral. Route through sysToSatsString + BigInt multiply + satsStringToSys so the display matches the on-wire representation exactly. P2: Don't pre-clear sessionExpired on the Sign-in CTA click (src/components/SessionExpiredBanner.js). AuthContext.login() already flips the flag off on a successful sign-in, and the banner is suppressed on /login anyway. Clearing it in the Link's onClick was destructive — if the user cancelled the nav or login failed, they'd land back on a public page with no context for why protected actions broke. Added a regression test that verifies the flag persists past the click when the login service is never actually called. Made-with: Cursor
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c43adaffaf
ℹ️ 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".
| // normal "you are not logged in" state and showing an expiry | ||
| // banner would be confusing UX. | ||
| const handleAuthLost = useCallback(() => { | ||
| const wasAuthenticated = statusRef.current === AUTHENTICATED; |
There was a problem hiding this comment.
Avoid reading stale auth status in auth-loss handler
handleAuthLost decides whether to raise sessionExpired from statusRef.current, but that ref is only synchronized in a passive useEffect. During status transitions (for example, right after a user-initiated logout), a 401 from an in-flight protected request can arrive before the effect runs, so this line still sees authenticated and incorrectly flips sessionExpired to true. That produces a misleading “session expired” banner for an intentional sign-out flow; the pre-transition status check needs to read from a value updated synchronously with setStatus.
Useful? React with 👍 / 👎.
P2 (AuthContext): eliminate the handleAuthLost / logout race that could raise the "session expired" banner for a user-initiated sign-out. Two changes working together: 1. Synchronous status ref. `statusRef` was updated in a passive useEffect, which lagged one tick behind `setStatus`. Replaced all direct `setStatus(...)` calls with a `commitStatus(next)` helper that writes the ref AND schedules the React state update in the same tick. This closes a same-tick race where an interleaved handleAuthLost would read a still-AUTHENTICATED ref after logout had already committed ANONYMOUS. 2. `logoutPendingRef` guard. A user-initiated logout can still run for hundreds of ms on the network. During that window a parallel protected request can 401 because the SERVER is tearing down the session in response to our own /auth/logout. handleAuthLost now no-ops while logoutPendingRef is true, so the banner only rises for surprise session losses — never for a voluntary sign-out the user is already driving. Added a dedicated regression test that drives the race with a manually-resolved logout promise and asserts that `sessionExpired` stays `false` both during and after the overlapping handleAuthLost fire. Made-with: Cursor
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b035a010a
ℹ️ 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".
| const at = start + i * step; | ||
| if (at > end) break; |
There was a problem hiding this comment.
Align projected payments to superblock boundaries
buildApproximateSchedule currently counts startEpoch as payment #1 (at = start + i * step), but proposal payouts are executed on superblocks, so the first payable point is the next superblock after startEpoch, not the raw start timestamp. In windows where startEpoch is shortly after a superblock, this overestimates how many payments can land before endEpoch, which can hide the new truncation warning and mislead users into submitting a proposal that cannot pay all requested installments.
Useful? React with 👍 / 👎.
P1 (NewProposal): align projected payments to superblock boundaries. `buildApproximateSchedule` was treating payment #1 as if it landed on `startEpoch` (`at = start + i * step`), but Syscoin governance payouts only execute on superblocks — the first payable point is the next superblock after `startEpoch`, not the raw start timestamp. For proposals whose `startEpoch` lands just after a superblock, this overestimated how many payments could fit inside the voting window and silently hid the truncation warning. Fix: * Step by `(i + 1) * step` so the schedule starts at the NEXT superblock under the worst-case alignment assumption (the frontend has no network anchor for superblock timing, so erring conservative is correct — it may show one false "won't fit" warning but never hides a real one). * Render the schedule panel for ALL `paymentCount >= 2` proposals, not only those with a non-empty projected list. Otherwise a window shorter than one cycle would suppress the panel AND the truncation warning, recreating the very bug Codex flagged. * Add a dedicated copy variant for `schedule.length === 0` so users with short windows hear "too short to land any" in plain English. * Tighten surrounding explanatory copy to call out that the listed dates are worst-case estimates; real superblocks may run a bit earlier depending on start alignment. Added a regression test for the zero-row case and updated the existing truncation test to exercise a 45-day window that now fits exactly one payment under the corrected math. Made-with: Cursor
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Summary
Closes the UX/QA gaps identified in the SYSHUB001v3 audit. Pure UX/surface polish — no business-logic changes.
Scope
ProposalStatusrate-limit terminal panel + failReason mapping fix. Dispatcher emitssubmit_rejected/collateral_not_found/duplicate_governance_hash; the page now maps those codes to accurate copy instead of the stalecore_rejectedlabel. A newclassifyCoreRejectionhelper inspectsfailDetailand renders a dedicated, non-alarming terminal panel for theObject creation rate limit exceededcase (protocol-level quota, not a user bug).ProposalVoteModalexplicit Retry-N-of-Y counter. TracksfailedCount,retryableCount, anddedupCountseparately so the CTA reads "Retry 2 of 3 failed" when some rows can't be retried (MN no longer owned, missing collateral outpoint). Benign duplicate rows (already-voted) get their own clarifier instead of being presented as failures.AuthContextnow tracks asessionExpiredflag that flips true only when a 401 arrives while the user was alreadyAUTHENTICATED(not on boot). New globalSessionExpiredBannerrenders a non-blocking notice with a "Sign in again" link that preserves the current path aslocation.state.from, and dismiss clears the flag without redirecting.paymentCount >= 2, the Review step renders an approximate superblock-cadence schedule (30-day stride) with each payment's date and amount, plus a total-budget line. A truncation warning appears when the voting window can't fit every requested payment..site-footer__innernowflex-wraps and the copy block getsmin-width: 0so the copyright + social links don't overlap at mid-breakpoints.--muted(#63748d→#525f76) and strengthened--panel-outlinealpha (0.12→0.22) so every muted-text + panel combination clears WCAG AA 4.5:1 comfortably. Placeholder color pinned to--mutedso UA defaults don't regress it on Safari.GovernanceActivityLinkcard deep-links to/governancewhere the canonicalGovernanceActivitywidget lives. Intentionally no second fetch — avoids two sources of truth for vote receipts.Out of scope (deferred to PR10)
sys_signAndSend). Requirescraco+ Node polyfills forsyscoinjs-liband live testnet validation; split out to keep PR9 reviewable.Test plan
npm test -- --watchAll=false --testPathPattern=ProposalStatus— new rate-limit panel + updated failReason copy tests green.npm test -- --watchAll=false --testPathPattern=ProposalVoteModal— new Retry-X-of-Y label + benign-dup note tests green.npm test -- --watchAll=false --testPathPattern=SessionExpiredBanner— renders only on silent auth loss; not on boot; not on /login; dismiss clears flag; re-login clears flag.npm test -- --watchAll=false --testPathPattern=NewProposal— new Review-step schedule + truncation-warning + single-payment hide tests green.npm test -- --watchAll=false --testPathPattern=GovernanceActivityLink— renders heading; CTA is a real<a>withhref="/governance".npm test -- --watchAll=false --testPathPattern=pages/Account— 7/7 pass, no regression from the new card insert.Made with Cursor