Skip to content

fix: Enhance error handling for connection issues in TapSigner flow NFC interactions#689

Open
kartikangiras wants to merge 1 commit intobitcoinppl:masterfrom
kartikangiras:errorhandling
Open

fix: Enhance error handling for connection issues in TapSigner flow NFC interactions#689
kartikangiras wants to merge 1 commit intobitcoinppl:masterfrom
kartikangiras:errorhandling

Conversation

@kartikangiras
Copy link
Copy Markdown
Contributor

@kartikangiras kartikangiras commented Apr 19, 2026

Summary

Replace string-based connection error checking with type-safe error handling across Rust, iOS, and Android platforms.

fixes #439

Testing

  • cargo check
  • cargo test --no-run
  • just ci
  • just fmt

Platform Coverage

  • Tested on iOS device
  • Tested on Android device
  • Tested on iOS simulator
  • Tested on Android simulator
  • Not tested

Checklist

Summary by CodeRabbit

  • Bug Fixes
    • NFC connection failures now show a dedicated "Connection Lost" alert and are distinguished from authentication failures, improving error clarity across platforms.
  • New Features
    • Added user guidance instructing to "hold your phone still" when a tag connection is lost during NFC actions (PIN entry, backup, signing).

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 19, 2026

Greptile Summary

This PR replaces string-based connection error detection with type-safe FFI methods (isConnectionError(), isAuthError()) across Rust, iOS, and Android, adding ConnectionError/UnknownError variants to TransportError and propagating them through UniFFI to the platform layers.

  • P1 – iOS compile error: TapSignerNFC.swift line 400 sets tapSignerError = .ConnectionError(...) but ConnectionError is not a direct case of TapSignerReaderError in Rust; the wrapped path .tapSignerError(.connectionError(...)) must be used instead. iOS was not device-tested per the checklist.
  • P1 – Overflow-guard logic bug: TapSignerConfirmPinView.swift line 186 clamps to args.startingPin.prefix(6) (the old/factory PIN) instead of pin.prefix(6) when a user types more than six digits.

Confidence Score: 3/5

Not safe to merge: the iOS layer has a likely compile error and a PIN-scrambling overflow bug that must be resolved first.

Two P1 findings exist: (1) a type mismatch in TapSignerNFC.swift that will prevent iOS compilation, and (2) a logic bug in the confirm-PIN overflow guard that silently replaces the user's input with the old PIN. iOS was not tested on device or simulator per the checklist.

ios/Cove/TapSignerNFC.swift (compile error on line 400) and ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift (overflow guard bug on line 186).

Important Files Changed

Filename Overview
rust/src/tap_card.rs Adds ConnectionError(String) and UnknownError(String) variants to TransportError with correct FFI conversions; logic is sound and coverage is complete.
rust/src/tap_card/tap_signer_reader.rs Adds is_connection_error() helper and its FFI export isConnectionError; pattern-matches correctly against TransportError::ConnectionError(_) via the wrapped TapSignerError variant.
ios/Cove/TapSignerNFC.swift P1 compile error: sets tapSignerError = .ConnectionError(...) but ConnectionError is not a direct case of TapSignerReaderError; also contains redundant session.invalidate() calls inside the already-invalidated delegate callback.
ios/Cove/Flows/TapSignerFlow/TapSignerEnterPinView.swift Correctly uses error.isConnectionError() in derive, backup, and sign action handlers to show a user-friendly "Connection Lost" alert.
ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift Adds connection-error handling in changeTapSignerPin(); contains a P1 logic bug where the PIN overflow guard clamps to args.startingPin.prefix(6) instead of pin.prefix(6).
android/app/src/main/java/org/bitcoinppl/cove/nfc/TapCardNfcManager.kt Catches all APDU exceptions as ConnectionException; type-safe but overly broad compared to iOS's specific TagConnectionLost check.
android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt Replaces string-based error checks with type-safe isAuthError() and isConnectionError() FFI calls; error routing for all actions is correct.

Sequence Diagram

sequenceDiagram
    participant UI as iOS/Android UI
    participant NFC as TapCardNFC / TapCardNfcManager
    participant Transport as TapCardTransport
    participant Rust as Rust (UniFFI)

    UI->>NFC: scan NFC tag
    NFC->>Transport: transmitApdu(commandApdu)
    alt Tag connection lost
        Transport-->>Rust: throw TransportError.ConnectionError(msg)
    else Other APDU error
        Transport-->>Rust: throw TransportError.UnknownError(msg)
    end
    Rust-->>NFC: TapSignerReaderError.tapSignerError(.connectionError)
    NFC->>NFC: error.isConnectionError() → true
    NFC-->>UI: show "Connection Lost" alert
Loading

Comments Outside Diff (3)

  1. ios/Cove/TapSignerNFC.swift, line 400-404 (link)

    P1 ConnectionError is not a case of TapSignerReaderError

    tapSignerError is typed as TapSignerReaderError?, but ConnectionError is a variant of TransportError, not TapSignerReaderError. The Rust enum TapSignerReaderError wraps transport errors under the TapSignerError(TransportError) variant. Setting .ConnectionError(...) here will fail to compile on iOS (the PR notes iOS was not device-tested). The correct wrapper based on the Rust definition and UniFFI conventions should use the nested type:

  2. ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift, line 185-187 (link)

    P1 Wrong variable used in PIN overflow guard

    The overflow guard clamps to args.startingPin.prefix(6) — the old/factory PIN — instead of pin.prefix(6). When pin.count > 6 and old.count >= 6 (e.g. rapid paste input), confirmPin is silently overwritten with the first six characters of the previous PIN rather than the user's current input, producing incorrect confirmation behavior.

  3. ios/Cove/TapSignerNFC.swift, line 393-408 (link)

    P2 session.invalidate() inside tagReaderSession(_:didInvalidateWithError:) is a no-op

    tagReaderSession(_:didInvalidateWithError:) is the delegate callback iOS fires after the session is already invalidated. Per Apple's CoreNFC documentation, calling session.invalidate(errorMessage:) on an already-invalidated session is effectively a no-op (and in older OS versions could raise an internal exception). These invalidate calls can be safely removed from the body of this callback — the error message passed here will not be shown to the user.

Reviews (1): Last reviewed commit: "Enhance error handling for connection is..." | Re-trigger Greptile

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 20, 2026

📝 Walkthrough

Walkthrough

Type-safe NFC connection-error handling was added across Rust, UniFFI, Android, and iOS. A new ConnectionError transport variant and isConnectionError predicate were introduced in Rust and exported; Android/iOS transports now emit that variant for tag-connection failures and UI flows detect it to show a dedicated "Tag connection lost, please hold your phone still" alert.

Changes

Cohort / File(s) Summary
Rust Transport & FFI
rust/src/tap_card.rs, rust/src/tap_card/tap_signer_reader.rs
Added TransportError::ConnectionError(String) variant; added TapSignerReaderError::is_connection_error() and exported isConnectionError() via UniFFI to classify connection errors.
Android FFI Bindings
android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt
Added TransportException.ConnectionException and updated FfiConverterTypeTransportError to map discriminant 7ConnectionException and shift UnknownException to 8.
Android NFC & PIN flows
android/app/src/main/java/org/bitcoinppl/cove/nfc/TapCardNfcManager.kt, android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt
Map APDU/transceive failures to ConnectionException; update PIN-action catch blocks to detect isConnectionError(e) and emit "Tag connection lost..." message.
iOS Transport & PIN flows
ios/Cove/TapSignerNFC.swift, ios/Cove/Flows/TapSignerFlow/TapSignerEnterPinView.swift, ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift
Map NFC transceive .readerTransceiveErrorTagConnectionLost to TransportError.ConnectionError; use error.isConnectionError() in PIN entry/confirmation to present "Connection Lost" alert with the dedicated message.
Rust Apdu Error Mapping
rust/src/tap_card.rs (Apdu conversion)
Updated From<TransportError> for ApduError to map ConnectionError(_) into an appropriate ApduError::CkTap variant (was previously catch-all).
iOS concurrency/front-end modifiers
ios/Cove/AppManager.swift, ios/Cove/ImportWalletManager.swift, ios/Cove/Security.swift, ios/Cove/WalletManager.swift, ios/Cove/ShareSheet.swift
Various class declaration changes: several classes annotated @MainActor and/or final; ShareableFile made private final. No functional logic changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Mobile UI
    participant Transport as Platform Transport
    participant Rust as Rust FFI
    participant NFC as System NFC

    User->>UI: Initiate PIN operation (derive/backup/sign)
    UI->>Transport: send APDU/transceive
    Transport->>NFC: transmit command
    NFC-->>Transport: transceive fails (tag moved / connection lost)
    Transport->>Rust: convert/error -> TransportError
    Rust-->>Transport: TransportError::ConnectionError
    Transport->>Rust: call isConnectionError() (FFI)
    Rust-->>Transport: true
    Transport->>UI: surface ConnectionException / error
    UI->>UI: detect connection error, show "Tag connection lost, please hold your phone still"
    UI-->>User: Connection Lost alert
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • praveenperera

"A rabbit nibble, quick and spry,
Found connection errors waving bye.
From Rust to phone, a clearer cue,
'Hold your phone still' — now shown to you.
Hoppity hop, the checks are new!" 🐇

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Several changes unrelated to connection error handling have been introduced: @MainActor annotations added to AppManager, ImportWalletManager, KeychainAccessor, WalletManager classes and their methods, which are concurrency/actor isolation changes beyond the issue scope. Remove the @MainActor annotations from AppManager, ImportWalletManager, KeychainAccessor, and WalletManager that are unrelated to connection error handling and should be addressed in separate PRs.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: enhancing error handling for NFC connection issues in TapSigner flow with a specific, clear description.
Description check ✅ Passed The PR description follows the template with summary, testing commands, and checklist items completed. Platform testing boxes appropriately remain unchecked.
Linked Issues check ✅ Passed The PR implementation comprehensively addresses all requirements from issue #439: adds ConnectionError variant, implements is_connection_error() helper, exports via UniFFI, and updates iOS/Android transport layers accordingly.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@praveenperera
Copy link
Copy Markdown
Contributor

doesnt compile

@kartikangiras kartikangiras force-pushed the errorhandling branch 2 times, most recently from 1c6455b to 5550361 Compare April 22, 2026 00:26
@Vineet1101
Copy link
Copy Markdown

Hey @kartikangiras your ci tests are failing coz you haven't pushed the latest bindings. You can generate new bindings and push them to get ci passing

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
ios/Cove/TapSignerNFC.swift (1)

398-407: ⚠️ Potential issue | 🔴 Critical

Compile error: TapSignerReaderError has no direct .ConnectionError case.

tapSignerError is typed TapSignerReaderError?. The TapSignerReaderError enum cases are .TapSignerError(TransportError), .PsbtSignError, .ExtractTxError, .UnknownCardType, .NoCommand, .InvalidPinLength, .NonNumericPin, .SetupAlreadyComplete, .InvalidChainCodeLength, and .Unknown — there is no top-level .ConnectionError. The ConnectionError variant lives on TransportError (line 29349), so line 400 must wrap it:

Proposed fix
             case .readerTransceiveErrorTagConnectionLost:
-                tapSignerError = .ConnectionError("Tag connection lost, please hold your phone still")
+                tapSignerError = .TapSignerError(
+                    .ConnectionError("Tag connection lost, please hold your phone still")
+                )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/TapSignerNFC.swift` around lines 398 - 407, The code assigns
tapSignerError using a non-existent top-level case `.ConnectionError`; change
assignments to wrap the transport-level error inside the TapSignerReaderError by
constructing a TransportError.ConnectionError and passing it into
TapSignerReaderError.TapSignerError — e.g. set tapSignerError =
.TapSignerError(.ConnectionError("Tag connection lost, please hold your phone
still")) for the readerTransceiveErrorTagConnectionLost branch (and likewise for
the default branch use .TapSignerError(.ConnectionError("Unable to read NFC tag,
try again"))), leaving the session.invalidate(...) calls as-is.
rust/src/tap_card.rs (1)

105-118: ⚠️ Potential issue | 🔴 Critical

TransportError::ConnectionError is silently lost when round-tripped through ApduError.

When TapcardTransportProtocol::transmit_apdu returns TransportError::ConnectionError(_), the CkTransport::transmit_apdu impl on Line 86-89 propagates it via ?, which goes through the From<TransportError> for ApduError conversion at Line 113-114 → ApduError::CkTap(CkTapError::BadArguments). Higher-level reader code (e.g. backup, change, derive in tap_signer_reader.rs) then does .map_err(TransportError::from), which converts back via the From<ApduError> for TransportError impl at Line 96 → TransportError::CkTap(CkTapError::BadArguments).

Net effect: by the time the iOS/Android UI calls error.is_connection_error(), the variant is no longer ConnectionError — it has become CkTap(BadArguments), and is_connection_error() returns false. The PR's whole purpose is defeated for any error that originates inside the FFI transport callback.

Either add a corresponding variant (e.g. ConnectionLost) to rust_cktap::apdu::Error upstream and round-trip through it, or bypass the ApduError boundary for transport-callback errors by preserving the original TransportError via context passing or short-circuiting in CkTransport::transmit_apdu.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/src/tap_card.rs` around lines 105 - 118, The From<TransportError> for
ApduError impl is collapsing TransportError::ConnectionError into
CkTap(BadArguments), losing the original connection error when
CkTransport::transmit_apdu uses `?`; update the conversion so connection-level
transport errors round-trip intact: either add a new ApduError variant (e.g.
ConnectionLost or TransportConnectionError) in rust_cktap::apdu::Error and map
TransportError::ConnectionError(_) -> ApduError::ConnectionLost (and adjust the
reverse From<ApduError> -> TransportError to recreate
TransportError::ConnectionError), or short-circuit in CkTransport::transmit_apdu
to propagate TransportError directly for transport-callback errors (bypass the
ApduError boundary) so callers like backup/change/derive preserve
is_connection_error() semantics. Ensure you update the matching arms in
From<TransportError> for ApduError and the corresponding From<ApduError> for
TransportError to maintain a true round-trip for connection errors.
🧹 Nitpick comments (2)
android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt (1)

255-260: Optional: extract the duplicated connection-error branch.

The same three lines (Log.w + errorMessage = "Tag connection lost, please hold your phone still") are repeated in deriveAction, backupAction, and signAction. Consider a small helper that takes the caught Exception and returns the resolved error message (mirroring the existing isAuthError/isConnectionError pattern) to keep the three failure handlers in lockstep going forward.

Also note: the actual triggering of this branch depends on the Rust TransportError::ConnectionError variant surviving the ApduError round-trip — see the issue raised on rust/src/tap_card.rs.

Also applies to: 323-328, 383-388

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt`
around lines 255 - 260, The three catch branches in deriveAction, backupAction,
and signAction duplicate the same connection-error handling
(Log.w("TapSignerEnterPin", "TapSigner connection lost") and
manager.errorMessage = "Tag connection lost, please hold your phone still");
extract this into a small helper function (e.g., resolveTapSignerError(Exception
e): String or handleTapSignerError(Exception e, Manager manager)) that uses the
existing isAuthError/isConnectionError predicates and returns or applies the
appropriate error message (and performs the Log.w for connection errors); then
replace the duplicated blocks in deriveAction, backupAction, and signAction with
calls to that helper so all three handlers stay in sync.
ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift (1)

84-84: Optional: extract the duplicated "Connection Lost" alert.

The same (title: "Connection Lost", message: "Tag connection lost, please hold your phone still") literal appears here and three more times in TapSignerEnterPinView.swift (derive/backup/sign branches). Consider a dedicated AppAlertState case (e.g. .tapSignerConnectionLost) or a shared constant so future copy/format changes are made in one place — same pattern already used by .tapSignerInvalidAuth and .tapSignerNoBackup just above.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift` at line 84,
Extract the duplicated alert literal into a single shared AppAlertState case
(e.g. .tapSignerConnectionLost) or a shared constant and replace the repeated
inline tuple in the error.isConnectionError() branches with that new
case/constant; specifically update the branches inside TapSignerConfirmPinView
(where error.isConnectionError() is used) and the three occurrences in
TapSignerEnterPinView (derive/backup/sign paths) to return/apply
.tapSignerConnectionLost instead of duplicating (title: "Connection Lost",
message: "Tag connection lost, please hold your phone still").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@android/app/src/main/java/org/bitcoinppl/cove/nfc/TapCardNfcManager.kt`:
- Around line 284-287: The catch-all in TapCardNfcManager that currently does
"catch (e: Exception)" and maps every error to
TransportException.ConnectionException should be narrowed: handle NFC
connection-specific failures (e.g., catch android.nfc.TagLostException and/or
IOException that indicates tag lost from the transceive call) and throw
TransportException.ConnectionException for those, while for all other exceptions
(SecurityException, IllegalStateException, malformed APDU bugs, etc.) log the
original error message and rethrow a TransportException.UnknownException
preserving e as the cause; update the Log.e call to include e.message and the
exception (e) for better debugging. Ensure these changes are implemented in the
catch block around the transceive call in TapCardNfcManager so only genuine
tag-connection errors map to ConnectionException and all other exceptions
propagate as UnknownException with the original exception attached.

---

Outside diff comments:
In `@ios/Cove/TapSignerNFC.swift`:
- Around line 398-407: The code assigns tapSignerError using a non-existent
top-level case `.ConnectionError`; change assignments to wrap the
transport-level error inside the TapSignerReaderError by constructing a
TransportError.ConnectionError and passing it into
TapSignerReaderError.TapSignerError — e.g. set tapSignerError =
.TapSignerError(.ConnectionError("Tag connection lost, please hold your phone
still")) for the readerTransceiveErrorTagConnectionLost branch (and likewise for
the default branch use .TapSignerError(.ConnectionError("Unable to read NFC tag,
try again"))), leaving the session.invalidate(...) calls as-is.

In `@rust/src/tap_card.rs`:
- Around line 105-118: The From<TransportError> for ApduError impl is collapsing
TransportError::ConnectionError into CkTap(BadArguments), losing the original
connection error when CkTransport::transmit_apdu uses `?`; update the conversion
so connection-level transport errors round-trip intact: either add a new
ApduError variant (e.g. ConnectionLost or TransportConnectionError) in
rust_cktap::apdu::Error and map TransportError::ConnectionError(_) ->
ApduError::ConnectionLost (and adjust the reverse From<ApduError> ->
TransportError to recreate TransportError::ConnectionError), or short-circuit in
CkTransport::transmit_apdu to propagate TransportError directly for
transport-callback errors (bypass the ApduError boundary) so callers like
backup/change/derive preserve is_connection_error() semantics. Ensure you update
the matching arms in From<TransportError> for ApduError and the corresponding
From<ApduError> for TransportError to maintain a true round-trip for connection
errors.

---

Nitpick comments:
In
`@android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt`:
- Around line 255-260: The three catch branches in deriveAction, backupAction,
and signAction duplicate the same connection-error handling
(Log.w("TapSignerEnterPin", "TapSigner connection lost") and
manager.errorMessage = "Tag connection lost, please hold your phone still");
extract this into a small helper function (e.g., resolveTapSignerError(Exception
e): String or handleTapSignerError(Exception e, Manager manager)) that uses the
existing isAuthError/isConnectionError predicates and returns or applies the
appropriate error message (and performs the Log.w for connection errors); then
replace the duplicated blocks in deriveAction, backupAction, and signAction with
calls to that helper so all three handlers stay in sync.

In `@ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift`:
- Line 84: Extract the duplicated alert literal into a single shared
AppAlertState case (e.g. .tapSignerConnectionLost) or a shared constant and
replace the repeated inline tuple in the error.isConnectionError() branches with
that new case/constant; specifically update the branches inside
TapSignerConfirmPinView (where error.isConnectionError() is used) and the three
occurrences in TapSignerEnterPinView (derive/backup/sign paths) to return/apply
.tapSignerConnectionLost instead of duplicating (title: "Connection Lost",
message: "Tag connection lost, please hold your phone still").
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 597c8a24-4e3b-455a-8f12-5f0db0ddac14

📥 Commits

Reviewing files that changed from the base of the PR and between 6de3b13 and c3ee0e1.

⛔ Files ignored due to path filters (1)
  • ios/CoveCore/Sources/CoveCore/generated/cove.swift is excluded by !**/generated/**
📒 Files selected for processing (8)
  • android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt
  • android/app/src/main/java/org/bitcoinppl/cove/nfc/TapCardNfcManager.kt
  • android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt
  • ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift
  • ios/Cove/Flows/TapSignerFlow/TapSignerEnterPinView.swift
  • ios/Cove/TapSignerNFC.swift
  • rust/src/tap_card.rs
  • rust/src/tap_card/tap_signer_reader.rs

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
ios/Cove/Flows/TapSignerFlow/TapSignerEnterPinView.swift (1)

131-142: ⚠️ Potential issue | 🟡 Minor

Minor inconsistency in signAction: app.sheetState is not cleared on connection-lost.

In the existing generic failure branch (Line 141) app.sheetState = .none is set after presenting the alert, but the new isConnectionError() branch (Lines 135-136) does not do the same. If the intent is to dismiss the NFC sheet on a fatal sign failure, the connection branch should mirror it; if the intent is to keep the sheet open for retry on connection loss, then this is fine — but the asymmetry is worth a deliberate decision.

Also (across all three actions in this file), the same .general(title: "Connection Lost", message: …) literal is duplicated and re-duplicated again in TapSignerConfirmPinView.swift. Consider centralizing as e.g. AppAlertState.tapSignerConnectionLost, mirroring .tapSignerInvalidAuth / .tapSignerNoBackup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/Flows/TapSignerFlow/TapSignerEnterPinView.swift` around lines 131 -
142, The connection-error branch in the sign failure handler in
TapSignerEnterPinView.swift is inconsistent: add app.sheetState = .none inside
the else-if error.isConnectionError() branch (the same place where the generic
failure sets sheetState = .none) so the NFC sheet is dismissed on fatal sign
failures; then remove the duplicated literal by introducing a reusable alert
case (e.g. AppAlertState.tapSignerConnectionLost) and replace the hard-coded
.general(title: "Connection Lost", message: ...) occurrences in
TapSignerEnterPinView and TapSignerConfirmPinView with that new AppAlertState
constant to centralize the message.
rust/src/tap_card.rs (1)

105-118: ⚠️ Potential issue | 🔴 Critical

Critical: TransportError::ConnectionError is lost in the round-trip conversion — is_connection_error() will never detect connection errors from the transport callback.

When the Swift callback returns TransportError::ConnectionError, the error flow through CkTransport::transmit_apdu (line 87) invokes the lossy conversion at line 114, mapping it to ApduError::CkTap(BadArguments). When this error is later converted back to TransportError via the From<ApduError> implementation (lines 93–103), ApduError::CkTap becomes TransportError::CkTap, not ConnectionError. The original variant is discarded.

Since is_connection_error() (line 544 in tap_signer_reader.rs) matches specifically on TransportError::ConnectionError(_), it will always return false for any connection error that originates from the platform callback, defeating the purpose of detecting network disconnection.

To fix:

  • Add a ConnectionError variant to the internal ApduError type (or equivalent bridge type) so the round-trip preserves the variant, OR
  • Handle TransportError::ConnectionError at the callback boundary in TapcardTransport::transmit_apdu before it's converted to ApduError, and preserve it via a separate channel (e.g., reader state or thread-local), OR
  • Remove the ConnectionError variant from TransportError and the is_connection_error() check if platform-side connection handling is preferred.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/src/tap_card.rs` around lines 105 - 118, The
TransportError::ConnectionError variant is being lost when converted into
ApduError in the From<TransportError> for ApduError impl (currently mapping
ConnectionError to ApduError::CkTap(BadArguments)), so is_connection_error() can
never detect platform connection failures; fix by adding a ConnectionError
variant to the internal ApduError enum (or equivalent bridge type) and update
the From<TransportError> for ApduError conversion to map
TransportError::ConnectionError(_) -> ApduError::ConnectionError(...), and
ensure the reverse conversion (From<ApduError> for TransportError or the path in
TapcardTransport::transmit_apdu) maps ApduError::ConnectionError back to
TransportError::ConnectionError so the round-trip preserves the error; update
any matching sites like is_connection_error() to match the new
ApduError::ConnectionError where appropriate.
🧹 Nitpick comments (4)
android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt (1)

252-260: Nit: identical 5-line error-classification blocks repeated three times.

Lines 252-260, 320-328, and 380-388 are essentially the same if isAuthError / else if isConnectionError / else chain with only the action-context differing. Consider extracting a small helper such as:

private fun classifyTapSignerError(e: Exception): String = when {
    isAuthError(e)       -> "Wrong PIN, please try again"
    isConnectionError(e) -> "Tag connection lost, please hold your phone still"
    else                 -> "Connection failed, please try again"
}

and using it in each catch (combined with the existing Log.w calls). Keeps wording consistent and makes future error categories one-line additions.

Note: with the current broad catch (e: Exception) → ConnectionException mapping in TapCardNfcManager.transmitApdu, isConnectionError(e) will return true for almost every non-auth failure, so the “Connection failed, please try again” else-branch is effectively unreachable until that mapping is narrowed (see comment on TapCardNfcManager.kt).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt`
around lines 252 - 260, Extract the repeated error-classification logic in
TapSignerEnterPinView into a small helper (e.g., private fun
classifyTapSignerError(e: Exception): String) that returns the appropriate
message based on isAuthError(e) / isConnectionError(e) / else, then replace each
catch-site’s manager.errorMessage assignment with manager.errorMessage =
classifyTapSignerError(e) while keeping the existing Log.w calls (so logs remain
context-specific); reference isAuthError, isConnectionError,
manager.errorMessage, and the TapSignerEnterPinView catch blocks when making the
change.
ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift (1)

84-84: Consider extracting the duplicated "Connection Lost" alert into a helper / AppAlertState case.

The exact same (title: "Connection Lost", message: "Tag connection lost, please hold your phone still") literal is repeated here and three times in TapSignerEnterPinView.swift. Centralizing as e.g. AppAlertState.tapSignerConnectionLost (mirroring .tapSignerInvalidAuth / .tapSignerNoBackup) keeps wording consistent, simplifies future localization, and avoids drift if the message is updated.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift` at line 84,
Extract the repeated alert literal into a single AppAlertState case (e.g.
AppAlertState.tapSignerConnectionLost) and replace the inline initializers in
TapSignerConfirmPinView (the if branch using error.isConnectionError()) and the
three occurrences in TapSignerEnterPinView.swift with that new case; make sure
the new AppAlertState case mirrors the pattern used by
.tapSignerInvalidAuth/.tapSignerNoBackup so existing alert consumers (alertState
= .init(...)) continue to work and localization strings are centralized.
rust/src/tap_card.rs (1)

28-29: Stylistic: prefer a more descriptive payload than a free-form String.

ConnectionError(String) mirrors UnknownError(String) but, like CkTap(CkTapError), this is a closed concept — most call sites only ever pass the same hard-coded "Tag connection lost, please hold your phone still". Consider either a unit variant (ConnectionError) or an enum payload describing concrete sub-cases (TagLost, Timeout, Io(String)) so the message can live entirely in UI/localization, not in the transport error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/src/tap_card.rs` around lines 28 - 29, The ConnectionError(String) enum
variant should use a more structured payload: either convert it to a unit
variant ConnectionError or replace its String payload with a small enum (e.g.,
ConnectionIssue with variants TagLost, Timeout, Io(String)) so callers can pass
a discrete case instead of free-form text; update the Display/#[error(...)]
formatting for the enum (and any match arms creating ConnectionError) to
construct the new variant and adjust places that currently pass the hard-coded
"Tag connection lost, please hold your phone still" message to use
ConnectionError::TagLost (or the unit ConnectionError) and leave the
human-readable message to the UI/localization layer; references: the enum that
contains ConnectionError, the existing UnknownError(String) and
CkTap(CkTapError) variants to mirror their patterns.
ios/Cove/AppManager.swift (1)

41-42: Drop redundant per‑member @MainActor annotations.

Once the class is annotated @MainActor (line 7), all stored properties and instance methods inherit that isolation. The explicit @MainActor on isLoading (41), getWalletManager (98), getSendFlowManager (113), resetRoute(to:) (220) and loadAndReset(to:) (230) are now no‑ops and just add noise.

Proposed cleanup
-    `@MainActor`
     var isLoading = false
-    `@MainActor`
     public func getWalletManager(id: WalletId) throws -> WalletManager {
-    `@MainActor`
     public func getSendFlowManager(_ wm: WalletManager, presenter: SendFlowPresenter) -> SendFlowManager {
-    `@MainActor`
     func resetRoute(to routes: [Route]) {
-    `@MainActor`
     func loadAndReset(to route: Route) {

Also applies to: 98-99, 113-114, 220-221, 230-231

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/AppManager.swift` around lines 41 - 42, The class is already
annotated `@MainActor`, so remove the redundant per-member `@MainActor` annotations
on the stored property and methods to reduce noise: delete the `@MainActor` before
the isLoading property and the `@MainActor` annotations on the methods
getWalletManager, getSendFlowManager, resetRoute(to:), and loadAndReset(to:),
leaving the class-level `@MainActor` intact so all members remain main-isolated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ios/Cove/AppManager.swift`:
- Line 8: Remove the trailing whitespace after the attribute declaration so
`@Observable ` becomes `@Observable`; edit the `AppManager.swift` file to delete
the extra space following the `@Observable` attribute (ensure there are no other
trailing spaces on that line) and save/run swiftformat to confirm the warning is
cleared.
- Around line 7-9: AppManager is annotated `@MainActor` which makes its methods
actor-isolated, but FfiReconcile's callbacks (reconcile(message:) and
dispatch(action:)) are invoked from non-Swift/FFI contexts; mark both methods as
nonisolated so they can be called from any thread and keep the existing explicit
MainActor dispatch inside their bodies (e.g., continue using
DispatchQueue.main.async or Task { `@MainActor` ... } within reconcile(message:)
and dispatch(action:) to run UI/main-thread work). Locate the implementations of
reconcile(message:) and dispatch(action:) in AppManager and add the nonisolated
modifier to each method signature while preserving the current main-thread
dispatch logic.

In `@ios/Cove/ImportWalletManager.swift`:
- Line 4: Remove the trailing whitespace after the `@Observable` attribute in
ImportWalletManager.swift so that the annotation is exactly "@Observable" with
no extra spaces; update the declaration that uses the `@Observable` attribute (the
annotation line in the ImportWalletManager class/file) to eliminate the trailing
space so swiftformat no longer flags it.

In `@ios/Cove/WalletManager.swift`:
- Line 6: Remove the trailing whitespace after the `@Observable` attribute in
WalletManager.swift: edit the line containing "@Observable " to be "@Observable"
(no trailing space), save and re-run swiftformat/lint to confirm the warning is
cleared; ensure no other lines in the file have trailing spaces.
- Around line 5-7: WalletManager is `@MainActor` but must implement nonisolated
reconcile and reconcileMany from AnyReconciler/WalletManagerReconciler; mark the
instance methods reconcile(...) and reconcileMany(...) as nonisolated, remove
the redundant DispatchQueue.main.async wrappers inside those methods (since
they'll be called from background threads), and ensure the `@Observable`
annotation has no trailing whitespace on the class declaration line.

---

Outside diff comments:
In `@ios/Cove/Flows/TapSignerFlow/TapSignerEnterPinView.swift`:
- Around line 131-142: The connection-error branch in the sign failure handler
in TapSignerEnterPinView.swift is inconsistent: add app.sheetState = .none
inside the else-if error.isConnectionError() branch (the same place where the
generic failure sets sheetState = .none) so the NFC sheet is dismissed on fatal
sign failures; then remove the duplicated literal by introducing a reusable
alert case (e.g. AppAlertState.tapSignerConnectionLost) and replace the
hard-coded .general(title: "Connection Lost", message: ...) occurrences in
TapSignerEnterPinView and TapSignerConfirmPinView with that new AppAlertState
constant to centralize the message.

In `@rust/src/tap_card.rs`:
- Around line 105-118: The TransportError::ConnectionError variant is being lost
when converted into ApduError in the From<TransportError> for ApduError impl
(currently mapping ConnectionError to ApduError::CkTap(BadArguments)), so
is_connection_error() can never detect platform connection failures; fix by
adding a ConnectionError variant to the internal ApduError enum (or equivalent
bridge type) and update the From<TransportError> for ApduError conversion to map
TransportError::ConnectionError(_) -> ApduError::ConnectionError(...), and
ensure the reverse conversion (From<ApduError> for TransportError or the path in
TapcardTransport::transmit_apdu) maps ApduError::ConnectionError back to
TransportError::ConnectionError so the round-trip preserves the error; update
any matching sites like is_connection_error() to match the new
ApduError::ConnectionError where appropriate.

---

Nitpick comments:
In
`@android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt`:
- Around line 252-260: Extract the repeated error-classification logic in
TapSignerEnterPinView into a small helper (e.g., private fun
classifyTapSignerError(e: Exception): String) that returns the appropriate
message based on isAuthError(e) / isConnectionError(e) / else, then replace each
catch-site’s manager.errorMessage assignment with manager.errorMessage =
classifyTapSignerError(e) while keeping the existing Log.w calls (so logs remain
context-specific); reference isAuthError, isConnectionError,
manager.errorMessage, and the TapSignerEnterPinView catch blocks when making the
change.

In `@ios/Cove/AppManager.swift`:
- Around line 41-42: The class is already annotated `@MainActor`, so remove the
redundant per-member `@MainActor` annotations on the stored property and methods
to reduce noise: delete the `@MainActor` before the isLoading property and the
`@MainActor` annotations on the methods getWalletManager, getSendFlowManager,
resetRoute(to:), and loadAndReset(to:), leaving the class-level `@MainActor`
intact so all members remain main-isolated.

In `@ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift`:
- Line 84: Extract the repeated alert literal into a single AppAlertState case
(e.g. AppAlertState.tapSignerConnectionLost) and replace the inline initializers
in TapSignerConfirmPinView (the if branch using error.isConnectionError()) and
the three occurrences in TapSignerEnterPinView.swift with that new case; make
sure the new AppAlertState case mirrors the pattern used by
.tapSignerInvalidAuth/.tapSignerNoBackup so existing alert consumers (alertState
= .init(...)) continue to work and localization strings are centralized.

In `@rust/src/tap_card.rs`:
- Around line 28-29: The ConnectionError(String) enum variant should use a more
structured payload: either convert it to a unit variant ConnectionError or
replace its String payload with a small enum (e.g., ConnectionIssue with
variants TagLost, Timeout, Io(String)) so callers can pass a discrete case
instead of free-form text; update the Display/#[error(...)] formatting for the
enum (and any match arms creating ConnectionError) to construct the new variant
and adjust places that currently pass the hard-coded "Tag connection lost,
please hold your phone still" message to use ConnectionError::TagLost (or the
unit ConnectionError) and leave the human-readable message to the
UI/localization layer; references: the enum that contains ConnectionError, the
existing UnknownError(String) and CkTap(CkTapError) variants to mirror their
patterns.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4061e850-50ae-4a1d-81d6-d4e3a601d8c8

📥 Commits

Reviewing files that changed from the base of the PR and between c3ee0e1 and 785de38.

⛔ Files ignored due to path filters (1)
  • ios/CoveCore/Sources/CoveCore/generated/cove.swift is excluded by !**/generated/**
📒 Files selected for processing (13)
  • android/app/src/main/java/org/bitcoinppl/cove/flows/TapSignerFlow/TapSignerEnterPinView.kt
  • android/app/src/main/java/org/bitcoinppl/cove/nfc/TapCardNfcManager.kt
  • android/app/src/main/java/org/bitcoinppl/cove_core/cove.kt
  • ios/Cove/AppManager.swift
  • ios/Cove/Flows/TapSignerFlow/TapSignerConfirmPinView.swift
  • ios/Cove/Flows/TapSignerFlow/TapSignerEnterPinView.swift
  • ios/Cove/ImportWalletManager.swift
  • ios/Cove/Security.swift
  • ios/Cove/ShareSheet.swift
  • ios/Cove/TapSignerNFC.swift
  • ios/Cove/WalletManager.swift
  • rust/src/tap_card.rs
  • rust/src/tap_card/tap_signer_reader.rs
✅ Files skipped from review due to trivial changes (1)
  • ios/Cove/ShareSheet.swift
🚧 Files skipped from review as they are similar to previous changes (2)
  • rust/src/tap_card/tap_signer_reader.rs
  • ios/Cove/TapSignerNFC.swift

Comment thread ios/Cove/AppManager.swift
Comment on lines +7 to +9
@MainActor
@Observable
final class AppManager: FfiReconcile {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find FfiReconcile protocol declaration and call sites that may now cross actor boundaries.
rg -nP '\bprotocol\s+FfiReconcile\b' --type=swift -C5
rg -nP '\bAppManager\.shared\.(dispatch|pushRoute|popRoute|setRoute|resetRoute|reset|reloadWallets|selectWallet|loadWallets|toggleSidebar)\b' --type=swift -C2
rg -nP '\b\.reconcile\s*\(' --type=swift -C2

Repository: bitcoinppl/cove

Length of output: 6326


🏁 Script executed:

# Get the full FfiReconcile protocol definition with isolation attributes
rg -A 20 'public protocol FfiReconcile' --type=swift ios/CoveCore/Sources/CoveCore/generated/cove.swift

Repository: bitcoinppl/cove

Length of output: 827


🏁 Script executed:

# Get AppManager.reconcile() and dispatch(action:) implementations
cat -n ios/Cove/AppManager.swift | head -350 | tail -150

Repository: bitcoinppl/cove

Length of output: 5335


🏁 Script executed:

# Check the context of AuthManager.reset() call
cat -n ios/Cove/AuthManager.swift | sed -n '115,130p'

Repository: bitcoinppl/cove

Length of output: 443


🏁 Script executed:

# Get broader context around AuthManager.reset() call
cat -n ios/Cove/AuthManager.swift | sed -n '100,135p'

Repository: bitcoinppl/cove

Length of output: 1205


🏁 Script executed:

# Find all dispatch(action:) calls to check their context
rg -n 'dispatch\(action:' --type=swift -B 3 -A 1

Repository: bitcoinppl/cove

Length of output: 50371


🏁 Script executed:

# Check for any Swift compilation errors in the project
find . -name "*.swift" -type f | head -5

Repository: bitcoinppl/cove

Length of output: 276


🏁 Script executed:

# Search for nonisolated declarations in AppManager or similar patterns
rg -n 'nonisolated func (reconcile|dispatch)' --type=swift

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

# Check if there are any `@unchecked` Sendable or actor isolation suppression patterns
rg -n '@unchecked|nonisolated|-suppress' --type=swift ios/Cove/AppManager.swift

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

# Check AppManager.swift around the reconcile and dispatch methods for any actor isolation issues
cat -n ios/Cove/AppManager.swift | sed -n '238,245p'

Repository: bitcoinppl/cove

Length of output: 348


🏁 Script executed:

# Look for any build configuration or Swift settings that enable strict concurrency
fd -i 'build.settings|swiftsettings|xcconfig' --type f | head -10

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

# Check if there are any generated error/warning indicators in the codebase
rg -i 'error:|warning:|fixme' ios/Cove/AppManager.swift | head -20

Repository: bitcoinppl/cove

Length of output: 131


Mark reconcile(message:) and dispatch(action:) as nonisolated.

The class-level @MainActor annotation makes both methods main-actor-isolated, but FfiReconcile protocol declares reconcile(message:) with no isolation attributes. Since Rust FFI callbacks invoke this method from non-Swift contexts (potentially from background threads), a main-actor-isolated implementation would violate actor boundaries at runtime. The existing DispatchQueue.main.async wrapper in the reconcile() implementation confirms this was a known concern. Both protocol-conforming methods (reconcile and dispatch) should be marked nonisolated to allow FFI entry from any context, with the main actor dispatch handled explicitly in the method body as already done.

🧰 Tools
🪛 GitHub Check: swiftformat

[warning] 8-8:
Remove trailing space at end of a line. (trailingSpace)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/AppManager.swift` around lines 7 - 9, AppManager is annotated
`@MainActor` which makes its methods actor-isolated, but FfiReconcile's callbacks
(reconcile(message:) and dispatch(action:)) are invoked from non-Swift/FFI
contexts; mark both methods as nonisolated so they can be called from any thread
and keep the existing explicit MainActor dispatch inside their bodies (e.g.,
continue using DispatchQueue.main.async or Task { `@MainActor` ... } within
reconcile(message:) and dispatch(action:) to run UI/main-thread work). Locate
the implementations of reconcile(message:) and dispatch(action:) in AppManager
and add the nonisolated modifier to each method signature while preserving the
current main-thread dispatch logic.

Comment thread ios/Cove/AppManager.swift

@Observable final class AppManager: FfiReconcile {
@MainActor
@Observable
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Trim trailing whitespace.

swiftformat is flagging a trailing space after @Observable.

Proposed fix
-@Observable 
+@Observable
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Observable
`@Observable`
🧰 Tools
🪛 GitHub Check: swiftformat

[warning] 8-8:
Remove trailing space at end of a line. (trailingSpace)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/AppManager.swift` at line 8, Remove the trailing whitespace after
the attribute declaration so `@Observable ` becomes `@Observable`; edit the
`AppManager.swift` file to delete the extra space following the `@Observable`
attribute (ensure there are no other trailing spaces on that line) and save/run
swiftformat to confirm the warning is cleared.


@Observable class ImportWalletManager: ImportWalletManagerReconciler {
@MainActor
@Observable
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Trim trailing whitespace.

swiftformat is flagging a trailing space after @Observable.

Proposed fix
-@Observable 
+@Observable
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Observable
`@Observable`
🧰 Tools
🪛 GitHub Check: swiftformat

[warning] 4-4:
Remove trailing space at end of a line. (trailingSpace)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/ImportWalletManager.swift` at line 4, Remove the trailing whitespace
after the `@Observable` attribute in ImportWalletManager.swift so that the
annotation is exactly "@Observable" with no extra spaces; update the declaration
that uses the `@Observable` attribute (the annotation line in the
ImportWalletManager class/file) to eliminate the trailing space so swiftformat
no longer flags it.

Comment on lines +5 to +7
@MainActor
@Observable
final class WalletManager: AnyReconciler, WalletManagerReconciler {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect generated UniFFI bindings & related protocols for actor isolation expectations.
fd -t f 'WalletManagerReconciler|wallet_manager' ios/CoveCore | head -50
rg -nP '\b(protocol|func)\s+(WalletManagerReconciler|reconcile|reconcileMany|dispatch)\b' --type=swift -C2
rg -nP '\bAnyReconciler\b' --type=swift -C3
rg -nP '\bWeakReconciler\b' --type=swift -C3

Repository: bitcoinppl/cove

Length of output: 34306


🏁 Script executed:

cat -n ios/Cove/WalletManager.swift | head -280 | tail -250

Repository: bitcoinppl/cove

Length of output: 9241


🏁 Script executed:

# Check for trailing whitespace on line 6 specifically
sed -n '6p' ios/Cove/WalletManager.swift | od -c | grep -E '(space|tab)$' && echo "Trailing whitespace found" || echo "No trailing whitespace"

Repository: bitcoinppl/cove

Length of output: 141


🏁 Script executed:

# Check if reconcile/reconcileMany methods have nonisolated marker
rg -A 5 'func reconcile\(message:|func reconcileMany\(messages:' ios/Cove/WalletManager.swift

Repository: bitcoinppl/cove

Length of output: 542


🏁 Script executed:

# Check line 6 directly with hexdump alternative
sed -n '6p' ios/Cove/WalletManager.swift | cat -A

Repository: bitcoinppl/cove

Length of output: 73


🏁 Script executed:

# Search for any recent fixes or build-related comments
rg -i 'mainactor|nonisolated|compilation|doesnt compile|actor isolation' ios/Cove/WalletManager.swift

Repository: bitcoinppl/cove

Length of output: 100


🏁 Script executed:

# Check if there are any other managers with `@MainActor` to compare pattern
rg '@MainActor' ios/Cove/ --type swift | grep -E 'class|struct'

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

# Look at other reconciler implementations to see if they use nonisolated
rg -B2 -A8 'func reconcile\(message:' ios/Cove/*.swift | grep -E '(nonisolated|@MainActor|class )' -A1 -B1

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

# Look for any PR or branch information, recent commits
git log --oneline -20 ios/Cove/WalletManager.swift

Repository: bitcoinppl/cove

Length of output: 148


🏁 Script executed:

# Check if `@MainActor` is recently added (compare with git)
git show HEAD:ios/Cove/WalletManager.swift | head -10

Repository: bitcoinppl/cove

Length of output: 353


🏁 Script executed:

# Verify current full signature of reconcile to be absolutely certain
sed -n '238,244p' ios/Cove/WalletManager.swift

Repository: bitcoinppl/cove

Length of output: 288


🏁 Script executed:

# Check generated protocol definition more carefully to see isolation context
rg -A 10 'public protocol WalletManagerReconciler' ios/CoveCore/Sources/CoveCore/generated/cove.swift | head -20

Repository: bitcoinppl/cove

Length of output: 418


🏁 Script executed:

# Final check: does WalletManager or other managers actually have nonisolated on reconcile?
rg 'nonisolated.*func reconcile' ios/Cove/ --type swift

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

# Check other managers to see if they have `@MainActor`
rg -B2 'class.*AnyReconciler.*Reconciler {' ios/Cove/ --type swift | grep '@MainActor' | wc -l

Repository: bitcoinppl/cove

Length of output: 211


🏁 Script executed:

# Verify the exact line 6 content with hex
sed -n '6p' ios/Cove/WalletManager.swift | xxd

Repository: bitcoinppl/cove

Length of output: 103


🏁 Script executed:

# Check if the repo has recent actor-related changes
git log --oneline --all -30 | grep -i 'actor\|mainactor\|isolation'

Repository: bitcoinppl/cove

Length of output: 41


🏁 Script executed:

# Verify trailing space on line 6 using sed and wc
sed -n '6p' ios/Cove/WalletManager.swift | wc -c
# Compare with line 5 (should have no trailing space)
sed -n '5p' ios/Cove/WalletManager.swift | wc -c

Repository: bitcoinppl/cove

Length of output: 65


🏁 Script executed:

# Check what line 6 actually contains by looking at raw bytes
sed -n '6p' ios/Cove/WalletManager.swift | od -An -tx1

Repository: bitcoinppl/cove

Length of output: 102


Add nonisolated to reconcile/reconcileMany methods to satisfy protocol conformance under @MainActor isolation.

WalletManager is marked @MainActor, making all instance methods main-actor isolated. However, both AnyReconciler and the generated WalletManagerReconciler protocol declare reconcile and reconcileMany as non-isolated. The class conformance won't compile unless these specific methods are marked nonisolated.

The internal DispatchQueue.main.async wrapping becomes redundant once methods are nonisolated, since they're called from Rust on a background thread; removing it and marking the methods nonisolated is the idiomatic pattern.

Also fix trailing whitespace on line 6 (@Observable).

🧰 Tools
🪛 GitHub Check: swiftformat

[warning] 6-6:
Remove trailing space at end of a line. (trailingSpace)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/WalletManager.swift` around lines 5 - 7, WalletManager is `@MainActor`
but must implement nonisolated reconcile and reconcileMany from
AnyReconciler/WalletManagerReconciler; mark the instance methods reconcile(...)
and reconcileMany(...) as nonisolated, remove the redundant
DispatchQueue.main.async wrappers inside those methods (since they'll be called
from background threads), and ensure the `@Observable` annotation has no trailing
whitespace on the class declaration line.


@Observable final class WalletManager: AnyReconciler, WalletManagerReconciler {
@MainActor
@Observable
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Trim trailing whitespace.

swiftformat is flagging a trailing space after @Observable.

Proposed fix
-@Observable 
+@Observable
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Observable
`@Observable`
🧰 Tools
🪛 GitHub Check: swiftformat

[warning] 6-6:
Remove trailing space at end of a line. (trailingSpace)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ios/Cove/WalletManager.swift` at line 6, Remove the trailing whitespace after
the `@Observable` attribute in WalletManager.swift: edit the line containing
"@Observable " to be "@Observable" (no trailing space), save and re-run
swiftformat/lint to confirm the warning is cleared; ensure no other lines in the
file have trailing spaces.

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.

Replace string-based connection error checking with type-safe error handling

3 participants