Skip to content

fix(ios): stop hiding the store-update prompt behind a stale listing gate - #3114

Merged
innolope-dev merged 1 commit into
devfrom
chore/ios-store-listing-live
Sep 10, 2026
Merged

innolope-dev merged 1 commit into
devfrom
chore/ios-store-listing-live

Conversation

@innolope-dev

Copy link
Copy Markdown
Collaborator

Every iOS install has been silently denied the update row and the store prompt by a constant that went stale.

What happened

IOS_APP_STORE_LISTING_LIVE was added on 2026-09-02 (e75d9eed9, "iOS store prompt gated") with this comment:

The iOS listing (App Store Connect app 6786373552) is not published yet: the store URL 404s, so store-update prompts stay hidden on iOS until then.

"Until then" arrived and nobody flipped it:

$ curl -sL https://apps.apple.com/us/app/id6786373552 | grep '<title>'
<title>‎Peanut: Send Money & Card App - App Store</title>   ← HTTP 200

Meanwhile, on both dev and main:

export const IOS_APP_STORE_LISTING_LIVE = false

Which fed straight into the only thing that decides whether iOS sees an update at all:

const storeUpdateOffered = storeUpdateRequired && (!isIOSNative() || IOS_APP_STORE_LISTING_LIVE)

false there means no row, no modal, nothing — the invisible failure mode, and it was one boolean rather than anything inherent.

Removed, not flipped

A true boolean named LISTING_LIVE tells the next reader nothing except that someone once expected it to be false. The row condition now reads as what it means: an update only the store can deliver is offered on both platforms. Two call sites, no tests referenced it, no docs mentioned it.

What this does and does not fix

Does: an iOS binary older than the published App Store build now gets the prompt instead of silence, and can act on it.

Does not rescue installs already stuck behind a store update, for two independent reasons:

  1. The published App Store build is 1.5.0. An iOS 1.5.0 binary is offered nothing newer, so the prompt would be a dead end for exactly that cohort.
  2. A device running a bundle built before this change carries the old gate. Nothing shipped now reaches them anyway.

I want to be explicit because I got this wrong an hour ago and said flipping it would cancel the need for a new iOS build: it does not. Recovering the stuck iOS cohort still needs a newer iOS build in TestFlight or the App Store. This change is about the population that can act.

The gap it leaves

Nothing checks the store's actual version, so the prompt can point at a store with nothing newer — inherent to a client-side prompt, and true on Android too.

The cheap, honest improvement is available from work already in flight: #3111 computes the per-platform floor of the bundle being withheld, which is the version the user needs. The modal could name it — "Update to 1.7.0 in the App Store" rather than a vague "an update is needed" — which stays truthful even when the store hasn't caught up. That needs the floor plumbed through OtaUpdateContext, so it belongs with #3111 rather than here.

For a real store-version check there's the iTunes Lookup API on iOS (itunes.apple.com/lookup?id=… returns version) and no official equivalent for Play. That's a third-party call from the app with CSP and privacy implications — worth a decision, not worth smuggling into a one-line PR.

Verification

123 tests pass across the Profile suites and migration.utils; tsc --noEmit clean; prettier --check clean.

…gate

IOS_APP_STORE_LISTING_LIVE was added on 2026-09-02 because the App Store URL
404'd, with the comment saying prompts stay hidden on iOS "until then". Then
arrived and nobody flipped it: the listing has been serving
`Peanut: Send Money & Card App` for a while, and the constant still said false,
so every iOS install has been silently denied the update row and the store
prompt.

Removed rather than flipped. A `true` boolean named LISTING_LIVE tells the next
reader nothing except that someone once expected it to be false, and the row
condition reads as what it means now — an update the store has to deliver is
offered on both platforms.

This is not the rescue for installs already stuck behind a store update: the
published App Store build is 1.5.0, so an iOS 1.5.0 binary is offered nothing
newer, and a device running a bundle built before this change carries the old
gate anyway. What it fixes is the population that CAN act — an iOS binary older
than the published build now sees the prompt instead of nothing at all.
@vercel

vercel Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
peanut-wallet Ready Ready Preview Sep 10, 2026 10:34pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 86331748-f790-44ff-a286-df0291711f17

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 8999.44 → 8999.31 (-0.13)
Findings: 0 net (+6 new, -6 resolved)

🆕 New findings (6)

  • high hotspot — src/components/Profile/index.tsx — 47 commits, +330/-269 lines since 6 months ago
  • medium high-mdd — src/components/Profile/index.tsx:28 — Profile: MDD 83.1 (uses across many lines from declarations)
  • medium complexity — src/components/Profile/index.tsx — CC 22, MI 63.04, SLOC 86
  • medium complexity — src/constants/migration.consts.ts — CC 1, MI 52.18, SLOC 31
  • low high-dlt — src/components/Profile/index.tsx:28 — Profile: DLT 17 (calls 17 distinct functions — high context load)
  • low missing-return-type — src/components/Profile/index.tsx:28 — Profile: exported fn missing return type annotation

✅ Resolved (6)

  • src/components/Profile/index.tsx — 46 commits, +329/-265 lines since 6 months ago
  • src/components/Profile/index.tsx:30 — Profile: MDD 84.7 (uses across many lines from declarations)
  • src/components/Profile/index.tsx — CC 24, MI 62.59, SLOC 88
  • src/constants/migration.consts.ts — CC 1, MI 51.81, SLOC 32
  • src/components/Profile/index.tsx:30 — Profile: DLT 18 (calls 18 distinct functions — high context load)
  • src/components/Profile/index.tsx:30 — Profile: exported fn missing return type annotation

@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • ✅ unit: 7107 ran, 0 failed, 0 skipped, 2.2m

📊 Coverage (unit)

metric %
statements 77.1%
branches 63.6%
functions 71.1%
lines 78.1%
⏱ 10 slowest test cases
time test
🐢 9.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Network failure keeps loading while retries remain, then shows the generic error
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_RECENT_REFUND fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › User KYC not approved fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › a refused idempotency key tells the user to scan again, not to contact support
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_USER_NOT_PROVISIONED fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_SOURCE_OVER_MONTHLY_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › routes the KYC rejection on its wire code, and does not retry it
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_VOLUME_NEAR_CAP fails fast with copy that names the real cause
3.1s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
3.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Going offline blames the connection, and reconnecting clears it for the recovered scan
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

The stale iOS listing gate is removed cleanly: iOS now gets the existing store-update row and modal when the updater reports that a newer native binary is required.

Checked clean

  • Confirmed the detached worktree is clean and pinned to the supplied head; its merge base matches the supplied dev base.
  • Traced storeUpdateRequired from both staged-bundle inspection and the live Capgo check through the Profile row and store-only modal.
  • Verified a staged OTA bundle still takes precedence over the store-update modal and each modal keeps the correct apply path.
  • Confirmed the iOS App Store URL now resolves to the live Peanut listing.
  • Exact-head unit, typecheck, format, lint, native-export, analysis, and aggregate CI checks passed; ds-shots was still running at review time.
  • A local focused Jest invocation was unavailable because this detached worktree has no installed Jest binary; the exact-head unit gate passed instead.

Security review: did not run — this change has no security, privacy or money surface, so it was not asked. This review is one reviewer short.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 78554f4c37f0 · Context: repo, web · Took 8m

@github-actions

Copy link
Copy Markdown
Contributor

🖼 Visual diff — 9 screens moved

14 of 74 shots changed · 60 identical · baseline 688837c → head 78554f4

worst % screen widths
12.48% avatar-picker 320, 430
0.07% send 320, 430
0.07% add-money 320, 430
0.07% home-avatar 320
0.03% kyc-action-required 320, 430
0.03% add-money-crypto 320, 430
0.03% unverified 430
0.03% withdraw 430
0.01% identity-verification 320

job summary · before/after/diff images — artifact

Fixture screenshots, no backend. Advisory — this check never blocks a merge. Posted from the default branch by ds-shots-comment.yml; the report it renders is untrusted data.

@innolope-dev
innolope-dev merged commit ceca7dc into dev Sep 10, 2026
23 of 24 checks passed

This branch was successfully deployed

1 active deployment
Preview — 78554f4c Deployed Sep 10, 2026 by vercel[bot]
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.

1 participant