Skip to content

fix: compute relative timestamps from actual data#581

Merged
QuantumExplorer merged 3 commits into
dashpay:v1.0-devfrom
thepastaclaw:fix/579-hardcoded-timestamps
Feb 17, 2026
Merged

fix: compute relative timestamps from actual data#581
QuantumExplorer merged 3 commits into
dashpay:v1.0-devfrom
thepastaclaw:fix/579-hardcoded-timestamps

Conversation

@thepastaclaw
Copy link
Copy Markdown
Collaborator

@thepastaclaw thepastaclaw commented Feb 16, 2026

Replace hardcoded display strings like "Received: 1 day ago" with real relative timestamps computed from document createdAt/updatedAt fields using chrono::Utc::now().

Fixes #579

Split from #580 — timestamps only, no unrelated changes.

Summary by CodeRabbit

  • New Features
    • Timestamps in contact requests and payment history now show human-friendly relative times (e.g., "just now", "2 days ago") for clearer context.
    • Payment guidelines updated to note that addresses are never reused to protect privacy.

Replace hardcoded display strings like "Received: 1 day ago" with
real relative timestamps computed from document createdAt/updatedAt
fields using chrono::Utc::now().

Fixes dashpay#579
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

Added a new format_relative_time(timestamp: u64) -> Option<String> utility and replaced hardcoded relative timestamp labels with dynamic computed values in DashPay contact requests and send-payment UI components.

Changes

Cohort / File(s) Summary
DashPay UI utilities
src/ui/dashpay/mod.rs
Added pub(crate) fn format_relative_time(timestamp: u64) -> Option<String> using chrono and chrono_humanize to produce human-readable relative times (handles seconds vs milliseconds, returns None for 0/unparsable).
Contact Requests UI
src/ui/dashpay/contact_requests.rs
Replaced hardcoded "Received: ..." / "Sent: ..." labels with dynamic format_relative_time(request.timestamp) and defaulted to "Received: unknown" / "Sent: unknown" when formatting fails. Imported format_relative_time from parent module.
Send Payment UI
src/ui/dashpay/send_payment.rs
Replaced static payment-history timestamp with dynamic format_relative_time(payment.timestamp) (prefixed with "• " when present). Imported format_relative_time. Also extended payment guidelines text to mention address privacy (addresses are never reused).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nudged the clocks with gentle paws,

Now timestamps hop with truer cause.
No more old days stuck on view,
Each moment shows its proper hue.
Hooray—time hops fresh for me and you! 🕰️🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses the first objective from issue #579 (compute relative timestamps) but omits the second objective (fix the 'bio' field name bug in contacts.rs), which remains unimplemented. Apply the field-name correction: change .get("bio") to .get("publicMessage") in src/backend_task/dashpay/contacts.rs:454 to fully resolve issue #579.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% 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 PR title 'fix: compute relative timestamps from actual data' directly and specifically describes the primary change—replacing hardcoded timestamp labels with dynamic computations from actual timestamp fields.
Out of Scope Changes check ✅ Passed All changes directly support the timestamp computation objective from issue #579; no unrelated modifications or scope creep detected in the three modified files.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into v1.0-dev

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@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: 2

🤖 Fix all issues with AI agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.

In `@src/ui/dashpay/contact_requests.rs`:
- Around line 26-51: The function `format_relative_time` is duplicated (in
contact_requests.rs and send_payment.rs); extract it into a shared utility
(e.g., add it to `src/ui/dashpay/mod.rs` or a new `src/ui/helpers.rs`) as a
single pub function (pub fn format_relative_time(...)) and re-export or import
it from both `contact_requests.rs` and `send_payment.rs`; update both files to
remove their local copies and call the shared `format_relative_time`, ensuring
visibility (pub/use) and tests/build still compile and preserving the existing
behavior (including handling of `LocalResult` and the millis threshold).

In `@src/ui/dashpay/send_payment.rs`:
- Around line 797-806: DashPayPaymentHistory currently lacks a timestamp so
format_relative_time(payment.timestamp) gets zeros; update the backend
tuple/type to include a timestamp field (e.g., extend DashPayPaymentHistory from
a 5-tuple to include timestamp or convert to a struct with a timestamp), change
any constructors that set timestamp: 0 in display_task_result and elsewhere to
populate the real timestamp from the fetched payment record, and adjust all
callsites (including the UI code that builds payment_time_text and the consumer
of DashPayPaymentHistory) to read the new timestamp field so
format_relative_time receives real timestamps instead of zero.
- Around line 38-42: The u64->i64 casts for timestamp used when building
dt_result (via Utc.timestamp_millis_opt and Utc.timestamp_opt) can wrap on
extreme values; replace the direct casts with a checked conversion using
i64::try_from(timestamp).ok()? and use that converted i64 for both
timestamp_millis_opt / timestamp_opt branches (adjusting control flow so the
early ? return is allowed — the enclosing function already returns Option),
ensuring you reference the converted value instead of doing timestamp as i64.
- Around line 34-54: The function `format_relative_time` is duplicated; extract
it into a shared utility module (e.g., create a `helpers` or `dashpay::util`
module) and move the implementation there, keeping the same signature `fn
format_relative_time(timestamp: u64) -> Option<String>`; then replace the local
implementations in `send_payment.rs` and `contact_requests.rs` with
`use`/`pub(crate) use` or an import from the new module and call the single
implementation instead so both files reference the shared function.
🧹 Nitpick comments (2)
🤖 Fix all nitpicks with AI agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.

In `@src/ui/dashpay/send_payment.rs`:
- Around line 38-42: The u64->i64 casts for timestamp used when building
dt_result (via Utc.timestamp_millis_opt and Utc.timestamp_opt) can wrap on
extreme values; replace the direct casts with a checked conversion using
i64::try_from(timestamp).ok()? and use that converted i64 for both
timestamp_millis_opt / timestamp_opt branches (adjusting control flow so the
early ? return is allowed — the enclosing function already returns Option),
ensuring you reference the converted value instead of doing timestamp as i64.
- Around line 34-54: The function `format_relative_time` is duplicated; extract
it into a shared utility module (e.g., create a `helpers` or `dashpay::util`
module) and move the implementation there, keeping the same signature `fn
format_relative_time(timestamp: u64) -> Option<String>`; then replace the local
implementations in `send_payment.rs` and `contact_requests.rs` with
`use`/`pub(crate) use` or an import from the new module and call the single
implementation instead so both files reference the shared function.
src/ui/dashpay/send_payment.rs (2)

38-42: u64 as i64 cast can silently wrap on extreme values.

The timestamp as i64 casts on lines 39 and 41 will wrap to negative if a u64 exceeds i64::MAX. While realistic epoch timestamps won't hit this, a defensive i64::try_from(timestamp).ok()? is cheap and prevents surprises from corrupt data.

Proposed fix
-        Utc.timestamp_millis_opt(timestamp as i64)
+        Utc.timestamp_millis_opt(i64::try_from(timestamp).ok()?)
     } else {
-        Utc.timestamp_opt(timestamp as i64, 0)
+        Utc.timestamp_opt(i64::try_from(timestamp).ok()?, 0)
     };

Note: this requires a small restructure since the early ? return needs the enclosing function to return Option, which it already does.

🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@src/ui/dashpay/send_payment.rs` around lines 38 - 42, The u64->i64 casts for
timestamp used when building dt_result (via Utc.timestamp_millis_opt and
Utc.timestamp_opt) can wrap on extreme values; replace the direct casts with a
checked conversion using i64::try_from(timestamp).ok()? and use that converted
i64 for both timestamp_millis_opt / timestamp_opt branches (adjusting control
flow so the early ? return is allowed — the enclosing function already returns
Option), ensuring you reference the converted value instead of doing timestamp
as i64.

34-54: Duplicated format_relative_time — extract to a shared module.

This function is identical to the one in src/ui/dashpay/contact_requests.rs (lines 28-50). Having the same logic in two places means future changes (e.g., adjusting the "just now" threshold or adding more granularity) must be applied in both locations.

Consider moving it to a common utility module (e.g., src/ui/helpers.rs or a new src/ui/dashpay/util.rs) and importing it from both files.

🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@src/ui/dashpay/send_payment.rs` around lines 34 - 54, The function
`format_relative_time` is duplicated; extract it into a shared utility module
(e.g., create a `helpers` or `dashpay::util` module) and move the implementation
there, keeping the same signature `fn format_relative_time(timestamp: u64) ->
Option<String>`; then replace the local implementations in `send_payment.rs` and
`contact_requests.rs` with `use`/`pub(crate) use` or an import from the new
module and call the single implementation instead so both files reference the
shared function.

Comment thread src/ui/dashpay/contact_requests.rs Outdated
Comment on lines +797 to +806
let payment_time_text = format_relative_time(payment.timestamp)
.map(|t| format!("• {}", t))
.unwrap_or_default();
if !payment_time_text.is_empty() {
ui.label(
RichText::new(payment_time_text)
.small()
.color(DashColors::text_secondary(dark_mode)),
);
}
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check the file exists and examine lines around 856 and 889
wc -l src/ui/dashpay/send_payment.rs

Repository: dashpay/dash-evo-tool

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Check lines 856 and 889 to see if they hardcode timestamp: 0
sed -n '850,860p' src/ui/dashpay/send_payment.rs
echo "---"
sed -n '883,893p' src/ui/dashpay/send_payment.rs

Repository: dashpay/dash-evo-tool

Length of output: 1172


🏁 Script executed:

#!/bin/bash
# Search for format_relative_time implementation to see how it handles values
rg -n "fn format_relative_time" --type=rust -A 10

Repository: dashpay/dash-evo-tool

Length of output: 1655


🏁 Script executed:

#!/bin/bash
# Look for DashPayPaymentHistory structure and construction
rg -n "DashPayPaymentHistory" --type=rust -B 2 -A 8

Repository: dashpay/dash-evo-tool

Length of output: 2863


🏁 Script executed:

#!/bin/bash
# Check how payments are created in display_task_result (lines 856 and 889 context)
sed -n '840,870p' src/ui/dashpay/send_payment.rs
echo "---"
sed -n '870,910p' src/ui/dashpay/send_payment.rs

Repository: dashpay/dash-evo-tool

Length of output: 2946


Backend payment history lacks timestamp data structure to support relative timestamps.

Both lines 856 and 889 hardcode timestamp: 0 with a TODO comment. Since format_relative_time() returns None for zero timestamps, the relative timestamp label is silently omitted for all payments fetched via display_task_result(). This partially undermines the fix from issue #579—users only see relative timestamps for payments loaded from the local database, not freshly fetched ones.

The root cause is that DashPayPaymentHistory in src/backend_task/mod.rs:161 is defined as a 5-tuple (tx_id, contact_name, amount, is_incoming, memo) without a timestamp field, so there is no mechanism to propagate real timestamps from the backend. Addressing this requires extending the tuple structure to include timestamp and updating the backend to fetch and return actual payment timestamps.

🤖 Prompt for AI Agents
Before applying any fix, first verify the finding against the current code and
decide whether a code change is actually needed. If the finding is not valid or
no change is required, do not modify code for that item and briefly explain why
it was skipped.
In `@src/ui/dashpay/send_payment.rs` around lines 797 - 806, DashPayPaymentHistory
currently lacks a timestamp so format_relative_time(payment.timestamp) gets
zeros; update the backend tuple/type to include a timestamp field (e.g., extend
DashPayPaymentHistory from a 5-tuple to include timestamp or convert to a struct
with a timestamp), change any constructors that set timestamp: 0 in
display_task_result and elsewhere to populate the real timestamp from the
fetched payment record, and adjust all callsites (including the UI code that
builds payment_time_text and the consumer of DashPayPaymentHistory) to read the
new timestamp field so format_relative_time receives real timestamps instead of
zero.

Deduplicate the identical format_relative_time function that existed
in both contact_requests.rs and send_payment.rs. Move it to the
dashpay mod.rs as a pub(crate) function and import from both files.
@thepastaclaw
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

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

🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In `@src/ui/dashpay/mod.rs`:
- Around line 32-36: The code casts timestamp: u64 to i64 directly when calling
Utc.timestamp_millis_opt / Utc.timestamp_opt which can wrap for values >
i64::MAX; replace the direct casts with a safe conversion (e.g. use
i64::try_from(timestamp) or timestamp.checked_sub(...) pattern) and handle the
Err/overflow case explicitly before computing dt_result (for example return None
or clamp to i64::MAX), updating the branch that constructs dt_result (the
Utc.timestamp_millis_opt / Utc.timestamp_opt calls) to use the safely converted
i64 value instead of (timestamp as i64).
- Around line 26-48: The function format_relative_time is currently placed
between import blocks; move the entire fn format_relative_time(...)
implementation so all use statements (the import block around lines ~21–22 and
the later import ending at ~49) are contiguous and the function appears after
the imports. Ensure you update the file so only import declarations appear
together and the format_relative_time function (and any duplicate placement
referenced at 49-49) is relocated below the final import to restore clean module
organization.
- Around line 26-48: Add inline unit tests for the pure utility function
format_relative_time: create a #[cfg(test)] mod with multiple #[test] cases that
assert (1) timestamp == 0 returns None, (2) a small seconds-range timestamp
(e.g., now - few seconds) yields Some("just now"), (3) a known past timestamp
returns a non-empty human-readable Some(String) matching expected substring, and
(4) a value just below/above the 1_000_000_000_000 threshold exercises both the
seconds and milliseconds branches; locate tests alongside format_relative_time
in src/ui/dashpay/mod.rs and use deterministic fixed timestamps (or compute from
Utc::now()) so they are reliable.
🧹 Nitpick comments (3)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In `@src/ui/dashpay/mod.rs`:
- Around line 32-36: The code casts timestamp: u64 to i64 directly when calling
Utc.timestamp_millis_opt / Utc.timestamp_opt which can wrap for values >
i64::MAX; replace the direct casts with a safe conversion (e.g. use
i64::try_from(timestamp) or timestamp.checked_sub(...) pattern) and handle the
Err/overflow case explicitly before computing dt_result (for example return None
or clamp to i64::MAX), updating the branch that constructs dt_result (the
Utc.timestamp_millis_opt / Utc.timestamp_opt calls) to use the safely converted
i64 value instead of (timestamp as i64).
- Around line 26-48: The function format_relative_time is currently placed
between import blocks; move the entire fn format_relative_time(...)
implementation so all use statements (the import block around lines ~21–22 and
the later import ending at ~49) are contiguous and the function appears after
the imports. Ensure you update the file so only import declarations appear
together and the format_relative_time function (and any duplicate placement
referenced at 49-49) is relocated below the final import to restore clean module
organization.
- Around line 26-48: Add inline unit tests for the pure utility function
format_relative_time: create a #[cfg(test)] mod with multiple #[test] cases that
assert (1) timestamp == 0 returns None, (2) a small seconds-range timestamp
(e.g., now - few seconds) yields Some("just now"), (3) a known past timestamp
returns a non-empty human-readable Some(String) matching expected substring, and
(4) a value just below/above the 1_000_000_000_000 threshold exercises both the
seconds and milliseconds branches; locate tests alongside format_relative_time
in src/ui/dashpay/mod.rs and use deterministic fixed timestamps (or compute from
Utc::now()) so they are reliable.
src/ui/dashpay/mod.rs (3)

32-36: u64 as i64 cast can silently wrap for values > i64::MAX.

Realistic timestamps won't hit this, but a defensive guard is cheap and prevents surprising negative-date results from corrupt data.

Proposed fix
-    let dt_result = if timestamp > 1_000_000_000_000 {
-        Utc.timestamp_millis_opt(timestamp as i64)
+    let Ok(ts) = i64::try_from(timestamp) else {
+        return None;
+    };
+    let dt_result = if timestamp > 1_000_000_000_000 {
+        Utc.timestamp_millis_opt(ts)
     } else {
-        Utc.timestamp_opt(timestamp as i64, 0)
+        Utc.timestamp_opt(ts, 0)
     };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/dashpay/mod.rs` around lines 32 - 36, The code casts timestamp: u64 to
i64 directly when calling Utc.timestamp_millis_opt / Utc.timestamp_opt which can
wrap for values > i64::MAX; replace the direct casts with a safe conversion
(e.g. use i64::try_from(timestamp) or timestamp.checked_sub(...) pattern) and
handle the Err/overflow case explicitly before computing dt_result (for example
return None or clamp to i64::MAX), updating the branch that constructs dt_result
(the Utc.timestamp_millis_opt / Utc.timestamp_opt calls) to use the safely
converted i64 value instead of (timestamp as i64).

26-48: Function body is placed between two import blocks.

Lines 26–48 define format_relative_time sandwiched between use statements (line 21–22 and line 49). Moving the function below all imports (after line 50) improves readability.

Also applies to: 49-49

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

In `@src/ui/dashpay/mod.rs` around lines 26 - 48, The function
format_relative_time is currently placed between import blocks; move the entire
fn format_relative_time(...) implementation so all use statements (the import
block around lines ~21–22 and the later import ending at ~49) are contiguous and
the function appears after the imports. Ensure you update the file so only
import declarations appear together and the format_relative_time function (and
any duplicate placement referenced at 49-49) is relocated below the final import
to restore clean module organization.

26-48: Consider adding inline unit tests for this shared utility.

format_relative_time is a pure function now shared across multiple UI files — it's a good candidate for inline #[cfg(test)] coverage (e.g., zero → None, seconds-range → "just now", known past timestamp → reasonable relative string, millis-threshold boundary).

As per coding guidelines: "Unit tests should be inline in source files using #[test] attribute."

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

In `@src/ui/dashpay/mod.rs` around lines 26 - 48, Add inline unit tests for the
pure utility function format_relative_time: create a #[cfg(test)] mod with
multiple #[test] cases that assert (1) timestamp == 0 returns None, (2) a small
seconds-range timestamp (e.g., now - few seconds) yields Some("just now"), (3) a
known past timestamp returns a non-empty human-readable Some(String) matching
expected substring, and (4) a value just below/above the 1_000_000_000_000
threshold exercises both the seconds and milliseconds branches; locate tests
alongside format_relative_time in src/ui/dashpay/mod.rs and use deterministic
fixed timestamps (or compute from Utc::now()) so they are reliable.

@QuantumExplorer QuantumExplorer merged commit 2dc62a5 into dashpay:v1.0-dev Feb 17, 2026
5 checks passed
QuantumExplorer pushed a commit that referenced this pull request Feb 17, 2026
* fix: compute relative timestamps from actual data (#581)

* fix: compute relative timestamps from actual data

Replace hardcoded display strings like "Received: 1 day ago" with
real relative timestamps computed from document createdAt/updatedAt
fields using chrono::Utc::now().

Fixes #579

* style: fix import ordering per cargo fmt

* refactor: extract format_relative_time to shared dashpay module

Deduplicate the identical format_relative_time function that existed
in both contact_requests.rs and send_payment.rs. Move it to the
dashpay mod.rs as a pub(crate) function and import from both files.

---------

Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>

* fix: update platform for DIP-18 HRP and improve SPV sync progress (#575)

* fix: update dashpay/platform to d6f4eb9 for DIP-18 HRP fix

The previous platform revision used incorrect bech32m HRP prefixes
(evo/tevo) for Platform addresses. The updated commit uses the
correct DIP-0018 prefixes (dash/tdash).

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

* refactor(spv): use dash-spv SyncProgress API directly

Replace the intermediate SyncProgress/DetailedSyncProgress/SyncStage
translation layer with direct use of dash_spv::sync::SyncProgress.
This eliminates ~120 lines of bridge code in spawn_progress_watcher()
and determine_sync_stage(), and lets the UI query per-manager progress
(headers, filter_headers, filters, masternodes, blocks) via the
upstream API.

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

* fix(spv): improve sync progress accuracy and robustness

- Log headers() error before discarding for easier debugging
- Restore download-window optimization for headers progress on
  checkpoint-resumed syncs (progress starts near 0% not 83%)
- Replace catch-all _ => 0.0 with explicit SyncState variant matches
  so new variants produce compile errors
- Fix filters progress to use current_height/target_height instead of
  downloaded/target_height (session count vs absolute height mismatch)
- Restore peer count in sync status text
- Add "Querying peer heights" label and diffs_processed to masternode
  status for more informative sync messages

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

* fix(spv): use height-based blocks progress to prevent bar from jumping

The blocks progress bar was bouncing backward because it used
processed/requested session counters whose denominator grows as filters
discover more matching blocks. Switch to last_processed block height
relative to headers target_height, which only increases. Display
"current / target" heights instead of percentage on the blocks bar.

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

* fix(spv): improve progress bar resilience across resume and network switch

- Add windowed progress tracking for filters (consistent with headers/filter_headers)
- Reset blocks_stage_start on Error state so recovery gets a fresh window
- Track spv_progress_network to detect network changes and rebuild progress
  state from the new network's sync_progress instead of resetting to zero
- Eliminate redundant clone in progress watcher (move instead of clone twice)
- Consolidate all progress state reset logic into rebuild_spv_progress_state()

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* build: add git and cargo permissions to Claude Code workflow (#565)

* build: add git and cargo permissions to Claude Code workflow

Allow Claude to run git fetch/merge/checkout/rebase/push and
cargo build/test/clippy/fmt commands. Switch model to opus.

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

* fix: use claude_args for model and allowed tools

The `model` and `allowed_tools` inputs are not declared in
claude-code-action@v1 and are silently ignored. Move them to
`claude_args` with --model and --allowedTools flags. Also fix
deprecated colon syntax (`:*`) to space syntax (` *`).

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

* fix: use single quotes to prevent glob expansion in allowed tools

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

* build: sandbox cargo commands and add git permissions for Claude

- Add safe-cargo.sh wrapper that strips CI secrets before running cargo
- Use --allowedTools for git and safe-cargo, --disallowedTools for raw cargo
- Document safe-cargo usage in CLAUDE.md

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

* fix: switch safe-cargo.sh from denylist to allowlist approach

Use `env -i` (start with empty environment, explicitly pass only
what cargo needs) instead of `env -u` (strip known secrets). This
is more robust against future secrets being added to the workflow.

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

* build: deny Claude from editing CI scripts and workflows

Prevent Claude from modifying .github/scripts/ and .github/workflows/
to ensure the safe-cargo wrapper cannot be tampered with.

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

* fix: conditionally pass optional env vars in safe-cargo.sh

Empty PROTOC="" caused prost build scripts to fail with
"protoc not found". Now optional vars (PROTOC, CC, CXX, etc.)
are only passed when set and non-empty.

Tested: build, test, fmt all pass through the wrapper.

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

* rabbit feedback

* build: move safe-cargo.sh to scripts/ and allow +nightly fmt

Move safe-cargo.sh from .github/scripts/ to top-level scripts/ for
better discoverability. Add detailed comment explaining why the wrapper
exists (prevent CI secret exfiltration via build scripts). Update all
references in claude.yml, CLAUDE.md, and permission settings. Add
`+nightly fmt` to allowedTools so Claude can follow CLAUDE.md
formatting instructions in CI.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>
Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
lklimek added a commit that referenced this pull request Feb 17, 2026
…d UX (#577)

* fix: update dashpay/platform to d6f4eb9 for DIP-18 HRP fix

The previous platform revision used incorrect bech32m HRP prefixes
(evo/tevo) for Platform addresses. The updated commit uses the
correct DIP-0018 prefixes (dash/tdash).

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

* refactor(spv): use dash-spv SyncProgress API directly

Replace the intermediate SyncProgress/DetailedSyncProgress/SyncStage
translation layer with direct use of dash_spv::sync::SyncProgress.
This eliminates ~120 lines of bridge code in spawn_progress_watcher()
and determine_sync_stage(), and lets the UI query per-manager progress
(headers, filter_headers, filters, masternodes, blocks) via the
upstream API.

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

* fix(spv): improve sync progress accuracy and robustness

- Log headers() error before discarding for easier debugging
- Restore download-window optimization for headers progress on
  checkpoint-resumed syncs (progress starts near 0% not 83%)
- Replace catch-all _ => 0.0 with explicit SyncState variant matches
  so new variants produce compile errors
- Fix filters progress to use current_height/target_height instead of
  downloaded/target_height (session count vs absolute height mismatch)
- Restore peer count in sync status text
- Add "Querying peer heights" label and diffs_processed to masternode
  status for more informative sync messages

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

* fix(spv): use height-based blocks progress to prevent bar from jumping

The blocks progress bar was bouncing backward because it used
processed/requested session counters whose denominator grows as filters
discover more matching blocks. Switch to last_processed block height
relative to headers target_height, which only increases. Display
"current / target" heights instead of percentage on the blocks bar.

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

* fix(spv): improve progress bar resilience across resume and network switch

- Add windowed progress tracking for filters (consistent with headers/filter_headers)
- Reset blocks_stage_start on Error state so recovery gets a fresh window
- Track spv_progress_network to detect network changes and rebuild progress
  state from the new network's sync_progress instead of resetting to zero
- Eliminate redundant clone in progress watcher (move instead of clone twice)
- Consolidate all progress state reset logic into rebuild_spv_progress_state()

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

* fix(spv): update ConnectionStatus immediately on connect/disconnect

Propagate SPV status into ConnectionStatus right after start_spv() and
stop_spv() so the UI reflects the change on the next frame instead of
waiting for the next throttled trigger_refresh() cycle (2-10 seconds).

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

* fix(spv): check cancellation token in listener tasks to prevent shutdown hang

SPV finality and reconcile listeners blocked app shutdown for up to 10s
because they never checked the global cancellation token. Add cancel
branches to their tokio::select! loops so they exit immediately on
shutdown.

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

* chore: remove log spamming line

* fix(spv): make listener handler calls cancellation-aware and add shutdown trace logging

Wrap reconcile_spv_wallets(), handle_spv_finality_event(), and the
debounce sleep inside tokio::select! with the cancellation token so
shutdown can interrupt them even when blocked on locks held by the
SPV sync thread.

Add trace-level logging to TaskManager::shutdown() for per-task join
timing to aid future shutdown diagnostics.

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

* fix(spv): disable Disconnect button during stopping and poll status faster

- Disable the Disconnect button while SPV status is Stopping to prevent
  double-clicks and provide visual feedback
- Poll SPV status every 200ms during Stopping instead of the 10s
  connected interval so the Stopped transition is reflected within 1s
- Reset the throttle timer in stop_spv() so fast polling starts
  immediately

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

* refactor(tasks): add mandatory task names to spawn_sync for shutdown diagnostics

Change spawn_sync() signature to require a &'static str name. The
JoinSet now yields the task name on completion, letting shutdown()
log which tasks finished and which ones timed out.

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

* chore: log task names

* refactor(spv): eliminate separate OS thread and tokio runtime

Replace the dedicated SPV thread + 4-worker tokio runtime with a
spawned task on the main 12-worker runtime via TaskManager::spawn_sync.

This simplifies shutdown (SPV loop now tracked in unified JoinSet),
removes cross-runtime complexity, and improves debuggability.

Additional changes:
- Fix: zeroize xprv_str after wallet import (security H-1)
- Fix: sanitize devnet_name in build_spv_data_dir to prevent path traversal
- Add 21 integration tests covering lifecycle, concurrency, deadlock
  detection, and live testnet sync

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

* fix(spv): derive stop_token as child of global cancel for clean shutdown

The spv_request_handler selected on stop_token, which was independent
of the global cancellation token.  During window-close shutdown only
the global token was cancelled, leaving the request handler running
and causing a ~5s hang until the TaskManager timeout aborted it.

Fix by creating stop_token as a child_token() of the global cancel.
This also simplifies run_spv_loop and run_sync_and_monitor by removing
the redundant global_cancel parameter — a single stop_token now covers
both explicit SpvManager::stop() and application-wide shutdown.

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

* CLAUDE: minor changes about docs

* fix(spv): bump rust-dashcore to d8bc066 for faster disconnect and add shutdown tracing

Pin rust-dashcore patches to commit d8bc066 which includes:
- sync manager task loop exits on network errors instead of logging indefinitely
- sync coordinator signal_shutdown() cancels tasks before network disconnect
- connection tasks race Peer::connect() against shutdown token

Add debug tracing to SpvManager::stop() and run_sync_and_monitor() to
measure client.stop() duration, and trace-level polling of SPV status
in ConnectionStatus.

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

* chore: update rust-dashcore rev

* merge: resolve v1.0-dev conflicts for #577 (#585)

* fix: compute relative timestamps from actual data (#581)

* fix: compute relative timestamps from actual data

Replace hardcoded display strings like "Received: 1 day ago" with
real relative timestamps computed from document createdAt/updatedAt
fields using chrono::Utc::now().

Fixes #579

* style: fix import ordering per cargo fmt

* refactor: extract format_relative_time to shared dashpay module

Deduplicate the identical format_relative_time function that existed
in both contact_requests.rs and send_payment.rs. Move it to the
dashpay mod.rs as a pub(crate) function and import from both files.

---------

Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>

* fix: update platform for DIP-18 HRP and improve SPV sync progress (#575)

* fix: update dashpay/platform to d6f4eb9 for DIP-18 HRP fix

The previous platform revision used incorrect bech32m HRP prefixes
(evo/tevo) for Platform addresses. The updated commit uses the
correct DIP-0018 prefixes (dash/tdash).

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

* refactor(spv): use dash-spv SyncProgress API directly

Replace the intermediate SyncProgress/DetailedSyncProgress/SyncStage
translation layer with direct use of dash_spv::sync::SyncProgress.
This eliminates ~120 lines of bridge code in spawn_progress_watcher()
and determine_sync_stage(), and lets the UI query per-manager progress
(headers, filter_headers, filters, masternodes, blocks) via the
upstream API.

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

* fix(spv): improve sync progress accuracy and robustness

- Log headers() error before discarding for easier debugging
- Restore download-window optimization for headers progress on
  checkpoint-resumed syncs (progress starts near 0% not 83%)
- Replace catch-all _ => 0.0 with explicit SyncState variant matches
  so new variants produce compile errors
- Fix filters progress to use current_height/target_height instead of
  downloaded/target_height (session count vs absolute height mismatch)
- Restore peer count in sync status text
- Add "Querying peer heights" label and diffs_processed to masternode
  status for more informative sync messages

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

* fix(spv): use height-based blocks progress to prevent bar from jumping

The blocks progress bar was bouncing backward because it used
processed/requested session counters whose denominator grows as filters
discover more matching blocks. Switch to last_processed block height
relative to headers target_height, which only increases. Display
"current / target" heights instead of percentage on the blocks bar.

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

* fix(spv): improve progress bar resilience across resume and network switch

- Add windowed progress tracking for filters (consistent with headers/filter_headers)
- Reset blocks_stage_start on Error state so recovery gets a fresh window
- Track spv_progress_network to detect network changes and rebuild progress
  state from the new network's sync_progress instead of resetting to zero
- Eliminate redundant clone in progress watcher (move instead of clone twice)
- Consolidate all progress state reset logic into rebuild_spv_progress_state()

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* build: add git and cargo permissions to Claude Code workflow (#565)

* build: add git and cargo permissions to Claude Code workflow

Allow Claude to run git fetch/merge/checkout/rebase/push and
cargo build/test/clippy/fmt commands. Switch model to opus.

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

* fix: use claude_args for model and allowed tools

The `model` and `allowed_tools` inputs are not declared in
claude-code-action@v1 and are silently ignored. Move them to
`claude_args` with --model and --allowedTools flags. Also fix
deprecated colon syntax (`:*`) to space syntax (` *`).

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

* fix: use single quotes to prevent glob expansion in allowed tools

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

* build: sandbox cargo commands and add git permissions for Claude

- Add safe-cargo.sh wrapper that strips CI secrets before running cargo
- Use --allowedTools for git and safe-cargo, --disallowedTools for raw cargo
- Document safe-cargo usage in CLAUDE.md

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

* fix: switch safe-cargo.sh from denylist to allowlist approach

Use `env -i` (start with empty environment, explicitly pass only
what cargo needs) instead of `env -u` (strip known secrets). This
is more robust against future secrets being added to the workflow.

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

* build: deny Claude from editing CI scripts and workflows

Prevent Claude from modifying .github/scripts/ and .github/workflows/
to ensure the safe-cargo wrapper cannot be tampered with.

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

* fix: conditionally pass optional env vars in safe-cargo.sh

Empty PROTOC="" caused prost build scripts to fail with
"protoc not found". Now optional vars (PROTOC, CC, CXX, etc.)
are only passed when set and non-empty.

Tested: build, test, fmt all pass through the wrapper.

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

* rabbit feedback

* build: move safe-cargo.sh to scripts/ and allow +nightly fmt

Move safe-cargo.sh from .github/scripts/ to top-level scripts/ for
better discoverability. Add detailed comment explaining why the wrapper
exists (prevent CI secret exfiltration via build scripts). Update all
references in claude.yml, CLAUDE.md, and permission settings. Add
`+nightly fmt` to allowedTools so Claude can follow CLAUDE.md
formatting instructions in CI.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>
Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* chore: bump rust-dashcore

* chore: imports

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Pasta Lil Claw <pasta+claw@dashboost.org>
Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>
PastaPastaPasta added a commit that referenced this pull request Feb 17, 2026
* fix: skip best chain lock polling in SPV mode (#567)

* Initial plan

* Skip chain lock refresh in SPV mode

Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

* Document SPV guard intent

Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

* fix: compute relative timestamps from actual data (#581)

* fix: compute relative timestamps from actual data

Replace hardcoded display strings like "Received: 1 day ago" with
real relative timestamps computed from document createdAt/updatedAt
fields using chrono::Utc::now().

Fixes #579

* style: fix import ordering per cargo fmt

* refactor: extract format_relative_time to shared dashpay module

Deduplicate the identical format_relative_time function that existed
in both contact_requests.rs and send_payment.rs. Move it to the
dashpay mod.rs as a pub(crate) function and import from both files.

---------

Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>

* fix: update platform for DIP-18 HRP and improve SPV sync progress (#575)

* fix: update dashpay/platform to d6f4eb9 for DIP-18 HRP fix

The previous platform revision used incorrect bech32m HRP prefixes
(evo/tevo) for Platform addresses. The updated commit uses the
correct DIP-0018 prefixes (dash/tdash).

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

* refactor(spv): use dash-spv SyncProgress API directly

Replace the intermediate SyncProgress/DetailedSyncProgress/SyncStage
translation layer with direct use of dash_spv::sync::SyncProgress.
This eliminates ~120 lines of bridge code in spawn_progress_watcher()
and determine_sync_stage(), and lets the UI query per-manager progress
(headers, filter_headers, filters, masternodes, blocks) via the
upstream API.

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

* fix(spv): improve sync progress accuracy and robustness

- Log headers() error before discarding for easier debugging
- Restore download-window optimization for headers progress on
  checkpoint-resumed syncs (progress starts near 0% not 83%)
- Replace catch-all _ => 0.0 with explicit SyncState variant matches
  so new variants produce compile errors
- Fix filters progress to use current_height/target_height instead of
  downloaded/target_height (session count vs absolute height mismatch)
- Restore peer count in sync status text
- Add "Querying peer heights" label and diffs_processed to masternode
  status for more informative sync messages

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

* fix(spv): use height-based blocks progress to prevent bar from jumping

The blocks progress bar was bouncing backward because it used
processed/requested session counters whose denominator grows as filters
discover more matching blocks. Switch to last_processed block height
relative to headers target_height, which only increases. Display
"current / target" heights instead of percentage on the blocks bar.

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

* fix(spv): improve progress bar resilience across resume and network switch

- Add windowed progress tracking for filters (consistent with headers/filter_headers)
- Reset blocks_stage_start on Error state so recovery gets a fresh window
- Track spv_progress_network to detect network changes and rebuild progress
  state from the new network's sync_progress instead of resetting to zero
- Eliminate redundant clone in progress watcher (move instead of clone twice)
- Consolidate all progress state reset logic into rebuild_spv_progress_state()

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* build: add git and cargo permissions to Claude Code workflow (#565)

* build: add git and cargo permissions to Claude Code workflow

Allow Claude to run git fetch/merge/checkout/rebase/push and
cargo build/test/clippy/fmt commands. Switch model to opus.

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

* fix: use claude_args for model and allowed tools

The `model` and `allowed_tools` inputs are not declared in
claude-code-action@v1 and are silently ignored. Move them to
`claude_args` with --model and --allowedTools flags. Also fix
deprecated colon syntax (`:*`) to space syntax (` *`).

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

* fix: use single quotes to prevent glob expansion in allowed tools

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

* build: sandbox cargo commands and add git permissions for Claude

- Add safe-cargo.sh wrapper that strips CI secrets before running cargo
- Use --allowedTools for git and safe-cargo, --disallowedTools for raw cargo
- Document safe-cargo usage in CLAUDE.md

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

* fix: switch safe-cargo.sh from denylist to allowlist approach

Use `env -i` (start with empty environment, explicitly pass only
what cargo needs) instead of `env -u` (strip known secrets). This
is more robust against future secrets being added to the workflow.

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

* build: deny Claude from editing CI scripts and workflows

Prevent Claude from modifying .github/scripts/ and .github/workflows/
to ensure the safe-cargo wrapper cannot be tampered with.

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

* fix: conditionally pass optional env vars in safe-cargo.sh

Empty PROTOC="" caused prost build scripts to fail with
"protoc not found". Now optional vars (PROTOC, CC, CXX, etc.)
are only passed when set and non-empty.

Tested: build, test, fmt all pass through the wrapper.

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

* rabbit feedback

* build: move safe-cargo.sh to scripts/ and allow +nightly fmt

Move safe-cargo.sh from .github/scripts/ to top-level scripts/ for
better discoverability. Add detailed comment explaining why the wrapper
exists (prevent CI secret exfiltration via build scripts). Update all
references in claude.yml, CLAUDE.md, and permission settings. Add
`+nightly fmt` to allowedTools so Claude can follow CLAUDE.md
formatting instructions in CI.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: handle errors instead of panicking on corrupted database blobs (#561)

* fix: return error instead of panicking on corrupted database blobs (#560)

- Replace `unreachable!()` in `get_scheduled_votes` with warning log and
  default to false for unexpected `executed` column values
- Change `QualifiedIdentity::from_bytes()` to return `Result` instead of
  panicking via `.expect()`
- Propagate deserialization errors as `rusqlite::Error` in all 6 callers
  so corrupted database is surfaced to the user rather than silently
  ignored or crashing the app
- Add `CorruptedBlobError` newtype in database module to eliminate
  repeated `FromSqlConversionFailure` boilerplate

Closes #560

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

* chore: impl thiserror

* chore: fmt

* build: add Claude Code GitHub workflow and settings (#552)

Cherry-pick from v1.0-dev to enable @claude mentions in PRs.

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

* chore: fix after merge

* fix: skip corrupted identity blobs in get_wallets instead of aborting

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

* chore: fail on corrupted identity

* doc: document error handling in the db

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* feat: track DAPI endpoint availability in connection status (#533)

* fix: connection status not updated

* chore: rabbit feedback

* Initial plan

* Track DAPI connection status and display in tooltips and connection info

- Add dapi_total_endpoints and dapi_available fields to ConnectionStatus
- Factor DAPI availability into overall_connected status (RED when no endpoints available)
- Query SDK AddressList during periodic refresh for endpoint counts and availability
- Display DAPI status in connection indicator tooltip
- Display DAPI status in network chooser Connection Status card (all modes)
- Add dapi_status_label() helper for consistent status text formatting

Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

* Address review feedback: store available endpoint count and extract DRY helper

- Changed dapi_available from AtomicBool to AtomicU16 (dapi_available_endpoints) to store
  the count of available endpoints instead of just a boolean
- Display format now shows "Available ({available}/{total} endpoints)"
- Extracted repeated DAPI status rendering into add_dapi_status_label() helper function
  in network_chooser_screen.rs to eliminate code duplication

Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

* Fix rustfmt formatting issues in connection_status.rs

- Reorder atomic imports (AtomicU8 before AtomicU16) per rustfmt
- Wrap long .store() call to respect line length
- Wrap long format!() strings to respect line length

Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

* Fix borrow checker error: extract DAPI status values before mutable self borrow

The add_dapi_status_label helper was capturing a &ConnectionStatus reference
(derived from self) in closures, which extended the immutable borrow past the
self.render_spv_sync_progress() mutable borrow on line 594.

Fix: change add_dapi_status_label to accept pre-computed owned values
(dapi_total, dapi_available, dapi_label) instead of &ConnectionStatus, and
extract those values early alongside other status fields.

Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

* Switch Platform to c2c88e4 and use get_live_addresses() for accurate available count

Updated dash-sdk dependency to Platform commit c2c88e4a988ce930 which adds
AddressList::get_live_addresses() method. Replaced the workaround that used
get_live_address().is_some() (which could only tell if at least one endpoint
was live) with get_live_addresses().len() to get the exact count of available
non-banned DAPI endpoints.

Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

* chore: typo + network changes

* chore: apply feedback

* chore: rabbit review

* chore: rabbit feedback

* chore: rabbitting

* Remove overall_connected_with method (deleted upstream in base branch)

Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

* Merge v1.0-dev into copilot/update-dapi-connection-status

Resolved modify/delete conflict on src/context.rs: removed the file
since v1.0-dev refactored it into src/context/mod.rs and submodules,
which already include our Arc<ConnectionStatus> changes.

Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>

* chore: fix platfom versioning issues

* chore: add todo

* chore: fmt

---------

Co-authored-by: Lukasz Klimek <842586+lklimek@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

* refactor(spv): eliminate separate thread/runtime, improve shutdown and UX (#577)

* fix: update dashpay/platform to d6f4eb9 for DIP-18 HRP fix

The previous platform revision used incorrect bech32m HRP prefixes
(evo/tevo) for Platform addresses. The updated commit uses the
correct DIP-0018 prefixes (dash/tdash).

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

* refactor(spv): use dash-spv SyncProgress API directly

Replace the intermediate SyncProgress/DetailedSyncProgress/SyncStage
translation layer with direct use of dash_spv::sync::SyncProgress.
This eliminates ~120 lines of bridge code in spawn_progress_watcher()
and determine_sync_stage(), and lets the UI query per-manager progress
(headers, filter_headers, filters, masternodes, blocks) via the
upstream API.

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

* fix(spv): improve sync progress accuracy and robustness

- Log headers() error before discarding for easier debugging
- Restore download-window optimization for headers progress on
  checkpoint-resumed syncs (progress starts near 0% not 83%)
- Replace catch-all _ => 0.0 with explicit SyncState variant matches
  so new variants produce compile errors
- Fix filters progress to use current_height/target_height instead of
  downloaded/target_height (session count vs absolute height mismatch)
- Restore peer count in sync status text
- Add "Querying peer heights" label and diffs_processed to masternode
  status for more informative sync messages

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

* fix(spv): use height-based blocks progress to prevent bar from jumping

The blocks progress bar was bouncing backward because it used
processed/requested session counters whose denominator grows as filters
discover more matching blocks. Switch to last_processed block height
relative to headers target_height, which only increases. Display
"current / target" heights instead of percentage on the blocks bar.

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

* fix(spv): improve progress bar resilience across resume and network switch

- Add windowed progress tracking for filters (consistent with headers/filter_headers)
- Reset blocks_stage_start on Error state so recovery gets a fresh window
- Track spv_progress_network to detect network changes and rebuild progress
  state from the new network's sync_progress instead of resetting to zero
- Eliminate redundant clone in progress watcher (move instead of clone twice)
- Consolidate all progress state reset logic into rebuild_spv_progress_state()

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

* fix(spv): update ConnectionStatus immediately on connect/disconnect

Propagate SPV status into ConnectionStatus right after start_spv() and
stop_spv() so the UI reflects the change on the next frame instead of
waiting for the next throttled trigger_refresh() cycle (2-10 seconds).

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

* fix(spv): check cancellation token in listener tasks to prevent shutdown hang

SPV finality and reconcile listeners blocked app shutdown for up to 10s
because they never checked the global cancellation token. Add cancel
branches to their tokio::select! loops so they exit immediately on
shutdown.

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

* chore: remove log spamming line

* fix(spv): make listener handler calls cancellation-aware and add shutdown trace logging

Wrap reconcile_spv_wallets(), handle_spv_finality_event(), and the
debounce sleep inside tokio::select! with the cancellation token so
shutdown can interrupt them even when blocked on locks held by the
SPV sync thread.

Add trace-level logging to TaskManager::shutdown() for per-task join
timing to aid future shutdown diagnostics.

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

* fix(spv): disable Disconnect button during stopping and poll status faster

- Disable the Disconnect button while SPV status is Stopping to prevent
  double-clicks and provide visual feedback
- Poll SPV status every 200ms during Stopping instead of the 10s
  connected interval so the Stopped transition is reflected within 1s
- Reset the throttle timer in stop_spv() so fast polling starts
  immediately

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

* refactor(tasks): add mandatory task names to spawn_sync for shutdown diagnostics

Change spawn_sync() signature to require a &'static str name. The
JoinSet now yields the task name on completion, letting shutdown()
log which tasks finished and which ones timed out.

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

* chore: log task names

* refactor(spv): eliminate separate OS thread and tokio runtime

Replace the dedicated SPV thread + 4-worker tokio runtime with a
spawned task on the main 12-worker runtime via TaskManager::spawn_sync.

This simplifies shutdown (SPV loop now tracked in unified JoinSet),
removes cross-runtime complexity, and improves debuggability.

Additional changes:
- Fix: zeroize xprv_str after wallet import (security H-1)
- Fix: sanitize devnet_name in build_spv_data_dir to prevent path traversal
- Add 21 integration tests covering lifecycle, concurrency, deadlock
  detection, and live testnet sync

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

* fix(spv): derive stop_token as child of global cancel for clean shutdown

The spv_request_handler selected on stop_token, which was independent
of the global cancellation token.  During window-close shutdown only
the global token was cancelled, leaving the request handler running
and causing a ~5s hang until the TaskManager timeout aborted it.

Fix by creating stop_token as a child_token() of the global cancel.
This also simplifies run_spv_loop and run_sync_and_monitor by removing
the redundant global_cancel parameter — a single stop_token now covers
both explicit SpvManager::stop() and application-wide shutdown.

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

* CLAUDE: minor changes about docs

* fix(spv): bump rust-dashcore to d8bc066 for faster disconnect and add shutdown tracing

Pin rust-dashcore patches to commit d8bc066 which includes:
- sync manager task loop exits on network errors instead of logging indefinitely
- sync coordinator signal_shutdown() cancels tasks before network disconnect
- connection tasks race Peer::connect() against shutdown token

Add debug tracing to SpvManager::stop() and run_sync_and_monitor() to
measure client.stop() duration, and trace-level polling of SPV status
in ConnectionStatus.

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

* chore: update rust-dashcore rev

* merge: resolve v1.0-dev conflicts for #577 (#585)

* fix: compute relative timestamps from actual data (#581)

* fix: compute relative timestamps from actual data

Replace hardcoded display strings like "Received: 1 day ago" with
real relative timestamps computed from document createdAt/updatedAt
fields using chrono::Utc::now().

Fixes #579

* style: fix import ordering per cargo fmt

* refactor: extract format_relative_time to shared dashpay module

Deduplicate the identical format_relative_time function that existed
in both contact_requests.rs and send_payment.rs. Move it to the
dashpay mod.rs as a pub(crate) function and import from both files.

---------

Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>

* fix: update platform for DIP-18 HRP and improve SPV sync progress (#575)

* fix: update dashpay/platform to d6f4eb9 for DIP-18 HRP fix

The previous platform revision used incorrect bech32m HRP prefixes
(evo/tevo) for Platform addresses. The updated commit uses the
correct DIP-0018 prefixes (dash/tdash).

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

* refactor(spv): use dash-spv SyncProgress API directly

Replace the intermediate SyncProgress/DetailedSyncProgress/SyncStage
translation layer with direct use of dash_spv::sync::SyncProgress.
This eliminates ~120 lines of bridge code in spawn_progress_watcher()
and determine_sync_stage(), and lets the UI query per-manager progress
(headers, filter_headers, filters, masternodes, blocks) via the
upstream API.

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

* fix(spv): improve sync progress accuracy and robustness

- Log headers() error before discarding for easier debugging
- Restore download-window optimization for headers progress on
  checkpoint-resumed syncs (progress starts near 0% not 83%)
- Replace catch-all _ => 0.0 with explicit SyncState variant matches
  so new variants produce compile errors
- Fix filters progress to use current_height/target_height instead of
  downloaded/target_height (session count vs absolute height mismatch)
- Restore peer count in sync status text
- Add "Querying peer heights" label and diffs_processed to masternode
  status for more informative sync messages

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

* fix(spv): use height-based blocks progress to prevent bar from jumping

The blocks progress bar was bouncing backward because it used
processed/requested session counters whose denominator grows as filters
discover more matching blocks. Switch to last_processed block height
relative to headers target_height, which only increases. Display
"current / target" heights instead of percentage on the blocks bar.

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

* fix(spv): improve progress bar resilience across resume and network switch

- Add windowed progress tracking for filters (consistent with headers/filter_headers)
- Reset blocks_stage_start on Error state so recovery gets a fresh window
- Track spv_progress_network to detect network changes and rebuild progress
  state from the new network's sync_progress instead of resetting to zero
- Eliminate redundant clone in progress watcher (move instead of clone twice)
- Consolidate all progress state reset logic into rebuild_spv_progress_state()

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* build: add git and cargo permissions to Claude Code workflow (#565)

* build: add git and cargo permissions to Claude Code workflow

Allow Claude to run git fetch/merge/checkout/rebase/push and
cargo build/test/clippy/fmt commands. Switch model to opus.

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

* fix: use claude_args for model and allowed tools

The `model` and `allowed_tools` inputs are not declared in
claude-code-action@v1 and are silently ignored. Move them to
`claude_args` with --model and --allowedTools flags. Also fix
deprecated colon syntax (`:*`) to space syntax (` *`).

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

* fix: use single quotes to prevent glob expansion in allowed tools

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

* build: sandbox cargo commands and add git permissions for Claude

- Add safe-cargo.sh wrapper that strips CI secrets before running cargo
- Use --allowedTools for git and safe-cargo, --disallowedTools for raw cargo
- Document safe-cargo usage in CLAUDE.md

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

* fix: switch safe-cargo.sh from denylist to allowlist approach

Use `env -i` (start with empty environment, explicitly pass only
what cargo needs) instead of `env -u` (strip known secrets). This
is more robust against future secrets being added to the workflow.

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

* build: deny Claude from editing CI scripts and workflows

Prevent Claude from modifying .github/scripts/ and .github/workflows/
to ensure the safe-cargo wrapper cannot be tampered with.

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

* fix: conditionally pass optional env vars in safe-cargo.sh

Empty PROTOC="" caused prost build scripts to fail with
"protoc not found". Now optional vars (PROTOC, CC, CXX, etc.)
are only passed when set and non-empty.

Tested: build, test, fmt all pass through the wrapper.

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

* rabbit feedback

* build: move safe-cargo.sh to scripts/ and allow +nightly fmt

Move safe-cargo.sh from .github/scripts/ to top-level scripts/ for
better discoverability. Add detailed comment explaining why the wrapper
exists (prevent CI secret exfiltration via build scripts). Update all
references in claude.yml, CLAUDE.md, and permission settings. Add
`+nightly fmt` to allowedTools so Claude can follow CLAUDE.md
formatting instructions in CI.

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

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>
Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* chore: bump rust-dashcore

* chore: imports

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Pasta Lil Claw <pasta+claw@dashboost.org>
Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com>

* ci: cancel in-progress workflow runs on new push (#590)

* ci: cancel in-progress workflow runs on new push

Add concurrency groups to Tests and Clippy workflows so that
previous runs are automatically cancelled when a new commit is
pushed to the same branch or PR.

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

* trigger ci

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: use correct DashPay profile field "publicMessage" instead of "bio" (#582)

The DashPay contract schema defines the field as "publicMessage",
not "bio". This caused profile bios to never load in contact info.

Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>

---------

Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: lklimek <842586+lklimek@users.noreply.github.com>
Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.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.

fix: hardcoded timestamps and wrong profile field name in DashPay screens

2 participants