Settings (logging section) gains two support-friendly actions, now that the logging
foundation actually produces logs (W7-2):
- Open log folder: opens the config dir (Platform::openFolder) so users can find
dragonx-debug.log / dragonx-crash.log.
- Copy diagnostics: copies a plaintext support snapshot to the clipboard via the new
App::buildDiagnosticsReport() — version, build variant, platform, connection status,
active wallet path + existence + size, encryption/lock state, sync heights, and (full-
node) daemon status/running/crash-count/lastError, plus the log paths. No secrets.
Build-clean; ctest 1/1. Remaining QoL: persistent alert history, a daemon/RPC error
banner, and the W6-2 refresh-staleness badge. See docs/wallet-hardening.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The seed-phrase "Save" already wrote the file 0600 and zeroed the in-memory buffer, but
the success message was a bare "Saved to <path>" — no hint that it's a permanent
UNENCRYPTED copy of the seed at a predictable location. The message now reads
"Saved an UNENCRYPTED seed file — move it to secure offline storage and delete this
copy: <path>". English source updated; the res/lang back-fill of this changed key is
deferred to the batch i18n pass.
Also documents W5-3 (lite create-time passphrase) as a product decision rather than a
speculative change: the field is already wiped on every path (minimal security risk),
but the labeled masked "passphrase" input at lite create/open/restore is never consumed
by the backend — so either remove the dead UI or wire it into the lite encrypt flow.
Finishes the actionable part of the wallet-hardening P0-A cluster (docs/wallet-hardening.md).
Build-clean; ctest 1/1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
F8 (security). Two related fixes to how the wallet decides whether an RPC target is
safe to send Basic-auth credentials to:
- isLocalHost() was matching any host that merely *starts* "127." via
rfind("127.",0)==0, so "127.evil.com" (and "127.0.0.1.attacker", "127.300.0.1",
"1270.0.0.1") were misclassified as loopback and treated as local. It now uses a
strict isExactIPv4Loopback() parser: exactly four 0-255 dot-separated octets with
the first == 127. localhost / ::1 / [::1] handling is unchanged.
- A remote rpchost over plain HTTP (no rpctls=1) previously only produced a
dismissible warning and then sent rpcuser:rpcpassword in cleartext, where a
local-network MITM could capture them. tryConnect() now REFUSES that connection
(clear status line + one-time notification, no creds sent) unless the user opts in
explicitly with rpcallowplaintext=1 in DRAGONX.conf (new
ConnectionConfig::allow_plaintext_remote, parsed in parseConfFile; policy in the
new allowsPlaintextRemote()). Local/embedded daemons and rpctls=1 remotes are
unaffected.
BREAKING: a wallet configured for remote plaintext RPC will stop connecting until
rpcallowplaintext=1 (or rpctls=1) is added to DRAGONX.conf. Must be called out in the
release notes. The Settings-toggle UI is deferred (the conf-key opt-in is the recovery
path; see docs/daemon-startup-hardening.md).
Adds testIsLocalHost and testAllowsPlaintextRemote to test_phase4.cpp; one i18n key
(English) added to i18n.cpp.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
F3: the daemon connect loop retried forever with only an animated spinner when the
daemon was reachable-but-never-ready (stuck in RPC warmup / -28, or an external daemon
that never finishes init) -- no error, no guidance, no escape. It now stamps
connect_stall_since_ the moment the daemon first goes "reachable but not ready" (the
warmup branch + applyDaemonInitStatus) and clears it on connect / disconnect /
warmup-complete. A pure, unit-testable util::connectHasStalled() helper (new
util/connect_stall.h, 45s default from ui.toml [screens.loading].stall-timeout-sec)
drives a "Taking longer than expected" notice in renderLoadingOverlay(): a title, a
reassuring body with elapsed seconds, and a full-node hint to Settings > Restart Daemon
or the Console. The background retry keeps running underneath, so the notice self-clears
the instant it connects. Guarded off while the daemon is in State::Error (that case is
owned by the existing crash-count hint).
The overlay is a pure draw-list layer with no interactive widgets, so this follows the
existing crash-hint idiom (guidance text, not injected buttons); the stalled state is
computed locally in the overlay, so the only new App member is connect_stall_since_.
Adds testConnectHasStalled to test_phase4.cpp and three i18n keys to i18n.cpp (English
source of truth; the res/lang/*.json back-fill is deferred to a single
add_missing_translations.py run at the end of the batch).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three verified daemon-startup edge-case fixes centered on the config/params
filesystem path:
- F7: new non-throwing Platform::ensureDirectory(dir, outError) with one
consistent "Cannot create <dir>: <reason>. Check permissions / free space."
message. Replaces the unchecked/throwing create_directories sites at main.cpp
(pre-init: log + Windows MessageBox + return 1), connection.cpp's
autoDetectConfig (was the *throwing* overload -- could raise an uncaught
filesystem_error through its callers; now sets the new
ConnectionConfig::dir_error), and both app.cpp daemon-dir sites (surface via
daemon_status_ + return false). The primary connect path (app_network.cpp)
checks dir_error and shows it instead of mislabelling it "waiting for config".
embedded_resources.cpp already checked its error_code, so it is left as-is.
- F6: verifySaplingParams() now hash-verifies each param against its pinned
canonical SHA-256 (source of truth: scripts/build-lite-backend-artifact.sh)
instead of only checking existence, so a truncated / corrupt-but-present param
is rejected up front rather than failing later on a shielded operation. A
<params_dir>/.sapling_verified marker keyed on size:mtime avoids re-hashing
~48MB on every startup. Logic extracted to the injectable, unit-testable
verifySaplingParamsIn(dir, digests); reuses util::sha256Hex (no new hash impl).
- F5: startEmbeddedDaemon() now checks extractEmbeddedResources()'s return and
the previously-dropped copy_file error_code in the daemon-binary fallback loop,
aborting with a clear status (sb_daemon_extract_failed / sb_daemon_files_failed)
instead of failing opaquely at spawn. An absent source file stays non-fatal.
Adds testPlatformEnsureDirectory and testVerifySaplingParams to test_phase4.cpp.
i18n keys added to i18n.cpp (English source of truth); the res/lang/*.json
back-fill via add_missing_translations.py is deferred to a single run at the end
of the batch. Progress tracked in docs/daemon-startup-hardening.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A per-card audit found 38 interactive controls with no tooltip. Add them, with
new tt_* i18n keys (English source; per-language JSONs fall back to English
until translated):
- Chat & Contacts (8): emoji/bubble style, accent, density, text size, poll
rate, timestamps, enter-sends — the segmented/beginRow helpers added none.
- Node & Security (26): the whole lite wallet lifecycle / backup-keys /
encryption panel, plus the full-node RPC-details toggle and daemon Refresh.
- Debug Options (4): the screenshot-sweep / full-UI-sweep / open-folder /
seed-demo-chat buttons.
Theme, Wallet, Explorer, About were already fully covered.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Transactions that carried a chat message (sent or received, messages + contact
requests) now show a "Message" badge in the History tab, and a new "Chat" option
in the type-filter combo shows only those transactions.
Detection reuses what the chat store already tracks: ChatStore gains an inline
chatTxids() returning the set of on-chain txids that carried a chat message; the
tab builds it once per frame (cheap — O(chat messages)) and tests txid membership
for the badge and the filter. Empty when chat is disabled or the wallet has no
chat identity. Both variants populate the chat store before this tab renders, so
the same code serves full-node and lite with no separate handling.
The "Message" badge reuses the stacked top-pill slot (chat txs are send/receive,
not the autoshield "shield" type, so it and the "Shielded" badge are mutually
exclusive; chat takes precedence) in a distinct Secondary() colour. Guards the
summary-card accent idx_map so the new filter value (4) can't index it OOB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The status-bar chat-buffer indicator was hardcoded English. Add semantic keys
(chat_buffer_sending[_one]/preparing/loading/ready) to the English source and
route chatBufferStatusText() through TR()+snprintf like the other counted
strings — a singular/plural pair for the send count, %d/%d for the buffer
fill. Translate all five into de/es/fr/ja/ko/pt/ru/zh (additive JSON edits,
format specifiers preserved so the runtime overlay accepts them), and rebuild
the CJK subset font for the two new glyphs (버퍼's 퍼 U+D37C, 缓冲's 冲 U+51B2).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give the lite console the parity analog of the full-node RPC command
reference: the same searchable two-pane modal (browse/search, detail
pane, examples, Insert / Insert & run, destructive confirm), driven by
the lite backend's own command set instead of daemon RPC.
- ConsoleCommandExecutor::commandReference() returns the category table
(full node = consoleCommandCategories(); lite = new
liteConsoleCommandCategories() -- 25 backend verbs in 5 categories).
The shared renderCommandsPopup reads the table from the executor.
- The Commands button now shows for both variants (gated on
commandReference()!=nullptr); title/tooltip/arg-quoting branch on
hasRpcReference() (clarified to mean "speaks JSON-RPC / full node").
- Lite args are bare tokens (the backend takes one unsplit arg string
and does not strip quotes), so the param-builder's JSON string
auto-quoting is disabled for lite -- Insert & run emits runnable bare
commands. send uses the JSON-array form (its positional form is
unreachable via the single-arg transport); new uses zs/R; import
takes just the key (the backend hardcodes birthday=0).
Adds 7 i18n keys across all 8 languages (additive). Full-node behavior
is unchanged. Adversarially reviewed (data vs the backend registry,
plumbing/regression, i18n) with all findings fixed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lite console tab lagged the full-node one on i18n: its toolbar status,
status lines, help, and error/warning strings were hard-coded English or
worded for a full node (a "daemon" the lite wallet doesn't have).
- toolbarStatus()/statusLines(): route through TR() (reusing the existing
lite_net_* keys where they already map)
- printHelp(): enumerate the 25 pass-through backend verbs for discoverability,
since the C++ tab intercepts `help` before the backend's own HelpCommand runs
- gate the destructive 'stop' confirmation to the full node (hasRpcReference);
lite has no node, so it lets 'stop' fall through to the backend instead of
showing a phantom "shut down the node" warning behind a dead gate
- word the not-connected error per variant (daemon vs "no wallet open")
- TR() the "(no output)" command-result fallback
Adds 7 i18n keys across all 8 languages (additive); rebuilds the CJK subset
for the one new glyph (U+C5D4 for the Korean "backend"). Build + ctest +
source-hygiene green; changes adversarially verified (no logic/i18n defects).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render the portfolio trend sparkline in every row style, each with a layout that features it:
- Table: a dedicated header-labelled TREND column (48dp rows), aligned across rows.
- Cards: a left-aligned info stack (icon + label / value+delta chip / DRGX) with a full-height sparkline filling the rest of the width (content-driven, so short groups give the chart more room).
- Spotlight: a big-value-over-a-full-width-chart-band hero tile (92dp).
Default sparkline interval is now MONTH (a real curve from the daily series, not the young minute buffer). Removed the per-row container accent line (redundant with the icon/sparkline colour) and the per-group shielded/transparent bar (private-by-default makes it near-always 100%, and dropped its now-dead SumPortfolioSplit helper). DPI-scale the sparkline stroke. Adds the "Trend" column string across 9 languages.
Relocated the Market controls out of a removed settings modal + gear: the chart line/candle picker now sits top-right of the chart (left of the trade button, candle-capable ranges only), and the Table/Cards/Spotlight picker sits left of Manage. Dropped the market fetch-prices toggle (still in the general Settings page).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add contacts_shape_tab, the market settings-modal + column-header keys; rename the portfolio-style labels to Table / Cards / Spotlight (same keys, new values). Rebuild the CJK subset for the new glyphs. All 8 languages, additive.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chat tab overhaul:
- Header/layout: single-row header (name + key-lock + compact click-to-copy address +
right-aligned icon toolbar with a settings "notch"), composer moved below the message
box (emoji toggle left of a bottom-flush input), tightened list-pane controls.
- Message list: per-day date separators (Today/Yesterday/date), time-only group headers,
tight same-sender grouping with iMessage-style merged corners, per-run peer avatar,
delivery status (clock -> check), hover-reveal per-message time. Message base ~18px.
- Customization: a settings "notch" gear opens a modal (also under Settings ->
Chat & Contacts) with segmented controls for emoji style / bubble style / density /
timestamps, sliders for poll rate + text size, a bubble-accent dropdown, Enter-to-send.
Bubble style/accent/density/text-size/timestamps all applied live in the message loop.
- Settings: app-wide clock-format control lives in Settings -> General; chat keeps a
per-tab override. Both chat panes now use the app's smooth wheel-scroll.
- i18n: new strings across all 8 languages; CJK subset rebuilt for the added glyphs.
Inline contact rename, hide/mute, 0-conf fast-scan and the export path are carried through.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tier A + B chat-tab visual pass:
- Widen the list<->thread gutter (1px -> 10px) for breathing room.
- Redesign the thread header: avatar + name on the left with a
right-aligned frameless icon toolbar (add-contact/export/mute/hide,
each with a tooltip); the name is clipped to the toolbar's left edge
so a long contact label can't overrun the icons.
- Replace the "Copy Full Address" button with a click-to-copy shortened
address (copies the full z-addr), preceded by a key-verify lock whose
tooltip shows a comparable identity-key fingerprint.
- Show a "Waiting for reply" chip in the header when the peer's identity
key isn't known yet.
- Emoji picker: frameless grid with tight cells (glyphs fill the cell)
and leftover width spread into the column gaps so it stays edge-to-edge.
- Larger 30px composer emoji toggle that lights up while the picker is
open; footer height + byte-counter centering adjusted to match.
New i18n keys (chat_copy_address_tip / chat_verify_key / chat_awaiting_key)
added additively to all 8 languages; CJK subset font rebuilt for the new
zh glyph.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reworked the emoji picker from a floating popup (which overlapped the message
area) into an overlay that takes over the conversation-list pane while open:
a Cancel button + a keyword search box at the top, then a responsive grid below
that wraps to the pane width.
Each emoji now carries search keywords (grin/heart/fire/…) so the search box
filters the ~150-emoji set. The 🙂 composer button toggles the overlay;
selecting an emoji appends it to the composer (byte-cap respected) and the
overlay stays open for multiple picks.
+1 CJK glyph (絵) for the ja "Search emoji" string.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Emoji picker: a 🙂 button on the composer row opens a scrollable grid of ~150
common single-codepoint emoji (all verified present in the bundled NotoEmoji
subset); clicking appends to the composer, respecting the byte cap. ImGui does
no shaping, so the set is single-codepoint only (ZWJ sequences / flags omitted).
- Show hidden: when there are hidden conversations, a "Show hidden (N)" toggle
appears in the list; toggling it reveals them (dimmed) and the thread header's
Hide button becomes Unhide. Off by default, and resets when nothing is hidden.
8-language strings; no new CJK glyphs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hide conversations:
- A "Hide" action in the thread header drops a conversation from the list. The
messages stay in the seed-encrypted store (on-chain history can't be deleted);
a new INCOMING message un-hides it (you can't un-receive), so nothing is lost.
- Hidden cids persist in settings (mirrors the mute list) and are skipped by both
the conversation list and the unread badge.
New-conversation address picker:
- A "Choose from contacts" dropdown lists the address book's shielded (z-address)
contacts and fills the recipient field on selection; manual paste still works.
8-language strings + CJK subset (+1 glyph 届).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chat sends move 0 value, so the network fee is structurally load-bearing (it's
the only thing that forces a real shielded input; 0-value + 0-fee builds a
degenerate, unrelayable tx). Three gaps addressed:
1. Fee floor — broadcastChatMemos now uses max(getDefaultFee(), kChatMinFeeDrgx),
so a 0 / too-low global default-fee setting can't silently break chat.
2. Real delivery status — the echo was marked Sent on SUBMIT regardless of the
on-chain outcome (the z_sendmany callback was empty), so failures were
invisible and the Retry affordance never fired for async failures. Add a third
ChatDelivery::Sending state (appended so persisted 0=Sent stays valid); record
the echo in-memory as Sending, and resolve it to Sent/Failed from the
z_sendmany completion callback — persisting only the final status (so a restart
never shows a stuck spinner; a stray persisted Sending loads as Sent). A subtle
"sending…" label shows while in flight.
3. Pay-from-funded + pre-check — z_sendmany spends from one z-address, and the
identity reply address may be unfunded while funds sit elsewhere. chatPayFromZaddr
picks a spendable z-address that can cover the fee (preferring the identity
address); the memo still advertises the identity address as reply-to, so paying
from a different note is transport-transparent. If nothing can cover the fee, a
clear "need a small shielded balance" toast replaces the cryptic failure.
Full node only for the callback path; lite resolves optimistically on queue.
8-language strings + CJK subset (+1 glyph 賄).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Translate the 14 chat strings added by the backlog work (relative time, retry,
jump-to-latest, empty states, search, export, message-too-long, mute/unmute) into
de/es/fr/ja/ko/pt/ru/zh, added additively (no existing key overwritten).
Also fix a key collision the English pass introduced: the new empty-state
sub-hint reused "chat_empty_hint", which already meant the standalone list hint —
so English and the translations disagreed. Split it into a distinct
"chat_empty_start" for the "Start one with New conversation" sub-line, leaving
the original chat_empty_hint intact.
Rebuilt the CJK subset font: +3 glyphs (刚静音) for zh "刚刚"/"静音", 0 removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chat-tab backlog from the audit:
- V3/V7/Q7/Q9 — thread overhaul: direction-aligned rounded bubbles with
sender/minute grouping and a grouped meta line; peer avatar in the header;
hover shows the full timestamp; a right-aligned "not sent · Retry" affordance
re-sends failed outgoing messages; a floating "Latest" pill appears when the
thread is scrolled up.
- V4 — centered empty states (icon + title + hint) for the locked, no-conversations,
and no-selection panes.
- V6 — faint sidebar tint on the conversation list + a tight single-line seam.
- Q5 — compact relative time ("now"/"5m"/"3h"/"2d"/"Mon DD") in the list preview.
- Q6 — multi-line composer (Enter sends, Ctrl+Enter newline) with a live byte
counter against the on-chain body cap (= (512−len"utf8:")/2 − ABYTES = 236),
Send disabled + counter reddened when over.
- Q8 — case-insensitive conversation search over name + last body (thread stays
open even when filtered out); "no matches" hint.
- Q11 — export a decrypted conversation to a plaintext file in the config dir
(restricted perms, plaintext-warning tooltip), toasting the path.
English strings added to i18n.cpp; per-language JSONs follow.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Q3: copy the peer z-address from the thread header (SmallButton), and right-click
any message to copy its body.
- Q2: an "Add contact" action in the header when the peer isn't already known —
one click saves them to the address book (rename later in Contacts).
- Q4: capture ChatService::ingest's new-message count (previously discarded) and
fire an in-app toast when new encrypted chat arrives while the user isn't on the
Chat tab (main-thread MainCb sites only).
i18n (EN + 8 languages, additive; no new CJK glyphs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a switch fails, the failure modal now distinguishes a CORRUPT target wallet
from other failures and offers a one-click repair:
- The switch worker watermarks the node's captured console output before the
start and, if the node dies in init, scans this start's output for a corruption
signature ("Failed to rename … .bak" / "salvage failed" / "wallet.dat corrupt" /
"Error loading wallet") → sets switch_wallet_corrupt_.
- The Failed modal then shows an accurate "this wallet appears corrupt" message
(instead of the generic "Couldn't open that wallet") plus a "Try to repair
(salvage)" button that retries the switch with the target node started under
-salvagewallet (recovers readable keypairs; implies -rescan).
- EmbeddedDaemon::setSalvageOnNextStart (one-shot, precedence salvage > zap >
rescan) + controller forwarder; switchToWallet gains a salvage arg.
Salvage operates only on the corrupt target (never the good wallet, no fund
movement). Adversarially verified: output isolation, one-shot lifecycle, state
handling, re-entrancy, no success-path regression.
i18n (EN + 8 languages) + 2 new CJK glyphs baked into the subset font.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The switch modal now shows more than a bare phase line:
- The title carries the target wallet ("Switching wallet — savings") and a
"from <previous>" caption for context (names prettified: wallet.dat → "Default
wallet", wallet-<name>.dat → "<name>", in-place links → "External wallet").
- During the Reconnecting phase — the ~30-60s where the node loads the block
index, verifies, and rescans — it surfaces the node's LIVE init stage
(state_.warmup_status/description via the existing translateWarmup mapping:
"Loading blockchain data…", "Verifying blockchain…", "Scanning for
transactions…") instead of a static "Reconnecting…".
- An elapsed timer (m:ss) so the wait visibly progresses.
i18n: from/elapsed/default-wallet/external-wallet labels (EN + 8 languages,
additive; no new CJK glyphs). No switch-flow logic change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Stop the running node?" confirm modal now stays open through the entire
switch — stop → wait-for-exit → start → reconnect — showing a live phase, and
auto-closes the moment the new node connects. This turns the up-to-a-minute
graceful-shutdown wait from an apparent freeze into visible progress.
- WalletSwitchPhase (Stopping/Starting/Reconnecting/Failed) + atomic phase and
dialog-open flags; the worker advances the phase, onConnected closes the modal,
and a failed switch shows the accurate reason with a Close button.
- Owned switches (no confirm) also show the progress modal directly.
- "Continue in background" escape hatch so a long rescan / a hung startup never
traps the user (the switch keeps running; a toast reports the result).
- isWalletSwitchInProgress() keeps the frame loop redrawing so the phase text and
spinner animate while otherwise idle.
- i18n (EN + 8 languages, additive) + a modal-switch-progress sweep surface.
State machine adversarially verified 6/6 (thread-safety, no stuck modal,
confirm→progress transition, owned/unowned, no phase leak, redraw scoping).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When switching wallets, the node the app would restart may be one this session
ADOPTED — a dragonxd already running at launch (left up by "keep node running",
or started by the user). Rather than stopping it silently (or the old dead-code
"stop any external dragonxd first" refusal), show a "Stop the running node?"
confirmation first; switching then stops it (RPC stop + wait for the port to
fully free) and relaunches on the selected wallet.
- switchToWallet(walletFile, stopDaemonConfirmed=false): when the node is adopted
(externalDaemonDetected) and not yet confirmed, defer to the dialog and return.
- renderSwitchStopDaemonDialog(): BlurFloat overlay (house style) with a warning
header; confirm re-enters switchToWallet(w, true); cancel/X aborts, node keeps
running. Owned nodes (started this session) still restart silently.
- i18n (EN + 8 languages, additive) + CJK subset rebuild; modal-switch-stopnode
sweep surface for visual review.
Gate/re-entrancy adversarially verified (no state leak; owned switches ungated;
prompt reappears on a reverted switch; dead-daemon-before-confirm handled).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
When a row is probed as an HD wallet (has hdseed/hdchain records) but the
mnemonic flag can't be read — e.g. the rare tier-1 byte-scan fallback on an
unusual BDB variant or a >256 MB file — it now shows a neutral "HD wallet"
badge (ICON_MD_ACCOUNT_TREE) rather than a bare "?", which read as alarming.
"Unknown" (?) is reserved for a scan that couldn't even establish it's HD
(incomplete, no HD marker seen). Both still yield to the Lock badge when
encrypted. Seed/legacy/hd/unknown remain mutually exclusive.
Adds wallets_badge_hd / _hd_short strings (EN source + all 8 languages,
additive) and rebuilds the CJK subset font for the new glyphs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the audit: while a switch/first-load is in flight the wallet identity hash
is empty, so contact/portfolio scope-writes silently fell back to "global"
(leaking a wallet-specific entry into every wallet) and scope-filters showed
every wallet's scoped entries.
- Contacts: a NEW wallet-scoped contact created while the identity is unknown is
now refused with a clear message (tick global or wait); editing an existing
scoped contact preserves its scope instead of demoting it to global.
- Portfolio: the "Add group" button is disabled while the identity is unknown
(with a tooltip), so a new group can't get an empty/global scope.
- Both views' visibility filters now show ONLY global entries when the identity
is unknown — never another wallet's scoped contacts/groups — instead of
showing everything.
- +2 i18n strings (8 langs; reworded one zh string to stay within the CJK subset).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Animate contact avatars end to end:
- texture_loader gains LoadAnimatedRGBA: decodes an image into a downscaled
RGBA frame sequence + per-frame durations — animated GIF via stb
(stbi_load_gif_from_memory) and animated WebP via libwebp's WebPAnimDecoder;
stills (and APNG, which stb reads as one image) return a single frame. Frames
are box-downscaled (smaller cap for animations) to bound VRAM, capped at 300.
- The contacts avatar cache now holds a frame sequence; currentAvatarFrame()
advances animated avatars by the ImGui clock and is used everywhere avatars
draw (list, cards, table, grid, preview). When a live animated frame is drawn
it flags the render loop (ConsumeContactsAvatarAnimation, clear-on-read) so
main.cpp keeps producing frames while animation plays and idles when it stops
or the contacts view is hidden.
- New animate_avatars setting (default on) + a Settings appearance toggle
("Animate avatars"); off shows the first frame only. currentAvatarFrame
honors it. +i18n (8 langs, CJK subset rebuilt for 帧/播/첫).
Verified: a 3-frame GIF and a 3-frame animated WebP both decode to 3 frames
with correct 120ms delays through the exact libwebp/stb calls used here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From an adversarial review of the avatar edit-dialog + image picker:
- HIGH: the per-row Delete icon called doDelete() (which erases from
book.entries()) INSIDE the loop iterating that same vector — out-of-bounds
reads / wrong rows on the confirming click. Defer it until after the loop.
- HIGH: the decode stack was compiled PNG-only (STBI_ONLY_PNG) while the image
picker accepts .jpg/.jpeg/.bmp/.gif, so picking a JPEG (the common photo
case) silently produced a non-loading avatar + an orphaned copy on disk.
Enable JPEG/BMP/GIF decoders, and guard the pick: verify the source decodes
before copying/committing (new contact_avatar_bad_image string, 8 langs).
- MED: the fixed, non-scrolling edit card floored bodyH at 260*dp, which could
push Save/Cancel below the card on short windows — floor lowered so the
footer always stays inside.
- LOW: the live-preview panel rounding is now dp-scaled (10*dp) to match the
real list card it mirrors.
- LOW: re-picking the same source path after its contents changed showed a
stale cached texture — evict the avatar texture cache entry on re-pick.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Four refinements now that the two-column layout has room:
1. Image mode shows a large circular preview of the chosen picture (or a
placeholder circle with an add-photo glyph, or a broken-image glyph if the
file went missing), the filename, and centered Choose/Remove buttons —
instead of a bare button.
2. The live-preview avatar is larger (r20 → r26) and the address is now
middle-truncated (head + tail) so both ends read, like the real list row.
3. Badge mode shows the two actual chips — Z (shielded) and T (transparent) —
with labels, making clear the badge is auto-picked from the address type,
rather than a line of text.
4. The Badge/Icon/Image segmented control gains glyphs (badge / palette /
image) beside its labels via a small two-font inline control (the shared
SegmentedControl helper is single-font).
Adds contact_avatar_shielded / _transparent keys and rewords the badge hint
(the chips now carry the Z/T meaning); +8-language translations, all within
the existing CJK subset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuild the add/edit contact dialog on the portfolio-editor design language:
- A live preview card at the top shows the contact exactly as it renders in the
list (avatar + name + address), updating as you type and pick an avatar.
- An avatar picker (Badge / Icon / Image segmented control) lets you keep the
default Z/T type badge, choose a Material wallet icon from a searchable grid,
or set a custom image. The picker area is fixed-height so the modal doesn't
jump when switching modes.
- Custom images go through a new in-app ImagePicker (image_picker.h): a
Material overlay that browses the filesystem starting at the user's Pictures
folder, shows a thumbnail grid (decoded to raw pixels, box-downscaled to a
small texture, cached per directory and freed on navigate/close, budgeted a
few decodes per frame so large folders don't hitch), and returns the chosen
path. The chosen image is copied into <config>/contact-avatars/ (named by an
FNV hash of the source path, so re-picking is idempotent) and stored as
"img:<path>". Like FolderPicker, it takes over the modal surface while open.
- Paste is now available on both add and edit; buttons are content-sized.
Adds three sweep surfaces (contacts-edit-{icon,badge,image}) that open the
dialog on a seeded contact in each avatar mode, plus i18n keys (+ 8-language
translations; reworded two zh/ja strings to stay within the existing CJK
subset, so no font rebuild).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the confirmed findings from a workflow audit of the Contacts tab body:
- Label clip: the Label column was WidthFixed 150px and clipped long labels
mid-word ("drgx pool payout a…"). Make all three columns WidthStretch (label 1.5,
address 2.6, notes 1.0) so the label grows with the tab and the notes column no
longer reserves a fixed empty block on the right (notes = low weight, per request).
- Globe badge tofu: the global-contact ICON_MD_PUBLIC badge was drawn with the text
font (no Material glyphs) → rendered as "?". Push Type().iconSmall() around it.
- Plural: "1 addresses saved" -> add address_book_count_one ("%zu address saved",
8 langs, %zu kept so the format signature matches) and branch on count == 1.
- DPI: the SameLine badge gaps (6/8px) and the table-height floor (120px) are now
* Layout::dpiScale(); the WidthStretch columns are relative so need no scaling.
- Toolbar: give the four actions leading Material icons (person-add / edit / delete /
content-copy) and accent the primary "Add New" — via a small local icon+label
tactile-button helper (ImGui has no two-font button), icons inherit the
disabled-aware text alpha so they gray out with BeginDisabled().
Build + ctest + hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Presentation-only migration of ShieldDialog::render (Shield coinbase / Merge funds)
— a fund-moving modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth win.width,
idSuffix shielddialog}) replacing the legacy positional overload
- drop the description divider and the form->footer divider (keep the post-submit
operation-id section divider)
- 3 StyledButton -> TactileButton (submit / cancel / check-status)
- TR() the hardcoded "No shielded (z) address yet …" note -> new shield_no_zaddr_hint
key, 8 langs (em-dash preserved); the "DRGX" ticker stays hardcoded per convention
The fund path is byte-for-byte unchanged: the can_submit guard, fee/utxo clamps, the
"ANY_TADDR" setup, the z_shieldcoinbase / z_mergetoaddress worker->post calls + their
opid/status/Notifications/trackOperation callbacks, and the BeginDisabled/tooltip logic.
Verified: build + ctest + hygiene clean; a 2-lens adversarial review
(fund-path-untouched / presentation-i18n) returned zero findings, proving the fund
path diffs byte-for-byte identical to HEAD via isolation diffs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Presentation-only migration of RenderSendConfirmPopup (the confirm-before-broadcast
popup) — a fund-moving modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth popupW,
idSuffix confirmsend}) replacing the legacy positional overload; p_open stays
nullptr (the popup keeps its own Escape + Cancel handling)
- TR() the one hardcoded status string in the synchronous live-revalidation guard
("Cannot send now …") -> new send_cannot_send_now key, 8 langs (em-dash preserved)
The fund path is byte-for-byte unchanged: the live re-validation guard, the
app->sendTransaction call + its success/failure callback, the lite-locked
requestLiteUnlock branch, the s_sending/s_show_confirm state, the Escape handler,
and the FROM/TO cards + fee-tier/summary rendering.
Verified: build + ctest + hygiene clean; a 2-lens adversarial review
(fund-path-untouched / ui-i18n) returned zero findings, with the fund-path lens
proving the send path diffs byte-for-byte identical to HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring renderAntivirusHelpDialog onto the reference design and remove its hardcoded
English (the last Wave-1 modal):
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth 560,
idSuffix antivirus}) replacing the legacy positional overload
- the hardcoded "Windows Defender Blocked Miner" title and all 10 step/intro/bullet
strings -> TR() (11 new av_* keys + reuse `close`; 8-lang translations, Windows
env vars / xmrig.exe kept literal, menu names localized)
- 2 StyledButton -> TactileButton, drop the footer divider, dp-scale the button widths
- CJK subset rebuilt (glyphs 1741->1751; kept — new glyphs are used)
NOTE: this dialog is inside #ifdef _WIN32, so the Linux build compiles it out and
cannot verify it — needs a Windows build to compile-check. The edits mirror the
already-verified daemon-update-prompt / migrate-to-seed struct-overlay pattern and
leave the ShellExecuteA call untouched. i18n.cpp + the daemon prompt (cross-platform)
build + ctest + hygiene clean on Linux.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate the three PIN dialogs in App::renderPinDialogs (Set PIN / Change PIN /
Remove PIN) onto the settings-modal reference design — presentation-only, plus a
deliberate secret-hygiene addition:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth 420/420/400,
idSuffix pinsetup/pinchange/pinremove}) replacing the legacy positional overload
- drop each dialog's redundant post-description Separator
- every rendered-chrome string -> TR(): titles/buttons reuse settings_set_pin /
settings_change_pin / settings_remove_pin, the Set-PIN confirm reuses
wiz_pin_confirm; 7 new pin_* keys added to the English source + all 8
res/lang/*.json additively; ImGui::Text -> TextUnformatted for labels
- HARDENING: wipe the passphrase/PIN input buffers on BlurFloat dismiss
(X/Esc/outside-click) for all three dialogs. Set PIN's wallet-passphrase buffer
(pin_passphrase_buf_) was previously left resident on dismiss; it is now zeroed.
The vault/RPC path is byte-for-byte unchanged: the submit-time memsets, the
walletpassphrase/walletlock RPCs, vault_->store/changePin/retrieve/removeVault,
SecureVault::secureZero, and setPinEnabled/save are untouched. The pin_status_ and
toast strings inside the worker callbacks are left in English (a separate
service-layer i18n pass), to avoid editing the secret path.
Verified: build + ctest + hygiene clean; i18n complete in 8 langs with no format
specifiers; a 3-lens adversarial review (secret-hygiene / presentation-only /
ui-i18n) returned zero findings, with the presentation lens proving the vault path
diffs byte-for-byte identical against HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate App::renderDecryptWalletDialog ("Remove Wallet Encryption") onto the
settings-modal reference design — presentation-only for a fund-critical dialog,
plus one deliberate secret-hygiene addition:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth 480,
idSuffix "decrypt"}) replacing the legacy positional overload
- Phase 0 amber TextWrapped warning -> material::DialogWarningHeader(decrypt_warning)
- drop the redundant Phase 0 Separator
- every rendered-chrome string across all 4 phases (PassphraseEntry / Working /
Success / Error) -> TR() (14 new decrypt_*/try_again keys added to the English
source + all 8 res/lang/*.json additively; the button reuses
settings_remove_encryption, the label reuses change_pass_current, cancel/close reused)
- HARDENING: wipe decrypt_pass_buf_ on BlurFloat dismiss (X/Esc/outside-click),
mirroring the change-passphrase fix; canClose() gates pOpen so this cannot fire
during the Working phase
The fund-moving async worker pyramid (unlock -> export -> stop -> backup ->
restart -> re-import, vault/PIN cleanup) is byte-for-byte unchanged; the three
in-worker toast notifications and the "Incorrect passphrase" status are left in
English to avoid touching the fund path (a separate service-layer i18n pass).
Verified: build + ctest + hygiene clean; i18n complete in 8 langs with no format
specifiers and the wallet.dat.encrypted.bak literal preserved; a 3-lens adversarial
review (secret-hygiene / fund-path-untouched / ui-i18n) returned zero findings, with
the fund-path lens proving the async pyramid diffs IDENTICAL against HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Migrate the Encrypt Wallet (3-phase) and Change Passphrase dialogs in
renderEncryptWalletDialog onto the settings-modal reference design —
presentation-only, no change to the crypto/state-machine/secret-wipe logic:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth, idSuffix})
replacing the legacy positional overload (same visual style, dp-scaled width)
- amber TextWrapped warning -> material::DialogWarningHeader(wiz_encrypt_warning)
- drop redundant Separator dividers
- every hardcoded English string -> TR() (13 new enc_*/change_pass_* keys added to
the English source + all 8 res/lang/*.json additively; CJK subset rebuilt)
The strength-meter math, 3-phase transitions, "cannot close while encrypting"
guard, memset/SecureVault buffer wipes, and the encrypt/changePassphrase/vault
async paths are byte-for-byte unchanged. Verified: build + ctest + hygiene clean,
wiz_strength %s format-string safe across all languages, and a 3-lens adversarial
review (secret-hygiene / presentation-only / ui-i18n) returned zero findings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 2 (first security modal), presentation-only + the review's one finding.
Migration: struct-form BlurFloat overlay (was legacy positional); full i18n
(reusing export_private_key / key_export_private_warning / key_export_private_key /
select_address / key_export_fetching / export / copy / close, plus one new
key_export_failed); DialogWarningHeader; centered DialogActionFooter (Export +
Close) + a Copy TactileButton; async UX (export_in_progress_ spinner, export_error_
line). The exported key stays in the char[256] buffer shown via a read-only
InputTextMultiline (deliberately NOT AddressCopyField, which would copy the secret
into a lingering std::string). exportPrivateKey is untouched.
Security fix (found by the adversarial review): the 0a "outside-click/Esc wipe" via
the Begin-returns-false early-return was UNREACHABLE — BeginOverlayDialog clears
show_export_key_ mid-frame but still returns true, so an outside-click/Esc dismiss
left the exported spending key un-wiped in export_result_. Add the in-body
`if (!show_export_key_)` teardown before EndOverlayDialog (the proven import-dialog
pattern), which is the real wipe point for every dismiss path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1. DialogWarningHeader care header (was a hand-rolled red icon+text);
centered DialogActionFooter (Export + Close) replacing the StyledButtons + two
separators; on full success the saved path shows in a copy field with a green
check, partial/error render inline; export progress uses LoadingDots. Full i18n:
the previously-hardcoded notification + status strings now use TR (8 new
export_keys_* keys in en + 8 langs). Added hide() + a modal-export-all-keys sweep
surface. Also fixes a latent Begin/EndOverlayDialog imbalance on the 0-address
early return. No change to the export RPC logic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1. Struct-form BlurFloat overlay (was the legacy positional overload);
DialogWarningHeader care header; centered DialogActionFooter (Create Backup +
Close) replacing the StyledButtons + separator; full i18n — title/destination/
button reuse existing backup_* keys, plus two new keys (backup_warn one-line care
line, backup_overwrite_confirm) added in en + 8 langs. No change to backupWallet.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a "Sweep to my wallet (don't keep the key)" option to the Import Private
Key dialog (spending keys only). Rather than keeping the key, it imports the
key, then sends ALL its funds to a destination you control — a freshly
generated shielded address by default, or an existing wallet address you pick.
Flow (App::sweepPrivateKey): import the key (rescan; the stock node has no
address index, so its UTXOs/notes can't be enumerated otherwise) → confirm it
holds spendable funds → resolve the destination → z_sendmany balance-minus-fee
from the key's address to the destination, via the existing tested submitZSendMany
wrapper + async-operation tracker. The imported key is left in the wallet with an
empty balance (there is no remove-key RPC) — stated plainly in the UI caveat.
Design notes forced by this daemon (verified against external/dragonx):
- z_importkey returns null here, so the shielded source address is found by
diffing z_listaddresses before/after import (aborts if the pre-snapshot fails,
so a wrong address can never be picked). Transparent uses importprivkey's return.
- z_sendmany, NOT z_mergetoaddress: moves a single-UTXO transparent source (the
common paper-wallet case) and fails loudly instead of silently leaving a
remainder. Amount computed in integer satoshis for an exact fixed-decimal string.
- Clear errors for no-funds / unconfirmed / dust-below-fee / already-imported.
- The mode + destination are locked during a running sweep; a sweep that finishes
while the dialog is closed still shows its Done/Error+txid on reopen; sweep UI
can't bleed into the watch-only viewing dialog.
i18n: 8 new sweep_* keys (en + 8 langs); dynamic status strings are English,
matching the beginSweepToSeedWallet precedent.
Reviewed across three adversarial multi-agent rounds (fund-safety focus): round 1
caught the feature was broken for shielded keys (z_importkey null) and rejected
single-UTXO transparent sources; round 2 caught reopen/double-submit/precision
issues; round 3 verified the fixes. NOT yet exercised against a live daemon —
needs a small-amount mainnet test before it's trusted, like migrate-to-seed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Design pass on the Import Private/Viewing Key dialog:
- Group the optional scan-from-height section (label, slider, field, hint) in a
subtle glass panel via a draw-list splitter so it reads as one distinct,
clearly-optional block.
- Replace the boxed reveal / Paste / Clear buttons with frameless Material icon
buttons: the eye toggle sits flush at the Key field's right edge (rounded to
match the input frame), Paste / Clear are faint icon pills with tooltips.
- Add a centered %-of-chain label to the slider (mirrors the send-tab amount bar).
New: hovering Paste peeks at the clipboard and verifies it before pasting —
a tooltip classifies the clipboard the same way the field indicator does
(✓ valid type for this dialog / ⚠ a key for the other button / ✗ not a key),
with a preview line masked with '*' unless the key is currently revealed (then a
truncated peek). Empty clipboard shows "Clipboard is empty".
i18n: added paste_clip_empty (en + 8 langs); the preview reuses the field
indicator's type / wrong-type keys.
Reviewed via three adversarial multi-agent passes (splitter/panel, icon buttons,
slider math, and the paste preview's correctness/security + i18n); the confirmed
findings (eye hover-pill rounding, unrecognized-state color) are addressed here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The optional "scan from block height" control in the Import Private/Viewing
Key dialog now leads with a slider styled like the send-tab amount bar
(rounded track, accent fill, glass thumb) bounded by the chain tip, with the
numeric field beside it for exact entry and the current chain height
right-aligned above. When the node's height is unknown (state_.sync.blocks == 0,
e.g. not yet synced) the slider is omitted and the field spans the full row —
which also fixes the previous cramped 140px field that left the row half empty.
- New static helpers in app.cpp: formatIntWithCommas() and RenderScanHeightControl()
(mirrors RenderAmountBar in send_tab.cpp). The height decimal string is the single
source of truth shared by slider + field; the slider reserves/queries its
InvisibleButton before drawing so a drag has no one-frame lag; the bar height is
ImGui::GetFrameHeight() so it aligns with the numeric field.
- Import handler clamps the parsed start height to the chain tip.
- i18n: added import_scan_tip ("current height") in en + 8 langs.
Reviewed via an adversarial multi-agent pass (slider-math / state-sync /
dpi-layout / consistency); the one confirmed finding (bar/field height mismatch)
is fixed here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the key/viewing-key button split:
- Rename the "Import Key…" button to "Import Private Key…" (settings_import_key
value in en + all 8 langs, reusing the existing import_key_title wording) so it
reads as the natural counterpart to "Import Viewing Key…". The dialog title was
already "Import Private Key". The viewing-key wrong-type redirect hint now names
the renamed button.
- Show the "scan from block height" field in the private-key (spending) dialog too,
not just the viewing-key one. z_importkey accepts a start height, so a shielded
spend import of a recent key needn't rescan the whole chain. Transparent WIF
(importprivkey) has no such param, so once a transparent key is recognized the
field is disabled, its buffer cleared, and a "Transparent keys always rescan
fully" note replaces the hint. importPrivateKey already gates the start-height
append on (viewing || shielded), so transparent imports never receive it.
- i18n: shared scan keys renamed import_viewkey_scan_{label,hint} ->
import_scan_{label,hint}; added import_scan_transparent (en + 8 langs).
Reviewed via an adversarial multi-agent pass (rpc-routing / i18n / ui-logic /
consistency); only two nits surfaced (stale disabled-field value + a stale
comment), both fixed here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backup & Data now has two distinct import actions instead of one
auto-detecting dialog:
- "Import Key" imports a spending key (transparent WIF or shielded
z-spending key) with the strong "grants access to funds" warning.
- "Import Viewing Key" imports a watch-only shielded viewing key
(zxviews…) with a milder eye/watch-only note and an optional
"scan from block height" field — z_importviewingkey accepts a start
height, so watching a recent address needn't rescan the whole chain.
The shared Material dialog (renderImportKeyDialog) branches on
import_view_mode_: title, warning tone, field label, the live type
indicator, and the recognition guard. A key valid for the *other*
button (e.g. a spending key pasted into the viewing-key dialog) now
shows a redirect hint rather than a generic "unrecognized" error.
importPrivateKey gains a startHeight arg, appended to the shielded
import RPCs (z_importviewingkey / z_importkey) and ignored for
transparent WIF (importprivkey has no start-height param). The
scan-height buffer is wiped on open/close alongside the key buffer.
i18n: 9 new keys (button label + tooltip, viewing title/note/field,
scan label/hint, two wrong-type redirect hints) with translations for
all 8 languages; CJK subset rebuilt (+1 glyph). A modal-import-viewkey
sweep surface is added for the viewing-mode dialog.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rework the full-node Import Key dialog into a safer, Material-consistent, more
capable flow:
- Security: the key is masked by default (Password) with a reveal (eye) toggle,
a DialogWarningHeader "only import a key you own" note, and the buffer is
wiped with sodium_memzero on close and on successful import.
- Progress: import triggers a blocking full-chain rescan; the dialog now shows
"Importing & rescanning — this can take several minutes" with LoadingDots and
disables Import while it runs (no double-submit). Offline shows a "connect a
running node" note and disables Import up front.
- Viewing keys: the classifier gains isViewingKey / isRecognizedImportKey;
importPrivateKey auto-detects a shielded viewing key (zxviews…) and routes to
z_importviewingkey (watch-only) vs z_importkey / importprivkey. The live type
indicator shows Transparent / Shielded spending / Shielded viewing (watch-only).
- Result: on success the imported address is captured from the RPC result and
shown via AddressCopyField.
- Material restyle (OverlayDialogSpec/BlurFloat + TactileButton + Esc) and full
i18n (12 new keys x 8 languages; CJK subset rebuilt).
Verified: rendered on dark + light skins; a seeded zxviews… key shows the masked
field + "Shielded viewing key (watch-only)" indicator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The detail pane's Parameters section is now an editable form: each parameter is
a type-hinted input field (string / number / json, optional marked), and a live
"Builds" preview shows the command being assembled. String values are
auto-quoted; a required field left empty keeps its placeholder and disables
"Insert & run"; trailing empty optionals are omitted.
Insert and Insert & run now use the assembled command (not just the template),
so Insert & run works for parameterized commands once the required fields are
filled — destructive commands still confirm first. Param fields reset when the
selected command changes or the modal opens. Keyboard nav (Up/Down/Enter) is
scoped to the search box so typing in a field doesn't move the selection.
A best-effort parseParamSpecs turns the human-readable param templates into
fields; JSON-array params (z_sendmany, createrawtransaction) surface as a single
"json" field the user pastes into. Adds the console_ref_builds label + 8
translations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the flat, terse command-reference table with a guided two-pane
explorer built for users who don't know the RPC commands:
- Search by name OR task: keyword synonyms let "balance" find getbalance,
"send money" find sendtoaddress; results are ranked (name-prefix > name >
keyword > desc).
- Two panes (mirrors the portfolio editor): a category-grouped master list
(mono names, a warning dot on consequential commands) + a rich detail pane
with a plain-language explanation, a per-parameter type breakdown (string /
number / json, optional dimmed), and a concrete example.
- Insert into console (fills the input with a template to review + run) and,
for no-parameter commands, Insert & run — with a confirm for destructive
ones. Deferred via pending_submit_ so the modal needs no executor.
- Keyboard: type to filter, Up/Down to move, Enter to insert; auto-focus and
auto-select the top result.
Data model: ConsoleCommandEntry gains details/example/keywords/destructive
(C++17 defaults, so the ~60 un-enriched entries are untouched); ~20 common
commands enriched and 18 consequential ones flagged. Command docs stay English
(technical); the UI chrome + 7 category names are translated into all 8
languages (CJK subset rebuilt, +3 glyphs).
Verified: two-pane renders on dark + light skins and at font_scale 1.5; detail
pane shows explanation/params/example; danger dots + safety badge on
destructive commands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>