Skip to content

fix(wallet): correct network-switched explore.db, merchant logo placeholder, and test-merchant re-seed#856

Open
romchornyi wants to merge 7 commits into
masterfrom
fix/merchant-logo-placeholder
Open

fix(wallet): correct network-switched explore.db, merchant logo placeholder, and test-merchant re-seed#856
romchornyi wants to merge 7 commits into
masterfrom
fix/merchant-logo-placeholder

Conversation

@romchornyi

@romchornyi romchornyi commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Issue being fixed or feature implemented

Three related Explore Dash problems, all stemming from how explore.db is downloaded and replaced. On testnet the merchant list was showing the mainnet database (no Brinker, fewer merchants than Android), merchants without a logo drew a flat empty asset, and the PiggyCards test merchants disappeared after a network switch.

Wrong database served after a network switch. Sync bookkeeping is per-network (kExploreDatabaseLastVersion_Mainnet/_Testnet) but both networks share one explore.db on disk. After switching chains the saved version for the new network already read as up to date and the file check passed, so the app kept serving the other network's merchants. The record of which network the file belongs to is now stored, and a download is forced when it differs. storageRef is also computed per access rather than cached at init, so the forced download targets the current network's archive instead of the launch network's.

No fallback when a merchant has no logo. Merchant surfaces drew the flat emptyLogoImageName asset. They now use a generated MerchantLogoPlaceholder — the merchant's name or initials on a colour derived deterministically from the name, matching the Android MerchantInitialIcon (HSV, Java hashCode over the name so colours line up across platforms).

Test merchants vanished after the database was replaced. The PiggyCards test merchants are seeded once per session, guarded by didInsertTestMerchants. That was safe while the file never changed mid-session, but the network-switch re-download replaces explore.db in place: after switching to testnet the fresh archive has no test merchants, yet the guard was already set from the mainnet database seeded at launch, so seeding returned early. The guard is now cleared when the database-updated notification fires.

What was done?

  • Store the installed database's network and force a re-download when it no longer matches the current chain; compute storageRef per access.
  • Add MerchantLogoPlaceholder and route the merchant logo slots through it (POI details, list cell, map annotation, DashSpend pay intro / confirmation dialog, gift-card header, and the redesigned all-locations header).
  • Clear didInsertTestMerchants on databaseHasBeenUpdatedNotification so the test merchants re-seed against the new file. The existing count check still short-circuits before touching the FTS triggers when they are already present.

How Has This Been Tested?

xcodebuild -scheme dashwallet succeeds. Verified against the two booted simulators:

  • After the network-switch fix, the testnet database on the active simulator refreshed to the testnet archive — Brinker and both Piggy Cards Test Merchant entries present, where before it held the stale mainnet file.
  • Confirmed the test-merchant path directly against the live testnet DB: the five 2e393… merchants were absent (count 0) while the FTS triggers were intact, so clearing the session guard lets the count check fall through to insertion. The same five IDs are already present on the other simulator, exercising the insert path.

Two cherry-picked commits did not build against this tree and were repaired: DashSpendConfirmationDialog used a .placeholder { } modifier absent from SDWebImageSwiftUI 3.1.3, and ExploreDatabaseSyncManager referenced a currentNetworkName that existed nowhere — the original DWEnvironment…isMainnet() call was restored.

Breaking Changes

None. Existing installs have no network marker, so the database refreshes once on the next launch.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • New Features

    • Added consistent merchant logo placeholders using merchant names or initials when logos are unavailable or loading.
    • Updated merchant lists, maps, details, DashSpend, and gift card views with dynamic placeholders.
    • Placeholders now adapt to different sizes and shapes with deterministic colors and readable text.
  • Bug Fixes

    • Improved Explore database synchronization when switching networks or replacing the local database.
    • Ensured test merchant data can be reseeded after database replacement.

jeanpierreroma and others added 4 commits July 23, 2026 00:36
Sync bookkeeping is per-network (kExploreDatabaseLastVersion_Mainnet/_Testnet)
but both networks share a single explore.db on disk. After switching chains the
saved version for the new network already read as up to date and the file check
passed, so the app kept serving the other network's merchants — testnet users
saw the mainnet database (no Brinker, fewer merchants than Android).

Record which network the installed database belongs to and force a download when
it differs. Existing installs have no marker, so they refresh once on next launch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…new screen

The cherry-picked commit did not build against this tree, so two call sites
needed repair:

- DashSpendConfirmationDialog used `.placeholder { }`, a modifier
  SDWebImageSwiftUI 3.1.3 does not have — the pod was already on this version
  when the commit was written, so it can never have compiled. Rewritten with the
  content/placeholder initializer, the form the other ported call sites use. The
  `.fade` error beside it was only a knock-on from the broken chain.

Also route AllMerchantLocationsView's header through MerchantLogoPlaceholder.
That screen landed after the original commit, so it was still drawing the flat
empty-logo asset while every other merchant surface had moved on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The PiggyCards test merchants are seeded once per session, guarded by
`didInsertTestMerchants`. That was fine while the database file never changed
mid-session, but the network-switch re-download in this branch replaces
explore.db in place: after switching to testnet the fresh archive has no test
merchants, yet the guard was already set from the mainnet database seeded at
launch, so `addPiggyCardsTestMerchants` returned early and the testnet list was
missing Piggy Cards Test Merchant and the four flexible test brands.

Clear the guard when `databaseHasBeenUpdatedNotification` fires, so seeding
re-evaluates against the new file. The existing count check still short-circuits
before touching the FTS triggers when the merchants are already present, so a
normal sync that changes nothing does no extra work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@romchornyi, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 363ad7a1-70c3-497c-9ecb-248abc7c27ba

📥 Commits

Reviewing files that changed from the base of the PR and between 8f0c98f and 4fab21b.

📒 Files selected for processing (4)
  • DashWallet.xcodeproj/project.pbxproj
  • DashWallet/Sources/Models/Explore Dash/Services/ExploreDatabaseSyncManager.swift
  • DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendConfirmationDialog.swift
  • DashWallet/Sources/UI/SwiftUI Components/MerchantLogoPlaceholder.swift
📝 Walkthrough

Walkthrough

Changes

Merchant logo placeholders

Layer / File(s) Summary
Placeholder rendering implementation
DashWallet/Sources/UI/SwiftUI Components/MerchantLogoPlaceholder.swift
Adds deterministic merchant colors, initials or names, adaptive text sizing, SwiftUI rendering, UIKit image rendering, and previews.
Merchant icon integration
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/..., DashWallet/Sources/UI/Explore Dash/Views/DashSpend/...
Replaces static or generic logo fallbacks with merchant-name-based placeholders across merchant and DashSpend views.
Project source wiring
DashWallet.xcodeproj/project.pbxproj
Adds the new Swift file reference, group membership, and Sources build-phase entries.

Explore Dash database synchronization

Layer / File(s) Summary
Network-aware database synchronization
DashWallet/Sources/Models/Explore Dash/Services/ExploreDatabaseSyncManager.swift
Selects Firebase storage by current network, detects mismatched installed data, forces downloads, and persists the installed network.
Database replacement seeding reset
DashWallet/Sources/Models/Explore Dash/Infrastructure/Database Connection/ExploreDatabaseConnection.swift
Resets the test-merchant seeding guard after the on-disk database is replaced.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: jeanpierreroma, hashengineering

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the three main changes: network-aware explore.db handling, merchant logo placeholders, and test-merchant reseeding.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/merchant-logo-placeholder

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.

@romchornyi romchornyi changed the title fix(explore): correct network-switched explore.db, merchant logo placeholder, and test-merchant re-seed fix(wallet): correct network-switched explore.db, merchant logo placeholder, and test-merchant re-seed Jul 22, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
DashWallet/Sources/Models/Explore Dash/Services/ExploreDatabaseSyncManager.swift (1)

128-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the network log marker.

Prefix this DSLogger message with 🌐 for consistent network-log filtering.

As per coding guidelines, “Use emoji logging prefixes … with DSLogger for easier debugging and log filtering.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DashWallet/Sources/Models/Explore`
Dash/Services/ExploreDatabaseSyncManager.swift at line 128, Update the DSLogger
message in the ExploreDatabaseSyncManager network-mismatch path to begin with
the 🌐 network log marker, preserving the existing message content and
interpolation.

Source: Coding guidelines

DashWallet/Sources/UI/SwiftUI Components/MerchantLogoPlaceholder.swift (1)

82-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated hue-derivation logic between color(for:) and uiColor(for:).

Both methods repeat the identical trim/lowercase/hash/modulo computation to get hue. Consider extracting a shared private static func hue(for name: String) -> Int used by both.

♻️ Proposed refactor
+    private static func hue(for name: String) -> Int {
+        let key = name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
+        return key.isEmpty ? 0 : ((javaHashCode(key) % 360) + 360) % 360
+    }
+
     static func color(for name: String) -> Color {
-        let key = name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
-        let hue = key.isEmpty ? 0 : ((javaHashCode(key) % 360) + 360) % 360
-        return Color(hue: Double(hue) / 360.0, saturation: 0.3, brightness: 0.6)
+        return Color(hue: Double(hue(for: name)) / 360.0, saturation: 0.3, brightness: 0.6)
     }
     static func uiColor(for name: String) -> UIColor {
-        let key = name.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
-        let hue = key.isEmpty ? 0 : ((javaHashCode(key) % 360) + 360) % 360
-        return UIColor(hue: CGFloat(hue) / 360.0, saturation: 0.3, brightness: 0.6, alpha: 1)
+        return UIColor(hue: CGFloat(hue(for: name)) / 360.0, saturation: 0.3, brightness: 0.6, alpha: 1)
     }

Also applies to: 113-117

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DashWallet/Sources/UI/SwiftUI` Components/MerchantLogoPlaceholder.swift
around lines 82 - 86, Extract the duplicated name normalization and hash-based
hue calculation from color(for:) and uiColor(for:) into a shared private static
hue(for name: String) -> Int helper. Update both methods to use this helper
while preserving the existing empty-name and modulo behavior.
DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift (1)

49-56: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Cache generated placeholder images instead of re-rendering on every reuse. Both call sites regenerate a MerchantLogoPlaceholder.image(...) bitmap (font-fit math, path fill, text draw) unconditionally on every cell/annotation reuse cycle, even when the result will be immediately replaced by a cached SDWebImage load. Adding a memoization cache (e.g., NSCache<NSString, UIImage> keyed by name|size|cornerRadius) inside MerchantLogoPlaceholder.image(...) would fix both sites at once.

  • DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift#L49-L56: memoize/reuse the placeholder image generated in update(with:) instead of rendering it on every cell configuration.
  • DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapAnnotationView.swift#L67-L74: memoize/reuse the placeholder image generated in update(with:) instead of rendering it on every annotation reuse.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DashWallet/Sources/UI/Explore` Dash/Merchants &
ATMs/List/Cells/PointOfUseItemCell.swift around lines 49 - 56, Cache generated
placeholder images inside MerchantLogoPlaceholder.image(...) using a key
composed of merchant name, size, and corner radius. Apply this shared
memoization to both update(with:) call sites: DashWallet/Sources/UI/Explore
Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift#L49-L56 and
DashWallet/Sources/UI/Explore Dash/Merchants &
ATMs/List/Views/ExploreMapAnnotationView.swift#L67-L74, preserving their
existing image-loading behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@DashWallet/Sources/Models/Explore`
Dash/Services/ExploreDatabaseSyncManager.swift:
- Around line 49-53: Update syncIfNeeded and downloadDatabase to snapshot the
current network name and StorageReference once at the start of the full sync,
then reuse those values for metadata and data requests. Before extraction and
marker persistence, verify the snapshot still matches the active network; if it
changed, discard or restart the operation so the extracted archive cannot update
the wrong network’s marker.

In `@DashWallet/Sources/UI/Explore`
Dash/Views/DashSpend/DashSpendConfirmationDialog.swift:
- Around line 89-100: Update the MerchantLogoPlaceholder invocation in the
WebImage placeholder of DashSpendConfirmationDialog to pass usableFraction:
0.84, matching the surrounding RoundedRectangle clipShape and the established
POIDetailsView and AllMerchantLocationsView usage.

In `@DashWallet/Sources/UI/SwiftUI` Components/MerchantLogoPlaceholder.swift:
- Line 132: Update the renderer.image closure in MerchantLogoPlaceholder to omit
the unused ctx parameter, using the appropriate closure syntax while preserving
the existing image-rendering behavior.

---

Nitpick comments:
In `@DashWallet/Sources/Models/Explore`
Dash/Services/ExploreDatabaseSyncManager.swift:
- Line 128: Update the DSLogger message in the ExploreDatabaseSyncManager
network-mismatch path to begin with the 🌐 network log marker, preserving the
existing message content and interpolation.

In `@DashWallet/Sources/UI/Explore` Dash/Merchants &
ATMs/List/Cells/PointOfUseItemCell.swift:
- Around line 49-56: Cache generated placeholder images inside
MerchantLogoPlaceholder.image(...) using a key composed of merchant name, size,
and corner radius. Apply this shared memoization to both update(with:) call
sites: DashWallet/Sources/UI/Explore Dash/Merchants &
ATMs/List/Cells/PointOfUseItemCell.swift#L49-L56 and
DashWallet/Sources/UI/Explore Dash/Merchants &
ATMs/List/Views/ExploreMapAnnotationView.swift#L67-L74, preserving their
existing image-loading behavior.

In `@DashWallet/Sources/UI/SwiftUI` Components/MerchantLogoPlaceholder.swift:
- Around line 82-86: Extract the duplicated name normalization and hash-based
hue calculation from color(for:) and uiColor(for:) into a shared private static
hue(for name: String) -> Int helper. Update both methods to use this helper
while preserving the existing empty-name and modulo behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 07186338-afcc-4524-aaa3-8ffe0145f304

📥 Commits

Reviewing files that changed from the base of the PR and between 0bc4b5d and 8f0c98f.

📒 Files selected for processing (11)
  • DashWallet.xcodeproj/project.pbxproj
  • DashWallet/Sources/Models/Explore Dash/Infrastructure/Database Connection/ExploreDatabaseConnection.swift
  • DashWallet/Sources/Models/Explore Dash/Services/ExploreDatabaseSyncManager.swift
  • DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/Details/Views/POIDetailsView.swift
  • DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/AllMerchantLocations/AllMerchantLocationsView.swift
  • DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Cells/PointOfUseItemCell.swift
  • DashWallet/Sources/UI/Explore Dash/Merchants & ATMs/List/Views/ExploreMapAnnotationView.swift
  • DashWallet/Sources/UI/Explore Dash/Views/DashSpend/Components/DashSpendPayIntro.swift
  • DashWallet/Sources/UI/Explore Dash/Views/DashSpend/DashSpendConfirmationDialog.swift
  • DashWallet/Sources/UI/Explore Dash/Views/DashSpend/GiftCardDetails/Components/GiftCardDetailsMerchantHeader.swift
  • DashWallet/Sources/UI/SwiftUI Components/MerchantLogoPlaceholder.swift

Comment thread DashWallet/Sources/UI/SwiftUI Components/MerchantLogoPlaceholder.swift Outdated
jeanpierreroma and others added 3 commits July 23, 2026 01:16
The network mismatch check only runs from `start()` (once, at first configure)
and the 24h timer, so switching mainnet⇄testnet while the app is running did not
trigger it: the wrong network's merchants stayed until the next cold launch. That
matched the report — switch to testnet, and Brinker only appeared after quitting
and reopening the app.

Observe DWCurrentNetworkDidChange and re-run syncIfNeeded on a switch. The chain
is already reset before the notification is posted, so the installedDatabaseNetwork
mismatch is seen immediately and forces a same-session re-download; the download
then re-seeds the test merchants via the existing notification path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Snapshot the network name and its StorageReference once in syncIfNeeded and
  thread both through downloadDatabase. storageRef and currentNetworkName are
  computed from the live chain, so a switch between the metadata fetch, the data
  fetch and the marker save could pair one archive's timestamp/checksum with
  another's bytes, or record installedDatabaseNetwork for a network whose archive
  was never extracted. Each async hop now bails when the chain has moved on, and
  the download is discarded before extraction rather than overwriting explore.db
  with the wrong network's data — the switch already scheduled its own sync. The
  now-unused networkSpecificFileName is folded into downloadDatabase.

- Pass usableFraction: 0.84 to the DashSpendConfirmationDialog placeholder. It is
  clipped to a rounded rect, not a circle, so it should use the same fraction as
  the other rounded-rect sites instead of the circle-tuned 0.66 default.

- Replace the unused ctx closure parameter in MerchantLogoPlaceholder with _.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Updates MARKETING_VERSION across all 20 build configurations to 8.7.0;
CURRENT_PROJECT_VERSION was already 6 across all 28. Every target — dashwallet,
dashpay, TodayExtension, WatchApp and its extension — reports the same version.
The Info.plist files resolve both through build settings and needed no edit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants