Skip to content

fix(shielded): render restored entries' unknown dates honestly - #940

Merged
QuantumExplorer merged 1 commit into
developfrom
claude/fervent-nightingale-2dfcb7
Aug 8, 2026
Merged

fix(shielded): render restored entries' unknown dates honestly#940
QuantumExplorer merged 1 commit into
developfrom
claude/fervent-nightingale-2dfcb7

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Aug 8, 2026

Copy link
Copy Markdown
Member

App-side companion to platform#4342 (restored-history reconstruction fidelity).

What

The SDK's restore-scan reconstruction now writes createdAtMs = 0 for shielded activity entries whose original date is not recoverable from chain data — previously it stamped the scan wall clock, which grouped weeks-old restored transfers under Today and dated the identical entry (same entryId) differently on two devices restoring the same wallet (observed: "Aug 7 07:20 / block 411495" vs "Aug 8 11:13 / block 412108").

This renders the sentinel as what it is:

  • ShieldedActivityItem gains hasKnownDate (createdAtMs > 0); its date becomes the .distantPast sort sentinel when unknown, so unknown-age entries sink to the oldest end of the history instead of reading as newest (or as January 1970).
  • The home list groups all unknown-date entries under one trailing Date unknown section instead of a spurious epoch-day header; the section header suppresses its fabricated weekday.
  • The details sheet's Date row shows Unknown; the row's time label is empty rather than "12:00 AM".
  • The SDK also stops stamping scan-tip block heights on these entries (blockHeight absent); the details sheet already renders an absent height by omitting the Block row — no app change needed there.

Behavior before the SDK lands

No-op: no current PersistentShieldedActivity rows carry createdAtMs == 0, so nothing renders differently until the app consumes an SDK build with platform#4342.

Verification

Clean dashpay scheme build (ARCHS=arm64, iphonesimulator). Unit-test target remains broken repo-wide (pre-existing).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of transaction and shielded activity entries with unavailable dates.
    • Unknown dates now appear in a dedicated “Date unknown” group without fabricated weekday or time information.
    • Detail views identify unavailable timestamps as “Unknown,” while entries with known dates remain unchanged.

Follow-up (not in this PR)

Within the trailing Date unknown section, entries currently render in the arbitrary-but-deterministic order the store returns. platform#4342 now also ships PersistentShieldedActivity.minNotePosition (commitment-tree position = exact chain order); once the app's platform checkout carries that SDK, a follow-up wires the section's ordering to it so restored history reads newest-first in its true on-chain sequence. Kept out of this PR because referencing the new SDK field would break the build against the current checkout.

The SDK's restore-scan reconstruction now writes createdAtMs = 0 for
shielded activity entries whose original date is not recoverable from
chain data (platform#4342) — previously it stamped the scan wall clock,
which grouped weeks-old restored transfers under 'Today' and dated the
same entry differently on two devices restoring the same wallet.

Render the sentinel as what it is:

- ShieldedActivityItem gains hasKnownDate (createdAtMs > 0); its date
  becomes the .distantPast sort sentinel when unknown, so unknown-age
  entries sink to the oldest end of the history instead of reading as
  newest (or as January 1970).
- The home list groups all unknown-date entries under one trailing
  'Date unknown' section instead of a spurious epoch-day header, and
  the section header suppresses its fabricated weekday.
- The details sheet's Date row shows 'Unknown'; the row's time label
  is empty rather than '12:00 AM'.

No-op until the app consumes an SDK build with platform#4342 — no
current rows carry createdAtMs == 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The transaction history now identifies shielded entries with unrecoverable timestamps. These entries sort using Date.distantPast, display unknown date values, and appear in a localized “Date unknown” group without a fabricated weekday.

Changes

Unknown date handling

Layer / File(s) Summary
Date validity and display
DashWallet/Sources/UI/Home/Views/ShieldedActivityHistory.swift, DashWallet/Sources/UI/Home/Views/TransactionListDataItem.swift
Shielded activities record whether their timestamp is known. Unknown timestamps use Date.distantPast for sorting, show no time, and display “Unknown” in details.
History grouping and section headers
DashWallet/Sources/UI/Home/Views/HomeViewModel.swift, DashWallet/Sources/UI/Home/Views/HomeView.swift, DashWallet/en.lproj/Localizable.strings
Unknown-date items use the localized “Date unknown” group. Their section headers omit the weekday label.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: jeanpierreroma, llbartekll, romchornyi

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 clearly and concisely describes the main change: honest rendering of unknown dates for restored shielded entries.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fervent-nightingale-2dfcb7

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

@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: 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 `@DashWallet/Sources/UI/Home/Views/ShieldedActivityHistory.swift`:
- Around line 134-137: Update the Core receipt matching flow in
matchingCoreReceipt to skip the time-window date filter when the pending row’s
hasKnownDate is false, rather than using the .distantPast/Unix-epoch value.
Preserve the existing time-window filtering for rows with known createdAtMs
values so ambiguous zero-amount matches can still resolve correctly.
🪄 Autofix

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 Plus

Run ID: 9c9fe0ae-4fd8-4adf-8ccf-d4b6ec8480f2

📥 Commits

Reviewing files that changed from the base of the PR and between 7664bae and 334fcb4.

📒 Files selected for processing (5)
  • DashWallet/Sources/UI/Home/Views/HomeView.swift
  • DashWallet/Sources/UI/Home/Views/HomeViewModel.swift
  • DashWallet/Sources/UI/Home/Views/ShieldedActivityHistory.swift
  • DashWallet/Sources/UI/Home/Views/TransactionListDataItem.swift
  • DashWallet/en.lproj/Localizable.strings

Comment on lines +134 to +137
hasKnownDate = row.createdAtMs > 0
date = hasKnownDate
? Date(timeIntervalSince1970: Double(row.createdAtMs) / 1000.0)
: .distantPast

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 18 'func matchingCoreReceipt|matchingCoreReceipt' DashWallet/Sources
rg -n -C 10 'createdAtMs|hasKnownDate|isAwaitingTransparentReceipt' \
  DashWallet/Sources/UI/Home/Views/ShieldedActivityHistory.swift \
  DashWallet/Sources/UI/Home/Views/HomeViewModel.swift

Repository: dashpay/dashwallet-ios

Length of output: 39979


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 30 'CoreWithdrawalReceiptMatchPolicy|selectedIndex|activityDate|isWithinProjectionMatchWindow' DashWallet/Sources DashWallet/Tests DashWallet 2>/dev/null | head -n 500

Repository: dashpay/dashwallet-ios

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from dataclasses import dataclass
from datetime import datetime, timezone

`@dataclass`
class Candidate:
    amount: int
    timestamp: float

def is_within_match_window(timestamp: float, anchor: float) -> bool:
    delta = timestamp - anchor
    return -3600 <= delta <= 86400

def selected_index(expected_amount: int, activity_date: float, candidates: list[Candidate]):
    if not candidates:
        return None
    if len(candidates) == 1:
        return 0

    in_window = [
        i for i, candidate in enumerate(candidates)
        if is_within_match_window(candidate.timestamp, activity_date)
    ]

    if expected_amount > 0:
        exact_in_window = [
            i for i in in_window
            if candidates[i].amount == expected_amount
        ]
        if len(exact_in_window) == 1:
            return exact_in_window[0]

        exact = [
            i for i, candidate in enumerate(candidates)
            if candidate.amount == expected_amount
        ]
        if len(exact) == 1:
            return exact[0]

    return in_window[0] if len(in_window) == 1 else None

epoch = 0.0
current = datetime.now(timezone.utc).timestamp()

cases = {
    "single candidate": (100, [Candidate(100, current)]),
    "multiple candidates with unique amount": (
        100,
        [Candidate(100, current), Candidate(200, current + 60)],
    ),
    "multiple candidates with ambiguous amount": (
        100,
        [Candidate(100, current), Candidate(100, current + 60)],
    ),
    "multiple candidates with zero expected amount": (
        0,
        [Candidate(0, current), Candidate(200, current + 60)],
    ),
}

for name, (amount, candidates) in cases.items():
    result = selected_index(amount, epoch, candidates)
    print(f"{name}: selectedIndex={result}")
PY

Repository: dashpay/dashwallet-ios

Length of output: 378


Handle unknown dates in Core receipt matching.

When row.createdAtMs == 0, HomeViewModel.swift:1554 passes the Unix epoch to matchingCoreReceipt. If the destination has multiple candidates and the amount is zero or ambiguous, CoreWithdrawalReceiptMatchPolicy.selectedIndex returns nil, so the pending row remains alongside the Core transaction. Skip the time-window filter when the date is unknown.

🤖 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/Home/Views/ShieldedActivityHistory.swift` around lines
134 - 137, Update the Core receipt matching flow in matchingCoreReceipt to skip
the time-window date filter when the pending row’s hasKnownDate is false, rather
than using the .distantPast/Unix-epoch value. Preserve the existing time-window
filtering for rows with known createdAtMs values so ambiguous zero-amount
matches can still resolve correctly.

@QuantumExplorer
QuantumExplorer merged commit 63e63d1 into develop Aug 8, 2026
1 of 4 checks passed
@QuantumExplorer
QuantumExplorer deleted the claude/fervent-nightingale-2dfcb7 branch August 8, 2026 20:37
QuantumExplorer added a commit that referenced this pull request Aug 9, 2026
…sset-lock reconstruction (#943)

* feat(shielded): chain-order the Date-unknown band; trust SDK asset-lock reconstruction

Consumes platform #4342 (SDK-side tracked-asset-lock reconstruction +
minNotePosition on scan-derived shielded entries):

- The trailing "Date unknown" history band (PR #940) now orders by exact
  on-chain sequence: ShieldedActivityItem carries the SDK's
  minNotePosition (smallest note commitment-tree position, nil when the
  row predates the field), TransactionListDataItem exposes it as
  chainOrderKey, and the pre-grouping sort tiebreaks equal dates by it
  descending — identical order on every device that restores the seed.

- The app-side restore fallback (PR #939's addReconstructedLocks raw-tx
  reparse) is reduced to a coverage diagnostic: SDK reconstruction now
  writes store-backed PersistentAssetLock rows for every historical
  funding tx (verified 9/9 on a restored testnet wallet — identity,
  platform, and shielded types all classify), and store rows always won
  over the fallback anyway. Any asset-lock tx with no row is logged as a
  reconstruction gap. The reconstructed* sentinels and
  Transaction.reconstructedLockAmountDuffs go with it.

- statusRaw 5 (RecoveredFromChain) is mapped explicitly: dedup prefers
  consumed (4) over recovered (5), and the tx detail sheet shows
  "Restored — completion unknown" instead of falling through. Note the
  SDK currently delivers reconstructed rows at 1/3, not 5 — the false-
  pending consequence and the enrichment gap are SDK-side, tracked for a
  platform follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(dashpay): registration recovery includes RecoveredFromChain locks

With platform #4347, a registration asset lock stranded by an app kill
whose block chain-locks before relaunch surfaces as statusRaw 5
(RecoveredFromChain) instead of staying in the 0…3 window the recovery
lookup filtered on — the stranded payment would have stopped being
offered for recovery. Treat everything but the Consumed (4) tombstone
as unfinished: a 5-lock with no identity is a genuinely incomplete
registration, and the SDK resume path explicitly supports consuming it.
Completed registrations never resume from this lock — both call sites
probe the identity (local row, then the Platform slot, reconciling the
lock to Consumed) first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <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.

1 participant