Commit Graph
625 Commits
Author SHA1 Message Date
DanSandClaude Opus 4.8 2fdf33f99b feat(chat): "Request" pill on unsolicited conversations in the list
The conversation list now marks a pending stranger request with a small
"Request" pill (bottom-right of the row), so it's distinguishable before you
open it; the preview text is clipped to the pill's left so they don't overlap.
Completes the request-affordance pair with the in-thread Accept/Block card.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 18:27:44 -05:00
DanSandClaude Opus 4.8 092a802959 feat(balance): pending chip in the Donut, Shield and Timeline layouts too
Extends the labeled "Pending +X" chip (with its awaiting-confirmations tooltip)
to three more layouts via a shared DrawPendingChip() helper. It draws on the
ticker line (the USD value sits on the line below in these layouts, so there's no
overlap) and is skipped if it would overflow the card. The remaining draw-list
layouts with right-aligned content (Consolidated, Minimal, the grid variants)
are left for a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 18:27:44 -05:00
DanSandClaude Opus 4.8 e9eeaf00c3 feat(chat): Accept/Block card for unsolicited contact requests
An unsolicited first message from a stranger (an incoming ContactRequest, with
nothing sent by us and the peer not in contacts) now shows an Accept / Block
card above the thread instead of looking like a normal conversation. Accept adds
the peer to the address book (which clears the request state); Block deletes and
blocks the conversation (reversible from the Blocked manager). ConvSummary gains
a derived pendingRequest flag. (The list-row "Request" pill and a header Block
button were left out to avoid the fragile hand-drawn header-toolbar geometry.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 13:12:57 -05:00
DanSandClaude Opus 4.8 572b98cbcf feat(shield): pick the mining address from a dropdown + show a confirm preview
Shield-coinbase replaced its free-text "*"-wildcard address box with a dropdown
of the wallet's mining (transparent) addresses and their balances ("All mining
addresses" default), and the confirm step now previews "Shield up to X DRGX from
N mining address(es)… to <label>? Network fee: Y" instead of only relabeling the
button. Also blocks submitting from an empty source or one with no funds. The
z_shieldcoinbase call (args/fee/limit/destination) is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 13:09:41 -05:00
DanSandClaude Opus 4.8 bcfc324426 feat(balance): labeled "Pending +X" chip with tooltip in more layouts
The unconfirmed balance was shown in only one layout as an unlabeled "+X" that
read as a mystery. It's now a labeled "Pending +X" chip with an "awaiting
confirmations" tooltip — the Classic card's badge is relabeled, and the shared
compact hero (Dashboard + Two-row layouts) now draws it too. (The remaining
hand-drawn layouts can reuse the same pattern as a follow-up.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 13:07:43 -05:00
DanSandClaude Opus 4.8 7cea9b7339 feat(bootstrap,validate): Retry keeps the mirror; Validate works offline
- Bootstrap: Retry now reuses the source the user actually started (was always
  the default), and a "Try mirror" button offers the alternate server on
  failure (s_lastUrl remembered on start, cleared on a fresh Confirm).
- Validate Address: classification + checksum are local, so the dialog no
  longer requires a node connection — offline/lite now validate and show the
  type, with ownership reported as "Unknown — node offline / not checkable in
  the lite wallet" instead of a dead "not connected" screen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 13:05:40 -05:00
DanSandClaude Opus 4.8 f3f0b32a48 feat(ux): robust sync ETA, persist Explorer settings, gate export-all out of lite
- Sync ETA: a new pure util::SyncRateEstimator (App-owned) replaces the
  function-local statics that went stale after a rescan/restart (producing the
  "412h" ETA). It self-resets on a height drop and on connect/disconnect/rescan/
  restart (via an atomic flag, since onConnected can run off-thread), formats
  with days, and uses the Warning() token.
- Settings: the Explorer-tab URL fields + custom-fees/fetch-prices checkboxes
  now persist on edit — they were the only controls still relying on the
  About-only Save button that a user on this tab never sees.
- Export-all-keys: hidden in the lite build (which has its own key export),
  instead of dead-ending on "Not connected to the daemon".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 13:02:46 -05:00
DanSandClaude Opus 4.8 8fe20f6385 feat(mining): persistent start/stop error caption + timeout-aware pool status
- A mining start/stop/pool failure now sets a persistent App::mining_last_error_
  (cleared on the next attempt), rendered as a red caption with "Press Mine to
  try again" below the Mine button (plus a Dismiss, and a Windows-Defender
  "How to fix" on an AV block), so a crashed/blocked/failed miner is no longer
  just a button that "did nothing" once its toast faded. Two more hardcoded
  English strings (pool-payout-invalid, AV-blocked) moved to i18n.
- Pool connection status is now a persistent "Status" row in the pool stats
  card (PoolMiningState.running_since / last_error): it flips from "Connecting…"
  to "Can't reach pool — check the URL" after a 30s timeout instead of sitting
  on red forever, and no longer vanishes once logs arrive. The old transient
  banner in mining_tab.cpp was removed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 12:09:49 -05:00
DanSandClaude Opus 4.8 15c1181aef feat(shield): colour shield/merge status by outcome + spinner; friendly errors
The shield/merge status line was amber only while in-flight and uncoloured for
every terminal state (success, failure, uncertain). It now tracks an OpOutcome
(display-only) and colours the terminal line Success/Error/Warning accordingly,
shows a LoadingDots spinner while the 120s scan runs, and maps the raw daemon
error through friendlyRpcError. The actual shield/merge submit logic is
unchanged (no "Try again" that could double-submit a fund move).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 11:55:40 -05:00
DanSandClaude Opus 4.8 0101cc707c feat(network): a clear auth-failed node banner with a fix-it action
When the node rejects the wallet's RPC login (bad rpcuser/rpcpassword), the
status bar showed developer jargon ("Auth failed — check rpcuser/rpcpassword")
and the only action was Reconnect, which just repeats the failure. Now a
dedicated AuthFailed banner reason surfaces "Wallet can't sign in to the node"
with a plain-language detail and a real fix: Restart node for the embedded
daemon (regenerates its cookie creds) or Open config folder for an external one
(to fix DRAGONX.conf). Driven by a new rpc_auth_failed_ flag (set on the auth
path, cleared on each connect attempt), with evaluator unit tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 11:53:29 -05:00
DanSandClaude Opus 4.8 adf61e43d6 feat(transfer): add Send-tab guards, a correct TxID label + copy, inline reasons
The address-transfer dialog (own-address moves) enabled Confirm on amount alone,
so a disconnected / syncing / locked wallet failed only at submit. It now
mirrors the Send tab's guards (strictly more restrictive — the transfer logic is
unchanged) and re-checks live state before broadcast, shows an inline reason
when blocked (enter amount / not connected / syncing / locked), and labels the
result as a "Transaction ID" (not "Operation ID") with a copy button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 11:44:19 -05:00
DanSandClaude Opus 4.8 fb281714f9 feat(i18n,mining): translate warmup block/tx-type labels; fix the disconnected-solo mine tooltip
- Warmup/daemon status no longer appends a hardcoded English " (Block N)" — it
  uses a translated warmup_block_fmt key.
- Recent-transaction type labels (Sent/Received/Mined) are now translated in the
  UI consumer (recentTypeLabel in balance_recent_tx.cpp) with a generic
  "Transaction" fallback, instead of the data layer's raw English getTypeDisplay().
- Mining: a disconnected solo miner hovering the dead Mine button now gets a
  "not connected to the node" tooltip instead of a misleading "Start Mining"
  (the tooltip chain had no branch for the solo !isConnected() disable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 11:42:29 -05:00
DanSandClaude Opus 4.8 74c497922e fix(hidpi): scale security-dialog buttons, market caption, QR inset by dpiScale
HiDPI geometry debt from the audit: 15 security-dialog buttons used a raw 40px
height (undersized vs the scaled label at 1.5x), the market "Updated" caption
used a raw SameLine(0,12), and the QR popup's 28px overlay-inset budget wasn't
scaled (wrapping the address at HiDPI). All now go through ui::Layout::dpiScale().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 11:39:45 -05:00
DanSandClaude Opus 4.8 ae16c06d24 feat(contacts,chat): a "Message" action linking Contacts to Chat
- Contacts: a "Message" context-menu item for shielded (z) contacts hands the
  address to the Chat tab (new OpenChatNewConversation entry point) and switches
  there with the New-conversation dialog prefilled; disabled with a reason for
  transparent-only contacts. (Kept to the safe context-menu + picker path, not
  the hand-drawn 4-slot row action cluster, to avoid mis-wiring the destructive
  Delete slot.)
- Chat: the New-conversation picker no longer silently hides transparent
  contacts — they're shown disabled with a "needs a shielded address to chat"
  tooltip instead of vanishing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 09:52:21 -05:00
DanSandClaude Opus 4.8 408393a547 feat(chat): add a "Copy my chat address" button
The conversation header only copies a PEER's address, so a user had no way to
hand out their own chat address for others to message them. Add a "Copy my chat
address" link in the left pane (public App::chatMyAddress() forwards the stable
seed-derived reply z-address; copied on click only, with an "address copied"
toast and a warning if none exists yet).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 09:45:03 -05:00
DanSandClaude Opus 4.8 a80be443d1 feat(market): flag a stale price instead of showing it as a fresh green number
The aggregate price is deliberately kept on a failed or old refresh (it's never
cleared), so the hero number could be hours out of date while still drawn in the
"live" green. It now turns amber with an "as of N min/h ago" caption and a hover
tooltip carrying the friendly fetch error, when the last refresh failed or the
aggregate is >=5 min old (only when the hero is the aggregate, not an exchange
pair with its own feed). The hardcoded "Price fetch failed" default is now i18n.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 09:42:34 -05:00
DanSandClaude Opus 4.8 a792a1ec0c feat(ux): clearer chat delivery, explorer empty/range states, honest settings label
Three next-batch UX-audit fixes:
- Chat delivery: the sending/sent glyph has higher contrast and a tooltip
  ("Sent to the network — arrives after it confirms", so a check isn't mistaken
  for "delivered"); a failed send now carries the real reason (ChatMessage::
  fail_reason, set from onChatBroadcastResult via friendlyRpcError, in-memory
  only) in the "not sent" hover tooltip.
- Explorer: a block height beyond the tip says so ("Block N is beyond the chain
  tip (M)") instead of silently snapping to the top; the empty block list shows
  Not connected / Syncing (N behind) / Loading instead of an endless "Loading".
- Settings: the misleading "Reset to Defaults" button (which only reloads the
  last-saved values) is relabeled "Discard unsaved changes" with accurate copy.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 09:20:47 -05:00
DanSandClaude Opus 4.8 a11130769d feat(send): show fee + total on the compose form; fix the stuck confirm on recheck
- The network fee and running total are now shown on the compose form (once an
  amount is entered), instead of only being revealed in the confirm popup.
- When the pre-broadcast re-validation fails (balance dropped, sync restarted,
  fee pushed total over available), the confirm popup now closes and surfaces a
  warning toast — previously the reason was drawn on the form behind the still-
  open popup, so the user was left re-clicking a button that did nothing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 09:14:44 -05:00
DanSandClaude Opus 4.8 4c6a78b3b0 fix(security): color seed-save by outcome; explain the Encrypt button; friendly key-export errors
- Seed-backup dialog: a failed "Save to file" was drawn in the success (green)
  colour; it's now red on failure, green on success (flag reset on close).
- Encrypt-wallet dialog: the disabled Encrypt button now says why once the user
  starts typing ("Use at least 8 characters." / "The passphrases don't match."),
  and a "Show passphrase" checkbox reveals both fields.
- Key-export dialog: the raw RPC exception (e.g. "...walletpassphrase first")
  is routed through util::friendlyRpcError(), so a locked wallet reads
  "Unlock your wallet first, then try again." instead of daemon jargon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 09:09:29 -05:00
DanSandClaude Opus 4.8 7d2972f315 feat(mining): explain solo/pool, add a payout-address create path, surface the miner download
New-user mining cluster from the UX audit:
- Solo/Pool toggle: hover tooltips on each half explaining the trade-off
  (solo = no fees but long waits on a small CPU; pool = frequent small payouts,
  needs the miner + a shielded payout address).
- Payout field: when it's empty (new wallets have no z-address, and a pool
  payout must be shielded) the bookmark button is replaced by a one-click
  "Generate a shielded payout address" button (createNewZAddress → fills the
  field, persists, toasts), instead of only a hover hint to the Receive tab.
- Miner updater pill: when no miner is installed it becomes a prominent accent
  "Download miner" call-to-action with a "pool mining needs DRG-XMRig" tooltip,
  instead of a subtle grey "Update" pill that's easy to miss.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 09:03:24 -05:00
DanSandClaude Opus 4.8 8eee351faa fix(history): show shielding moves as neutral "Shielded", not red "Sent"
A coinbase/transparent→shielded move (often auto-shield) was drawn with a red
"Sent" label and send arrow yet a green "+X" amount — an internally
contradictory row that reads as "money leaving" to anyone who auto-shielded. It
now renders as a neutral "Shielded" row (shield icon + the shielded accent
colour for label and amount), and since a shield is your own funds going
private — not a spend — it's excluded from the Sent card total and the Sent
filter (it merges into one row visible under "All").

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 08:57:38 -05:00
DanSandClaude Opus 4.8 9db73323b6 feat(ui): friendly RPC errors + pause-on-hover/ellipsis toasts
Two of the systemic error-surfacing foundations from the UX audit:

- util::friendlyRpcError() maps the common raw daemon/JSON-RPC errors
  (warming up / -28, locked / -13, method-not-found / -32601, insufficient
  funds, unreachable node) to short translated guidance with a next step,
  falling back to the trimmed raw text. Wired into the send (money) path: the
  toast shows the friendly cause, the overlay keeps the raw text below it for
  diagnostics. The helper is reusable for the remaining raw-error sites.
- Toasts now pause their auto-dismiss while hovered and ellipsize over-long
  text with the full message in a hover tooltip, instead of hard-clipping it
  mid-word (durations were already lengthened earlier).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-17 16:53:37 -05:00
DanSandClaude Opus 4.8 4a8d07edce feat(balance): show a "catching up — funds are safe" strip while syncing
A syncing node skips the balance RPC, so the balance card paints a bare
0.00000000 / $-.-- that reads as lost funds after an update (a documented
support trap). Add one shared notice at the top of the Balance tab — above
every layout — shown only for the normal catching-up case (connected, network
tip known, still verifying, not a rescan/witness rebuild). Offline, no-peers,
a busy node and rescans stay the node-status banner's job, so the two never
stack.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-17 16:48:33 -05:00
DanSandClaude Opus 4.8 643ac7daef feat(seed,console): persistent re-nudging seed backup; confirm destructive console commands
- Seed backup: the one-time 12s toast that marked itself "reminded" the instant
  it fired (so a funded, unbacked wallet was never nudged again) is replaced by
  an actionable alert with a "Back up now" link that opens the backup dialog and
  persists in the bell panel. "Reminded" now means the user actually viewed the
  phrase (set from the backup dialog), so the nudge re-fires each session until
  they do, and escalates to a warning once a balance exists.
- Console: fund-moving / key-revealing commands (dumpprivkey, z_exportkey,
  z_sendmany, z_mergetoaddress, …) now require a confirming re-entry when typed
  or PASTED, honoring the command reference's `destructive` flag that was
  previously enforced only by the reference dialog's Run button. (Output
  masking of key dumps in the scrollback is still a follow-up.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-17 16:46:09 -05:00
DanSandClaude Opus 4.8 e27d88eca4 feat(chat): explain the no-identity state and why a send is stuck
- Chat tab no longer shows "Unlock your wallet" for every missing-identity
  case. A new App::chatIdentityStatus() distinguishes Locked (real),
  Preparing (unlocked, deriving the identity) and Unavailable (view-only or a
  legacy wallet with no exportable secret — correctly latched). Each gets its
  own icon + message, and the Unavailable prompt points at Back up / Migrate
  instead of telling the user to unlock an already-unlocked wallet.
- A chat message that sits queued ("Sending") because the wallet is offline or
  has no shielded funds for the fee now surfaces the reason (throttled, after a
  short dwell), wiring the ready-made chat_toast_not_connected /
  chat_toast_need_funds strings that were never shown. The send stays queued
  and goes out when the condition clears.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-17 16:41:30 -05:00
DanSandClaude Opus 4.8 6efe3c0f5c feat(send,peers): show full recipient + contact label on confirm; actionable peers empty state
- send confirm: the FROM/TO cards now show the FULL address (wrapped) instead
  of a middle-truncated preview, and the saved-contact label when the address
  matches the address book ("TO  ·  Alice") — the strongest human check against
  a swapped recipient on the one screen meant to catch it. Cards auto-grow.
- peers tab: the "No connected peers" empty state now carries the same
  actionable hint as the node-status banner instead of being a bare dead-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-17 16:37:28 -05:00
DanSandClaude Opus 4.8 750771ebfb feat(network): give the "no peers" banner an honest detail and a Restart action
The no-peers banner headline ("Connected, but no network peers") was followed
by a detail line that just repeated connection_status ("Connected"/"Peers: 0"),
reading as a contradiction, and it offered no action at the exact moment the
wallet looks synced but is silently stalled at a stale height.

- NoPeers now clears the passthrough detail; App renders a translated,
  actionable hint (node_banner_no_peers_detail) for this reason instead.
- When the embedded daemon is the node, offer a Restart button (re-triggers
  peer discovery via the -addnode seeds); an external node stays action-less
  since it is the user's own to restart.
- Update + extend the evaluator unit tests (external = no action/empty detail;
  embedded = RestartNode).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-17 16:33:17 -05:00
DanSandClaude Opus 4.8 a99f71a0cd fix(ux): land quick-win UX audit fixes across settings, exports, toasts, nav
Batch of safe, isolated fixes from the 10-surface UX audit:

- settings: Auto-shield / Save-z-transactions toggles now persist on change
  (they discarded CB()'s return and relied on a Save button that only renders
  on the About tab, so the choice was silently lost on relaunch).
- exports: show the resolved Platform::getConfigDir() instead of a hardcoded
  ~/.config path (wrong on Windows/macOS) in both the all-keys and CSV
  dialogs; all-keys also reveals the file path on a partial export.
- notifications: error/warning toasts last 9s/7s (were 4s/3.5s) so failures
  are readable.
- qr popup / tx-details: copy-to-clipboard now confirms with a toast.
- about: Escape-to-close + readable heading contrast via OnSurfaceMedium().
- sidebar: suppress single-key nav (J/K/arrows/[) while an overlay dialog or
  ImGui popup is open, so it no longer switches the tab behind a modal.
- i18n: strip the stale hardcoded path out of file_save_location in i18n.cpp
  and all eight res/lang/*.json locales (additive, one key each).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-17 16:27:58 -05:00
DanSandClaude Opus 4.8 b47d55a3d0 feat(balance): show total period-earnings in every layout
#4 wired the total-earnings figure into the compact hero and the classic
layout only. This adds it to the remaining layouts in each one's native
style: donut / shield / timeline / minimal beside the USD line, consolidated
to the right of the USD, vertical-stack left of the Total row's amount, and
vertical-2x2 as a second line under the Total cell (dashboard and two-row
already show it via RenderCompactHero). All go through FormatTotalEarnings,
which returns nothing unless the global earnings toggle is on, so every
layout's default appearance is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-16 20:02:04 -05:00
DanSandClaude Opus 4.8 e28cacbe8e style(balance): use the shared SegmentedControl for the earnings toolbar
The Overview earnings controls (interval 1H/1D/1M/1Y, received/net mode, and the
global show-all toggle) were ad-hoc ImGui buttons. They now use
material::SegmentedControl -- the same rounded-track pill the Receive tab's
All/Shielded/Mining filter uses -- so the interval is a text segmented control,
the mode is two icon segments with per-segment tooltips, and the toggle is a
single matching segment (highlighted when on). Verified in a headless GL capture.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-16 16:48:33 -05:00
DanSandClaude Opus 4.8 064c9d1b61 feat(balance): inline period-earnings in the address list and totals
Surfaces the period-earnings aggregator in the Balance tab:
- Toolbar (shared address list): interval pills (1H/1D/1M/1Y), a received-
  vs-net mode toggle, and a global show-all toggle, placed right of "Hide 0
  Balances". The New Z-Address / New mining address buttons move to a footer
  row across from "Showing X of Y" to make room.
- Per address: the row draws its earnings on line 2, left of the $USD value
  (green positive, red negative in net mode), shown when the global toggle
  is on or the address is opted in via a new "Show/Hide earnings" right-click
  item. Works in every layout via the shared list.
- Total: FormatTotalEarnings (gated on the global toggle, so the default
  appearance is unchanged) is wired into the compact hero (dashboard/minimal)
  and the classic layout, left of / beside their USD figure. The remaining
  layouts' totals are not wired yet.
- Settings > Appearance: an "Earnings Display Format" selector (Coin /
  Coin + fiat / Fiat).

New i18n keys are translated into all eight locales; the CJK subset font is
rebuilt for the new glyphs. Builds + test suites pass on both variants.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-15 11:22:12 -05:00
DanSandClaude Opus 4.8 faf45e60e4 fix(mining): make "Est. Daily" honest about variance and fees
The mining "Est. Daily" was a solo expected-value (your hashrate share x
blocks/day x a fixed block reward) rendered in the same green as the real
Today / Yesterday / All-Time earnings, off an instantaneous hashrate and
gross of pool fees. It read like a dependable daily income when it is a
long-run statistical average with huge (Poisson) variance.

Framing:
- The estimate is now drawn in a neutral colour, not the green "money
  earned" colour (the actual mined totals stay green).
- A sub-label in BOTH modes marks it as an average ("avg", or "avg, net"
  in pool mode when the fee is applied), replacing the pool-only
  "before pool fee" note.
- A hover tooltip over the column explains the variance ("often nothing
  for long stretches, then a lump - NOT a steady daily income") and the
  fee status (after the N% fee / before the fee when unknown / nothing for
  solo and 0%-fee pools).

Accuracy:
- Uses a trailing-average hashrate instead of the instantaneous / 10s
  snapshot: pool prefers hashrate_15m (then 60s, then 10s); solo uses the
  mean of hashrate_history once there are >=10 samples, else the instant
  rate. This stops a spike or a momentary dip from swinging the figure.
- Subtracts the known pool fee so pool-mode figures are net; synthetic or
  unmatched pools stay gross and the tooltip says the fee is unknown.

The static block reward and true pool-reported earnings are intentionally
out of scope (the latter needs a new pool-API field). New i18n keys are
translated into all eight locales; the CJK subset font is rebuilt for the
three new glyphs. Builds + test suites pass on both variants.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-15 10:56:36 -05:00
DanSandClaude Opus 4.8 2282f332a6 feat(history): view the transactions involving an address
Adds a "View transactions" action that opens the History tab pre-filtered
to a chosen address. SetTransactionsAddressFilter() stores a pending filter
that RenderTransactionsTab consumes on its next frame — pre-filling the
search box and resetting the type filter to All — then the caller navigates
to NavPage::History. This reuses the existing "prefill + navigate" pattern
(cf. SetSendFromAddress) and all of History's rendering, detail and export.

Triggers:
- Contacts: a context-menu item and a 4th per-row action icon (copy /
  view-transactions / edit / delete; the trailing width is widened to fit).
- Balance address list: a per-address context-menu item (own z/t addresses).
- Receive tab: the recent-received rows now carry their address into the
  filter instead of jumping to History unfiltered.

The History search also now matches from_address, so filtering by one of your
own addresses catches sends FROM it, not only receives INTO it.

Scoping is honest, not exhaustive: transparent is well covered, but shielded
counterparties are private and the transparent sender of received funds is
never recorded on-chain, so this shows transactions *involving* the address.
On Lite, from_address is thin, so own-address send filtering is weaker.

New i18n keys view_transactions / tt_view_transactions translated into all
eight locales (no new glyphs). Builds + test suites pass on both variants.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-15 10:13:15 -05:00
DanSandClaude Opus 4.8 5fcab14e4e feat(settings): adjustable opacity for the favorite gold outline + star
Adds a "Favorite Highlight Opacity" slider (15-100%, default 100%) to
Settings > Appearance > Scale & Effects. It scales the alpha of the four
favorite gold tokens (fill/border/icon/outline) at their resolve site in
the Balance address list via material::ScaleAlpha, so the gold row outline
and the star button fade together. The multiplier is skin-agnostic (it
scales whatever alpha the theme resolved) and touches only favorited rows;
non-favorite rows and the white selection highlight are unchanged, and the
100% default is byte-identical to the previous look.

The preference lives in Settings (favorite_opacity, clamped 0.15-1.0 in both
the setter and on load), persists across restarts, updates live, and is not
low-spec gated (it's a pure color-alpha preference, independent of the
transparency sliders). New i18n keys favorite_opacity / tt_favorite_opacity
are translated into all eight locales; the CJK subset font is rebuilt to add
the two new glyphs the zh string needs.

Verified: full + lite builds clean, full + lite test suites pass, hygiene OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-15 09:58:17 -05:00
DanSandClaude Opus 4.8 e40c6c0a7c perf(market): clip off-screen rows in the portfolio editor
The portfolio-editor address list drew every row every frame inside the
fade-scroll child. It now runs through an ImGuiListClipper; the fixed
step (the InvisibleButton height plus item spacing) keeps each row's
cursor exactly where the unclipped loop put it, so the scroll extent for
the fade shader, the check marks, the selection and the "selected rows
float to the top" ordering are all unchanged.

Verified on DX11 (Windows) at normal and large window sizes and at
font scale 1.5, including skin switch and resize with the dialog open.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-14 16:55:07 -05:00
DanSandClaude Opus 4.8 4d95ef68f6 perf(contacts): clip off-screen rows in the Cards and List views
The contacts Cards/List row loop submitted every contact every frame.
It now runs through an ImGuiListClipper, so only the on-screen rows are
built; the fixed step (row height plus the item spacing pushed above)
matches the per-row cursor exactly, so the scroll extent, fade, hover
actions, selection and keyboard navigation are unchanged. Table view is
left unclipped (its rows can wrap).

Verified on DX11 (Windows) at normal and large window sizes and at
font scale 1.5, in both Cards and List views.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-14 16:55:07 -05:00
DanSandClaude Opus 4.8 8c5bd485dd fix(lite): probe servers without joining the thread on the UI thread
Adding a lite server from the network tab restarted the background
server probe immediately. Starting a probe while one is still running
joins the previous worker thread, and that join happened on the UI
thread, so the frame could stall until the outstanding probe finished.

- Adding a server now sets a dirty flag instead of restarting the probe
  inline; the next frame starts a fresh probe once the current pass has
  finished, so the UI thread never blocks on a join.
- The probe's libcurl transfers honor the cancel flag through a progress
  callback and abort cleanly, so a probe in flight stops promptly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 22:03:00 -05:00
DanSandClaude Opus 4.8 f3f85d602d fix(mining): stamp hashrate history and label the chart by real time
The hashrate sparkline guessed the age of its oldest sample from the
sample count, labelling the left edge "5m ago", "1m ago" or "start"
regardless of how fast samples actually arrived. Solo and pool samples
come in at different intervals, and a slow or stalled poll made those
labels wrong.

- Each hashrate sample now carries a wall-clock timestamp, kept in
  lockstep with the value as the history fills and rolls over, for both
  the solo and pool histories.
- The chart's left-edge label is derived from the oldest sample's actual
  timestamp, falling back to "start" only when no stamp is available.

Tests: a mining refresh records a timestamp per sample, and the
timestamp history stays the same length as the value history once the
history is capped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 17:42:30 -05:00
DanSandClaude Opus 4.8 95605dd7e9 fix(ui): stop global shortcuts auto-repeating; skip invalid skins
The global keyboard shortcuts tested keys without disabling key-repeat,
so holding one down fired it every frame: the theme cycle, balance and
market layout cycles, the effects and background toggles, the settings
and sidebar-collapse keys all re-triggered on a held key. Cycling themes
also stepped onto skins that had failed to load and wrote the broken
pick into settings.

- Every one-shot shortcut now checks for a fresh key press only, so a
  held key fires once.
- Theme cycling skips over invalid skins and only applies, persists, and
  shows the toast when a skin actually loads; if none can load, settings
  are left untouched and no toast appears.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 17:42:29 -05:00
DanSandClaude Opus 4.8 bbe2bf94dc fix(export): write the transaction CSV atomically and report failures
The transaction export streamed rows straight into an ofstream. A write
that failed partway, on a full disk or removable media, left a truncated
CSV on disk behind a success toast. The CSV formatting also lived inline
in the dialog with no test coverage.

- The CSV is now built in memory and written atomically (temp file plus
  rename), so a write failure is reported with an error toast and leaves
  no partial file.
- Formatting moves into a reusable helper, and the timestamp conversions
  use the thread-safe localtime variants and tolerate a conversion
  failure rather than dereferencing a null result.

Tests: field escaping neutralises formula-injection prefixes and quotes
special characters, and the full CSV has the expected header and rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 f76d71fede fix(chat): drop a redundant save and report a real contact-save result
Renaming or adding a contact from the chat tab called the address-book
mutator and then saved again, even though the mutator already persists.
It also reported success unconditionally, so a failed rename still
showed a success toast.

- The redundant explicit save is removed; the mutator's own save stands.
- A rename now shows success only when the mutator succeeds and an error
  toast when it does not.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 2a3e88c8dc fix(network): do not re-pin a lite server while a connect is in progress
Pinning a lite server while a connection to it was already being
established tore the in-flight connect down and restarted it, slowing
the very connection the user was waiting on.

- Pinning is now skipped while a connect is already underway, so the
  existing attempt is left to finish.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 b4ff9a10a2 fix(request): clear the stale payment URI, show it only for an address
The request-payment dialog kept its generated payment URI across opens
and closes. A dialog opened for an address with no payable shielded
address could still show the previous request's URI, and the string
lingered after the dialog closed.

- The payment URI is cleared when the dialog opens, when it closes, and
  on the close cleanup path.
- The URI field is shown only when there is both a URI and a real
  address to pay.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 76daf9619d perf(send): poll the clipboard for the paste hint only when it helps
The send tab read the OS clipboard every frame the paste button was
hovered, to preview a pasted address. SDL's clipboard read is a
synchronous round-trip that can stall, and the preview can only ever
latch when the recipient field is empty, so most of those reads were
wasted work.

- The clipboard is now read only while the button is hovered and the
  recipient field is empty, polled once on hover entry and then at most
  twice a second.
- Trimming the clipboard text moves to a shared helper that also bounds
  the copy so a pathological multi-megabyte clipboard is ignored.

Tests: the trim helper handles null, surrounding whitespace, an
all-whitespace string, and the length bound.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 3f82af99c4 refactor(market): resolve exchange/pair selection through one helper
The chart fetch and the Market tab both resolved the saved exchange and
pair names into the registry, with their own near-identical scans and
slightly different fallbacks. Keeping two copies risked the chart and
the chips disagreeing about which pair was selected.

- A single resolver maps the saved names to a registry index, matching
  the exchange name (any exchange when none is saved) then the pair
  display name, and falling back to the registry's first pair, the UI
  default. The chart fetch and the Market tab both call it.
- The Market tab now derives its selection indices from the saved names
  against the current registry every frame, so the indices stay correct
  when the live exchange list replaces the fallback; a steady-state fast
  path skips the scan once the indices already name the saved selection.

Tests: the resolver returns the right index for a matched exchange/pair,
an empty-exchange match, and the first-pair fallback on misses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 ce00621a4a perf(market): precompute the address-picker selection flag once
The portfolio address picker's sort comparator looked up each address in
the selected-address set on every comparison, two hash lookups per call,
and the per-row draw repeated the lookup again. On a long address list
that is a lot of redundant hashing every frame.

- Each filtered candidate now carries its selected flag, looked up once
  when the list is built. The comparator reads the stored flag, so the
  sort produces the same ordering with no lookups of its own.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 a631e4421a refactor(contacts): share case-insensitive compare, truncation helpers
The contacts list carried its own lowercase-and-compare sort, its own
lowercase substring search, and an inline linear middle-truncation loop
for fitting an address to a column width. These duplicated logic that
belongs in the shared text helpers.

- A new allocation-free case-insensitive compare replaces the per-sort
  lowercasing; it lowercases ASCII per byte and leaves bytes >= 0x80
  alone, so UTF-8 labels sort exactly as before.
- Search now uses the shared case-insensitive contains helper.
- Middle-truncation to a pixel width moves to a shared template that
  binary-searches the fit (about log2(n) width measurements instead of
  up to n) while producing the same result as the old linear shrink.

Tests: the compare matches lowercase-then-compare across ASCII, UTF-8
bytes and length ties, and the fit helper matches the old linear loop
across lengths and widths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 32ea4d0524 fix(mining): keep the thread benchmark running while the tab is hidden
The thread benchmark state machine was advanced from the Mining tab's
render path, so it only progressed while that tab was visible and the
window was on screen. Hiding the tab or minimizing the window froze the
benchmark mid-sweep.

- The benchmark now advances from the main update loop, ungated by
  connectivity, so it keeps running when the tab is hidden or the window
  is minimized.
- Its clock is driven from a steady wall-clock reading rather than the
  render delta, and each step is clamped like the other logic timers so
  a suspend/resume cannot leap a benchmark phase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 fae5ef05b9 fix(mining): restore the miner after a benchmark only if it was running
Cancelling a thread benchmark always stopped and restarted the pool
miner at its pre-benchmark thread count, even when the user had not been
mining before the benchmark began. That silently started the miner on a
plain cancel or a switch to solo mode.

- A cancel now always drops the candidate-thread miner, and restarts the
  pool miner only when the user was mining before the benchmark, read
  from a snapshot taken at the start rather than the live running state
  (the benchmark sweep transiently stops the miner between candidates,
  so a live check would drop the restore).
- Callers that must not restore the miner (leaving solo mode) pass the
  new restore flag as false.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 16:00:10 -05:00
DanSandClaude Opus 4.8 13989cf268 fix(notifications): prune expired toasts and skip invisible draws
The status-bar notifications only expired the front of the queue, so an
expired newer toast could sit in front of a still-live older one and
hide it from view. The liveness check that drives continuous repaints
also disagreed with what render() actually showed.

- Expired toasts are now pruned wherever they sit in the queue, not just
  at the front, so the newest remaining toast is always a live one.
- The visible toast is the newest live entry, skipping an expired newer
  one. The liveness check reports active while a toast is visible plus
  one trailing frame, so the final faded-out state is painted once and
  then repaints stop; it self-expires if render() is skipped.

Tests: pruning removes expired entries from anywhere in the queue, the
visible toast skips an expired newest entry, and liveness tracks it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 13:30:20 -05:00