Skip to content

Rewards v3 prod release: merge master, drop points copy where points can't unlock - #2579

Merged
MusabShakeel576 merged 32 commits into
qafrom
claude/rewards-v3-prod-release-2xrzv5
Sep 24, 2026
Merged

MusabShakeel576 merged 32 commits into
qafrom
claude/rewards-v3-prod-release-2xrzv5

Conversation

@MusabShakeel576

@MusabShakeel576 MusabShakeel576 commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Brings master into qa for #2576. The merge had no conflicts.

It also changes the rewards benefits screen. Each tier page said "Unlocks at 5M / 35M points" and opened a "how to earn points" sheet even when points could no longer reach that tier. The page now follows the membership state's pointsUnlockEnabled, which https://github.com/Solid-Money/solid-backend/pull/1861 reports per user. When points can't take the user higher, the line reads "Unlocks with a membership" if a lock or annual charge is on sale for that tier, is hidden otherwise, and the points sheet goes.

No other app change is needed. The rewards-page card, the skip-the-line sheet, the Buy FUSE targets and the upgrade flow already follow fuseSkipLine.enabled and pointsUnlockEnabled, and the backend now sets both per user.

The Sentry date finding is not a bug: #2576 (comment)

  • tsc --noEmit: 9 errors, the same 9 as master.
  • jest components/Rewards lib/__tests__/tierUpgrade.test.ts: 124 tests, all passing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VhnDQhJoWUKneB9KFcZgfV

claude and others added 30 commits September 17, 2026 13:25
…thdraw

Two symptoms from one support report (Solid Operations 13039057237): the
Swap and Send buttons had disappeared from home, and withdrawing from the
card did not work. The reporter holds $4.87 on a Rain card and $0 in their
wallet — every deposit they have ever made went to the card's own deposit
address, which never touches their Safe.

Home: the action row asked `depositCompleted`, which only ever knows about
wallet funding (a deposit on record, a token balance, a vault balance). A
cardholder who funds their card directly satisfies none of it, so the row
collapsed to a lone "Add Funds" on a screen that was showing them a balance
right above it. It now asks whether the user holds anything in any of the
pots the breakdown shows, with `depositCompleted` still in front so an
erroring or not-yet-settled balance query cannot hide the buttons either.
Swap and Send work off wallet tokens, so a card-only balance opens Send on
an empty asset list — which now says why, and points at Add Funds, instead
of rendering a blank list under "Select an asset".

Card withdraw: the asset picker was a portalled dropdown menu on web. Inside
the withdraw sheet on a phone browser it covered the "Withdraw" button and
swallowed taps on its own rows — in the recording the user spent nine
seconds tapping the asset they wanted and gave up with the sheet still open.
Both platforms now render the in-flow list native already had, from one
shared file.

Two more things the same screen got wrong:

- Every token the user's Rain contracts support was offered, so the two
  assets they could actually withdraw sat behind eight $0 rows, including a
  duplicate "USDC" and "USDT". Only funded assets are listed now (plus the
  selected one, and the full list when nothing is funded), and a symbol held
  on two chains names its chain.
- Picking the asset already selected cleared the amount, so "press Max, then
  confirm the destination" ended with an empty field. Only an actual change
  of asset clears it now.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FxQs7MKViUX9AL5bzUzj9o
…rvnpw

Fix card withdrawal asset picker and fund visibility issues
…ncel

fix(withdraw): prevent user cancellations from being reported as Sentry errors
…retry

fix: retryable issuer decline code
A login whose passkey the backend cannot tie to an account was answered
by replacing the screen with signup. The user had just passed Face ID, so
what they saw was their account being offered back to them as a new one —
and signup was a dead end anyway, refusing their own address as already
registered. Reported from Italy with a screen recording: three
authentications, then Create your account.

The 404 does not mean "new user". It means the passkey the authenticator
just signed with reaches no account: unknown to Turnkey, or belonging to
a sub-organization whose account row a failed signup never wrote. The
account itself is usually fine — it is the passkey that isn't on it —
so the way out is recovery, which attaches a working passkey to the
account they already have.

Keep them on onboarding, name what happened, and put the recovery prompt
in front of them. A login started from the landing step raises the
Welcome sheet, which is where that prompt and an explicit "Create an
account" live; the landing hero has room for neither, so until now those
attempts failed to a bare hero with no way forward.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRh59soi7iX4J3UwxVVGRS
handleLogin re-throws the original error so callers keep its `status` and
`code` — which left every caller holding the raw API message alongside
the sanitized one, and every toast read the raw one. The sanitized copy
went into `loginInfo.message`, which nothing reads.

So the screen still said "User not found": the bare 404 of a backend that
predates the typed codes, under a heading telling the user their passkey
isn't linked to an account. That is the near-term default, not an edge
case — the backend half of this fix ships separately.

Decide the copy in one exported place and call it from each toast:
onboarding, legacy onboarding, and the multi-account welcome screen,
which showed the same raw message next to the account row the user had
just tapped.

Reported by Seer on #2549.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRh59soi7iX4J3UwxVVGRS
…w-7gma44

Fix passkey login redirecting existing users to signup
Adds the repay step to the borrow-position sheet. Repay slides the sheet
into an amount + "Repay with" view; a successful repayment slides back to
the updated position.

- Wallet sources use repayFromSafe (approve + repay + approve(0) when the
  module is revoked); collateral sources use repayFromCollateral.
- A full repayment also withdraws the remaining escrowed collateral in the
  same user operation, gated on a buffer for interest and price moves so
  the withdraw can never revert the batch.
- Tender is read from repayTender per token. The deployed build predates
  it, so where the call fails the list falls back to USDC.e + soUSD.
- ABI entries generated with forge inspect from the v2 sources.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat: repay card credit from wallet or collateral
Desktop web only (the ResponsiveModal branch of CardBottomSheet); native
and the phone-width web bottom sheet render as before.

- Modal is 480px with a 24px inset all round and the body drops its 17pt
  inset: ~432px of content instead of ~306px, so the repay amount no
  longer clips and labels stop wrapping.
- The modal's own header row is hidden. Bodies draw a 36px close button
  in their first row (Repay: back · title · close), cutting ~122px of
  space above the first element to 24px.
- MAX stays an outlined pill instead of inverting to white.
- Repay shows the token amount under the dollar figure: exact for a
  partial wallet repayment, an estimate (≈) where the module sizes it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-layout

fix: tighten card sheets on desktop web, show repay token amount
- Home: "Spend mode › <mode> [toggle]" strip under the card (Figma
  26134:22854) for cardholders who pass canChangeMode (Wirex, spend-mode
  cohort, v2 reachable). Tapping flies the card to the pane and opens the
  spend-mode sheet once it lands. Toggle is on for Credit/Smart, off for
  Cash, and only reports the mode.
- Card pane: "Card spending isn't set up" banner when spending was set up
  and both modules are now off. Enable re-enables the module the Safe is
  registered on (v2 if migrated, else v1) via register(); hidden when v2
  is configured but unreadable so it never guesses v1 for a migrated Safe.
- Spend-mode drawer: selected pill is always white; Add funds opens
  WirexCardFundModal (hidden when funds can't be added); panel area holds
  Smart's height so the drawer no longer resizes between modes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-and-enable

feat: home spend-mode strip, re-enable card spending, drawer fixes
The referral target moved from $75 to $150 while rewards that qualified on $75
were still in their 30-day payout delay. Their rows read "$145.63/$150 spent"
next to "Qualified", which looked like a mistake and was reported as one.

The referral summary now sends each row's own bar (spendTargetUsd,
merchantTarget): the one a qualified friend cleared, today's for everyone else.
The row uses it for the spent line, the "to go" chip and the progress bar, and
falls back to the program-level target when talking to a backend that doesn't
send it yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C4DDGA1KnJ6ADwWHFgUX4Q
…fixes-6xxe0w

Support row-level spend/merchant targets in referral rewards
The Credit segment, Smart's figure and "Available to borrow" all read the
lens's availableToBorrowUsd, which is also clamped by the Safe's remaining
spending limit. A Safe with a $1,000 daily limit therefore showed $1,000 of
credit beside a $2,890 line, while Cash showed the unclamped balance.

Quote the line minus debt, still clamped by the per-Safe and global debt
caps (totalDebtUsd added to the ABI for the latter). The lens figure stays
a floor, which keeps a limits waiver correct.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After the v2 redeploy every cohort Safe has all modules off and is unknown
to the new core. The registration read then reported it as v1, so the
"Enable" row re-enabled v1 (or registered a fresh Safe on v1), putting a
cohort cardholder back on cash with Credit and Smart out of reach.

For a spend-mode cohort member with no working module and no v2
registration, report the Safe as awaiting v2: the enable row shows, and
the batch disables v1 if it is on, enables v2 and registers with the v1
caps clamped to v2's ceilings (v2 defaults when there were none). The
read waits for the cohort answer so it cannot report v1 first, and the
backend confirm is now told which module was registered.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-headroom

fix: show credit headroom, not the daily limit, as available to borrow
feat: set up spend-mode cohort cards on v2 when no module is enabled
Drop the spend-mode cohort gate: the home "Spend mode" strip, the card
page's spend-mode row and picker, and the borrow position card now show
for any Wirex card with working spending on a build that reaches v2. The
backend only used the cohort to answer spend-mode/access; ExtAuth never
checks it.

A Safe with no working module is now set up on v2 for every cardholder,
not only the cohort: new cards, a disabled v1 module, and Safes from a
replaced v2 core. v2's registerSafe starts every Safe in Debit, so no
setMode is needed. A working v1 Safe stays on v1 and migrates through the
picker; a build without v2 still falls back to v1.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tice

Implements Figma 26974:12806. The picker offers Cash and Credit only:
Smart is listed in HIDDEN_SPEND_MODES rather than removed, and a Safe
already on Smart still sees its own segment. Segment widths, the pill's
position and the reserved panel height all follow the offered modes, so
the hidden one leaves no gap.

Credit now shows a yellow notice between the caption and the Borrowed
card ("Currently credit mode works only with your USD yield balance"),
using the design's background and badge assets. Gaps follow the frame:
17 caption→notice, 42 notice→card, 34 card→button. The notice's rendered
height feeds the reserved height, so a three-line wrap on a narrow sheet
does not move the button between tabs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MayankMittal1 and others added 2 commits September 24, 2026 20:40
feat: open spend modes to every cardholder and register new Safes on v2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VhnDQhJoWUKneB9KFcZgfV
@vercel

vercel Bot commented Sep 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated
solid-app Ignored Ignored Sep 24, 2026 4:07pm UTC
solid-app-staging Ignored Ignored Sep 24, 2026 4:07pm UTC

Request Review

@claude

claude Bot commented Sep 24, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Review scope:

  • Scanned all 62 files (+5099/-414 lines) for syntax errors, logic errors, and security issues
  • Checked for debugging leftovers (console.log, debugger statements)
  • Checked for commented-out code, TODO/FIXME markers, test artifacts
  • Checked for hardcoded secrets, local config, and accidental edits
  • Verified CLAUDE.md compliance (no CLAUDE.md files exist in this repository)

🤖 Generated with Claude Code

@MusabShakeel576
MusabShakeel576 merged commit cdcbfaf into qa Sep 24, 2026
7 checks passed
@MusabShakeel576
MusabShakeel576 deleted the claude/rewards-v3-prod-release-2xrzv5 branch September 24, 2026 16:20
@MusabShakeel576 MusabShakeel576 changed the title Rewards v3 prod release: merge master into qa Rewards v3 prod release: merge master, drop points copy where points can't unlock Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants