The full UI sweep (dev builds only) ran its demo surfaces against the
real profile. Its teardown deleted wallet-{cold,trading,mining,donations,
2023,payroll}.dat from the real datadir, it created zero-filled wallet
files and picker-demo/extra-wallets folders, its demo wallet-index upsert
re-minted the active wallet's scopeId (hiding "w:"-scoped contacts), the
Contacts tab could re-attach legacy-scoped contacts to such a throwaway
id in addressbook.json, and it persisted the demo active wallet file and
Mining pool worker.
- Preflight: a full sweep is refused (toast + log line) while a real
workflow is in flight: a pending seed_migration_* setting or open
migrate dialog, a wallet switch, an encryption/import workflow, an
encrypt/PIN/lock operation, the seed-backup dialog, or a bootstrap.
The DRAGONX_FULL_SWEEP hook then exits as for an empty catalog.
- Persistence latches: AddressBook (snapshot on the first sweepBegin,
save/load latched while a demo-contacts surface is shown), WalletIndex
and Settings (active wallet file, contacts view mode, pool URL/worker)
for the whole sweep. Settings::sweepEnd() is the last statement of
clearDemoWalletData and flushes once only if a save was suppressed; a
quit mid-sweep writes nothing to the profile (the 0700 sandbox, with
zero-filled demo files only, may stay in the temp dir).
- Scope ids: during a full sweep activeWalletScopeId() returns the
persisted id or "" and never mints one, so the Contacts tab cannot
move legacy-scoped contacts to a throwaway id. The only address-book
write a sweep can still cause is the one-time legacy re-attach to the
wallet's persisted id, which opening Contacts does anyway.
- Sandbox: demo wallet files and folders go to a fresh random 0700 dir
under the temp dir (exclusive create, a pre-existing path or symlink
is refused), removed at sweep end. The teardown that deleted those six
wallet files is gone.
- Cleanup: the migrate-to-seed state, wizard phase, demo chat
conversations and a fabricated demo chat identity are reset at sweep
end; the seed-backup surface always shows the demo phrase and a real
z_exportmnemonic result is dropped while the sweep runs.
- i18n: one new key (screenshot_sweep_refused) in i18n.cpp and the 8
language files.
Tests: the address book snapshots only on the first sweepBegin, keeps
addressbook.json byte-identical while latched and restores the real
book; the wallet index keeps wallets.json and the real scope id, also
with the background writer attached; Settings restores the latched
values and flushes once only when a save was suppressed; the sandbox
helper refuses a pre-existing path or symlink and removes only a
prefixed dir, never a symlink's target.
Release note: v2.0.x UI sweeps could delete wallet-{cold,trading,mining,
donations,2023,payroll}.dat from the data directory and re-scope
contacts; this cannot be undone automatically.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every open of the Wallets dialog, Refresh, folder add or remove, and Open
on a stale row walked each user-added folder recursively on the UI thread
before the dialog drew: up to 40,000 entries per folder, roughly
50-150 ms warm on Linux and seconds on a cold disk, a network share or
Windows.
The datadir listing (a few top-level files) stays synchronous, so a
wallet just created shows at once. The recursive walk runs on a detached
worker with the same depth, hit and entry limits; its rows are merged at
the start of a later frame and only the new rows are probed.
- The dialog opens at once with the datadir rows. The external rows
arrive when the walk ends, with "Scanning folders..." beside the
scanned-folders caption, and the card grows once.
- Reopening within 60 s with the same folders reuses the last walk
without touching the disk, so its rows and sizes can be up to a minute
stale: a wallet copied into a folder in that minute needs Refresh.
Refresh and Open on a stale row always walk again.
- While a walk or a wallet probe runs (up to 30 s) the dialog keeps
frames coming, so rows and badges appear without moving the mouse.
- The 200-row cap now holds across all folders; each extra folder could
add one row past it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Open on a wallet row whose file was moved or deleted after the list was
built rebuilt the list from inside the dialog's row loop. The loop still
held a reference to that row and walked a sort order sized for the old
list, so the click read freed memory and could index past the end. Open
now flags a rescan for the next frame; the vanished row drops out one
frame later, with no crash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bundles the session's UI work (the touched files carry several of these
changes together, so they are committed as one coherent UI batch):
- FAQ: new RenderFaqDialog + data-driven faq_content, opened from a
status-bar "?" (and the Windows title bar), styled like the Wallets
modal with search, Wallet/Daemon tabs, and smooth scroll.
- DPI/font-scale audit: multiply hand-drawn absolute geometry by
Layout::dpiScale() across ~30 files so nothing renders native-size at
HiDPI / font_scale 1.5 (verified with a full sweep at 1.5x).
- Mining: chart now fills the horizontal space; thread stepper +/- buttons
match the input-box height; move the stratum-host toggle into
Node & Security (v1.3.0+).
- Settings: fix the auto-shield status text overlapping the grid.
- Sidebar: drop the peer-count badge on the Network button.
- i18n: wrap 193 hardcoded literals with TR() (keys/translations added in
the preceding i18n commit), so the security/PIN/lock flow, seed-backup
wizard, and witness-rebuild dialog localize.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the layout-improvement suggestions from the layout audit (visual arrangement
only; no functionality added or removed):
- Send/Receive: the recent-activity list now grows to fill the space below the fixed
compose/receive card (more history visible), with a centered empty-state when there is
none — instead of leaving dead canvas.
- Shield/Merge: pair the Fee and UTXO-Limit fields on one row to tighten vertical rhythm.
- Market: extend + frame the portfolio group-list as one contained panel (with a bottom
edge) and center its empty-state, closing the previously un-anchored gap.
- Overlay dialogs: raise the card glass fill/border alpha (35/50 -> 60/90 of 255) so the
dialog card reads as a distinct surface over busy backdrops (global, all overlays).
- Wallets: size the list height to the actual wallet count instead of always reserving 7
rows, removing the large gap before the scan/create prompts (still scrolls when many).
- Contacts: width-aware address truncation shows more of the address on wide rows.
- Transfer Funds: give the "sends the full balance" warning a warning icon + color so the
stakes stand out from the neutral result-preview lines.
- First-run wizard: collapse a completed Step 1 (Appearance) to the compact pill like
Step 2, so a finished step is no longer taller than the active one.
- Explorer: distribute the Chain card's two stats to match the density of the sibling
metrics grid.
- Validate Address: a "Results will appear here" caption fills the pre-interaction blank.
- Change Passphrase: add the warning banner its sibling security dialogs have.
- Migration ShowSeed: box the 24-word mnemonic grid (a GlassSectionScope behind the
existing RenderSeedWordGrid) so the critical secret reads as a distinct artifact —
purely visual, no seed/logic/state change.
Verified at 1280 across full-node + Lite + Windows (ctest green) and an adversarial diff
review (clean). New i18n key backfilled into all 8 languages.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The tail of the DPI/font-scale/responsiveness audit — ~26 remaining findings.
Container / recent-list (Theme-1 leftovers):
- Send: drop NoScrollbar|NoScrollWithMouse on ##SendFormScroll so Recent Sends is
reachable at font_scale 1.5 (parity with receive).
- Receive: cap the QR/form card via std::min(mainCardTargetH, availH - recentReserve)
so RECENT RECEIVED stays on-screen (identity at 1.0x).
- Wallets dialog: size the capped-mode list to whole rows so it no longer clips a
partial row / crowds "Create a new wallet".
Narrow-width (1024px) reflow:
- Console toolbar reserves space for ALL trailing controls (both icon toggles + zoom
buttons) so the +/- zoom no longer runs off-window.
- History sort combo sized to its measured widest localized label ("Newest first").
- Settings Theme/Layout/Language row: scale the wide→stacked breakpoint by dpiScale so
it drops to full-width stacked combos at 1.5x (Consolidated Card no longer clips).
- Recent-tx type label: derive the address column X from the measured label width so it
can't collide at narrow widths.
- Mining Recent Pool Payouts: floor the panel height to fit the empty-state caption.
Cosmetic ×dpiScale() on absolute geometry (no-ops at 1.0x): mining SOLO|POOL toggle &
idle combos, market pair-chips, password/PIN strength bars, receive/send currency
toggles, explorer search bar/rows/rounding, About-card logo, chat empty-state wrap,
recent-list address/time offsets, address-toolbar & two-row action buttons, console
line-gap/status-dot/pane rounding.
Verified at font_scale 1.5 and at 1024px across full-node + Lite + Windows (ctest
green) and an adversarial diff review (one over-reserve regression fixed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Security audit remediation (15 confirmed findings from the codebase audit):
- H-02: scrub+delete the decrypt-flow plaintext key export on ALL exit paths
(RAII guard) and purge stale obsidiandecryptexport* files at startup.
- M-01/L-03/L-04/L-05/L-07: sodium_memzero the Set-PIN and encrypt-PIN worker
passphrase/PIN copies, the RPC Basic-auth string (auth_), the exported/imported
key buffers (App::wipeSecrets, called from ~App and before main's _Exit), and
the first-run wizard "Skip" buffers.
- M-03/M-04/M-05/L-06: return locked COPIES from XmrigManager/EmbeddedDaemon
getters (dedicated error_mutex_; DaemonController::lastError now by value),
route xmrig last_error_ writes through a locked setter, and wrap
shutdown_status_/wizard_stop_status_ in a locking GuardedStatus
(wizard_stopping_external_ -> std::atomic).
- M-02: persist after a console send/shield/import in the lite backend.
- L-01: require the confirm click for z_shieldcoinbase/z_mergetoaddress.
- L-02: quote/escape each Windows daemon argv per the MSDN CommandLineToArgvW rules.
- L-08: pin json/tomlplusplus/libwebp FetchContent to immutable commit SHAs.
- I-01: extract updater archives from the already-verified in-memory buffer
(no disk re-read TOCTOU).
Feature: warn once (full-node) when the active wallet loads empty while a sibling
wallet file in the datadir holds keys. A funded salvage wallet.<ts>.bak routes to
the recovery/Restore flow; a funded sibling .dat routes to the wallet manager.
Per-wallet-file dismissal; gated on synced + address-list-loaded to avoid false
positives on warm reconnect / spent-down wallets.
UX fixes:
- send: show the TOTAL balance (with a spendable "available" note) in the source
dropdown and keep pending-change addresses visible.
- chat: insert emoji at the cursor position; restrict new-chat recipients to
shielded (z) addresses.
- console: optional auto-focus of the command input on tab open (off by default).
- shutdown: when "stop external daemon" is on, keep the shutdown screen up until
the external node actually exits, showing live status.
Adversarially reviewed; verified across full-node, lite, and Windows builds; tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The wallets list badged the active/linked row from the runtime seed status
(activeWalletSeedBadge → wallet_seed_status_, reset only on disconnect) in
preference to the offline on-disk probe. Two issues let a genuinely legacy
wallet render as "seed phrase":
- The offline probe's budget-fallback branch dropped the fMnemonicSeed flag:
res.mnemonic was set only in the (parsed && complete) branch. The probe shares
a 768 MB budget across all wallet files, so a large wallet (e.g. a 194 MB one)
probed after the budget is spent falls into the fallback, loses its seed/legacy
classification (mnemonic → 0), and the row defers to the runtime badge.
- With mnemonic == 0, the code used the runtime badge, which can still carry a
HasMnemonic from a previously-active mnemonic wallet — mislabelling the legacy
wallet.
Fix:
- Carry the definitive positives (fMnemonicSeed/hdSeed/mkey) from a cap-truncated
btree walk — a found marker is authoritative even when the scan didn't finish.
- Make the on-disk fMnemonicSeed read take precedence: it's the SAME flag the
daemon's IsMnemonicSeed()/z_exportmnemonic consult, so a definitive read wins;
the runtime badge is used only when the probe genuinely couldn't decide, and
never overrides a definitive on-disk classification.
Verified the wallet in question is truly legacy (fMnemonicSeed=false on disk,
matching the daemon's CHDChain serialization + IsMnemonicSeed). The flag reader
(hdChainMnemonicFlag) is already unit-tested; suite green.
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>
The wallet switcher couldn't tell a BIP39 seed-phrase wallet from a legacy /
raw-entropy HD wallet without loading it, so non-active rows fell back to bare
HD-record presence — which mislabeled every HD wallet as "Seed phrase".
The distinction is actually on disk: the daemon serializes CHDChain with an
fMnemonicSeed bool (VERSION_HD_MNEMONIC=3, byte offset 52), and the hdchain
record stays plaintext even in an encrypted wallet. Read it directly:
- wallet_file_probe.h: hdChainMnemonicFlag() decodes the flag from the hdchain
value (1 mnemonic / 2 no-phrase / 0 undecidable); WalletBtreeStats.mnemonicSeed
surfaces it from the tier-2 btree walk.
- wallets_dialog.h: the badge prefers runtime z_exportmnemonic for the active
wallet, then the on-disk flag, then HD-record presence — so every row is
classified correctly (or honestly shows "?" when the flag can't be read, e.g.
the tier-1 byte-scan fallback).
- tests: decode-level cases (v3 set/clear, v1/v2, truncated, garbage version)
plus an end-to-end btree walk asserting mnemonicSeed.
Offset math + badge logic adversarially verified against the daemon source.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Wallets modal's "Seed phrase" vs "Legacy" badge came from the offline
wallet.dat probe, which flags hdseed/chdseed/hdchain. But hdchain is present in
BOTH a BIP39-mnemonic wallet AND a legacy HD wallet (DragonX has no separate
mnemonic DB record — the mnemonic is derived from the HD seed), so a legacy HD
wallet was shown as "Seed phrase".
For the ACTIVE wallet the app already knows the truth at runtime via
z_exportmnemonic (wallet_seed_status_). The active row's badge now uses that
authoritative status (activeWalletSeedBadge: seed-phrase / legacy / undecided)
and only falls back to the probe for non-active or not-yet-decided wallets; the
"unknown" seed badge is suppressed once the runtime status is authoritative.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1. Swap the 5 StyledButton actions (Open / Create / Scan folder / Reveal /
Close) to TactileButton, and remove the ImGui::Separator above the footer (the
reference has no divider). Already struct-form + fully i18n'd. Deferred: R6
glass-panel grouping of the scanned-folders section (it interacts with the
dialog's pre-computed cardHeight — a separate, more careful change).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With the list now pinned at max height, a few wallets leave blank space below
the cards. Fill it — only when there's real room to spare — with a subtle
centered folder glyph + "Scan a folder to find more wallets" (OnSurfaceDisabled),
so the area reads as a gentle nudge toward the scan action rather than dead
space. Purely decorative (draw-list only, clipped to the list); the actions stay
below. New i18n key wallets_empty_hint, translated across all 8 languages.
Verified on the sweep: the hint centers in the gap and reads subtly on both dark
and light themes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two wallets-modal changes:
- The wallet list now always renders at its max height (kMaxVisibleRows) instead
of shrinking to the wallet count, so the modal is a consistent size whether you
have one wallet or many — fewer rows leave empty space, more than 7 scroll.
- Add a "Scanned folders" manager: each user-added scan folder is listed with a
control to stop scanning it (removeExtraFolder + save + re-scan so its wallets
drop out). Paths front-elide to keep the identifying leaf visible, with a
full-path tooltip. The card-height math reserves the manager's rows so nothing
clips. Two new i18n keys (wallets_scanned_folders / wallets_remove_folder),
translated across all 8 languages; CJK subset rebuilt.
Verified on the sweep at 1.0 and 1.5x DPI: max-height list on both the few- and
many-wallet surfaces, the folder manager renders and stays clear of the footer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A wallet reached via two different path strings — e.g. a symlinked mount
(/mnt/usb) and its real target (/media/usb) — used to hash to two different
in-place link names and list as two rows, so the same file looked like two
wallets with two datadir links.
Give WalletRow a canonPath (fs::canonical, symlinks resolved, computed once at
scan time so the every-frame render/active-marking path stays cheap) and key
both the link name and the row de-dup on it. Now the same physical wallet maps
to one identity regardless of the path used to reach it: one link name (honors
the "distinct per-target name" invariant), and scan() lists it once — datadir
first, so a datadir wallet wins over the same file seen via an external path.
The link also points straight at the resolved real file instead of through the
symlinked path. Genuinely distinct wallets keep distinct identities (verified
with a standalone symlinked-mount harness).
Closes the last deferred finding from the open-in-place adversarial review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
On Unix a symlink is the better datadir link than a hard link: it needs no
privilege, spans volumes (a wallet on a USB / other partition just works,
where a hard link can't), and is visibly a pointer rather than an
indistinguishable second copy. Windows keeps hard-link-first (a symlink there
needs admin / Developer Mode), falling back to a symlink.
Symlink-first made two latent issues in the pre-clear reachable, fixed here
after an adversarial review:
- Dangling prior symlink: fs::exists follows the link (false when the source
moved), so it was skipped and create_symlink then tripped on the occupied
path. Catch it with fs::is_symlink (lstat) and replace it.
- Stale reserved-name occupant: the old "hard_link_count == 1 → reuse" path
could load the WRONG wallet — a count-1 file at our name is not a valid link
to the source (a live hard link has count >= 2), it's a stale orphan (source
deleted, or a copy left by a datadir migrated across filesystems). Gate reuse
on fs::equivalent(link, src) (same inode) instead, and reclaim the name only
when the occupant is provably redundant (another hard link still holds the
data); otherwise refuse rather than risk destroying something unique. So
hard_link_count is no longer trusted to prove identity — only data-safety.
Also guard against a stale row (source moved/deleted between the scan and the
click): !exists(src) refreshes the list instead of linking a ghost the daemon
would silently swap for wallet.dat. Verified with a standalone harness across
fresh / re-open / valid-hardlink-reuse / stale-orphan / redundant-occupant /
vanished-source cases.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the "Import" action — which copied an out-of-datadir wallet into the
datadir and cluttered the list with hard-to-tell-apart duplicates — with an
in-place "Open" that links the real file into the datadir under a stable
per-target name (wallet-ip-<FNV8>.dat) and switches to it. The daemon only
loads a bare filename from its own datadir, so a link is the minimal bridge:
hard link first (no privileges, same volume — covers non-admin Windows),
symlink fallback (cross-volume), then an error. Never a copy (that would fork
the wallet) and never a delete of a real file.
Also:
- show each external wallet's originating sub-directory (…/Backups/2021)
- add a per-row "open folder location" button
- widen the modal (780 → 860) for the extra button
- guard the daemon against a dangling link (external file moved / USB
unplugged) by falling back to wallet.dat rather than creating an empty one
Per-target link names (not one shared name) make switching between two
external wallets a real -wallet switch, so the rescan/cache index tracks each
correctly. Drops the now-dead wallets_import* i18n keys and back-fills the new
open/folder strings across all 8 languages + rebuilds the CJK subset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Prepend the containing folder (last 1-2 path components, e.g. "…/Backups/2021")
to an external wallet's metadata line, so several same-named wallet.dat files
surfaced by the recursive scan are distinguishable at a glance (the info-icon
tooltip still shows the full path).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swap the rounded-rect StyledButton for material::IconButton (square size →
bgRounding = radius, so a circle) with the arrow glyph mathematically centered;
carries its own hover fill + asc/desc tooltip.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the sort dropdown with a material::SegmentedControl — Created / Addresses
/ Txs / Size — matching the Receive tab's address-type toggle, with the asc/desc
arrow beside it. Shorten the four sort labels to fit the segments (updated across
all eight languages + the back-fill script). The direction arrow is positioned
explicitly on the segment's baseline (the control is draw-list based and doesn't
advance the layout cursor).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make "is the active wallet" the primary sort key so the current wallet always
stays at the top regardless of the chosen sort/direction; the remaining wallets
sort by the selected key underneath it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Parse the earliest keymeta nCreateTime out of the wallet.dat btree (the daemon's
"wallet birthday") and surface it in each row's metadata line ("created Aug 2025").
Add a sort control above the list — Date created / Address count / Transaction
count / Wallet size, with an ascending/descending toggle (descending default:
newest / most / largest first). Sorting reorders a display-index array
(s_order) rather than s_rows, so the async, index-aligned probe batch is
untouched; sort keys read a single frame-consistent snapshot of the probe
results. Address count prefers the authoritative cached index value, else the
btree key count.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The async probe fills badges/counts a frame after the dialog opens, which the
single-frame screenshot sweep captured before it landed (badges/counts missing).
Run the probe synchronously when App::isScreenshotSweeping(), so the captured
modal-wallets frame is populated; interactive use keeps the detached-thread path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adopt the shared ApplySmoothScroll helper (with NoScrollWithMouse) for the
##walletList child, matching the Settings page and the other scroll areas, so
the wallet list eases on wheel input instead of jumping.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parseWalletBtree() walks the wallet.dat Berkeley DB btree directly (no libdb, no
daemon): validate the metapage magic, follow the "main" sub-database (its pgno is
stored big-endian in the master map), traverse internal/leaf pages, and tally
records by their length-prefixed type name — transparent + shielded spendable
keys, address-book, and tx count, plus exact encryption/seed flags. Every offset
is bounds-checked, pages are deduped at push time (stack stays O(npages)), and a
visited-set + page/key caps make it safe on a corrupt/adversarial imported file;
files using DB_CHKSUM/DB_ENCRYPT (which shift the page layout) are rejected so the
byte-scan fallback runs instead.
The async probe now uses this as the primary path (exact badges + counts), falling
back to the byte-scan only when the btree can't be fully parsed. The wallets list
shows "N keys · M txs" for probed rows (labeled "keys", not "addresses", since the
count includes change keys the daemon's address list omits). Validated read-only
against real wallets (counts cross-checked; enc/seed match the byte-scan) and a
hand-built minimal btree fixture in the unit suite.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Redesign the wallet.dat status badges as a right-aligned vertical stack of
"label icon" rows ("Seed phrase 🌱" over "Encrypted 🔒", "Legacy", or "Unknown"),
each with a hover tooltip; name/metadata reserve the stack width and truncate
before it.
Move the wallet.dat probing off the UI thread: scan() now builds the row list
synchronously (filename/size only) and hands the file reads to a detached
background thread whose results land in a shared, index-aligned batch that
render() reads under a mutex. A re-scan supersedes the old batch (cancel + swap);
the thread touches only its own heap batch (never s_rows/statics), so it's safe
across re-scans and shutdown. The dialog opens instantly even with a large
active wallet; badges fill in over the next few frames.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Search user-added wallet folders RECURSIVELY (bounded: depth/hit/visit caps,
skip_permission_denied, no symlink-follow, node-junk + node-subtree pruning);
the datadir stays top-level. r.dir is each file's real parent so Open/import
resolve subdir wallets. Auto-suffix the import destination on a name collision.
Add util/wallet_file_probe.h: read encryption/seed/shielded flags straight off
a wallet.dat WITHOUT loading it — validate the Berkeley DB btree magic, then a
bounded streaming byte-scan for the length-prefixed record markers the daemon
writes (mkey -> encrypted; hdseed/hdchain -> seed/HD; zkey/sapzkey -> shielded).
Reads no key material. The Wallets dialog shows Encrypted / Seed / Legacy badges,
plus an Unknown badge when a large file couldn't be fully scanned (so absence of
a lock never falsely reads as unencrypted). Unit-tested + validated read-only
against real wallets.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Scan another folder" now anchors the picker at getDragonXDataDir() (where the
datadir wallets live) instead of home, so the user starts from a familiar spot
and navigates up/out to find wallets elsewhere.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The wallets dialog hand-computed a content-sized card height. With many wallets
on a small / HiDPI (150%) window the computed height could exceed the viewport,
and the framework clips a too-tall card — footer and all.
Cap the card at 0.86 of the viewport height. When capped, the wallet list flexes
to the space left above the create/scan/footer controls and scrolls internally,
so those controls stay pinned and visible; uncapped (the common case, few
wallets) nothing changes and there's no spurious scrollbar. Add a
modal-wallets-many sweep surface (8 wallets, self-cleaning teardown) to exercise
the capped-scroll path. Verified at 100% + 150%.
This is the one real fixed-height clip risk from the modal audit; the other 44
modals use auto-height (grow-to-fit, capped at a viewport ratio) and were
confirmed non-clipping — e.g. the 24-word seed-backup grid renders fully at 150%
in a 720px window.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wallets dialog refinements:
- More space between wallet cards (cardGap: spacingSm -> spacingMd).
- "Scan another folder" is now always visible as a full-width button (no
expandable toggle) that opens the new picker.
- The Create-wallet row and the scan button span the full list width, so their
right edges align with the wallet cards / Open|Import buttons.
New in-app folder picker (folder_picker.h): a Material-styled modal replacing
the raw path input. Browse the filesystem (Up / Home / click a sub-folder),
see the *.dat wallet files present + a count, and pick a folder to scan. The
picker takes over the modal surface while open (the overlay framework doesn't
nest); it remembers the last browsed location across opens. Fixed-height card
(no auto-height feedback), UTF-8-aware truncation, full-node icons confirmed
in the embedded MaterialIcons range. Added a modal-folder-picker sweep surface;
verified at 100% + 150%, dark + light.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two issues on the card list: it showed a scrollbar (and clipped the last card)
with only a few wallets, and the cards felt cramped.
- Scrollbar: the per-card advance added a trailing gap after EVERY card (incl.
the last) and ImGui inserted its default ItemSpacing between the Dummy items,
so the content ran ~1 gap taller than the child's height budget -> overflow ->
scrollbar + clipped card. Zero ItemSpacing inside the list child, register each
card's footprint with a Dummy, and gap only BETWEEN cards (no trailing gap), so
the content matches the budget exactly. Sized the list to fit up to 7 cards
before it scrolls.
- Roominess: bigger name (subtitle1), more vertical padding (spacingMd), and a
touch more space after the icon.
- Removed the full-card InvisibleButton (hover is IsMouseHoveringRect again): its
overlap with the action button tripped ImGui's ConfigDebugHighlightIdConflicts,
drawing a red debug border around the list at 150% DPI.
Verified via the headless sweep at 100% and font_scale 1.5 — no scrollbar, no red
border, cards fully visible with breathing room.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the wallet table with a stack of Material-style cards inside a scrolling
child. Each wallet is a rounded card: a leading wallet icon, the filename as the
primary line, and a muted metadata subtitle (size · N addresses · balance DRGX ·
last opened). The action sits on the right — Open / Import buttons, or a green
"Active" chip for the current wallet. The current wallet's card gets a green tint,
border, and icon; external files show an info icon + tooltip; cards highlight on
hover. The dialog stays content-sized + fixed-height (computed from the wallet
count, up to 6 before it scrolls), so the monitor-drag stability is unaffected.
Adversarially reviewed (two-lens workflow + synthesis): the reviewers' DPI
unit-mixing and broken-scroll findings were false positives (LegacySize is the
DPI-scaled atlas size, and BeginChild takes physical px while the framework's
cardHeight takes logical) — the only real issue was byte-wise truncation that
could split a multibyte UTF-8 char, now fixed to pop whole code points. Verified
via the headless sweep at 100%, 150%, and light skin.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Act on the design review of the Wallets modal (all six points):
1. Content-sized card — size the card to the wallet count (up to 8 rows, then the
list scrolls) instead of a fixed 620px, killing the huge empty void a short
list floated in. Computed from the KNOWN row count, so it stays a fixed-height
dialog (no auto-height feedback — that's what slid content off-screen on a
monitor move).
2. Framed wallet list — BordersOuter + PadOuterX so the list reads as a contained
panel with room to grow, not loose rows on the backdrop.
3. Right-aligned numeric columns (Size / Addresses / Balance) and their headers,
via a custom header row + a right-aligned cell helper, so the numbers line up.
4. Consistent action column — the active wallet's row gets a subtle green tint and
an "Active" chip (badge), distinct from the Open/Import buttons.
5. De-cluttered bottom — "Create wallet" stays the primary action; the folder-scan
is tucked behind a "+ Scan another folder…" toggle.
6. Polish — the external-file marker is now a real info icon + tooltip (was
ICON_MD_FOLDER rendering as a "?" tofu, since that glyph isn't in the subset);
Refresh is an icon-only button, de-emphasized next to Close.
New i18n keys (wallets_add_folder_toggle, wallets_external_tt) + 8 langs; CJK
subset rebuilt. Verified via the headless sweep at 100% and font_scale 1.5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two follow-ups to the modal float work:
- Console RPC command reference (renderCommandsPopup) had the SAME auto-height
feedback bug as Wallets: BlurFloat with no cardHeight, and its fill-height
command list read GetContentRegionAvail() inside the AutoResizeY child ->
self-referential, sliding content off-screen after a monitor move. Give it a
FIXED viewport-relative cardHeight (fills ~84% of the viewport). Also removed
its duplicate body H6 heading (the framework h6 title already shows it) —
missed earlier because it's spec-form, not the positional overload.
- Wallets footer clipping: after the fixed-height conversion the content child
clips overflow (NoScrollbar), and the hardcoded 130*dp reserve below the table
was too small for the create/add-folder rows + separator + footer (~160*dp),
so the buttons clipped — worse at 150%. Compute the reserve from real widget
metrics (GetFrameHeightWithSpacing + caption height) so it holds at any DPI.
Added a modal-console-commands sweep surface (debug-gated ConsoleTab setter) so
the popup is captured. Verified both at 100% and font_scale 1.5 (150%): footers
on-screen, single headings, lists fill, nothing clipped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Wallets modal used the auto-height BeginOverlayDialog overload, whose content
child is AutoResizeY. It sized its table with `tableH = GetContentRegionAvail().y
- 130*dp` INSIDE that child — but on an AutoResizeY child GetContentRegionAvail().y
derives from the child's Size.y, carried over from the previous frame's content
height. That makes tableH self-referential: a fixed point on one monitor, but a
monitor move jumps dpiScale() (the 130*dp reserve + table column/row sizes switch
to new units while the child's Size.y is still old units), knocking it off
equilibrium. With only a lower clamp and no upper bound, the excursion runs
unbounded and pushes the create/folder/footer rows off the bottom.
Give the dialog a FIXED cardHeight (620) via the spec form — the pattern Manage
Portfolio already uses. The content child is then fixed-height (not AutoResizeY),
so GetContentRegionAvail().y is a stable, DPI-current "space left in the card",
tableH stops feeding back, and the fixed-height path already clamps the card to
the viewport (min(cardHeight*dp, vp_size.y-32)). Root-caused via a multi-agent
audit against the ImGui internals.
Verified via the headless sweep at 100% and font_scale 1.5 (150%): the table
absorbs the slack and the create/folder inputs + footer stay on-screen.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the glass card + boxed title bar from every dialog so content floats
directly on the blur backdrop under a plain h6 heading — the Manage-Portfolio
look — unifying the whole modal system on one visual language (reverses the
earlier card-for-small / float-for-large split).
- Framework: the positional BeginOverlayDialog overload (used by all ~34 card
dialogs) now defaults to floatingContent + plainHeading (draw_helpers.h). The
9 spec-form BlurFloat dialogs are unchanged; any dialog can still opt back to a
card via an explicit OverlayStyle::GlassCard spec. Authored widths are kept
(floating never resizes), so width-coupled layouts are unaffected.
- Dedup: the framework h6 title made each dialog's own repeated title heading
redundant. Removed the duplicate body heading from Import/Export key, Backup,
daemon update, seed backup, seed migration, Defender-blocked (app.cpp),
Wallets, and the send-confirm popup.
- Close paths: dropping the title-bar close X is covered — every dialog has a
footer button and/or the framework's outside-click dismiss (all pass p_open);
send-confirm keeps Esc + Confirm/Cancel.
Verified via the headless full UI sweep on dark + light: 17 modal surfaces float
card-less with a single clean heading, nothing clipped, close controls present.
Build clean, ctest passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the drag/resize dashboard grid on the Market tab's portfolio with
three full-width row styles — compact (single-line), detailed (two-line),
and value-hero — cycled with Left/Right arrows like the Overview layouts.
Rows live in an internal scroll region bounded to the portfolio's height
budget, so the section stays put as groups are added.
- pfDrawRow renders each group in the selected style, reusing the icon /
value / DRGX / 24h / sparkline pieces; per-entry field toggles honored.
- portfolio_style setting (0/1/2) persisted; arrow-key switcher + toast.
- Portfolio groups gain a per-wallet scope: new groups default to the
active wallet's identity hash; legacy groups (empty scope) stay visible
in every wallet. Scope filter drives both the height budget and the rows.
- Remove the now-dead grid machinery (mktDrawCard, pfComputeGridLayout,
PfCell, PfGridDrag, MktCardCtx + MktCtx grid fields) — net code drop.
- Full UI sweep: seed demo portfolio groups + wavy price history and add
market-rows-{compact,detailed,featured} surfaces so the styles are
captured per skin (snapshot/restore the real portfolio + market data).
- i18n: portfolio_style_{label,compact,detailed,featured} + 8 langs; CJK
subset rebuilt.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>