Commit Graph
508 Commits
Author SHA1 Message Date
DanSandClaude Opus 4.8 6c2a277751 fix(wallet): surface the real z_getnewaddress error; sweep coinbase in migrate-to-seed
From a legacy-wallet migration support case (mined T-funds, 3y-old Silent Dragon wallet.dat): the balance
shows but new-address and migrate both fail with unhelpful/silent results. Two fixes:

1) Real address-creation error. createNewAddress() swallowed the daemon's z_getnewaddress error and the UI
   showed a generic "Failed to create shielded address". It now keeps the daemon's reason (friendly-mapped
   via friendlyRpcError) in last_address_error_, which the Receive tab and request-payment dialog show
   instead of the generic string — so a legacy/degraded wallet sees the actual "HD seed not found". And when
   that specific error appears it flips wallet_degraded_ + warns once, even for an EXTERNAL daemon
   (detectWalletDegraded() only scans the embedded daemon's log and would otherwise miss it).

2) Migrate-to-seed sweep skipped coinbase. beginSweepToSeedWallet()'s z_mergetoaddress ignores coinbase
   UTXOs, so a miner's coinbase-only balance produced "Could not find any funds to merge", the legacy
   balance never reached ~0, and the adopt gate stalled silently. The sweep now falls back to
   z_shieldcoinbase("*", dest, fee, 50) to the SAME shielded destination when the merge reports no funds.
   z_shieldcoinbase returns an opid just like the merge, so the single-opid adopt gate (opid -> txid ->
   confirm -> balance ~0 -> adopt) is unchanged, and a batch limit / mixed wallet drains via the existing
   "Sweep remaining" re-sweep.

Fund-path: the z_mergetoaddress call and its args are byte-identical; the fallback only ADDS a
z_shieldcoinbase to the same dest when merge finds nothing, and nothing is adopted until the swept funds
confirm and the legacy balance is ~0 (gate unchanged). Runtime-verified behavior (clean build + ctest; the
RPC paths are not pure-testable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 19:47:38 -05:00
DanSandClaude Opus 4.8 3ee90993ed fix(resilience,perf): gate send on a known tip, surface dropped/stale txs, advance cached confs, memoize explorer
Backlog mediums (display/gating/caching only — no send/sweep/encrypt call or args changed):

Resilience:
- Gate Send/Review on a KNOWN peer-derived tip (SyncInfo::sendTipReady): a fresh reconnect or peerless node
  has tip_known=false, so its local height isn't a trustworthy network tip — sending is disabled with a
  clear tooltip until it is. Lite is exempt (its own sync gate). The z_sendmany call is untouched.
- History now shows "Dropped" for a conflicted tx (confirmations < 0) and "Expired?" for one unconfirmed
  > 1h, instead of "Pending" forever (new TransactionInfo::isConflicted/isStalePending; the tab inlines the
  check on its DisplayTx fields).
- A cached z_viewtransaction row's confirmations now advance by the blocks mined since it was sampled
  (observed_height), instead of freezing at the cached value; never goes backwards; pending (0) isn't
  extrapolated.

Performance:
- Explorer block list uses loadRangeCached() (memoized on the cache revision) instead of loadRange(), so it
  only hits SQLite after the cache changes, not every frame.

Tests: testResilienceMediums (tip gate, confirmations-advance, dropped/stale predicates) + loadRangeCached
memoization in testExplorerBlockCache (isolated on its own cache). Held: Overview/Balance per-frame list
cache, peers clippers, off-thread tx-history cache load (larger refactors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 19:12:32 -05:00
DanSandClaude Opus 4.8 df98f98669 perf(history): don't rebuild the O(N) tx display cache on every block for a confirmation-only change
Performance audit: tx_revision bumps on every new block because each tx's confirmation count rises, so
the History tab's groupByTxid + merge + sort + deep-copy display cache fully rebuilt once per ~36s block
on the UI thread, even when nothing but the counts changed.

Add WalletState::tx_structure_revision, bumped alongside tx_revision EXCEPT when setTransactionsIfChanged
sees a pure confirmation-count delta (new sameTransactionLayout: every row equal ignoring the confirmation
COUNT, but the pending/confirmations==0 state — which drives the pending-first sort and shield merge — must
still match). Every other writer bumps both (conservative default). The History tab's groups cache and
display cache now key on tx_structure_revision; on a confirmation-only delta it patches dtx.confirmations
in place (rows, order and indices into state.transactions are unchanged) instead of rebuilding, and re-stamps
the filter snapshot so "export current filter" stays valid. Displayed confirmations still update every block.

No fund path touched; display/caching only. Test: testTxStructureRevision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 14:44:13 -05:00
DanSandClaude Opus 4.8 08a4427e31 fix(send): treat a lost send reply as "outcome unknown", not a plain failure, to prevent a double-send
Resilience audit, highest-value finding: a z_sendmany (full node) or lite broadcast whose REPLY is
lost — a transport timeout / connection cut AFTER the request already left us — was reported as an
ordinary "Transaction failed". The user then retries and can broadcast the same payment twice, because
the daemon may already hold the operation (z_sendmany returns an opid and runs async).

Classify the ambiguous case and surface it distinctly, without touching the send itself:
- New pure helpers in rpc/rpc_failure.h: sendReplyMayBeLost() (an RpcTransportError that was requestSent
  with no HTTP status and a non-OK curl code — reply-timeout / got-nothing / our-own-abort; definitive
  rejections and any HTTP status are excluded), liteSendErrorMayBeInFlight() (a transport-style gRPC text
  heuristic, vetoing connect-phase and server-rejection text), and a readable-English marker tag.
- app_network.cpp: the z_sendmany catch records the flag; the failure branch (non-background only) tags
  the callback text and forces a wallet re-read so a committed send shows in History. onDisconnected()
  tags already-pending send callbacks the same way (they exist only after an opid was returned).
- Lite UserSend (app.cpp): a transport-style broadcast error is tagged identically.
- Send tab: a tagged outcome shows a non-success warning pointing at History and clears the compose form
  so any resend must be retyped deliberately; it is NOT presented as a retryable failure.

The actual rpc_->call("z_sendmany", {from, recipients, 1, fee}), the lite send/broadcast call, and all
amounts/fees/recipients/memo bytes are byte-identical; only result-handling and presentation change.
Pure classifier/marker regression tests added (testSendMaybeInFlight).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 14:30:16 -05:00
DanSandClaude Opus 4.8 70d5c233c0 fix(send): stop a fee-tier change from re-sending Max; compare affordability in satoshis
Fund-correctness audit, send path:
- Flagship wrong-send: s_send_max was only cleared on a successful send, so after
  Max → typing a smaller amount → changing the fee tier in the confirm dialog,
  the amount was silently overwritten back to the maximum and Confirm sent far
  more than the user typed. Clear s_send_max on any manual amount edit (DRGX and
  USD inputs), so a later fee bump never rewrites a hand-entered amount.
- A valid exact-maximum send was blocked by 1 ulp because amount+fee<=available
  was computed in IEEE doubles. New util::toSats/canAfford compare in int64
  satoshis; wired into the Review gate, the confirm-time re-check, the Send
  tooltip and the amount-error banner. This only unblocks a correct send; it
  changes no sent amount.
- Added util::coinsToZatoshis (reject non-finite / <=0 / rounds-to-0 / over-supply)
  and regression tests for formatAmountFixed, toSats/canAfford and coinsToZatoshis
  (incl. the exact failing inputs the audit found).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 13:10:55 -05:00
DanSandClaude Opus 4.8 c0d10e9664 security(hygiene): best-effort scrubbing of residual in-memory secret copies (Tier 4)
Defense-in-depth against a local attacker / core dump reading un-zeroed heap
copies of secrets (threat model is local, not remote). Best-effort by nature —
std::string SSO/reallocation and ImGui buffer growth can still strand copies —
so these shrink the exposure window rather than guarantee absence:
- rpc_client performCall: a scope guard now sodium_memzeroes the request body +
  payload tree (walletpassphrase / *_importkey args) after the call and detaches
  CURLOPT_POSTFIELDS. Request content sent to curl is unchanged.
- New ui::scrubImGuiInputTextState() zeroes ImGui's retained InputText buffers
  (live/revert/callback/deactivated) — the app memzeroed its own char[]s but
  ImGui kept copies. Called from wipeSecrets()/wipeDisplayedSecrets(). PINNED to
  ImGui 1.92.6 via a #error guard so a vendored bump forces a re-audit (the stb
  undo buffer is an incomplete type in the header, so it's left as a noted gap).
- Lite exportSeed/exportPrivateKeys wipe result.value + the parsed JSON tree.
- Seed-word grid (SplitSeedWords/RenderSeedWordGrid) zeroes its per-frame word
  and cell copies; the wizard word/chip vectors are zeroed before clearing.
- ChatStore::clear() + ResetChatTab() zero decrypted message bodies on
  lock/switch/teardown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 12:05:04 -05:00
DanSandClaude Opus 4.8 d16bbf3695 security: payment-URI amount validation, export filename sanitize, chat crash-guard, HTTPS-only, conf 0600, pool auto-lock
Six bounded defensive fixes:
- Payment-URI amount is now strictly validated (reject NaN/inf/sign/garbage,
  <=8 decimals, 0..21,000,000) at parse time, so a crafted QR/URI can't inject a
  bogus amount into the Send form. The z_sendmany call the user confirms is
  unchanged; this is input validation only.
- export-all-keys filename is sanitized (basename-only, no traversal) via a
  shared util::sanitizeExportFilename (factored out of the CSV export dialog).
- Chat append()/upsert() wrap the strict serialize in try/catch so a malformed
  (invalid-UTF-8, attacker-influenceable) body is skipped, not crashed on —
  matching appendBatch's existing drop behavior.
- http_download (used by the updaters + pool stats) restricts transfers AND
  redirects to HTTPS and bounds the download path's redirects, so a redirect
  can't downgrade to http:// or another scheme.
- The default DRGX.conf (rpcuser/rpcpassword) is written 0600 from the start via
  writeFileAtomically, closing the brief world-readable ofstream+chmod window.
- Auto-lock is suppressed only for SOLO mining (needs the wallet), not pool
  mining (pays an external address), which was holding the wallet open forever.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 11:39:38 -05:00
DanSandClaude Opus 4.8 83ce73f26b security(console): mask key-dump command RESULTS in the scrollback
The console redacted secret command ARGS but showed the OUTPUT of key-revealing
commands (dumpprivkey / z_exportkey / z_exportviewingkey / z_exportmnemonic /
dumpwallet / z_exportwallet, and the lite seed/export verbs) in full — visible
in the scrollback, Copy All, Ctrl+C and the filter. Now, when the submitted
command is key-revealing, its result is held ONLY in a member buffer (never in
the scrollback model, so Copy-All etc. can't leak it) and a masked placeholder
is shown. An explicit "Copy secret" button routes through the auto-clearing
clipboard; the buffer auto-wipes after 60s, on Clear, and on wallet lock
(via wipeDisplayedSecrets). The command string passed to the executor is
byte-identical. New pure helper ConsoleCommandRevealsSecret is unit-testable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 11:39:38 -05:00
DanSandClaude Opus 4.8 6b0f2eda22 security: scrub on-screen secrets on lock; redact lite console secret args; no-shell wallet rebuild
- Locking the wallet (manual / auto / lite) now calls wipeDisplayedSecrets(),
  which scrubs + closes any revealed seed phrase, migration seed, or exported
  key — previously a locked wallet could leave the phrase/key on screen and
  resident. Runs synchronously, before the async lock round-trip.
- The console secret-arg redaction list only had full-node RPC names, so lite
  verbs (import / timport / encrypt / decrypt / unlock) echoed keys and
  passphrases into the scrollback and up-arrow history; added them.
- The offline wallet-rebuild helper was invoked via a shell string
  (popen "sh -c") built from paths including the wallet filename, so a crafted
  name could inject a command; it now refuses any path with a shell
  metacharacter (rc stays -1 and the existing verify-before-swap fails safe).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 09:28:25 -05:00
DanSandClaude Opus 4.8 f213878a7c refactor(ui): semantic status text uses material tokens instead of hardcoded colors
Finishes the feedback-conventions hygiene thread: ~30 hardcoded ImVec4 green /
red / amber literals used for success / error / warning text now use
material::Success/Error/WarningVec4(), so semantic status colors follow the
active skin and stay legible in every theme (several hardcoded greens/reds read
poorly on the light/pastel skins). Covers validate-address, transaction-details,
key-export, export-all-keys, about, the Settings confirmations/lock state, and
the encryption/decryption dialogs. Brand/logo colors and muted white/grey
(disabled) text are left as-is; dynamic-alpha animated indicators are untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 08:51:10 -05:00
DanSandClaude Opus 4.8 8b07f776e5 feat(ux): copy-to-clipboard confirmations + scale wizard buttons for HiDPI
Feedback-convention + HiDPI cleanup (from the audit's living-hygiene threads):
- Copy actions that were silent now toast a confirmation: transaction
  address/txid (context menu, click-to-copy, detail buttons), peer and banned-
  peer addresses, the Settings data-dir copy, the chat message "Copy", and the
  key-export result copy.
- The first-run wizard's Skip/Back buttons used raw ImVec2(120/80,0) widths that
  rendered tiny at HiDPI; they now multiply by ui::Layout::dpiScale().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 08:44:55 -05:00
DanSandClaude Opus 4.8 8b46ead72a feat(export): CSV export scope — All / current filter / date range
Finishes the transactions-export partial. The dialog now offers three scopes:
- All transactions (unchanged default; exported with no copy).
- Current History filter — the Transactions tab publishes a TxFilterSnapshot of
  the visible legs (a merged autoshield row contributes both its send+receive
  legs, so the CSV stays a per-leg record like an unfiltered export); the radio
  is disabled with an explanation when no filter is active, and a snapshot that
  predates the current tx list (revision mismatch) is refused rather than
  silently exporting everything.
- Date range (YYYY-MM-DD, inclusive end-of-day; pending/dateless rows excluded
  with a note; invalid/reversed dates blocked inline).
The live count reflects what will actually be exported. formatTransactionsCsv
and the atomic write are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-19 00:19:24 -05:00
DanSandClaude Opus 4.8 8961cbdfc9 feat(export): sanitize CSV filename + Open-folder button
The transactions CSV export now sanitizes the typed filename (basename only,
illegal chars → '_', no leading dots/trailing dots-or-spaces, length-capped,
always .csv) so it can't be pointed outside the config dir, and the success
screen gets an "Open folder" button. (All-vs-filter / date-range export scope is
left for a follow-up — it needs tx-tab filter-snapshot plumbing.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:51:07 -05:00
DanSandClaude Opus 4.8 794150b11b feat(chat,notifications): first-run chat intro card; clickable toast history + "+N" badge
- Chat: a dismissible first-run intro card (how chat works — encrypted, sent as
  shielded txs with a small fee, tied to the seed) with a "Copy my chat address"
  button, shown in the empty thread pane; dismissal persisted. The copy logic is
  shared with the existing sidebar button (moved to a helper, unchanged).
- Toasts: clicking a toast now opens the existing notification-history panel, and
  a "+N more" badge appears when several live toasts are queued (render only ever
  showed the newest). Badge opacity is a ui.toml token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:51:07 -05:00
DanSandClaude Opus 4.8 2e74a3ca69 feat(explorer,keys): distinguish lookup failures; key-export unlock affordance
- Explorer: a hash lookup now tells a transport/RPC failure apart from a genuine
  not-found (RPC -5/-8), showing "Lookup failed: <friendly error>" instead of
  "no block or transaction found"; block-detail errors routed through
  friendlyRpcError. getblock/getblockhash/getrawtransaction calls unchanged.
- Key-export: when the wallet is locked, the dialog now says "Unlock your wallet
  first" with an Unlock button (lite → requestLiteUnlock; full-node → close to
  reveal the lock screen) instead of a raw error, and the fetching state animates
  with LoadingDots. The export RPC/args are unchanged.

(i18n.cpp carries the additive keys for these and the chat/CSV commits below.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:51:06 -05:00
DanSandClaude Opus 4.8 e94d88e723 fix(console): confirmed destructive commands now run; gate z_exportmnemonic
Two gaps in the destructive-command confirm:
- Regression: a command confirmed via the command-reference dialog's "Run" was
  replayed through submitConsoleCommand, which re-armed the destructive gate and
  silently did nothing until the user retyped it. The dialog now pre-arms the
  exact-line confirm so the replay executes.
- z_exportmnemonic (full-node seed-phrase reveal) wasn't in the reference
  catalog, so a pasted z_exportmnemonic ran instantly. Added a small
  secret-revealing fallback set (z_exportmnemonic, z_exportviewingkey) to the
  destructive check. Command execution is otherwise unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:51:06 -05:00
DanSandClaude Opus 4.8 097b2ea014 feat(ux): wizard handoff note, daemon restart reminder, settings polish (code)
The code changes intended with the settings_window removal in a90aa29 (a bad
git-add pathspec aborted staging them there, leaving that commit with only the
file deletions and CMakeLists still referencing them):
- First-run handoff info toast at the completion point.
- Daemon-updater "restart pending" reminder (Settings → Daemon Binary) + the
  Done screen's warning-level "restart required" headline.
- Debug restart-note raised to a readable color with the restart prompt beneath
  it; DEBUG OPTIONS header shows a lock + explanatory caption (auth gate
  unchanged); a few hardcoded literals → material tokens / dpiScale.
- Drop the legacy settings_window from the build (CMakeLists + the dev-only
  references) to match the file deletions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:36:15 -05:00
DanSandClaude Opus 4.8 a43ac0156f feat(chat): make the key-verify lock clickable with a fingerprint dialog
The header key-verify lock was an unexplained hover-only icon. It's now
clickable, opening a dialog that shows the contact's identity-key fingerprint
and explains comparing it out-of-band, with a Copy-key button and a clearer
tooltip. The crypto/verification logic is unchanged (the wording is careful that
the lock means "encrypted to this key", since there is no trusted/verified flag).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:35:32 -05:00
DanSandClaude Opus 4.8 534120d3de feat(peers): success toasts for ban/unban/clear + confirm the bulk Clear-All
Peer ban actions only toasted on failure; success was silent. Added success
toasts for ban/unban/clear-all, and the bulk "Clear All Bans" now takes a
confirming second click (per-row unban stays one-click). The setban/clearbanned
RPC calls are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:35:32 -05:00
DanSandClaude Opus 4.8 a90aa29478 feat(ux): wizard handoff note, daemon restart reminder, settings polish; drop legacy settings_window
- First-run handoff: one info toast ("node starting, will keep syncing in the
  background") at the completion point, instead of dropping the user onto a busy
  blank window with no explanation.
- Daemon updater: a newer installed binary now sets a persistent "restart
  pending" reminder (surfaced in Settings → Daemon Binary with a Restart button)
  and the updater's Done screen leads with a warning-level "restart required"
  headline — easy to miss before.
- Settings: the debug-categories "restart required" note is raised from ~35%
  alpha to a readable color with the restart prompt moved directly beneath it;
  the DEBUG OPTIONS header shows a lock + explanatory caption so it's clearly a
  gated diagnostics section (the auth gate itself is unchanged); a couple of
  hardcoded color/size literals now use material tokens / dpiScale.
- Removed the legacy duplicate settings_window.cpp/.h (only reachable via a dev
  flag, never in production) so edits can't silently miss one of two settings UIs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:35:32 -05:00
DanSandClaude Opus 4.8 3f1838cfdd feat(chat): confirm before exporting decrypted chat history to plaintext
The chat header's export button wrote a decrypted plain-text file immediately on
click, warning only via a hover tooltip. It now opens a confirm dialog that
states the file is unencrypted and shows the destination path before writing.
The export format, path and write call are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:04:58 -05:00
DanSandClaude Opus 4.8 dcb8e6eb25 feat(mining): persistent payout error, solo-address explainer, i18n cleanup
- The pool payout-address rule is no longer hover-only: an invalid payout
  address now outlines the field in red and shows a short reason below it (a row
  is reserved so it doesn't overlap the card beneath). The longer tooltip stays.
- The solo "Mining Addr" row gains a hover explainer and a "None yet" empty-state
  (with a tooltip pointing to Receive → New mining address) instead of a blank.
- Replaced hardcoded English in the earnings/stats cards ("(%d txn)", uptime
  "N/A"/"%lldm %llds"/"%lldh %lldm") with TR() keys.

Also carries the chat-export confirm strings used by the next commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 23:04:58 -05:00
DanSandClaude Opus 4.8 3f127f9b62 feat(request,balance): Request-Payment create-address empty-state + tx empty states
- Request-Payment no longer dead-ends with no shielded address: it shows a hint
  and a "Create a shielded address" button (auto-selecting the new address), and
  labels address entries with their contact/wallet names.
- The balance "Recent transactions" empty area uses a proper empty-state and now
  distinguishes "still loading / syncing" from "no transactions yet" instead of a
  dim one-liner that also showed mid-sync.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 22:50:30 -05:00
DanSandClaude Opus 4.8 f2d9590058 feat(send): usable-while-syncing form, calmer validation, honest clear/memo/source
Send-tab UX pass (presentation/validation only — the z_sendmany submit path, its
amounts, fee, recipients and memo bytes are unchanged, and the Review button plus
the confirm-time live re-check still block any send until the wallet is synced):
- The form is now editable while syncing (so the recipient/amount/memo can be
  pre-filled); only Review/Send stays gated, with an "available once synced" note.
- The recipient's red "Invalid address" no longer flashes from the first
  keystroke — it waits until the field loses focus.
- The memo field stays visible (text preserved) with a "requires a shielded
  recipient" explainer for transparent recipients, instead of vanishing + silently
  dropping what was typed.
- "Cancel" (which only cleared the form) is relabeled "Clear form", disabled when
  empty, and drops its redundant confirm (the timed Undo already covers it).
- The source picker shows address labels, and warns when the chosen source is
  transparent (non-private on this chain).

Also adds the string keys used here and by the next commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 22:50:30 -05:00
DanSandClaude Opus 4.8 ee6e99965c feat(balance,notifications): shielded-card tooltip + clearer toast severity
- The Shielded balance card gains the explanatory hover tooltip the mining card
  already has (suppressed while the pending-chip tooltip is showing).
- Toasts get a severity-tinted background fill and a slightly thicker accent
  border, so Success/Warning/Error/Info differ by more than hue alone (the
  per-severity leading icon already existed). Tint opacity is a ui.toml token.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 18:36:55 -05:00
DanSandClaude Opus 4.8 b3e09a3314 feat(onboarding): friendlier wizard bootstrap + Done screen starts the daemon
First-run bootstrap polish:
- Failed card wraps the (often long) error instead of clipping it, and offers a
  "Try mirror" button; Retry now keeps whichever source (main/mirror) was used.
- Bootstrap offer shows an honest size/time hint and relabels "Mirror" →
  "Use mirror" with a clearer tooltip.
- Daemon status dot/label is keyed off the EmbeddedDaemon::State enum instead of
  matching the English substring "Stopping" (which broke in every other
  language), and the faint ~55%-alpha label is raised to ~86%.
- Bootstrap Done screen: for the embedded daemon it now offers "Start Daemon"
  (and says the node will start) instead of telling the user to start it by hand.

Also carries the new string-table keys for these (and the shielded-card tooltip
used by the next commit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-18 18:36:55 -05:00
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 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 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