Skip to content

fix(swift-example-app): load document price on Purchase sheet appear#3947

Merged
QuantumExplorer merged 2 commits into
v3.1-devfrom
claude/sad-gagarin-ad1835
Jun 21, 2026
Merged

fix(swift-example-app): load document price on Purchase sheet appear#3947
QuantumExplorer merged 2 commits into
v3.1-devfrom
claude/sad-gagarin-ad1835

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Jun 21, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

The production document Purchase flow in SwiftExampleApp was blocked: the on-chain price never loaded, so the Purchase / Broadcast button stayed permanently disabled.

DocumentWithPriceView fetched the price only via the TextField's onChange(of: documentId). PurchaseDocumentView seeds documentIdField as the binding mounts (and the field is .disabled(true)), so onChange never fires and the user can't trigger it by editing → fetchDocument() never ran, transitionState.documentPrice stayed nil, and submit was gated off forever. Confirmed via Rust logs that zero GetDocuments queries fired while the Purchase sheet was open.

This is a merge regression: the same bug (plus the Purchase gating and a submit-button gating fix) was already fixed on the #3945 branch in commit 588435aff4, but the v3.1-dev merge (96cba166ba) dropped that commit, so it recurred.

What was done?

  • DocumentWithPriceView: added an .onAppear that triggers the fetch when the id is already populated at mount. .disabled(true) blocks hit-testing, not lifecycle events, so it fires in the Purchase flow. The editable TransitionInputView path starts empty → guarded no-op, and onChange still handles typing there.
  • DocumentsView: restored the relaxed Purchase gating so the action surfaces for a for-sale, tradeable doc whenever the wallet holds a controlled identity other than the owner (buyer ≠ owner) — matching the file's own surviving doc-comment. Without this, the Purchase sheet is unreachable for the two-identities-in-one-wallet scenario.

How Has This Been Tested?

Driven end-to-end on the iOS simulator (QA-iPhone16Pro, testnet), contract Contract with card (5jpKat9U82PGcfmeYm8QZZWX6q7zDo2C32PZMxHpbiGB):

Step Result
Create card doc (Identity 1 J52PSRr4…) doc GLXNTysahyLnUKPS1hzji…, rev 1
Set price 50000 credits Broadcast confirmed, rev 2, $price:50000
Open Purchase → price loads "Document found" + "50000 credits"; button enabled after selecting purchaser
Broadcast purchase (Identity 2 envSngUJ…) Broadcast confirmed
ZOWNERID flip (SwiftData) J52PSRr4…envSngUJ…, rev 2→3, $price cleared

The displayed price comes only from fetchDocument's network read (sdk.documentGet), and PurchaseDocumentView.onAppear had just nil'd the shared price — so its appearance is direct proof the probe now fires on appear.

Breaking Changes

None. Example-app UI only; no consensus, SDK, or FFI changes.

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 added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed document pricing and ownership details that were not loading when a document ID was pre-populated at view initialization.
  • New Features

    • Expanded availability of the purchase action to appear when trading is enabled and the wallet controls additional identities.

The production document Purchase flow was blocked: the on-chain price
never loaded, so "Purchase / Broadcast" stayed permanently disabled.

`DocumentWithPriceView` fetched the price only via the TextField's
`onChange(of: documentId)`. `PurchaseDocumentView` seeds `documentIdField`
as the binding mounts (and disables the field), so `onChange` never fires
and the user can't trigger it by editing -> `fetchDocument()` never ran,
`documentPrice` stayed nil, and submit was gated off forever.

Fix: trigger the fetch from `.onAppear` when the id is already populated
(`.disabled(true)` blocks hit-testing, not lifecycle events, so it fires
in the Purchase flow). The editable TransitionInputView path starts empty,
so this is a no-op there and `onChange` still handles typing.

Also restore the relaxed Purchase gating so the action surfaces for a
for-sale, tradeable doc whenever the wallet holds a controlled identity
other than the owner (buyer != owner) -- matching the file's own
doc-comment. Both of these were fixed once on the #3945 branch
(588435aff4) but the v3.1-dev merge (96cba16) dropped that commit, so
the bug recurred.

Verified end-to-end on the iOS simulator (testnet): created a card doc
owned by one identity, set price 50000 credits, opened Purchase as a
second controlled identity (price loaded + button enabled), broadcast the
purchase, and confirmed ZOWNERID flipped to the buyer (rev 2->3, $price
cleared).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@QuantumExplorer, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 50 minutes and 56 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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 credits.

🚦 How do rate limits work?

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

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, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b88b997e-de8d-4576-b115-5bb521de675e

📥 Commits

Reviewing files that changed from the base of the PR and between da9e68e and ed85f55.

📒 Files selected for processing (1)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift
📝 Walkthrough

Walkthrough

Two fixes in the SwiftExampleApp: DocumentWithPriceView adds an onAppear handler to trigger an initial fetch when the bound documentId is already populated on mount. DocumentsView refactors availableActions to introduce a tradeable flag and allows the purchase action to appear even when the wallet controls the document owner.

Changes

Document Pricing and Action Menu Fixes

Layer / File(s) Summary
onAppear initial fetch for pre-seeded documentId
packages/swift-sdk/SwiftExampleApp/.../DocumentWithPriceView.swift
Adds an onAppear modifier that calls handleDocumentIdChange(documentId) when documentId is non-empty on view mount, so pre-seeded IDs load pricing/ownership without waiting for a user edit.
availableActions purchase eligibility refactor
packages/swift-sdk/SwiftExampleApp/.../DocumentsView.swift
Extracts a local tradeable boolean from docType.tradeMode, gates setPrice on it, and moves purchase out of the else if !ownerIsControlled branch into a standalone condition, allowing purchase to appear even when ownerIsControlled is true.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • dashpay/platform#3945: Introduces the original replace/delete/transfer/setPrice/purchase action menu and its owner-controlled gating in DocumentsView, which this PR directly extends.

Suggested reviewers

  • shumkov
  • llbartekll
  • ZocoLini

Poem

🐇 A document appeared, already filled with care,
No onChange to wait for — onAppear was there!
The tradeable flag hops neatly into place,
purchase now shows up with a wider embrace.
Buy or sell, the bunny says — let logic lead the race! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(swift-example-app): load document price on Purchase sheet appear' accurately describes the main fix: enabling document price loading when the Purchase sheet appears via the onAppear lifecycle handler.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sad-gagarin-ad1835

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 and usage tips.

@thepastaclaw

thepastaclaw commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

✅ Review complete (commit ed85f55)

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 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
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift`:
- Around line 278-299: The tradeable variable definition uses the comparison
(docType?.tradeMode ?? 0) > 0, but other marketplace gating logic throughout the
codebase defines tradeable as tradeMode == 1. Change the comparison operator
from > 0 to == 1 in the tradeable variable assignment to align with the existing
contract semantics and prevent setPrice and purchase actions from being exposed
for unintended trade modes.
🪄 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: 24d6053b-7625-4206-891c-87ab89bef166

📥 Commits

Reviewing files that changed from the base of the PR and between 40c1126 and da9e68e.

📒 Files selected for processing (2)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentWithPriceView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift

Comment thread packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift Outdated
Align the Purchase/Set Price gating with the DirectPurchase mode
(`tradeMode == 1`) used elsewhere for marketplace operations
(TransitionInputView), instead of the looser `> 0`, so the actions are
not exposed for any future non-DirectPurchase trade mode. Functionally
identical today (only modes 0/1 exist).

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

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Narrow SwiftExampleApp fix to load price on Purchase sheet appear and to surface Purchase for two-identities-in-one-wallet flow. One in-scope consistency suggestion: the new shared tradeable predicate uses > 0 while the rest of the marketplace UI uses == 1. One optional hardening suggestion on the onAppear fetch path.

🟡 1 suggestion(s) | 💬 1 nitpick(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift`:
- [SUGGESTION] packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift:278: Align `tradeable` predicate with the rest of the marketplace UI (`== 1`)
  This PR introduces `let tradeable = (docType?.tradeMode ?? 0) > 0` as the shared predicate gating both Set Price and Purchase. The existing marketplace entry points in `TransitionInputView.swift` (lines 273/276 and 409/410) restrict tradeable types to `tradeMode == 1` (commented as "tradeable document types (tradeMode = 1)"). DPP currently treats `0` as not tradeable and `1` as direct purchase; if persisted state ever carries another positive value (protocol skew or a future non-direct-purchase mode), this menu will offer Set Price / Purchase that route to the direct-purchase broadcast path while the rest of the app hides them. Match the existing predicate so the production menu and the broadcast flow stay aligned.

Comment thread packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DocumentsView.swift Outdated
Comment on lines +120 to +134
.onAppear {
// `onChange(of: documentId)` does NOT fire when the id is
// established as the binding mounts — e.g. PurchaseDocumentView
// seeds `documentIdField` in its own `onAppear` and disables this
// view, so relying on onChange alone the price probe would never
// run and Purchase would stay gated off forever. Kick the fetch
// here so a pre-seeded id loads its price without a user edit.
// `.disabled(true)` only blocks hit-testing, not lifecycle events,
// so this still fires in the Purchase flow. When the field starts
// empty (the editable transition flow) this is a no-op and the
// existing onChange path continues to handle typing.
if !documentId.isEmpty {
handleDocumentIdChange(documentId)
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

💬 Nitpick: Guard onAppear fetch to avoid re-probing an already-loaded document

onAppear unconditionally calls handleDocumentIdChange(documentId), which clears documentExists, documentPrice, fetchedDocument, transitionState.canPurchaseDocument, and transitionState.documentPrice before re-arming a 500ms debounce. If the host sheet re-appears (sheet detent change, return from a nested presentation, parent remount), the Purchase button will briefly disable and the price will flash to nil. Guarding with if !documentId.isEmpty && fetchedDocument == nil && !isLoading preserves the pre-seeded fetch fix while avoiding redundant re-probes and transient state stomps.

source: ['claude']

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Declining the fetchedDocument == nil guard: it would regress the submit button. PurchaseDocumentView gates Purchase / Broadcast on the app-wide transitionState.documentPrice, which its own .onAppear clears to nil — the unconditional re-probe here is precisely what re-publishes that value (via fetchDocument's MainActor.run). With the guard, a sheet re-appear would skip the re-probe and leave transitionState.documentPrice stuck at nil → button permanently disabled, the exact bug this PR fixes. The transient flash is the acceptable cost of keeping the gating correct without a larger refactor. The clean way to also kill the flash is to gate submit on local @State instead of the shared transitionState (the approach in the dropped 588435aff4) — left as a follow-up to keep this PR's diff minimal.

🤖 Addressed by Claude Code

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Code Review

Narrow SwiftExampleApp UI fix. The only delta since the prior reviewed commit (da9e68e) is a clarifying comment above the tradeable predicate explaining that tradeMode == 1 (DirectPurchase) matches TransitionInputView's marketplace gating. Both reviewer agents found no in-scope issues, and verification of the file confirms the change is correct, idempotent, and scoped to UI behavior — no consensus, SDK, or FFI surface is touched.

@thepastaclaw

Copy link
Copy Markdown
Collaborator

Correction to my automated review at ed85f556 (review 4540094727): the approval outcome is unchanged, but the review body should have explicitly reconciled the prior findings.

Latest delta (da9e68ee..ed85f556)

  • DocumentsView.swift now uses tradeMode == 1 and documents that this is DirectPurchase, matching TransitionInputView marketplace gating.
  • New findings in the latest delta: none.

Prior finding reconciliation

  • FIXED: DocumentsView.availableActions now aligns the shared tradeable predicate with the rest of the marketplace UI (== 1).
  • STILL VALID (nitpick, carried forward): DocumentWithPriceView.onAppear still calls handleDocumentIdChange(documentId) whenever the ID is non-empty, without checking whether the same document is already loaded. A sheet re-appear can still briefly clear price/existence state and debounce a redundant fetch. This remains a small UI-polish nit, not a blocker; I’m not reposting a duplicate inline comment because the existing prior thread is the canonical place for it.

I also removed an incorrect auto-resolved reply on that still-valid onAppear nitpick thread. CodeRabbit had 0 inline findings and was rate-limited for this SHA, so coverage was degraded.

@QuantumExplorer QuantumExplorer merged commit 9a93387 into v3.1-dev Jun 21, 2026
3 checks passed
@QuantumExplorer QuantumExplorer deleted the claude/sad-gagarin-ad1835 branch June 21, 2026 22:11
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