@@ -570,32 +570,37 @@ private struct ResumableRegistrationRow: View {
570570 . padding ( . vertical, 2 )
571571 }
572572
573- /// Trailing view that depends on the lock's stage. At Broadcast
574- /// (`!canFundIdentity`) the lock isn't usable yet — SPV is waiting
575- /// on the masternodes to sign an InstantSendLock — so we show
576- /// a spinner instead of a button. SwiftData `@Query` is
577- /// reactive, so when the persister flips the row to
578- /// `InstantSendLocked` this view re-renders into the Resume
579- /// button without any extra plumbing.
580- @ViewBuilder
573+ /// Trailing Resume button. Every lock reaching this row is
574+ /// `statusRaw` 1...3 (per `crossWalletResumableLocks`) AND — by the
575+ /// list's `(walletId, identityIndex)` slot anti-join, which unions
576+ /// active-controller slots — is NOT owned by an in-flight
577+ /// registration. So every row is a genuinely-orphaned lock with no
578+ /// driver, and offering Resume is always safe:
579+ /// - InstantSendLocked / ChainLocked (`canFundIdentity`) submits the
580+ /// IdentityCreate as soon as it's tapped.
581+ /// - Broadcast (statusRaw 1) has no proof yet; resuming re-enters the
582+ /// finality wait via `resume_asset_lock` (which re-broadcasts the
583+ /// tx, then waits for the ChainLock — guaranteed finality, however
584+ /// long it takes). This previously rendered a permanent spinner,
585+ /// stranding a broadcast-but-interrupted registration lock (app
586+ /// killed mid-wait, or an aged lock whose IS quorum rotated) with
587+ /// no in-app recovery path.
588+ ///
589+ /// Unlike the address-funding sibling, no `hasActiveFunding` gate is
590+ /// needed here: identity locks carry their `identityIndex`, so the
591+ /// anti-join excludes the in-flight lock precisely, where the
592+ /// address flow (whose lock doesn't know its recipient) cannot.
593+ ///
594+ /// SwiftData `@Query` re-renders this row as the persister advances
595+ /// the lock's status.
581596 private var trailingAffordance : some View {
582- if lock. canFundIdentity {
583- Button ( action: onResume) {
584- Label ( " Resume " , systemImage: " arrow.clockwise " )
585- . labelStyle ( . titleAndIcon)
586- . font ( . callout)
587- }
588- . buttonStyle ( . borderedProminent)
589- . controlSize ( . small)
590- } else {
591- HStack ( spacing: 6 ) {
592- ProgressView ( )
593- . controlSize ( . small)
594- Text ( " Waiting for InstantSendLock or ChainLock… " )
595- . font ( . caption)
596- . foregroundColor ( . secondary)
597- }
597+ Button ( action: onResume) {
598+ Label ( " Resume " , systemImage: " arrow.clockwise " )
599+ . labelStyle ( . titleAndIcon)
600+ . font ( . callout)
598601 }
602+ . buttonStyle ( . borderedProminent)
603+ . controlSize ( . small)
599604 }
600605
601606 private func formatDuffs( _ amountDuffs: Int64 ) -> String {
0 commit comments