Completes the wallet-files dialog and localizes the feature.
- Create a new wallet: a name is normalized to wallet-<name>.dat and opened
via switchToWallet; the daemon mints it (seed-backed, -usemnemonic=1) on
first load. Rejects blank names + name collisions.
- Import: out-of-datadir wallets get an Import action that copies the file
into the datadir (never overwriting) under a wallet-*.dat name, then opens
it — the daemon only loads plain filenames from the datadir.
- i18n: the 32 new multi-wallet / per-wallet-contacts / mining-payout strings
translated into all 8 languages (de/es/fr/ja/ko/pt/ru/zh), CJK subset font
rebuilt. Verified: the Chinese Wallets dialog renders with no missing glyphs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Second phase of multi-wallet: list wallet files and switch the active one.
- Daemon plumbing: EmbeddedDaemon::setWalletFile -> start() passes -wallet=<name>
(non-default only; skipped during the isolated seed-migration start), and
DaemonController::syncSettings pushes active_wallet_file on each start.
- App::switchToWallet: persist the new active wallet, then stop + restart the
node on -wallet=<name> (rescan only if it was never synced in this datadir).
Reuses the seed-adopt restart coordination; WalletState clears on disconnect
and the P1 identity-scoped caches re-key, so no previous-wallet data leaks.
Guarded: full-node, embedded daemon, not mid-restart, no pending send.
- Wallets dialog (Settings -> Backup & Data -> "Wallets…"): a table of wallet
files (datadir wallet*.dat + user-added folders' *.dat) with size (disk) and
cached balance / address count / last-opened (wallet index), a current/Active
badge, Open (switch), and Add folder. Out-of-datadir wallets show "import to
open" (P3). Added as a sweep surface.
- Mining payout safety: non-destructive warning if the pool-mode worker looks
like a DragonX address not in the current wallet (stale after a switch).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
First phase of multi-wallet support: keep each wallet's data separate so loading
a different wallet no longer shows the previous one's data, and lay the metadata
groundwork for the wallet-files list.
- Address book scoped per wallet: AddressBookEntry gains a "scope" ("global" or a
wallet-identity hash); the Contacts tab shows global + current-wallet contacts
(App::activeWalletIdentityHash) with a "show in every wallet" toggle + globe
badge. Legacy entries migrate to "global". Scope-aware de-dup allows the same
address across different wallets.
- Wallet metadata index (data/wallet_index -> wallets.json): file-keyed cache of
balance / address count / identity / size / last-opened / synced-here, since
those can't be read off a wallet.dat without loading it. Populated on connect +
address refresh (change-detecting upsert). Plus an active_wallet_file setting
for the -wallet=<name> switch coming in P2.
Unit-tested (testAddressBookScope, testWalletIndex): migration, visibility filter,
scope-aware de-dup, upsert change-detection, save/reload round-trip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Hand-drawn absolute geometry (dl->AddText offsets, SetNextWindowSize,
explicit ImVec2 button widths, SameLine strides) is immune to the app's
DPI machinery (font-atlas rebuild + ScaleAllSizes), so several overlays
rendered native-size (tiny) on HiDPI / Windows 150% displays. Multiply the
raw logical-px geometry by Layout::dpiScale():
- seed_display: content-aware, DPI-scaled seed-grid column stride (a raw
130px stride let words collide with the next column's index at 150%)
- lock screen: card/logo/input/unlock-button + all vertical offsets
- migrate-to-seed + seed-backup dialogs: button widths
- send-confirm: confirm/cancel button widths
- about dialog: value-column x, edition inset, link/close button widths
- chat conversation list: row height + padding (fixes the preview clip)
Also freeze demo state during the full UI sweep: guard App::update() on
capture_mode_ so a running daemon's refresh can't clobber the injected
demo state (it blanked Send/Receive/History with a CDB error mid-sweep).
Verified on Windows 4K@150% and locally at font_scale=1.5 (same
dpiScale()==1.5 code path). Adds a DPI-scaling convention note to CLAUDE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The existing sweep only captures tabs. This adds a "Full UI sweep" (DEBUG
OPTIONS) that also drives every normally-hidden surface into view and captures
it under every skin, so all the UI a normal sweep misses can be reviewed at
once.
- The capture unit becomes a "surface" (SweepTarget: a base tab, optionally
with a setup()/teardown() that forces a modal / multi-step flow / state
overlay on top). Tabs are surfaces with a null setup. The existing tab sweep
is folded into the same state machine (one code path). All sweep logic +
the catalog + demo data move into a new src/app_sweep.cpp.
- Runs OFFLINE against injected demo data (installDemoWalletData snapshots then
restores the real state_ — WalletState isn't copy-assignable, so a targeted
field snapshot is used) and fires NO live op: capture_mode_ guards the
seed-backup RPC, auto-lock, and the migration pumps; the async-firing dialogs
are neutralized by pre-setting their state (fetch_started_ + demo phrase;
seed_migration_step_ set directly). Verified: 0 RPC/secret/send calls.
- Catalog (v1): tabs + the bool-flag modals (import/export key, backup,
seed-backup, about, settings) + the 6 migrate-to-seed steps + 4 wizard steps
+ lock/warmup/not-ready overlays + the send-confirm popup (via a new
ui::SweepShowSendConfirm debug hook, since its state is send_tab-static).
- Overlay/blur surfaces settle 8 frames (blur backdrop freeze); setup re-runs
each frame (OpenPopup popups must re-fire). updateScreenshotSweep now runs
before the first-run-wizard early-return in render() so the sweep advances
while a wizard step is shown. main.cpp drops vsync during a sweep so it isn't
throttled to the compositor's unfocused rate. Output:
<config>/screenshots-full/<surface>/<skin>.png + index.md.
- DRAGONX_FULL_SWEEP=1 runs the sweep headlessly then quits (CI / verification).
Verified end-to-end under WSLg: 288 PNGs (9 skins x 32 surfaces), all 1200x720,
[Sweep] done, clean quit, no daemon. Byte-identity across runs holds only for
static skins on data-free surfaces — the rest vary by animated theme effects /
live price / relative timestamps (expected for a review tool, not a bug).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New full-node wallets now get a real 24-word recovery phrase, and users can
reveal/back it up from Settings.
- Pass -usemnemonic=1 to dragonxd. The daemon reads it only inside
GenerateNewSeed() when a wallet has no seed yet, so it is inert on existing
wallets (safe to pass unconditionally) and makes every freshly-created
wallet mnemonic-backed — its phrase is then exportable via z_exportmnemonic
and portable to SDXLite/ObsidianDragonLite. (Existing/legacy wallets are
unaffected and keep using the chat identity's z_exportkey fallback.)
- App::exportSeedPhrase() wraps z_exportmnemonic on the RPC worker with
sodium_memzero wiping on every path; it flags the daemon's "not derived
from a mnemonic" error as a legacy wallet rather than a failure.
- New "Back up seed phrase" modal (Settings -> Backup & Data, full-node
gated): reveals the phrase in a numbered word grid, copy (45s clipboard
auto-clear) + save-to-file (0600), wipes the secret on every close path
incl. dismiss-mid-fetch. Shared ui/windows/seed_display.h grid helper.
- One-time nudge (settings flag seed_backup_reminded) toasts mnemonic-wallet
users to back up their phrase; legacy wallets are not nagged.
- 15 new strings translated into all 8 languages (additive, 957->972 keys);
CJK subset font rebuilt for the new glyphs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Outgoing echoes were recorded optimistically regardless of whether the broadcast
was actually submitted. Now broadcastChatMemos / broadcastChatMemosLite return
whether the send was submitted (false on immediate failures: not connected, no
spendable z-address, or a lite send already in progress), and the echo records a
ChatDelivery status (Sent / Failed). The Chat tab shows a "not sent" marker in
the error color on failed messages. The status persists in the DB (backward-
compatible: old rows deserialize as Sent).
(A later async failure — an opid that fails after submission — still surfaces via
the existing send-progress notification; only the submit gate is reflected here.)
Also removes the now-unused chat_readonly_note string (the composer replaced the
read-only footer). Test: delivery status round-trips through the DB. Verified:
Linux + Windows build with chat ON, ctest 100%, hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the outgoing side of HushChat: compose messages and start conversations.
The wire construction is the byte-exact inverse of the receive parser and is
proven by a self-consistent round-trip (build → parse → decrypt).
- src/chat/chat_outgoing.{h,cpp} (pure): buildOutgoingMessage encrypts via
encryptOutgoing and buildOutgoingContactRequest carries plaintext; both emit
the header memo JSON ({h,v,z,cid,t,e,p}, which nlohmann serializes
alphabetically to match SilentDragonXLite) + the payload memo, validating the
512-byte memo limit, the 64-hex peer key, and the "no leading '{'" rule for
request text. My public key goes in header "p"; the peer's key is the
encryption recipient.
- ChatService: composeMessage/composeContactRequest (encrypt with the held
identity) + recordOutgoing (echo an Outgoing ChatMessage into the store + DB —
we never harvest our own sends, so this is the only local record).
- App: sendChatMessage(cid,text) sources the peer z-addr + public key from the
conversation, composes, and records a random-id echo; startChatConversation
(zaddr,text) mints a random cid + composes a plaintext contact request;
chatReplyZaddr() picks a spendable z-addr. broadcastChatMemos() is the Phase-5
transport seam (network delivery + real-SDXL interop verification land there).
- Chat tab: a message composer (shown once the peer's key is known, else a
"waiting for reply" hint) + a "New conversation" modal that sends a contact
request to a z-address.
- Tests: outgoing round-trip through the receive parser + decrypt, contact-request
passthrough, validation guards, and ChatService compose + recordOutgoing echo.
Adversarially reviewed (crypto/interop, secret hygiene, logic, UI) — 0 findings.
Gated by DRAGONX_ENABLE_CHAT (default OFF). Verified: Linux + Windows(mingw)
build with chat ON, ctest 100%, hygiene clean; caches restored to the OFF default.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the first user-visible HushChat surface: a Chat tab in the sidebar with a
two-pane, read-only conversation view — a conversation list on the left and the
selected thread on the right — reading from the App-owned ChatService store.
- New src/ui/windows/chat_tab.{h,cpp} (RenderChatTab(App*), mirroring the
Contacts tab). Conversations are sorted by most-recent activity; peer
z-addresses resolve to contact names via the address book when known; each
message shows sender + timestamp + wrapped body, with a contact-request tag
and a read-only footer (composing arrives in a later phase). Empty states
cover "wallet locked / identity not ready" and "no conversations yet".
- Sidebar wiring: NavPage::Chat + registry entry (static_assert stays balanced),
NavPageSurface + GetNavIconMD (ICON_MD_CHAT), dispatch in app.cpp, trace/sweep
page names, and App::chatService() accessor. The tab is gated on the new
WalletUiSurface::Chat, which returns DRAGONX_ENABLE_CHAT != 0 — so it only
appears in chat-enabled builds and is hidden (and unreachable) by default.
- i18n: English defaults for the chat nav label + hint strings.
Verified: Linux + Windows(mingw) build with chat ON, ctest 100%, hygiene clean;
caches restored to the OFF default. The screenshot sweep now includes the Chat
tab (sweepPageName "chat") for per-theme visual review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Screenshot-sweep review follow-ups:
- sweepPageName() had its own page-name switch that lacked a Contacts case,
so the tab's screenshots landed under the fallback "page/" dir. Add the
case so they capture under "contacts/".
- The Z/T type badge (green/amber) washed out on light skins. Use darker,
more-saturated variants when IsLightTheme(); keep the brighter ones on dark.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 0d — the accessibility pass on the Contacts tab.
- Search box filters by label/address/notes (case-insensitive); a distinct
"no matching contacts" empty state.
- Sortable columns (ImGuiTableFlags_Sortable) — click a header to sort the
view by label/address/notes, ascending or descending.
- Keyboard nav (when the tab owns focus, no field/modal active): Up/Down move
the selection through the *visible* order, Enter edits, Delete deletes
(feeding the two-click confirm), Ctrl+C copies.
- Contrast: the address is no longer rendered as muted TextDisabled (it's the
row's key data) — normal legible text, with a coloured Z/T type badge.
Notes are legible too.
- The add/edit form focuses its first field on open (SetKeyboardFocusHere).
- The delete confirm is now visible: the Delete button relabels to
"Confirm delete?" while armed, instead of a transient toast.
Correctness: selection is tracked by STORAGE index, decoupled from the
filtered/sorted visible order, so edit/delete/copy always target the right
entry. (ImGui renders to a canvas with no OS accessibility tree, so this is a
keyboard/contrast/findability pass, not screen-reader support.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 0c. The Send recipient field only offered Paste and a tx-history
suggestion list — no way to pick a saved contact. Add a compact contacts
icon button next to Paste that opens a picker popup over the App-owned
address book; selecting a contact fills the recipient (re-validated next
frame).
- New header-only src/ui/windows/contact_picker.h: ContactPickerPopup(id,
book, outBuf, outSz) — a reusable popup listing contacts ([Z]/[T] tag +
label + short address) that copies the chosen address into the caller's
buffer. Kept out of the material layer so the design-system headers stay
free of data/ deps; reserved for Chat "new conversation" later.
- send_tab: shrink the recipient input to make room for the icon button
(ICON_MD_CONTACTS), wire the popup to app->addressBook(). Coexists with the
existing Paste + suggestion affordances.
- i18n: send_contacts_button tooltip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 0b. The address book was buried in Settings behind a modal dialog.
Make it a first-class "Contacts" tab (which will also become the chat
roster), rendered inline in the main content area.
- New NavPage::Contacts (after History; ICON_MD_CONTACTS) +
WalletUiSurface::Contacts. isUiSurfaceAvailable's `default: return true`
shows it in BOTH variants; uiSurfaceNeedsWalletData's default keeps it
usable before wallet data loads. All the touchpoints wired: NavPageSurface,
GetNavIconMD, the app.cpp dispatch case, the app_network.cpp tracePageName
case, and the `contacts` i18n label.
- New src/ui/windows/contacts_tab.{h,cpp}: RenderContactsTab lifts the
toolbar + table + add/edit modal out of address_book_dialog, rendered in a
BeginChild scroll region (peers_tab pattern) instead of an overlay; the
add/edit form stays a modal layered over the tab. Reuses the existing
address_book_* i18n keys and the dialogs.address-book schema.
- Delete address_book_dialog.{h,cpp}; remove its app.cpp render pump and the
dead App::show_address_book_. The Settings "Address Book…" button now
navigates to the tab (setCurrentPage) instead of opening the modal, so the
Tools & Actions grid layout is untouched.
- CMake: swap the dialog sources for contacts_tab.
Behavior-preserving move; search/sort/keyboard/contrast land in 0d. Visual
check pending the per-theme screenshot sweep.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 0a of the Contacts/Chat plan. The address book was a file-scoped
singleton (`s_address_book` + `getAddressBook()`) trapped inside
address_book_dialog.cpp, reachable only from that TU. Promote it to an
App-owned member so the upcoming Contacts tab, the Send contact picker,
and a future Chat roster all read one source of truth.
- app.h: add `data::AddressBook address_book_;` + `App::addressBook()`
accessors, next to the other owned data models.
- app.cpp: load it once in App::init() (idempotent; missing file is fine;
purely local, no daemon dependency).
- address_book_dialog.cpp: delete the singleton + getAddressBook(); read
through the App* the dialog already carries (dropping the dead
`(void)app;`). show() is static so it can't reach the App — drop its
per-open reload (the book is authoritative for the app lifetime and
self-saves on mutation). Drop the now-unused <memory> include.
No behavior change; groundwork for the Contacts tab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Centralise the remaining hardcoded English literals in two otherwise
fully-translated files (explorer_tab: 40 TR calls, shield_dialog: 23) so
they join the i18n system with an English source key + fallback:
- explorer_tab: the three search-error messages (invalid response, hash
not found, not-connected) now use explorer_* keys. Also guards a hash
lookup behind an active daemon connection and disables the block-detail
prev/next nav while a fetch is in flight (both were the reason these
error paths could fire).
- shield_dialog: the operation status/error strings (submitting, submitted,
failed, status, error-checking-status, shield/merge failed) now use
shield_* keys.
- i18n: add the new explorer_* and shield_* English source keys.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- mining_benchmark: when a thread benchmark finishes with no nonzero
hashrate sample (e.g. the pool never reported a rate), don't finish
silently — set a new `inconclusive` flag on ThreadBenchmarkUpdate and
let mining_tab warn. Keeps the state-machine core pure/no-I/O (it is
unit-tested), rather than calling the Notifications singleton from it.
- mining_controls: fold the two byte-identical idle-delay combo blocks
(non-scaling + thread-scaling branches) into one lambda parameterised
by combo id; removes ~30 lines of duplication.
- request_payment: drop the dead s_selected_addr_idx state and write the
address-selected comparisons as std::string == char* consistently.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tighten input handling across several dialogs so bad/edge input can't
produce a doomed request or a confusing display:
- app_security: passphrase strength meter now factors character-class
diversity — an all-one-class string downgrades one tier so "aaaaaaaa"
no longer scores as high as a mixed one.
- block_info: clamp the height field to the chain tip and hide/deny
"Next" at the tip (was only yielding a raw RPC error).
- address_label: trim surrounding whitespace before saving; a
whitespace-only label clears it (mirrors clearing the icon).
- send_tab: re-clamp the amount when a Max send has its fee bumped in the
confirm popup (kept the total within budget) and NUL-terminate the
strncpy'd address/memo when a payment URI overwrites the form.
- settings_page: reject an empty/whitespace-only lite wallet path before
dispatching an unusable open/restore request.
- peers_tab: guard ExtractIP against an empty address.
- transaction_details: disable "View in explorer" when the configured
explorer URL is empty/whitespace so we never open a garbage link.
- app: seed-backup "Skip" now requires a second, deliberate click with a
fund-loss warning before it dismisses.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address a cluster of low-severity robustness nits where an async
callback could be skipped (leaving a spinner spinning forever) or a
synchronous action could be re-fired mid-flight:
- app_network: add catch(...) fallbacks around the importPrivateKey and
submitZSendMany worker lambdas so a non-std throw can't escape the
worker and skip the main-thread callback (stuck "Importing…"/"Sending…").
- export_transactions: re-entrancy guard disabling Export while a write
is in flight.
- bootstrap_download: disable Cancel once clicked so the request is
visibly acknowledged and can't be re-fired before the worker stops.
- network_tab: give clear "already in list" feedback on a duplicate
server (keep the inputs, clear only the stale invalid-URL error) and
disable/relabel Refresh while a probe is in flight.
- key_export: offer Retry from the error branch (falls back to Reveal)
and guard against an empty/malformed address before dispatching.
- receive_tab: only enter the "generating…" state when a dispatch is
actually possible (guards a stuck spinner when disconnected).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Address-transfer dialog closed itself on submit (s_open=false the same frame),
so its in-dialog result screen was dead code. Keep the dialog open on submit:
s_sending drives the button to a disabled "Sending…" state, and the async
callback's result (success txid / error) now shows in the result screen with
its own Close button.
- Settings RPC connection editor was inert AND showed compile-time defaults.
Populate Host/Port/Username/Password from the auto-detected daemon config
(rpc::Connection::autoDetectConfig) and make the fields read-only — the RPC
credentials come from the daemon's DRAGONX.conf, so these now accurately
DISPLAY the live connection instead of pretending to be an editor (which did
nothing). The existing "auto-detected" note now matches the behaviour.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The last four (more involved) robustness items from the audit:
- deleteBlockchainData: post the deleted-item count to the main thread (via an
atomic, since Notifications isn't thread-safe) and show a completion toast
("Blockchain data deleted (N items). Daemon restarting…") — previously the
result was only logged.
- Pool mining: pool start has a connect delay with no feedback; announce
"Starting pool miner — connecting…" on a successful start and "Pool miner
connected and hashing." once the poll confirms it (contained pool_starting_
flag; the shared mining-toggle state machine is untouched).
- Force Quit (shutdown screen): gate it on the status text having STALLED (a
genuine hang) rather than a bare 10s timer — with a 20s hard-ceiling backstop —
and show a state-aware caution naming the stuck step (force-quitting mid daemon
flush risks the chainstate).
- Benchmark: require a confirming second click that first builds candidates to
estimate the duration ("Benchmark takes ~Ns and interrupts mining"), instead
of interrupting mining immediately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Send: re-validate against LIVE state at the "Confirm & Send" click (connected,
not syncing, amount > 0, total <= available). The confirm dialog persists
across frames, so a balance drop / sync restart / fee bump after Review could
otherwise broadcast a now-invalid transaction; it now shows a clear message
instead of sending.
- Console: a malformed JSON argument ({...}/[...] that fails to parse) was
silently sent to the daemon as a plain string. Now the command fails with a
clear "Argument N is not valid JSON" line instead of being silently mangled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Send: block Review from a view-only source (no spending key) with a tooltip,
instead of failing at submit; label the memo counter as bytes and warn in
colour once the field hits its cap (Sapling memos are byte-limited).
- Shield: show "No shielded (z) address yet — create one on the Receive tab
first" when the destination combo is empty, instead of an empty dropdown.
- Updater dialogs (xmrig/daemon): suppress the window X during an active
download/verify/extract so closing can't orphan/block on the worker — the
in-dialog Cancel is the way to abort.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
More robustness fixes from the audit (the import-key pattern, lower severity):
- Trim pasted input before use/validation: network add-server URL/label,
lite-wallet key import (+ block empty), validate-address, address-book entry.
- Confirmations for destructive/irreversible actions:
- Address-book Delete now needs a confirming second click (no undo).
- Console `stop` needs a confirming second `stop` (it shuts down the node).
- Wizard "Skip" encryption needs a confirming second click (keys stored
unencrypted).
- Send amount: normalize to 8dp (satoshi precision) on both the DRGX and USD
inputs so digits past 8dp aren't silently dropped between the preview/review
and what's actually sent.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The app-wide "import-key pattern" fixes (missing guards / untrimmed input / no
re-entrancy protection), from the robustness audit:
- Import-key dialog: unify the type indicator and the RPC dispatch on one
classifier (classifyPrivateKey is now prefix-aware — an "SK..." shielded key no
longer misroutes to the transparent RPC), trim manual input (not just Paste),
and gate both the indicator and the Import button on a shared
isRecognizedPrivateKey() so unrecognized/empty input can't be submitted.
- Shield: guard the submit button on connected + !syncing (like Send), with a
disabled tooltip, instead of firing into a raw daemon error.
- Mining: disable the pool Mine button when the payout address is empty
("enter a payout address first"), and trim the pool URL/worker on persist.
- Console: track in-flight RPC commands (atomic counter + busy() override) so the
input is disabled while a command runs instead of piling up on the worker.
- Maintenance (rescan/repair/delete-chain/reinstall-daemon): re-entrancy guard —
bail with "already in progress" instead of launching a duplicate destructive op.
- Bootstrap dialog: re-attach to an in-flight download on reopen instead of
resetting state and orphaning the running worker.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the app-wide robustness audit (calibrated to the import-key dialog). These
are the safety-critical fixes; several could cost users funds:
- Backup/export false success (FUND LOSS): exportAllKeys pre-seeded a header, so
a keyless result (the usual case when the wallet is encrypted+locked) still
looked non-empty and backupWallet wrote a private-key-less file and reported
"Backup saved". Now exportAllKeys returns an exported count; backupWallet and
the export-all dialog refuse to write / report success on 0 keys, disclose the
count, flag partial results as INCOMPLETE, and the backup dialog confirms
before overwriting an existing file (+ trims the path).
- Bootstrap: fail CLOSED — refuse to install an unverified multi-GB archive when
no checksum is published (was `return true`), mirroring the xmrig/daemon updaters.
- Restore-from-seed: require a valid BIP39 word count (12/15/18/21/24) with a live
"should be 24 words — you have N" hint instead of accepting any non-empty text.
- Encryption passphrase: detect leading/trailing whitespace and BLOCK with a
warning (not a silent trim, which would change the passphrase and lock the user
out).
- Receive payment QR: emit the canonical `drgx:` scheme with a URL-encoded memo
via a new shared util::buildPaymentUri (the request-payment dialog now routes
through it too, so they can't diverge); the old "dragonx:" + raw memo was
unparseable by the wallet's own scanner.
Also clamps the receive "Recent Received" list to the 4 most recent (companion to
the recent-lists commit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each pool row showed "<hashrate> N%" with nothing saying the % is the pool fee.
Make it "<hashrate> N% fee" and widen the left auto-balance card (25%/180dp ->
30%/210dp min) so the added text fits.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Lower the zebra-stripe contrast (scanline-line-alpha 4 -> 2; the derived dark
band softens with it) so the alternating rows are subtler.
- Give the input's terminal bar a 1px outline along its own edges (theme-aware),
matching the output panel's glass rim, and a near-white surface on light skins.
- Add a blinking terminal caret at the end of the input when it isn't focused.
- Report a daemon started before the wallet ("no wallet-managed EmbeddedDaemon"
but RPC connected) as "Running" instead of the misleading "no daemon".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The overview "Recent Transactions" and the send tab "Recent Sends" lists showed
every matching transaction in a scroll region. Cap them at the 4 most recent
(state.transactions is sorted newest-first, so a simple head-limit). The receive
"Recent Received" list gets the same clamp in the Tier-1 robustness commit
(same file as the QR fix).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lower-severity polish from the per-skin review:
- Market portfolio ratio bar: was full-saturation Success/Warning at a208 over a
white a10 track — the green read as "neon" on dark skins and the track was
invisible on light skins. Make it theme-aware: muted fills (a165) on dark, a
dark track on light so the bar is defined on white/pastel surfaces.
- Light-skin muted text: bump --on-surface-disabled 0.52 -> 0.62 on light/
color-pop-light/dune/marble/iridescent so secondary text (addresses, category
labels) clears the pastel/gradient backgrounds (analog of the dark-skin bump).
Remaining review nits are the "white-overlay-assumes-dark" track/hover pattern
(a broader separate sweep) and per-skin aesthetic trade-offs; deferred.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the high-severity, systemic legibility issues found in the per-skin
screenshot review (verified across all 9 skins):
- Status pills (history): "Confirmed" text was 55%-alpha (dim); make it full
opacity, and give status/shield pills a visible tinted fill (a48) + 1px border
(a90) so they stop vanishing on dark-red / near-white / gradient skins.
- Console light theming: light skins now use a near-white terminal surface, and
the log text/JsonBrace colors switch to the dark defaults in light themes (the
schema colors are dark-terminal-authored — honor them only in dark themes) so
text isn't pale-on-white.
- Dark-skin muted text: bump --on-surface-medium 0.85 / --on-surface-disabled
0.58 on dragonx/dark/color-pop-dark/obsidian (obsidian addresses were ~1.1:1).
- Disabled Send button: the disabled fill was hardcoded white (invisible on
light skins) — use a dark overlay in light themes.
- Light-skin inputs: stronger frame fill + border color and a 1px frame border
for light themes so fields (and buttons) have defined boundaries on white/
pastel surfaces.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extend the inputs layer with the two remaining form-field idioms, pixel-identical:
- material::LabeledInputRow(label, labelPos, ...) — horizontal label-beside-input
(Text + SameLine + SetNextItemWidth + InputText). Adopt at the settings RPC
host/port/user/pass rows.
- material::LabeledInputMultiline(label, id, buf, size, flags) — label above a
sized multiline input. Adopt at the address-book notes and request-payment
memo (drops the redundant SetNextItemWidth that InputTextMultiline's explicit
size already governs).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UI-standardization audit: the labeled single-line form field (a plain
ImGui::Text label above a fixed-width InputText/InputTextWithHint) was
open-coded across dialogs. Add material::LabeledInput(label, id, buf, size,
width=-1, hint=nullptr, flags=0) mirroring that idiom exactly — pixel-identical,
but a single chokepoint so input styling (label typography, frame, spacing) can
later evolve in one place. Returns the input's edited bool.
Adopt at the clean vertical (label-above) single-line sites: address-book
add/edit (label + address), shield from-address, export-all-keys + export-
transactions filename, validate-address input, request-payment label + URI.
Left as-is: horizontal label-beside-input rows (settings RPC host/port/user/
pass use Text + SameLine), multiline (InputTextMultiline), numeric (InputDouble),
combos, and hint-only inputs — none match the vertical single-line idiom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UI-standardization audit: the receive address-type filter (All/Z/T) was three
hand-rolled gapped ImGui::Button pills with per-segment PushStyleColor. Replace
with the shared material::SegmentedControl (rounded track + Primary inset-pill
on the active cell + centered caption labels), the same control the market
portfolio editor uses. Keeps the same right-aligned footprint (toggleTotalW)
and drives s_addr_type_filter from the returned clicked index. Verified across
all 9 skins via the screenshot sweep.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to c83348a: convert the three mining solo-mode header toggles
(idle-mining, thread-scaling, gpu-aware) from the hand-rolled "draw active
pill + glyph, then hit-test" idiom to material::IconButton using the restBg
(active-pill) path. Each keeps its exact behavior pixel-for-pixel: a circular
Primary-tinted pill when active (alpha 60 idle / 40 scale+gpu), a state-colored
glyph (Primary when on, muted when off), hand cursor + tooltip on hover, and
the idle button's cursor save/restore (savedCur, restored at block end) is
preserved. Completes the clean icon-button adoption; the raw-rect IsRectHovered
sites remain intentionally separate (different hit mechanism).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UI-standardization audit: ~66 call sites still used raw ImGui::Button /
ImGui::SmallButton instead of the app's canonical TactileButton, so those
buttons missed the standard glass fill + rim + tactile overlay (and the
light-theme flat treatment). Convert 59 plain action-button sites across the
wizard, security dialogs, settings, market/console/explorer tabs, and the
address dialogs to material::TactileButton / TactileSmallButton (drop-in:
same signature/return, args preserved verbatim). Danger buttons keep their
PushStyleColor wrappers — Tactile renders through them then overlays.
Deliberately left raw (not plain buttons): InvisibleButton hit-targets, the
frameless transparent-bg collapsible-header toggles (RPC/Debug), the icon-only
pagination chevrons, and the receive All/Z/T segmented filter — a glass rim
would clash with those intentionally borderless/segmented looks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UI-standardization audit: three draw patterns were copy-pasted across tabs and
dialogs. Promote each to a material:: helper and adopt at every verbatim site.
Output is pixel-identical (same colors/geometry threaded through as params).
- material::DrawProgressBar / ProgressBar — replaces 3 byte-identical rounded
fill bars in the daemon/xmrig/bootstrap download dialogs.
- material::CopyableTextOverlay — the click-to-copy affordance (hit button +
hand cursor + tooltip + hover underline + clipboard) shared by the peers
best-block hash and the 3 mining stats (difficulty/block/address). Returns
"copied this frame" so callers keep their own toast (no ui-layer dependency).
- material::DrawPill / PillSize — rounded bg + optional border + inset text
badge, shared by the explorer status pill, the market Z/T chip, and the
network official/custom tag.
Divergent one-offs left in place (transactions status pills use schema rounding
around pre-positioned multi-line text; the mining filter-pill is a button bg).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UI-standardization audit found two dead, contradictory "how to draw a button"
surfaces with zero call sites:
- src/ui/material/components/buttons.h (353 lines: TextButton/Outlined/Contained/
Button(spec)/IconButton/FAB) — included by 3 mining files but never used.
- ApplyTactile (draw_helpers.h) — a retrofit-tactile-onto-plain-button wrapper,
never called.
Remove the file, the 3 dead includes, and ApplyTactile. TactileButton/StyledButton
remain the canonical button helpers. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per the user's selection, convert the large multi-section dialogs to the BlurFloat
style (floating content on the blur, plain heading, no glass card), each KEEPING
its authored width so width-coupled content is untouched:
- Export all keys, Console RPC reference, Explorer block detail
- Updater dialogs: daemon update, xmrig update, bootstrap download (multi-state)
Settings and Address book intentionally kept as cards (the modal Settings *window*
is dead code — show_settings_ is never set; the live Settings surface is the nav
page, not a modal; Address book has a nested edit that reads better contained).
All other confirm/input dialogs stay card-on-blur ("card for small"). Updater
dialogs are multi-state — GUI-check each state (confirm/progress/done/failed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Convert two clean detail-view dialogs to the BlurFloat style (floating content on
the blur, plain h6 heading, no glass card) — the "float for large" half of the
plan, applied conservatively: keep each dialog's authored card width so its
width-coupled content (absolute SameLine positions, GetWindowWidth math) is
unchanged, and rely on the footer Close + outside-click for dismissal (the plain
heading drops the title-bar close X, matching the portfolio modal). Auto-height
preserved. A representative pair to GUI-review before rolling the treatment out to
the other large dialogs (or reverting if the card-on-blur reads better).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Grep-verified dead, zero live callers (the live paths all go through
BeginOverlayDialog and already got the blur backdrop):
- App::renderAboutDialog (app.cpp/app.h) — dispatch uses ui::RenderAboutDialog.
- src/ui/windows/import_key_dialog.{h,cpp} (ImportKeyDialog class) — live path is
App::renderImportKeyDialog.
- src/ui/windows/backup_wallet_dialog.{h,cpp} (BackupWalletDialog class) — live
path is App::renderBackupDialog.
Drop the two dead files from CMakeLists. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dogfood the Phase 0 framework: RenderPortfolioEditor now opens via
material::BeginOverlayDialog(BlurFloat) + EndOverlayDialog instead of its own
inline overlay scaffold, and uses the promoted material:: SegmentedControl /
RightAlignX / BeginFadeScrollChild helpers (overlay_scroll.h). Delete the
portfolio's inline backdrop/capture-once state machine + its PfEditState capture
statics + the four local helper copies. App::render's ModalRenderGuard is now
driven by the modal-agnostic AnyBlurOverlayActiveLastFrame() (timing-equivalent),
and LatchBlurOverlayActive() at frame end handles the acrylic re-capture on close.
Framework tweak: floating cards pop the card WindowPadding after BeginChild (so
nested children don't inherit it) and center button labels, keeping the net
style-var count at 2 so EndOverlayDialog is unchanged and GlassCard stays
byte-identical. RenderPortfolioEditor: 357 -> 282 lines. Needs GUI verification
that the portfolio modal is visually/behaviourally unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add market_iv_1h/1d/1w/1m and market_updated ("· Updated %s") keys and use them
in the chart interval strip (previously hardcoded "1H"/"1D"/"1W"/"1M") and the
pair-selector attribution line (previously a literal "· Updated %s"). Closes the
audit's i18n gaps in the market tab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the balance summary (fiat/DRGX/BTC + shielded ratio bar) and the
draggable/resizable custom-group card grid (drag/resize gestures, dot grid,
click-to-edit) out of RenderMarketTab into mktDrawPortfolio(MktCtx); the local
x-coord `cx` renamed `cx0` (ctx param is `cx`). Drop the now-unused
gap/dl/glassSpec/buf locals. RenderMarketTab is now ~150 lines (from 962) — a
thin orchestrator: precompute -> build MktCtx -> hero/chart/pair/portfolio.
Verbatim bodies. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the ~250-line chart block (interval strip + 24h stats + refresh, plotted
curve with grid/area-fill/hi-lo+time labels/hover crosshair+tooltip, empty
state) out of RenderMarketTab into mktDrawPriceChart(MktCtx), recomputing cheap
locals and reading the precomputed series via the ctx. Drop the now-unused
hs/pad locals. Verbatim body. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a MktCtx struct holding RenderMarketTab's precomputed geometry/series,
built once after the precompute, and extract the combined hero card (glass panel
spanning header + chart, price/ticker/period-badge/trade button) into
mktDrawPriceHero(MktCtx). Cheap locals (dl/fonts/dp/market) recomputed in the
helper; the hero's local x-coord `cx` renamed `cx0` (the ctx param is `cx`).
Verbatim body. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move the exchange/pair chip grid (wrapping flat chips + click-to-switch +
attribution line) out of RenderMarketTab into mktDrawPairSelector(app, registry,
availWidth), recomputing its own schema/fonts/market. Drop the now-unused ovFont
local. Verbatim body. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lift the ~90-line [&]-capturing drawCard closure out of RenderMarketTab's grid
code into a file-static mktDrawCard(MktCardCtx, min, max, entry, hov). The
captured locals (dl/state/market/mktDp/pfInset/pfCardPad/sub1/capFont) are
bundled into a MktCardCtx bound once per frame. Verbatim body. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pull the ~30-line row-major cell placement (honor stored placement else
auto-place) out of RenderMarketTab into a pure, deterministic helper taking
(entries, cols, minW, minH). Also bump the grid resize-readout buffer 16->32 to
silence a worst-case -Wformat-truncation the extraction surfaced. No behavior
change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pull the three detail-pane section blocks out of RenderPortfolioEditor into
pfDrawAppearanceSection / pfDrawPriceSection / pfDrawAddressSection (each
recomputes its own dp/fonts/state; the section dispatch is a 3-line switch).
Verbatim move — same widgets, IDs and drawing. RenderPortfolioEditor drops from
~760 to ~357 lines. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>