Commit Graph
100 Commits
Author SHA1 Message Date
DanSandClaude Opus 4.8 af2db4366c refactor(audit): de-duplicate the main.cpp backdrop draw + DPI transition
Two render-loop de-duplications, zero behavior change (verified byte-equivalent
on both the GL and DX11 backends via the mingw Windows cross-build):

- drawWindowBackdrop(app, bgDL, p0, p1, backdrop_active, lowSpec): the ~80-line
  gradient/texture/acrylic-capture/WindowBg-alpha block was copy-pasted in the
  main loop (with low-spec guards) and the resize-watcher lambda (without them).
  Extracted once; the main loop calls it with lowSpec=isLowSpecMode() and the
  resize watcher with lowSpec=false — each reproducing its CURRENT behavior
  exactly (pure dedup, no change). The intentional differences around it
  (resize's immediate present(0) vs the main loop's vsync, the main loop's
  try/catch, the BeginFrame low-spec guard) are left untouched.

- handleDisplayScaleChange(window, newScale, savedSizeForScale, lastKnownW/H,
  dpiTargetW/H, dpiResizeRetries, dpiResizePending): the SDL display-scale-change
  handler was duplicated ~verbatim in the idle-wait and poll event paths
  (differing only in debug-log text). Moved the body into one helper called from
  both; the #ifdef DRAGONX_USE_DX11 g_borderlessDpi and #ifdef __APPLE__ scale
  normalization are preserved.

Deliberately did NOT unify the full frame/present sequence — the resize path's
immediate present and lack of try/catch are intentional.

Full-node (GL) + Lite build clean; Windows/DX11 cross-build compiles main.cpp
clean; ctest 1/1; hygiene clean. NEEDS RUNTIME VERIFICATION: a live window
drag-resize (backdrop draws, no flicker) and a monitor DPI-scale change (window
resizes + fonts reload).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 21:00:40 -05:00
DanSandClaude Opus 4.8 cb74fbbff0 refactor(audit): unify config-dir resolution through Platform::getConfigDir
Settings::getDefaultPath and AddressBook::getDefaultPath each hand-rolled the
same per-platform #ifdef (Win SHGetFolderPath / macOS Application Support /
XDG ~/.config) with the DRAGONX_APP_NAME variant suffix. Route both through
util::Platform::getConfigDir() instead — one place owns the split — and add
the missing __APPLE__ branch to getConfigDir so it too returns the
macOS-native ~/Library/Application Support/<AppName>.

This also fixes a latent macOS inconsistency: transaction_history_cache
already resolves via getConfigDir(), which lacked the __APPLE__ branch, so on
macOS the tx cache landed in ~/.config while settings/addressbook went to
~/Library/Application Support. Now all three agree. No migration needed:
Linux/Windows paths are unchanged, and macOS packaging is not yet shipped.
Removes ~50 lines of duplicated #ifdef.

Full-node + Lite build clean; ctest 1/1; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 20:49:24 -05:00
DanSandClaude Opus 4.8 121a79e768 refactor(audit): batch 9 — de-duplicate balance/peers/transactions UI cards
Three UI de-duplications (rendering changes; the test suite skips the GUI, so
these need a manual screenshot check of the Balance/Peers/Transactions tabs).

1. Classic balance layout — the DEFAULT layout — hand-rolled its own ~550-line
   address-list + recent-tx renderers that had DIVERGED from the shared
   RenderSharedAddressList/RenderSharedRecentTx used by the other 9 layouts.
   Deleted the inline copies and called the shared renderers (as the minimal
   layout already does), so the default layout now gains set-label +
   add-to-portfolio context items, custom address icons, drag-to-reorder,
   keyboard nav, copy-flash, and fully TR()'d strings. Hero row and Classic
   sizing (tabs.balance.classic address-table-height=340) preserved; addrH is
   computed before the call exactly as before. This is a deliberate
   feature-parity behavior change for the default layout.

2. peers_tab info cards — extracted drawStatCell() (label + value/em-dash with
   the shared offset math) and one drawCardDivider() replacing two duplicate
   divider lambdas; plain cells drive off a per-card loop. Bespoke cells
   (Blocks "(X left)", copy-on-click Best Block, TLS check-icon) kept inline.

3. transactions_tab — one drawSummaryCard() replaces the three near-identical
   Received/Sent/Mined blocks (same glass panel, icon, hover outline,
   click-to-filter). Byte-equivalent.

Full-node + Lite build clean (net -651 lines, no new warnings); ctest 1/1;
hygiene clean. NEEDS SCREENSHOT VERIFICATION.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 20:18:02 -05:00
DanSandClaude Opus 4.8 262891229a refactor(audit): batch 8 — share the post-encrypt daemon-restart block
Extract App::restartDaemonAfterEncryption(taskName, announceRestartStatus)
from the two verbatim copies in encryptWalletWithPassphrase and
processDeferredEncryption. The two differed only in the async task name and
whether they set connection_status_ = "restarting_after_encryption" first
(the immediate encrypt path does; the deferred path does not) — both
parameterized. The 20x100ms settle loop, cancel/shutdown guards, and
stop/startEmbeddedDaemon sequence are preserved exactly.

Deliberately NOT done: the audit's "decrypt 5-deep callback pyramid" finding.
On inspection the nesting is load-bearing, not gratuitous — WalletSecurityWorkflow
is unsynchronized main-thread-only state, and each step's worker->MainCb bounce
is what keeps its mutations on the main thread (the UI reads snapshot()/importActive()
every frame); collapsing the pipeline onto one worker/async task would race the
UI, and the final restart+import stage requires an AsyncTaskManager::Token that
worker_->post can't supply. Left byte-for-byte intact.

Full-node + Lite build clean; ctest 1/1 (incl. WalletSecurity controller tests);
hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 20:03:40 -05:00
DanSandClaude Opus 4.8 dcd09dc542 refactor(audit): batch 7 — RPC/settings/lite/effects mechanical dedups
Six behavior-preserving consolidations from the audit:

- RPCClient::call() overloads share a private performCall() (payload dump +
  curl_easy_perform + http code) and a static parseRpcResult() (error->RpcError
  extraction). The timeout overload restores the prior timeout in both the
  success and catch paths, exactly as before.
- The six UnifiedCallback methods delegate to one splitUnified() that builds
  the (Callback, ErrorCallback) pair once (null-check preserved).
- One liteTrimCopy() in lite_connection_service replaces 5 file-local
  trim-copy helpers across the lite slice.
- lite_wallet_controller's inline height JSON parse now routes through the
  tested parseLiteHeightResponse().
- theme_effects.cpp: unpackRGB()/scaledAlpha() replace 14 RGB-unpack + 5
  alpha-scale copy-paste blocks.
- settings.cpp load() uses loadScalar()/loadClamped() helpers for ~44 scalar
  fields. Besides removing boilerplate this HARDENS loading: ~44 fields that
  previously only checked contains() now also verify the JSON type, so a
  malformed value in settings.json falls back to the default instead of
  throwing/misreading. Custom cases (enum parses, legacy migrations, arrays)
  stay inline; save() is unchanged.

Full-node + Lite build clean; ctest 1/1; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 19:56:54 -05:00
DanSandClaude Opus 4.8 4635a56e89 refactor(audit): batch 6 — de-duplicate the app_network send/balance paths
Three behavior-preserving consolidations in src/app_network.cpp:

1. Fold the three near-identical pending-send balance-delta blocks
   (upsertPendingSendTransaction debit, removePendingSendTransactions
   restore, applyPendingSendBalanceDeltas debit) into one
   App::applyPendingSendDelta(from, signedAmount, includeAggregates). The
   restore path's unclamped `+amt` is provably identical to the clamped
   signed form (balance/amount are >=0 invariants), so a single clamped
   helper reproduces all three exactly.

2. Collapse createNewZAddress/createNewTAddress into one
   App::createNewAddress(bool shielded, cb) with thin public forwarders,
   preserving the lite early-return, the dual push into the type list AND
   the combined state_.addresses view, and the dirty-flag/refresh bookkeeping.

3. Extract App::submitZSendMany() shared by sendTransaction and
   resendWithFeeGapWorkaround — the only differences (the TraceScope label
   and the retry-only send_feegap_retried_opids_.insert) become parameters.
   The in-flight counter, opid tracking, upsertPendingSendTransaction, and
   pending_send_callbacks_ bookkeeping are byte-equivalent.

Full-node + Lite build clean; ctest 1/1; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 19:36:27 -05:00
DanSandClaude Opus 4.8 9c533fa485 refactor(audit): batch 5 — share the updater HTTP/download layer
XmrigUpdater and DaemonUpdater carried byte-identical libcurl code —
httpGet, downloadToFile, and the write/progress callbacks — including the
security-critical TLS-verify (SSL_VERIFYPEER=1 / SSL_VERIFYHOST=2) and
max-body-size options, duplicated across two signature-verified
download-and-execute files that had to be hand-kept in sync.

Hoist that machinery into src/util/http_download.{h,cpp}:
- util::httpGetString(url, logTag)
- util::httpDownloadToFile(url, dest, logTag, maxBytes, onProgress)
threading progress + cancellation through a std::function (returning false
aborts the transfer) instead of the class `this` pointer. Each updater's
httpGet/downloadToFile become one-line forwarders that bind their own log
tag and archive-size cap (64 MiB miner / 256 MiB node, both preserved).

Every curl option is preserved byte-identically (verified by diffing the
setopt lines against the pre-change code — the only deltas are the
now-parameterized MAXFILESIZE cap and the XFERINFODATA payload). No behavior
change; the TLS/size-guard options now live in exactly one place.

Deliberately scoped DOWN per the audit's own guidance: the divergent
per-updater logic (release DTOs/parsers, checksum-table parsing, version
compare, and installResolved's per-member-SHA vs transitive-trust verify,
install strategy, and chmod scope) stays in each class — NOT merged behind a
base-class/virtual-hook layer, which would raise review cost on
signature-verified execute-code and risk a subtle security regression. The
shared-DTO/parser consolidation (~77 test refs of churn on stable code) is
left as a separate, lower-value follow-up.

Full-node + Lite build clean; ctest 1/1; source-hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 19:17:44 -05:00
DanSandClaude Opus 4.8 de11850c74 refactor(audit): batch 4 — UI design-system helpers + i18n
Mechanical, behavior-preserving consolidations from the audit. Rendering
extractions were kept byte-equivalent; sites that couldn't be made identical
were left as-is (noted below).

Shared helpers:
- util::truncateMiddle(s,maxLen) / (s,front,back) in text_format.h — replaces
  6 file-local middle-ellipsis truncators + several inline substr sites
  (send/receive/transactions/balance_recent_tx/explorer + app.cpp + 3 dialogs).
  Carries the maxLen<=3 guard, fixing the latent unsigned-underflow copies.
- material::LoadingDots() in draw_helpers.h — one animated-ellipsis source for
  8 copy-pasted spinner sites (identical GetTime()*3 phase preserved).
- Reuse the existing FormatHashrate() in explorer_tab + peers_tab (dropped two
  inline hashrate ladders).
- material::DrawButtonGlassOverlay() — the glass-fill/rim/tactile overlay block
  shared by TactileButton / TactileSmallButton / schema TactileButton.
- material::CollapsibleHeader() — the invisible-button + label + chevron idiom
  (3 of 5 settings_page sites; RPC/Debug headers skipped — non-identical).
- material::GlassCardScope (RAII) — the ChannelsSplit/Indent/DrawGlassPanel/
  ChannelsMerge card scaffold (5 settings_page cards; About/send/receive skipped
  — different padding / logo interleaving).
- material::DialogWarningHeader()/DialogConfirmFooter() — warning header +
  50/50 Cancel/danger footer across the confirm dialogs; button height moved
  from a hardcoded 40px into ui.toml (components.overlay-dialog.confirm-btn-height).
- File-local enterLowSpec()/exitLowSpec() collapse the 3 low-spec snapshot copies.

i18n: wrapped hardcoded English in the shared balance render paths and the
whole Lite lifecycle/security section in TR(), with English defaults added to
loadBuiltinEnglish() (res/lang/*.json left for the translation tooling — TR
falls back to English, so output is unchanged).

Full-node + Lite build clean; ctest 1/1; hygiene clean. These are rendering
changes — the Settings page (cards/headers/confirm dialogs), all tactile
buttons, and the Lite settings section warrant a screenshot check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:29:12 -05:00
DanSandClaude Opus 4.8 280a71e973 refactor(audit): batch 3 — decompose App::update() + collapse acrylic fork
Three P1 structural refactors from the audit; no behavior change.

1. Extract the ~180-line daemon-stdout rescan/witness parser out of the
   ~790-line App::update() into a pure, static, unit-testable
   NetworkRefreshService::parseDaemonRescanOutput() returning a
   DaemonRescanScan result struct (sits next to the existing static parse*
   siblings). The scanning half moved verbatim; App::update() keeps the
   state-application half, now reading scan.* fields. Adds
   testParseDaemonRescanOutput() covering all six daemon signals + edge
   cases against fixture log snippets — previously untestable without a
   live daemon.

2. Extract the ~95-line global keyboard-shortcut block (Ctrl+, / theme
   cycle / F5 / low-spec / effects / gradient / wizard) out of
   App::update() into App::handleGlobalShortcuts().

3. Collapse the ImGuiAcrylic frontend fork: the GLAD and DX11 namespaces
   were ~375 lines of semantically-identical code (the frontend makes zero
   direct GL/DX calls — all backend work delegates to AcrylicMaterial, which
   has a backend per API). Extended the single frontend's guard to
   #if defined(DRAGONX_HAS_GLAD) || defined(DRAGONX_USE_DX11) and deleted the
   DX11 duplicate, leaving the no-backend stub. Kills the Windows/DX11 drift
   hazard (acrylic frontend changes now made once).

Verified: full-node + Lite build clean; ctest 1/1 (incl. the new parser
tests); source-hygiene clean; and the collapsed acrylic path was
compile-verified under DX11 via the mingw-w64 Windows cross-build
(ObsidianDragon.exe links).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:40:40 -05:00
DanSandClaude Opus 4.8 7891c689fb refactor(audit): batch 2 — remove dead scaffolding from the shipping binary
Two dead-code removals surfaced by the audit; no runtime behavior change.

1. Delete the lite-lifecycle "readiness" scaffold (~1,586 lines). The pure
   dry-run evaluateLiteWalletServerLifecycleReadiness (whose own status text
   says "wallet lifecycle execution is still disabled in this scaffold") had
   zero callers; executeLiteWalletServerSelectionUi had zero callers; and
   executeLiteWalletLifecycleUiRequest's only caller was a fallback in
   settings_page reached only when app->liteWallet() is null. Deleted
   lite_wallet_server_lifecycle_readiness.{h,cpp} and the readiness /
   UI-execution machinery + enums + translation tables from both adapters,
   keeping the genuinely-live helpers (liteConnectionSettingsFromAppSettings,
   applyLiteConnectionSettingsToAppSettings, redactLiteServerSelectionValue,
   and the LiteWalletLifecycleUiExecutionInput DTO the live create/open/
   restore path still uses). The null-backend fallback now sets a plain
   "Lite wallet backend unavailable" status. This is exactly the
   readiness/scaffold pattern CLAUDE.md forbids regrowing.

2. Split the HushChat fixture/capture-manifest/seed-projection tooling
   (~2,050 lines, incl. the libsodium seed-projection) out of
   chat_protocol.cpp (1854 -> 284) and chat_protocol.h (586 -> 105) into a
   new chat_fixture_tooling.{h,cpp} compiled ONLY into the HushChatFixtureCheck
   dev tool — never into the app, lite, or test binaries (verified via nm).
   The app keeps only the runtime slice (memo parsing + tx metadata
   extraction) that network_refresh_service actually calls. Also moved the
   decrypt-preflight/hex helpers, which likewise had no runtime caller.

Note: the HushChat split is on gated-off experimental code and should be
coordinated with the pending dormant-HushChat-content handling (commit
af06b8b) before the lite PR — done here as a pure mechanical split, no redesign.

Full-node + Lite + HushChatFixtureCheck build clean; ctest 1/1; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:20:53 -05:00
DanSandClaude Opus 4.8 f8150434d4 fix(audit): batch 1 — latent unlock/path/rescan correctness bugs
Four correctness fixes surfaced by the codebase audit:

1. Lite address-book path bug: AddressBook::getDefaultPath() hardcoded
   "ObsidianDragon" while Settings::getDefaultPath() uses DRAGONX_APP_NAME,
   so the Lite build wrote addressbook.json into the full-node app's config
   dir instead of ObsidianDragonLite/. Now uses DRAGONX_APP_NAME on all
   platforms (no macOS path change — the getConfigDir consolidation, which
   would move the macOS dir, is deferred).

2. PIN-unlock lockout bypass: the PIN path duplicated unlockWallet's
   success/lockout logic and its RPC-error branch bumped the attempt counter
   but skipped the escalating-lockout math entirely — a PIN user hitting an
   RPC error escaped the lockout curve. Extracted App::applyUnlockSuccess()
   and App::applyUnlockFailure() and routed all unlock paths (passphrase,
   PIN vault-fail, PIN RPC-fail) through them, so the lockout curve now
   applies uniformly. (noVault stays a mode-switch, not a failed attempt.)

3. Witness/rescan reset drift: the ~11-line rescan+witness completion reset
   was copy-pasted at four sites (app.cpp x2, app_network.cpp x2); adding a
   witness field and missing a copy would leave stale progress. Folded into
   App::resetWitnessRescanProgress(). Left the distinct phase-transition
   reset in App::update() untouched (it sets witness_phase, not 0).

4. Truncation underflow: send_tab/receive_tab's size_t TruncateAddress
   copies computed (maxLen - 3) without guarding maxLen <= 3, which wraps
   and throws std::out_of_range on short inputs. Added the guard.

Full-node + Lite build clean; ctest 1/1; source-hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 15:54:36 -05:00
DanSandClaude Opus 4.8 530bf24abf fix(market): populate combined address list so portfolio sees addresses
The Market-tab portfolio (editor checklist + SumPortfolioBalance) reads the
combined WalletState::addresses view, but the full-node refresh path only ever
updated the authoritative z_addresses/t_addresses lists — rebuildAddressList()
was never called, so `addresses` stayed empty. Result: the manage-portfolio
modal showed no addresses to pick, and addresses added via the overview
right-click menu contributed no value to their entry (SumPortfolioBalance
found nothing in the empty combined list).

Rebuild the combined view in NetworkRefreshService::applyAddressRefreshResult
(the sole bulk updater of the address lists), and push newly created addresses
into the combined view immediately in the full-node create paths for
zero-latency parity with the overview (matching the lite branch). This also
repairs two other silently-broken full-node consumers of state.addresses: the
auto-shield target-address finder and the pool-mining transparent fallback.

Add a regression test (testWalletStateAddressListRebuild) covering the
empty-before / union-after rebuild and pending-send delta reflection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 03:27:44 -05:00
DanSandClaude Opus 4.8 e3b53258b7 fix(settings): swap Install bundled/Test connection; unique daemon Refresh ID
Swap the "Install bundled" and "Test connection" daemon-binary buttons:
Install bundled now sits in the left common row (Check for updates | Refresh
| Install bundled); Test connection leads the right-aligned maintenance
group. Each button keeps its own disabled-predicates, action, and tooltip.

Give the daemon-binary Refresh button a unique ImGui ID
("Refresh##daemonRefresh") so it no longer collides with the theme-section
Refresh buttons, which triggered Dear ImGui's "2 visible items with
conflicting ID!" error. The label still displays "Refresh".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 03:09:43 -05:00
DanSandClaude Opus 4.8 f9e4db4abf feat(settings): merge BACKUP & DATA into the WALLET card
Move the Backup & Data button row (Import/Export key, Export all, Backup, Export
CSV, and the full-node Download Bootstrap / Setup Wizard) into the WALLET card as
a "Backup & Data" sub-section under the privacy toggles + Tools & Actions, and
delete the standalone BACKUP & DATA card (its own glass panel + header). One
fewer top-level card; all actions preserved.

Full-node build clean; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 02:59:31 -05:00
DanSandClaude Opus 4.8 bdccddfeee feat(settings): rename RPC toggle to "RPC Connection..." (Tools & Actions style)
Rename the RPC collapsible header from "RPC CONNECTION" to "RPC Connection..." and
render it in the body2 font so it matches the "Tools & Actions..." toggle exactly.

Full-node build clean; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 02:53:08 -05:00
DanSandClaude Opus 4.8 d68bc27aac feat(settings): make RPC CONNECTION a collapsible toggle (like Tools & Actions)
Replace the static "RPC CONNECTION" overline header with a full-width clickable
toggle (label + expand/collapse arrow, transparent button with hover) that
reveals the Host/Port/Username/Password fields + auto-detected caption only when
expanded — same idiom as the "Tools & Actions…" section. Collapsed by default
(rpc_expanded). Field rendering/bindings unchanged.

Full-node build clean; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 02:49:19 -05:00
DanSandClaude Opus 4.8 51667b5a27 feat(settings): add "Open app folder" button next to "Open data folder"
Add a button to the left of "Open data folder" that opens the ObsidianDragon
config folder (util::Platform::getObsidianDragonDir() — ~/.config/ObsidianDragon
etc.: settings, themes, logs), distinct from the daemon data/blockchain folder.
The two buttons render as a right-aligned group on the data-dir row, each an
explicit button-font width so the group's right edge lands exactly on the card
edge (no overflow). New i18n: settings_open_app_dir / tt_open_app_dir.

Full-node build clean; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 02:44:43 -05:00
DanSandClaude Opus 4.8 96e05b4ba6 feat(settings): daemon maintenance actions right-aligned on Test row; fix Open-folder overflow
- Remove the "Advanced" toggle. The four maintenance actions (Install bundled /
  Rescan / Delete blockchain / Repair wallet) now render right-aligned on the same
  row as Check-for-updates / Refresh / Test connection (wrapping to a right-aligned
  next row only when the window is too narrow). Predicates preserved.
- Fix the Open-data-folder button overflowing the card's right edge (worse at high
  display scaling): its width was estimated with the CURRENT font, not the button
  font, so the estimate was wrong. Measure with the button font and give the button
  an explicit width so its right edge lands exactly on the card edge regardless of
  DPI/font-scale timing.
- Drop the now-unused node_advanced_expanded flag.

Full-node build clean; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 02:26:58 -05:00
DanSandClaude Opus 4.8 36386b2b84 fix(settings): keep "Open data folder" on the data-dir row
The right-aligned Open-data-folder button captured its Y from
GetCursorScreenPos() after the wallet-size text, by which point ImGui had already
advanced to the next line — so the button dropped below the data-dir row. Capture
the row's top Y before rendering the row and pin the button to it, so Data Dir /
Wallet Size / Open data folder sit on one line.

Full-node build clean; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 01:56:29 -05:00
DanSandClaude Opus 4.8 2fc577fb18 feat(settings): SECURITY on one fill-width row + Advanced daemon actions shown by default
- SECURITY: the auto-lock + PIN controls now continue on the same row as the
  encrypt/change/lock/remove controls (full-node), so the whole security band
  fills the card width instead of leaving the right half blank. Lite (no encrypt
  row) still starts auto-lock/PIN on a fresh row at the left edge.
- Daemon Binary "Advanced" (Install bundled / Rescan / Delete blockchain / Repair
  wallet) now defaults to expanded (node_advanced_expanded = true) so the actions
  are visible without a click; the toggle can still collapse them.

Full-node + lite build clean; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 01:43:46 -05:00
DanSandClaude Opus 4.8 5180f74a68 feat(settings): rebuild Node & Security as one fill-width card
Replace the column/grid/masonry machinery (which kept leaving empty gaps and
buried the daemon Advanced actions) with a single full-width card whose controls
are arranged horizontally to use the width:
- NODE: Data Dir + Wallet Size + Open-folder on one row.
- RPC: Host / Port / Username / Password four-across when wide (>=700), wrapping
  to 2x2 when narrow; auto-detected + plaintext warning kept.
- SECURITY: encryption buttons + Auto-lock combo + PIN controls on horizontal
  rows (factored into renderSecuritySection so lite gets auto-lock+PIN and
  full-node adds the RPC-backed encrypt/lock/remove block — same gating as before).
- DAEMON BINARY: inline Installed/Bundled/status; Check/Refresh/Test row; and the
  four maintenance actions (Install bundled / Rescan / Delete blockchain / Repair)
  behind a now-prominent, clearly-clickable "Advanced ▾" toggle instead of the
  easy-to-miss faint overline — fixing the "daemon options missing" report.

Lite keeps its existing wallet-lifecycle/backup/security block, full width.
Removed useGrid/stacked/column clips/dividers. Every control + state branch +
confirm flag + tooltip preserved (verified). Full-node + lite build clean; ctest
green; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 01:28:14 -05:00
DanSandClaude Opus 4.8 fc5ce019e2 feat(settings): 2x2 grid for Node & Security (fills the card width)
Single-column left every row hugging the left with a blank right half (and a
split RPC row); two-column variants left a bottom-corner gap. Rework the card as
a 2x2 grid on wide full-node windows so both halves carry real content:
  TOP-LEFT NODE/Data | TOP-RIGHT RPC
  BOTTOM-LEFT SECURITY | BOTTOM-RIGHT DAEMON BINARY
with the two top cells sharing a row bottom, a thin vertical + horizontal
divider, and the Daemon "Advanced" destructive buttons dropping to a full-width
4-button strip below the grid when expanded.

Gated behind one `useGrid` flag (supportsFullNodeLifecycleActions() &&
availWidth >= node-grid-breakpoint, default 900). When false — narrow windows and
lite — it falls through to the existing single full-width column, unchanged
(one-flag-revertible). Wallet Size moved to its own row (removes a mid-row gap in
both layouts). RPC / Security / Daemon rebased to per-cell X/width via aliases;
Advanced buttons factored into one lambda so the disabled predicates live in a
single place. Design produced + adversarially critiqued via a multi-agent pass.

New: components.settings-page.node-grid-breakpoint (ui.toml), "rpc_connection"
i18n string. Full-node + lite build clean; ctest green; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 00:55:52 -05:00
DanSandClaude Opus 4.8 7826be4b13 feat(settings): single full-width Node & Security column (no empty gap)
Two-column layouts left an unbalanced empty gap for this content (Node/RPC vs
Security+Daemon can't be evened into two columns), so drop the split entirely:
Node -> RPC -> Security -> Daemon Binary now stack vertically at full card width.
This guarantees no horizontal gap and gives the RPC grid and the Daemon Binary
button rows the full width they want.

Implemented by forcing the existing vertical-flow path (`stacked`) always on, so
the section bodies are unchanged; removes the now-unused column-split math and the
node-sec-stack-width schema key.

Full-node + lite build clean; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 00:20:30 -05:00
DanSandClaude Opus 4.8 5d6e78c719 feat(settings): masonry Node & Security — fill the empty bottom-right
The 60/40 split left the right (Security) column short while Node/RPC on the left
was tall, and Daemon Binary sat full-width below — leaving a large empty gap in
the card's bottom-right.

Rework it as a greedy two-column masonry: even 50/50 columns, and the Daemon
Binary section is placed into whichever column is currently shorter (clipped to
that column's width). In practice Node/RPC fills the left and Security + Daemon
Binary stack on the right, so the gap is filled and the two sides balance in
height. When the window is narrow the columns stack and Daemon Binary spans full
width below, as before. Daemon Binary now sizes to its column (dbColW) instead of
the full card width.

Full-node + lite build clean; source hygiene clean. Visual QA needed (column
balance + the daemon button rows in the narrower column).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 00:08:32 -05:00
DanSandClaude Opus 4.8 a1e4acfe86 feat(settings): fix Node & Security empty space — 70/30 split + Advanced in right column
The fixed 60/40 split left a large empty gap in the bottom-right: the SECURITY
column is short while NODE (data dir + RPC grid) is tall, and the DAEMON BINARY
row spans full width below both. Two changes:

- Widen the side-by-side split to 70/30 so NODE (the content-heavy column, esp.
  the RPC grid) gets the room and the right-side gap shrinks.
- Move the "Advanced" destructive-actions expander out of the full-width DAEMON
  BINARY row and into the right column below Security, filling the gap. Its four
  actions now stack vertically to fit the narrow column.

Full-node + lite build clean; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 23:48:45 -05:00
DanSandClaude Opus 4.8 419f8bb9cb fix(console): fill scanline banding into the empty space below the last row
The zebra banding is emitted per text row, so it stopped at the last line and
left the bottom fade-padding (up to 18% of the panel) unbanded — very visible
when scrolled/pinned to the bottom. After the text-row bands, continue the
alternating pattern (same parity, lineHeight pitch) down to the panel bottom so
the zebra fills the whole output area. Also covers the case where the content is
shorter than the panel.

Full-node + lite build clean; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 23:35:01 -05:00
DanSandClaude Opus 4.8 c5ef9e42ed fix(console): correct CRT scanline alternation (was skipping rows)
The per-line scanline shade alternated on rowIndex = floor((cumulativeY +
yOffset) / lineHeight + 0.5), but cumulativeY includes the 2px inter-line gap
while the divisor is lineHeight alone. That drifts ~0.14 row per line and, with
the rounding, jumps the index by 2 every ~7 rows — so two adjacent rows get the
same shade and the alternation visibly breaks in patches.

Replace it with a true per-visual-row counter (one increment per drawn sub-row),
seeded with the first on-screen line's sub-row ordinal so the shading stays
stable while scrolling. Parity now alternates exactly every row.

Full-node + lite build clean; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 23:25:09 -05:00
DanSandClaude Opus 4.8 f0243307f1 feat(portfolio): Overview right-click to add/remove an address to a portfolio
Add a "Portfolio" submenu to the Overview address-list context menu: it lists
each Market-tab portfolio entry with a checkmark showing whether this address is
in it, and clicking toggles membership (add/remove), persisting immediately. If
no entries exist yet, a disabled hint points to creating one.

Full-node + lite build clean; ctest green; source hygiene clean. Completes the
Market configurable-portfolio item (last of the todo.md batch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:44:04 -05:00
DanSandClaude Opus 4.8 95ddfa05ee feat(portfolio): Market card entries + "Manage" editor dialog
Build the configurable-portfolio UI on the persisted model:
- The portfolio card keeps the "all funds" summary and now renders each custom
  entry below it as a compact row (label · summed DRGX · USD), live-computed via
  SumPortfolioBalance over the wallet's per-address balances; the card grows to
  fit the entries.
- A right-aligned "Manage…" button on the card header opens a modal editor
  (material overlay dialog): list entries with Edit/Delete, "Add entry", and an
  edit form = label field + quick "All shielded / All transparent / Clear"
  selectors + a checklist of wallet addresses (with balances). Persists to
  Settings on each mutation.
- i18n English defaults for the new portfolio_* strings.
- Test namespace fix (AddressInfo is dragonx::, not dragonx::data::).

Full-node + lite build clean; ctest green; source hygiene clean. Remaining:
the Overview-tab right-click "add/remove address to a label" integration.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:41:20 -05:00
DanSandClaude Opus 4.8 ecc0043559 feat(portfolio): persisted portfolio entries + pure sum helpers
Foundation for the Market tab's configurable portfolio (custom labels tied to
groups of addresses):

- Settings gains a PortfolioEntry {label, addresses[]} list, persisted in
  settings.json as a JSON array-of-objects (same mechanism as lite_servers_),
  with get/setPortfolioEntries().
- data/portfolio.h (header-only, ImGui-free): SumPortfolioBalance() sums a
  group's DRGX balance against the live per-address list (unknown addresses
  contribute 0), plus PortfolioEntryContains/Add/Remove group helpers.
- Unit-tested (testPortfolioHelpers): empty/known/unknown-address sums, and the
  add/remove/contains membership semantics.

UI (editor dialog, Market card rework, Overview right-click) comes next.
Full-node + lite build clean; ctest green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 22:32:53 -05:00
DanSandClaude Opus 4.8 3a9ff5f021 feat(settings): make Node & Security columns responsive (stack when narrow)
The NODE + SECURITY split was a fixed 60/40 two-column layout regardless of
width, which cramps the RPC grid and wastes space on narrow windows. Make it
responsive: below a configurable content width (node-sec-stack-width, default
600) the two columns stack into one full-width column — SECURITY flows below
NODE — so each section gets the full width; side-by-side 60/40 is kept when
there's room. Column-geometry-only change (leftX/rightX/widths + the right
column's top Y); the content blocks are untouched. The rejoin already used
max(leftBottom, rightBottom), correct in both modes.

Full-node + lite build clean; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:46:11 -05:00
DanSandClaude Opus 4.8 1faaa54a07 feat(market): merge price + chart into one card; simplify stats
Combine the top two Market cards: the price-summary hero and the chart now share
a single glass panel (one panel spans the price/stats header + the chart drawn
below it; the chart no longer draws its own panel or an inter-card gap).

Simplify the stat strip from three columns to two — drop the niche BTC-price
column, keeping 24h Volume + Market Cap — and remove the "updated Ns ago"
staleness line (data freshness is still shown by the attribution "Updated" line
under the pair buttons).

Full-node + lite build clean; ctest green; source hygiene clean. Completes the
Market "round buttons + combine/simplify top cards" item.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:31:29 -05:00
DanSandClaude Opus 4.8 61f8c54742 feat(market): flatten pair selection into round buttons (no exchange dropdown)
Replace the exchange combo dropdown + the horizontally-scrolling pair chip bar
with a single flat selector: every trading pair across all exchanges is shown as
a round button (pair name + dimmed exchange sublabel) that wraps to multiple
rows. Clicking one selects that exchange+pair, persists it, and refreshes market
data. The CoinGecko attribution + last-updated line moves under the buttons.

Removes the now-unused pair-scroll state (scroll/drag/arrow machinery) and the
pair-bar-height budget input.

Full-node + lite build clean; source hygiene clean. (Second half of the Market
"round buttons + combine cards" item — the hero/chart card merge is next.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:20:12 -05:00
DanSandClaude Opus 4.8 48fc9ba8bb fix(settings): clamp About-logo height so it doesn't overlap the buttons
The About card draws its logo deferred in the left column, scaled to the card
height (cardMax.y - bottomPad). Because the buttons row is full-width at the
bottom of the card, the tall logo extended down over it. Clamp the logo to end
just above the buttons row (captured Y minus a small gap) instead of the card
bottom — aspect + reserved-width caps are unchanged.

Full-node + lite build clean; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:06:39 -05:00
DanSandClaude Opus 4.8 2552234610 feat(mining): fetch pool hashrates + miner version at startup
Warm the Mining tab's data during App::init() (both off the UI thread) instead of
only when the user opens the tab or clicks a mine / auto-balance button:
- kick the idempotent one-shot `xmrig --version` detection, and
- kick a background pool-stats refresh over the known pools.

Gated on supportsPoolMining(). The pool-stats snapshot lands on a later frame and
the auto-balance driver's self-throttling still applies, so this is just an
earlier first fetch — hashrates + version are visible as soon as the tab opens.

Full-node + lite build clean; ctest green; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:01:35 -05:00
DanSandClaude Opus 4.8 ff20a8a44e feat(console): terminal polish — lite/color-coded toggles, dark bg, monospace font
Work through the console items in todo.md:

- Lite filter toggles (#5): replace the single hasLogFilters() bool with a
  ConsoleLogFilterCaps struct so each backend advertises which toggles apply.
  Full node = daemon/errors/rpc-trace/app; lite = errors-only + app (its
  diagnostics ring maps to the App/Error channels; the text filter is always
  shown). Lite previously showed no toggles at all.
- Color-coded toggles (#6): each filter checkbox is tinted with its channel's
  accent color (daemon=blue, errors=red, rpc=secondary, app=teal) via a new
  channelAccentColor() that also drives the output's left accent bar — one source
  of truth for channel color.
- Darker terminal look (#7): drop the light glass rectangle on the input; both
  output and input now get a terminal-dark overlay (tabs.console.bg-darken-alpha,
  default 110) and the input field blends into it (transparent frame bg).
- Monospace font (#8): bundle Ubuntu Mono (res/fonts/UbuntuMono-R.ttf, Ubuntu
  Font License — same family as the existing Ubuntu fonts) via INCBIN, load it at
  caption size as Type().mono(), and render console output + input in it so
  pretty-printed JSON columns and terminal text align. Falls back to the
  proportional caption font if unavailable.

Full-node + lite build clean; ctest green; source hygiene clean; sandboxed
startup smoke confirms the mono font loads + atlas builds without crashing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 20:53:08 -05:00
DanSandClaude Opus 4.8 245d9bf976 feat(console): collapsible JSON in command output
Add fold/unfold of JSON objects and arrays in console command results, built on
the refactored channel + model foundation.

- ComputeConsoleFoldSpans() (console_input_model): pure brace/bracket matcher
  over a block of pretty-printed JSON lines; each opener line gets the offset to
  its matching closer at the same indentation (empty/one-line blocks are not
  foldable). Unit tested (nested objects, arrays, empty blocks, non-JSON, and a
  string value that merely contains a brace).
- ConsoleModelLine gains foldSpan (relative, so it survives front-eviction by the
  line cap) + a collapsed flag; ingest() carries the span and toggleCollapsed()
  flips openers (main thread). addFormattedResult computes the block's spans and
  ingests them atomically.
- computeVisibleLines() skips a collapsed block's interior (opener stays, its
  foldSpan lines through the closer are hidden); folding is bypassed while a
  filter is active so every match stays reachable.
- drawVisibleLines() draws a fold triangle in the free left gutter of opener
  lines (result/JSON channels carry no accent bar there), toggled by a click in
  the gutter cell, and appends a dim " ... }" / " ... ]" summary on collapsed
  openers.

Full-node + lite build clean; ctest green (incl. fold-span + model-fold tests);
source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 20:11:44 -05:00
DanSandClaude Opus 4.8 1c248d727a refactor(console): phase 5b — decompose toolbar/input/commands-popup methods
Finish the thin-view pass on the remaining large render methods (pure code
motion, no behavior change):

- renderToolbar (225 -> 99 lines): extracted drawToolbarStatus(), the
  drawLogFilterToggles() checkbox group, drawFilterInput(), and drawZoomControls().
- renderInput: factored the command echo + built-in interception + submit into
  submitConsoleCommand(exec, cmd) -> bool, leaving renderInput to own just the
  glass panel + InputText + completion/history callback.
- renderCommandsPopup (194 -> 135 lines): removed the duplicated lowercase-and-
  find filter logic (match-count loop and per-row filter now share
  consoleCommandMatchesFilter()) and extracted the [optional]-param dimming loop
  into drawConsoleCommandParams().

Full-node + lite build clean; ctest green; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 19:25:12 -05:00
DanSandClaude Opus 4.8 57291ef28c refactor(console): phase 5 — decompose renderOutput into focused sub-steps
renderOutput() was a ~410-line method doing filter-building, mouse/keyboard
interaction, the per-line draw loop, the new-output pill, and the right-click
context menu all inline. Split it into a thin orchestrator that calls five
single-purpose private methods (pure code motion, no behavior change):

  - computeVisibleLines()      -> the filter -> visible_indices_ + match count
  - handleOutputInteraction()  -> wheel-up, selection drag lifecycle, Ctrl+C/A
  - drawVisibleLines()         -> accent bars, JSON indent guides, selection +
                                  filter highlight, scanline capture, text
  - drawOutputContextMenu()    -> the right-click menu
  - drawNewOutputIndicator()   -> the jump-to-bottom pill

Ordering and every operation are preserved exactly (interaction still runs
after layout build and before the draw; selection bounds are recomputed post-
interaction; scanline_rows_ cleared before the draw pushes to it). Full-node +
lite build clean; ctest green; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 19:16:36 -05:00
DanSandClaude Opus 4.8 18c1de87db refactor(console): phase 4b — extract ConsoleScrollController
Pull the auto-scroll concern out of ConsoleTab into a focused, ImGui-free
ConsoleScrollController (console_scroll_controller.{h,cpp}). It owns the three
coupled fields that were loose on the god-class — auto_scroll_, the wheel-up
cooldown, and the new-lines-while-scrolled-up backlog count — and the state
machine tying them together: wheel-up detaches + starts a cooldown, the cooldown
gates the at-bottom re-enable check, backlog counting happens only while
detached, and re-pinning (checkbox / jump pill / reaching the bottom) clears it.

The view keeps only what needs ImGui: it measures scroll position, hands the
controller a plain `atBottom` bool + frame delta, and issues the actual
SetScrollHereY. ConsoleTab loses three more members and all the scattered
auto-scroll bookkeeping collapses to scroll_.* calls.

Adds testConsoleScrollController: pinned start ignores backlog, wheel-up detach +
cooldown gating, re-enable only when at bottom, checkbox toggle semantics, and
the jump-to-bottom pill. Full-node + lite build clean; ctest green; source
hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:52:15 -05:00
DanSandClaude Opus 4.8 0cdbbd024e refactor(console): phase 4 — extract ConsoleSelectionController from the god-class
Pull the text-selection concern out of ConsoleTab into a focused, ImGui-free
ConsoleSelectionController (console_selection_controller.{h,cpp}). It owns the
anchor/caret state and all the fiddly logic around it: the mouse-drag lifecycle
(beginDrag/updateDrag/endDrag), select-all, range ordering, text extraction over
the visible model, and the index shift applied when the buffer cap evicts lines
from the top.

ConsoleTab now delegates: the four scattered selection fields (is_selecting_,
has_selection_, sel_anchor_, sel_end_) and five helper methods (selectionStart/
End, isPosBeforeOrEqual, getSelectedText, clearSelection) collapse to a single
selection_ member plus a thin selectedText() wrapper. screenToTextPos (still
view-side — it needs the frame's layout) now returns the shared ConsoleTextPos,
so ConsoleTab::TextPos is retired.

The byte-offset / eviction math was previously only reachable through the live
ImGui renderer; it now has direct unit coverage (testConsoleSelectionController:
ordering, drag lifecycle, upward drag, select-all, partial/full/no-op eviction
shift, and filtered extraction). Full-node + lite build clean; ctest green;
source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:46:06 -05:00
DanSandClaude Opus 4.8 779c9682d4 refactor(console): phase 3 — thread-safe ConsoleModel ingest queue; fix data race
Introduce ConsoleModel (console_model.{h,cpp}): an ImGui-free, thread-safe line
store. Producers on any thread call ingest(), which only briefly locks a small
pending queue; the main thread calls drain() once per frame to move pending
lines into the visible deque and enforce the line cap, reporting how many were
added and evicted from the front.

This fixes the real data race. Previously a single lines_mutex_ guarded both the
line container AND the UI-only interaction state (selection, scroll, auto-scroll
counters), and it was held across the whole render pass — yet render() touched
auto_scroll_ / new_lines_since_scroll_ outside that lock, racing the background
RPC-trace and app-logger producers that call addLine() on worker threads.

Now:
- addLine() only ingests (lock-free w.r.t. the model); safe from any thread.
- render() drains once per frame, then does the cap/selection/scroll bookkeeping
  on the main thread (new drainModel()).
- The visible deque + all selection/scroll fields are main-thread-only, so every
  lines_mutex_ lock is gone — including the one wrapping the entire renderOutput.

Adds testConsoleModel() covering ingest/drain ordering, the line-cap eviction
counts, view-only clear (queued lines survive), and an 8-thread concurrent
ingest/drain stress with no lost lines. Full-node + lite build clean; ctest
green; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:35:25 -05:00
DanSandClaude Opus 4.8 33735b1d52 refactor(console): phase 2 — channel-as-semantic key; decouple executor from UI colors
Make ConsoleChannel the canonical semantic classification of every console
line. Producers (command executor, app log forwarders, result formatter) tag
each line with a channel; the UI derives both the text color and the left
accent-bar color from it at draw time, and the output filter keys off it.

This replaces the prior scheme of storing an ImU32 color per line and then
reverse-engineering the source from color-equality plus a "[daemon] "/"[xmrig] "
/"[app] "/"[rpc] " text prefix. Consequences:

- ConsoleLine now stores {text, ConsoleChannel} — no per-line color.
- addLine()/addRpcTraceLine() take a channel; the redundant text prefixes are
  gone (the accent bar carries the origin).
- The executor's ConsoleAddLineFn hands a channel, not an ImU32, so the backend
  no longer depends on ConsoleTab::COLOR_* — FullNode/Lite executors emit clean
  text + channel.
- The theme-remap loop that rewrote stored colors on light/dark flip is deleted;
  colors are resolved per-channel each frame (refreshColors on flip only).
- ConsoleOutputFilter drops its color fields; consoleLinePassesFilter() keys off
  the channel. Test updated to the channel-based API.
- Added a Warning channel (amber severity peer of Error/Success) so the
  notification + logger warning forwarders keep their color.

Full-node + lite build clean; ctest green; source hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 18:22:35 -05:00
DanSandClaude Opus 4.8 19e83eb12a refactor(console): phase 1 — extract pure ConsoleTextLayout (wrap/hit-test/selection)
Lift the console's word-wrap layout, screen->text hit-testing, and selection extraction
out of ConsoleTab into a pure, ImGui-free module (console_text_layout.{h,cpp}). Text
measurement is injected via a ConsoleTextMeasure interface — production uses an
ImFont-backed impl (identical CalcWordWrapPositionA/CalcTextSizeA calls as before), while
tests drive a fixed-width stub.

- BuildConsoleLayout: per-line wrap segments + heights + cumulative Y (was inline in
  renderOutput).
- HitTestConsoleLayout: point -> (visibleRow, byte col) (was screenToTextPos).
- ExtractConsoleSelection: selected text across visible lines (was getSelectedText).
- ConsoleTab now holds a single `ConsoleLayout layout_` (replacing 4 mutable members;
  drops the dead cached_wrap_width_) and calls the pure functions.
- New unit test testConsoleTextLayout covers wrapping, multi-row hit-testing, bottom
  clamp, and filtered/unfiltered selection — the biggest previously-untested surface.

No behavior change (algorithm preserved verbatim). Full-node + lite build clean, ctest
green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:58:29 -05:00
DanSandClaude Opus 4.8 f12153cdd0 refactor(console): phase 0 — remove dead code + fix channel/arg bugs
Zero-behavior-change cleanup ahead of the console refactor:
- Delete dead API/members: addCommandResult (unused), handleSelection (declared, no
  def), isAutoScrollEnabled (no callers), and scroll_to_bottom_ (written 8× but never
  read) with all its writes.
- Drop the unused screenToTextPos `line_height` parameter (+ 4 call sites) and the unused
  `segEnd` local — clears the two -Wunused warnings.
- Remove the toolbar filter checkboxes' dead `static bool s_prev_*` change-detectors,
  whose only effect was setting the dead scroll_to_bottom_.
- Fix the console_new_lines format/arg mismatch (format takes one %d; the call passed a
  spurious plural-suffix arg).
- Fix the channel/color incoherence: a "[daemon] error:" line kept red text but got a
  blue daemon bar — the prefix no longer downgrades an error channel.
- Delete 11 orphaned lite_console_* i18n keys left over from the console merge (keep
  lite_console_help_passthrough).

Full-node + lite build clean, ctest green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:40:38 -05:00
DanSandClaude Opus 4.8 8abc0ec113 feat(console): JSON indent guides on result lines
Draw faint vertical guides per 2-space nesting level on result (non-channel) lines, so
nested RPC JSON is easier to read. Draw-only in the per-line loop (like the channel bar),
so it doesn't touch the text layout or selection.

Note: collapsible JSON nodes were intentionally deferred — folding entangles with the
wrap cache / filter / selection line indices and warrants a runtime-verified pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:43:34 -05:00
DanSandClaude Opus 4.8 5a677f6202 feat(console): right-click "Copy value" for hashes/addresses under the cursor
Right-clicking the output now offers "Copy <value>" when a long alphanumeric token
(txid/blockhash/address, >=16 chars) is under the cursor — resolved via screenToTextPos.
Uses right-click so it never conflicts with the left-drag text selection.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:40:38 -05:00
DanSandClaude Opus 4.8 efec3a50a1 revert(console): drop the redundant jump-to-bottom pill
renderOutput already draws a clickable "N new lines" jump-to-bottom indicator
(console_tab.cpp ~996), so the pill added earlier duplicated it — and reused the
"console_new_lines" format string incorrectly. Remove the pill and the stray i18n entry;
the existing indicator stands.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:38:05 -05:00
DanSandClaude Opus 4.8 4ed69d4e9f feat(console): filter match highlight + match count
- Highlight the matched substring (case-insensitive) in every visible line with a
  translucent yellow behind the text, drawn per wrap-segment like the selection highlight.
- Show a live match count next to the filter input ("N matches", red when zero).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:34:18 -05:00
DanSandClaude Opus 4.8 e0d6d6bc4d feat(console): left-edge channel accent bar (replaces inline log prefixes)
Each line gets a `channel` inferred in addLine() from its color and any
"[daemon]"/"[xmrig]"/"[app]"/"[rpc]" prefix, which is then stripped. renderOutput draws
a thin colored bar in the existing left margin (padX) per line — node log (blue), mining
(amber), app (teal), rpc (secondary), command (accent), error (red). Drawn in the margin
so it never touches the text layout or selection; filtering is unaffected (it keys off
color, not the prefix).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:30:43 -05:00
DanSandClaude Opus 4.8 e9121fe49e feat(console): jump-to-bottom "N new" pill when scrolled up
Surface new_lines_since_scroll_ (already tracked) as a floating pill at the output
panel's bottom-right whenever the user is scrolled up with unseen output; clicking it
re-enables auto-scroll and snaps to the bottom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 16:25:34 -05:00
DanSandClaude Opus 4.8 b5001ad4db refactor(console): unify lite + full-node console behind a pluggable executor
The two consoles were separate implementations (the rich full-node ConsoleTab and a
minimal RenderLiteConsoleTab). Introduce ConsoleCommandExecutor to abstract the only
real differences — command execution + log source — so both variants share the one rich
terminal (the lite console now gains selection, zoom, copy, and JSON-colored output).

- console_command_executor.{h,cpp}: interface + FullNodeConsoleExecutor (dragonxd log
  ingestion + RPC command execution, result queue) and LiteConsoleExecutor (lite
  diagnostics ring + backend runConsoleCommand/takeConsoleResult, connection/sync status).
- ConsoleTab::render() now takes a ConsoleCommandExecutor&; log ingestion, command
  submission, and command results flow through it. The RPC command-reference popup and
  the daemon/errors/rpc-trace/app filter toggles are gated on the executor's capabilities;
  the toolbar status dot + a status header come from the executor.
- Safety preserved: `clear`/`cls` is intercepted as a view-only clear in the shared UI so
  it is NEVER forwarded (the lite backend's `clear` wipes tx history); command output is
  not persisted to diagnostics.
- App owns the executor (created per variant) and routes both NavPages to console_tab_;
  lite_console_tab.{h,cpp} deleted.

Needs runtime verification of BOTH consoles (full-node RPC + lite: clear, seed/export
secrecy, sync status, command results).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:59:17 -05:00
DanSandClaude Opus 4.8 1266b6e68e feat(market): auto-populate exchange pairs from CoinGecko (adds OurBit); DRGX dashboard
- Fetch /coins/dragonx-2/tickers once per session (App::refreshExchanges, reusing the
  price-fetch worker path) and build the exchange registry at runtime into
  state.market.exchanges; parseCoinGeckoTickers groups tickers by venue. The Market tab
  sources from this live list and falls back to the compiled-in registry (now including
  OurBit + Nonkyc.io) when offline. Fixes the missing OurBit pair and auto-tracks future
  listings.
- Fix stale selection defaults (TradeOgre / DRGX-BTC -> Nonkyc.io / DRGX/USDT) that never
  matched the registry, and make the attribution generic ("Price data from CoinGecko").
- Reframe the single-asset portfolio card as a DRGX holdings dashboard: relabel to
  "MY DRGX" and show the 24h change on the holdings value (colored by direction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:25:22 -05:00
DanSandClaude Opus 4.8 57f52b7a6c feat(ui): semibold window title + "Lite" suffix on the lite build
- Draw the brand word "ObsidianDragon" in Ubuntu Medium (the closest weight the
  project ships — no true 600 face) as a stand-in for semibold; append "Lite" in the
  Regular font under DRAGONX_LITE_BUILD so the lite window reads "ObsidianDragonLite".
- Fix a misleading comment: pool mining (and thus auto-balance) is available in both
  builds; only solo mining is full-node-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:13:40 -05:00
DanSandClaude Opus 4.8 a6964621a8 feat(settings): About logo scales to card height; Node&Security Advanced expander; lite gating
- About: draw the logo deferred, scaled to the card's full height (aspect-preserved,
  capped to the reserved width) so there's no empty space below it.
- Node & Security: split the daemon toolbar — Refresh + Test connection stay visible;
  the rare/destructive actions (Install bundled, Rescan, Delete blockchain, Repair
  wallet) move behind a collapsible "Advanced" header (reusing the DEBUG LOGGING idiom).
- Lite gating: hide the DEBUG LOGGING card (dragonxd debug= categories, no daemon in
  lite) and the RPC-backed encrypt/change/lock/remove block (lite has its own encryption
  in the NODE column) behind full-node guards; label the lite left column "WALLET" not
  "NODE". (Auto-lock + PIN remain in both.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:13:40 -05:00
DanSandClaude Opus 4.8 14f5fdc559 fix(overview): mining addresses visible at 0 balance; drag-reorder always applies
- Address list: a mining-flagged address stays visible even at 0 balance when
  "hide zero balances" is on (payout addresses shouldn't vanish).
- Drag-reorder: persist dense sort orders (0..N-1) for the whole visible list on
  drop via App::reorderAddresses, instead of a pairwise swap that no-ops when both
  rows are still at the default un-ordered state. First drag now always takes
  effect, and explicit order keeps overriding the starred/type/balance sort.
- Tests: 0-balance mining row survives hide-zero; ordered non-favorite outranks a
  favorite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:02:15 -05:00
DanSandClaude Opus 4.8 1a00ec3359 feat(mining): left pool card (Manual/Auto, metrics, scrollable pool list)
Split the pool-mode hashrate card into a 25%-width left card + 75% chart so the
Manual/Auto toggle and pool metrics no longer reflow the tab:
- left card: Manual|Auto toggle (with tooltips), a Local/Pool/Shares/Uptime
  metric stack, and a fixed-height scrolling pool list — rows click-to-select in
  Manual (reusing the existing save+restart path) and show a "mining here" dot in
  Auto. Fixed height keeps the card constant as pools are added.
- mode-toggle row: keep the suggested-pools dropdown + auto-mode URL disable;
  the toggle and pool list moved into the card.
- mining_controls: show the installed/running miner version ("Current: …")
  before and during mining instead of "none".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:35:33 -05:00
DanSandClaude Opus 4.8 5c490c8d53 feat(mining): auto-balance driver, per-pool algo, live miner version
- app: periodic weighted-random auto-balance of the pool while pool-mining
  (~30-min cadence; restarts the miner only when the pick actually changes),
  plus snapshot + refresh accessors for the UI.
- app_network: resolve the xmrig algo per pool at start (pool.dragonx.cc =
  rx/dragonx, pool.dragonx.is = rx/hush; custom hosts keep the setting).
- xmrig_manager: schema-aware pool-side hashrate readout (fixes a silent 0 for
  Miningcore pools); expose the running miner's version from its HTTP API and a
  cached `xmrig --version` detection so the UI can show it before mining.
- wallet_state: carry the running miner's version.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:35:22 -05:00
DanSandClaude Opus 4.8 d0586e5e75 feat(mining): pool registry, schema-aware stats fetcher, pool-select setting
Data + service layer for supporting multiple mining pools:
- pool_registry: KnownPool table (pool.dragonx.is + pool.dragonx.cc) carrying
  each pool's stratum host, xmrig algo, and stats-API schema; pure helpers
  findKnownPoolByUrl / resolvePoolAlgo / parsePoolHashrate / chooseWeightedPool
  (weighted-random by hashrate with incumbent stickiness).
- pool_stats_service: background, schema-aware hashrate fetcher (libcurl, TLS,
  browser UA for Cloudflare) modelled on XmrigUpdater.
- settings: pool_select_mode (manual | auto_balance), string-serialized.
- i18n strings + unit tests (both stats schemas, algo resolution, weighted pick).

Note: pool.dragonx.cc's stratum host is us.dragonx.cc:3333 — the .cc domain is
only the Cloudflare-proxied web/API host and does not accept stratum on :3333.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:35:13 -05:00
DanSandClaude Opus 4.8 24e8fb4942 chore: bump full-node to 2.0.0; stop tracking the generated manifest
Set project(ObsidianDragon VERSION 2.0.0) (was 1.3.0); the lite variant keeps
its own independent DRAGONX_LITE_VERSION (1.0.0). res/ObsidianDragon.manifest is
generated per-variant by configure_file from res/ObsidianDragon.manifest.in, so
it was wrongly tracked (it kept showing dirty, stamped with whichever variant
built last) - untrack it and gitignore it; the .in template remains the source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:20:56 -05:00
DanSandClaude Opus 4.8 1393d9ae1a feat(lite): vendor SDXL backend source; source deps from git.dragonx.is
Vendor the two local Rust crates that build the lite backend artifact into
third_party/silentdragonxlite/ (the qtlib C-ABI wrapper + the silentdragonxlitelib
core, with proto/res and all the lite-send fixes), and point
build-lite-backend-artifact.sh's default --backend-dir there, so the lite wallet
builds without the upstream SilentDragonXLite repo.

External build inputs are now only the Rust toolchain + git.dragonx.is + crates.io:
- the 6 librustzcash git deps point at the git.dragonx.is/DragonX/librustzcash
  mirror (pinned rev acff1444), not git.hush.is;
- the Sapling params are gitignored (not committed, no Git LFS) - the build fetches
  them from the git.dragonx.is/DragonX/zcash-params 'sapling-v1' release and verifies
  their SHA-256 before rust-embed bakes them in (ensure_sapling_params).

For fully offline builds, cargo vendor into lib/vendor/ and add a vendored-sources
redirect (vendor/ is gitignored; the script symlinks it into the prepared dir).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 15:06:42 -05:00
DanSandClaude Opus 4.8 3ce62326f9 chore(lite): send-smoke harness + checkpoint generator
tools/lite_send_smoke drives the real SDXL backend's exact GUI send path
(newaddr/status/list/tree/send/rescan) for diagnosing shielded sends.
scripts/gen-lite-checkpoints.sh generates verified mainnet checkpoints from a
synced dragonxd (getblockhash + getblockmerkletree), self-checking against a
known checkpoint before emitting.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 21:27:35 -05:00
DanSandClaude Opus 4.8 b20e7efb16 feat(lite): network-tab polish, reworked key-export/QR dialogs
Network tab: glow only the active node, drop the left accent bar. Key-export
dialog: fix the lite-wallet "Not connected" failure by exporting the key
locally via the SDXL backend when there's no daemon; rework the layout to
wrapping click-to-copy fields with a side QR (empty placeholder when hidden),
85% modal width, HRP-preserving key chunking, and a centered, emphasized
warning. QR popup matched to the same sizing and click-to-copy address. Shared
field rendering extracted to widgets/copy_field.h so both dialogs stay in sync.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 21:27:25 -05:00
DanSandClaude Opus 4.8 4473e7e00a feat(updater): in-app dragonxd updater + browse-all-releases
Add a full-node daemon updater (util/DaemonUpdater + daemon_download_dialog)
reachable from Settings -> NODE & SECURITY: downloads/verifies (SHA-256 +
enforced ed25519 signature) and atomically installs the latest dragonxd from
the project Gitea, with a "Restart daemon now" step. Add a shared "Browse all
releases..." picker (release_list_view) to both the miner and daemon updaters
so users can pin older/pre-release builds. Pure no-I/O cores
(daemon_updater_core / xmrig_updater_core) are unit-tested; sign-daemon-release.sh
signs release archives offline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 21:27:13 -05:00
DanSandClaude Opus 4.8 2e8e214689 refactor(ui): app-wide compact, translucent tooltips
Add material::Tooltip / BeginTooltip / EndTooltip wrappers (tooltip_style.h)
that scope a small window padding (8x4, dpi-scaled) and ~85% opacity to
tooltips only, then route the tooltip call sites through them. Menus and combo
dropdowns are untouched (they keep the global opaque PopupBg).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 21:26:52 -05:00
DanSandClaude Opus 4.8 69a6fb3e64 fix(fullnode): smooth witness-cache progress from the new daemon's done/total
Use the new daemon's "Reading blocks for witness rebuild: <done> / <total>" as an
exact fraction: the reported total is the denominator directly, so the bar sweeps
0..1 smoothly instead of being held near the top by the peak-anchored remaining
heuristic (kept only as a fallback for older daemons that log bare "<n> remaining").
Also snap to 100% on the parallel rebuild's completion line ("rebuilt <n> note
witness cache(s) … using <t> thread(s)"), which otherwise logs no progress, so the
bar visibly finishes before the rescan-complete signal clears it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 15:48:33 -05:00
DanSandClaude Opus 4.8 ed6120eef9 feat(fullnode): parse the new multi-threaded daemon's witness-rebuild progress
The updated dragonxd (parallel witness rebuild) replaced the per-block
"Building Witnesses for block <h> <frac> complete, <n> remaining" log line with a
clean serial read counter "Reading blocks for witness rebuild: <done> / <total>".
Parse the new line and map it onto the existing phase-2 path (remaining = total -
done), so the witness progress bar shows done/total against this daemon. The old
"Building Witnesses" matcher is kept for backward compatibility with older daemons;
"Setting Initial Sapling Witness for tx … i of N" (phase 1) is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:41:36 -05:00
DanSandClaude Opus 4.8 a5bd2dadd7 fix(fullnode): make witness sub-phase upgrade-only to stop progress thrash
The initial-witness pass ("Setting Initial Sapling Witness") and the cache walk
("Building Witnesses for block … remaining") interleave during a rescan — the daemon
does both per block. The phase selector picked phase 1 whenever a batch had only
initial-pass lines, so once the cache walk started an interleaved initial line would
flip the phase back to 1 and reset the bar to 0 every batch. Make the phase
upgrade-only (once the cache walk is seen it never drops back), so the reset happens
at most twice (→1, →2) and the cache-walk percentage advances monotonically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:09:29 -05:00
DanSandClaude Opus 4.8 25ee1496b4 fix(fullnode): make witness/rescan progress work on the real daemon
Verified by running the app against a live node and watching a real rescan. Three
issues that only surfaced at runtime:

- Wrong RPC name: this daemon (hush/komodo) exposes the runtime rescan as
  "rescan <height>", not bitcoin's "rescanblockchain". runtimeRescan() and
  RPCClient::rescanBlockchain() used the bitcoin name and failed with "Method not
  found" on every node. Corrected to "rescan".

- Witness/rescan progress never surfaced during a rescan: the daemon-output parser
  that drives it was gated behind rpcConnected, but a heavy rescan holds cs_main so
  getinfo times out and the RPC reads disconnected — silencing the parser exactly
  when it's needed. The parser reads the daemon's stdout pipe (no RPC), so it now
  runs whenever the daemon process is alive. It also now parses INLINE on the main
  thread instead of via fast_worker_, so it can't be starved when the worker is
  blocked on a getrescaninfo call (which waits on cs_main during a witness rebuild).

- Witness rebuild has TWO sub-phases with different scales — the initial-witness
  pass ("Setting Initial Sapling Witness for tx <hash>, <i> of <N>") and the cache
  walk ("Building Witnesses for block <h> <frac> complete, <n> remaining"). Tracking
  them with one monotonic value pinned the bar at the initial pass's ~100% through
  the whole cache walk. They're now tracked as distinct phases (witness_phase) with
  their own monotonic progress and labels ("Setting witnesses" vs "Rebuilding
  witnesses"), so neither resets/bounces and the long phase shows real movement.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 13:00:36 -05:00
DanSandClaude Opus 4.8 e2bc3623b6 fix(fullnode): stable overall progress for Sapling witness rebuild
The witness-rebuild bar reset repeatedly because the daemon's "Building Witnesses
for block <h> <frac> complete" line reports per-call progress: BuildWitnessCache is
re-invoked for each connected block and each call walks from its own start height to
the tip, so the fraction restarts every time. The earlier "Setting Initial Sapling
Witness for tx <i> of <m>" counter resets per call too, so neither is a usable
overall metric.

Derive a stable, monotonic percentage from the "<n> remaining" count instead: track
the largest "remaining" seen during the phase as the full span and show how far
remaining has fallen below it. The longest pass defines 0→100%; the short per-block
follow-up passes only nudge the bar near the end rather than resetting it. The
"Setting Initial" line now only marks the phase active. Per-phase tracking resets at
phase start and every rescan-completion site.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:44:01 -05:00
DanSandClaude Opus 4.8 b32fe07cb1 feat(fullnode): show Sapling note witness-rebuild progress
The daemon's post-rescan witness rebuild ("Building Witnesses for block ...")
is a distinct, often-long phase that previously showed only as an indeterminate
"Rescanning..." with no progress. Parse the daemon's witness-build log lines and
surface a dedicated progress indicator.

- Parse "Building Witnesses for block <h> <frac> complete, <n> remaining" (and the
  earlier "Setting Initial Sapling Witness for tx ..., <i> of <m>") from daemon
  output, extracting a 0..1 fraction and remaining-block count.
- New SyncInfo fields building_witnesses / witness_progress / witness_remaining,
  cleared at every rescan-completion site (warmup-end, getrescaninfo poll, runtime
  rescan callback, daemon-log "finished").
- Status bar shows "Rebuilding witnesses NN%" (priority over the generic rescan
  text); the loading overlay (shown during -rescan warmup) gets a labelled witness
  progress bar with the remaining-block count.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 12:23:52 -05:00
DanSandClaude Opus 4.8 a0532275dd feat(fullnode): auto-reconcile wallet after bootstrap; runtime rescan for pruned nodes
A wallet bootstrapped from a snapshot keeps its wallet.dat but never rescans, so
its spent-state is stale and the first send tries to spend already-spent notes and
is rejected. The startup -rescan flag can't fix it either: the snapshot lacks the
pre-snapshot block history -rescan needs, so it errors. The working fix is a runtime
rescanblockchain RPC from a height the snapshot actually has.

- Add App::runtimeRescan(startHeight): runs rescanblockchain via the worker, drives
  the rescanning UI state, and owns completion via the RPC callback (getrescaninfo
  is unavailable on this daemon). Suppresses the per-second mining/rescan pollers
  and the Core/balance/tx refreshes while the daemon holds cs_main for the scan.
- Add App::detectLowestAvailableBlockHeight(): async binary search via getblock for
  the lowest height whose block data is on disk → the snapshot base, and whether the
  node still has full history.
- Auto-reconcile after bootstrap: both completion sites (wizard + Settings download
  dialog) mark a pending rescan; once the daemon is back up and the tip is known,
  detect the base and runtimeRescan() from it (or -rescan restart on a full node).
- Settings "Rescan Blockchain" now probes first: full-history nodes get the existing
  -rescan restart; bootstrapped/pruned nodes get a prompt pre-filled with the
  detected base height that runs the runtime rescan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 11:48:30 -05:00
DanSandClaude Opus 4.8 7df00b0909 fix(ui): move Daemon binary into its own full-width row below Node & Security
The Daemon binary panel (info + the all-actions toolbar) lived inside the 60%-wide
NODE column, so the six-button toolbar clipped. Pull it out into a dedicated full-width
row rendered after the NODE + SECURITY columns reconcile, so it spans the whole card:
Installed | Bundled info side by side, status line, and the Install bundled | Refresh |
Test connection | Rescan | Delete blockchain | Repair wallet toolbar now have the full
container width and no longer clip. The NODE column keeps only the node/RPC info.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 02:41:33 -05:00
DanSandClaude Opus 4.8 090e288c44 fix(ui): put all node action buttons on one toolbar row under Daemon binary
Move Test Connection / Rescan Blockchain / Delete Blockchain / Repair Wallet onto the
same line as and right after the Daemon binary buttons (Install bundled | Refresh), so
all node actions form a single toolbar row beneath the Daemon binary panel. Buttons are
auto-sized to pack onto one line; each disabled-state group (connection vs embedded
daemon vs bundle present) keeps its own guard. Removes the former two stacked button rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 23:18:57 -05:00
DanSandClaude Opus 4.8 6544c10ac1 fix(ui): tidy Node maintenance layout + widen the Daemon binary panel
- Delete Blockchain and Repair Wallet now sit on one line, paired with the same
  uniform button width as Test Connection / Rescan Blockchain (a clean 2-column grid;
  all maintenance buttons share one rowBtnW sized across the four labels).
- The Daemon binary panel lays Installed and Bundled side by side across the node
  column (version + size·date | version + size) instead of stacked narrow lines, with
  the status line spanning underneath and Install bundled / Refresh paired below.
- Shorten the install button label to "Install bundled" so it fits the shared width;
  the tooltip still explains the full action. Date shown as YYYY-MM-DD.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:21:06 -05:00
DanSandClaude Opus 4.8 b2e104358d feat(fullnode): manage the daemon binary in Settings; stop auto-overwriting it
Previously the wallet re-extracted the bundled dragonxd on startup whenever the
installed binary's size differed from the bundle ("stale" overwrite), which could
replace a node a user had deliberately placed in dragonx/.

Now dragonx binaries (dragonxd/cli/tx) are auto-placed ONLY when missing — never
auto-overwritten on a size mismatch (needsParamsExtraction + extractEmbeddedResources).
Params/asmap keep their size-based refresh; a daemon dropped next to the wallet exe
still takes priority and is never touched.

Replacing the daemon is now an explicit action: Settings → "Daemon binary" reports the
installed binary's version (scanned from the file), size and modified date, compares it
to the version bundled in this build, and offers an "Install bundled daemon" button.
That stops the node, overwrites dragonxd/cli/tx with the bundled copies (waiting for the
process to release the file lock), and restarts — wallet/keys/chain data untouched.

Adds resources::{getInstalledDaemonInfo,getBundledDaemonInfo,reextractBundledDaemon}
(+ a version-string scanner) and App::reinstallBundledDaemon().

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 21:06:59 -05:00
DanSandClaude Opus 4.8 de70e68472 fix(fullnode): work around daemon note-selection fee-gap on shielded sends
dragonxd's z_sendmany picks notes to cover the recipient total (nTotalOut) but not
the miner fee, then rejects the build unless the selected notes cover amount+fee
(rpcwallet.cpp:5312 vs asyncrpcoperation_sendmany.cpp:278). So a shielded send whose
largest notes sum exactly to the amount fails with "Insufficient shielded funds,
have H, need H+fee" despite ample balance — e.g. sending exactly 2.0 from an address
whose biggest note is 2.0.

Since the failure is async (reported via the opid poll), detect it there: when a
shielded send fails with that message and the selected total H >= the requested
amount (selection covered the amount but stopped one note short of the fee — vs a
genuine shortfall where H < amount), re-issue the send once with a tiny self-output
(= fee) back to the from-address. That lifts the daemon's selection target past the
boundary so it grabs another note and can cover the fee; the recipient still receives
the exact amount. Retries are tracked so a second failure surfaces normally (no loop).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 14:27:52 -05:00
DanSandClaude Opus 4.8 0fe12d65df fix(ui): drop duplicate daemon log on the startup overlay
renderLoadingOverlay() rendered the daemon's recent output twice during startup:
a bare 4-line centered tail (section 2c, init/warmup only) and the styled
terminal-style box (section 4, always shown when the embedded daemon exists).
The bare tail was a strict subset of the box, so the same dragonxd output showed
stacked twice. Remove the redundant bare tail; keep the terminal box (which also
matches the shutdown screen's panel).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 07:48:53 -05:00
DanSandClaude Opus 4.8 37c8287a12 feat(fullnode): add "Repair Wallet" (-zapwallettxes=2) to Settings
When a note's stored record is corrupt or its tx isn't in the canonical chain,
z_sendmany fails to build a valid sapling spend proof even after a full -rescan,
because a plain rescan replays witnesses but keeps the existing tx/note records.
The zcashd repair for this is -zapwallettxes=2, which deletes all wallet tx/note
records and rebuilds them from the chain (keys/addresses preserved).

Adds a RepairWallet lifecycle operation that mirrors the existing -rescan plumbing
(one-shot zapOnNextStart flag on the embedded daemon; -zapwallettxes=2 implies and
supersedes -rescan), an App::repairWallet() that reuses the rescan status UI (so the
status bar + warmup-end completion detection apply), and a confirmed "Repair Wallet"
button + dialog in Settings → node maintenance (embedded daemon only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 07:42:10 -05:00
DanSandClaude Opus 4.8 6ff80354df fix(fullnode): reliable rescan completion + self-explaining shielded send errors
Two related fixes for the post-bootstrap "send fails / rescan stuck at 99%" trap:

1) Rescan completion now keys off warmup-end. A -rescan runs entirely inside daemon
   warmup (every RPC returns -28 until it finishes), so warmup completing IS the rescan
   completing. The old detectors relied on getrescaninfo (which some daemons answer with
   "Method not found") or a "Done rescanning"/bench log line the daemon may never print,
   leaving the status bar stuck at 99% — so users killed the rescan before it finished.
   When warmup ends and a rescan was confirmed active, clear the rescan state, flip to
   100%, refresh history/balance, and toast completion.

2) z_sendmany failures that mean stale shielded note data (shielded-requirements-not-met,
   missing sapling anchor, invalid sapling spend proof, bad-txns-sapling-*) now append a
   plain-language hint telling the user to run a full rescan, instead of surfacing only the
   raw daemon string.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 22:43:24 -05:00
DanSandClaude Opus 4.8 f58d009703 fix(lite): show real block height when synced (was 0)
The lite status bar showed "blocks: 0" once fully synced. The backend's
`syncstatus` only includes synced_blocks/total_blocks WHILE actively scanning;
at rest it returns just {"syncing":"false"}, so the parsed syncedBlocks was 0
and became state.sync.blocks. On the synced refresh path, additionally query the
backend `height` command (wallet last-scanned height, a fast local read) and use
it as the synced height/tip, so the block count is correct at rest.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 11:36:58 -05:00
DanSandClaude Opus 4.8 0e2c786ebf fix(lite): welcome "Restore from seed" now prompts for the seed inline
On first run the lite welcome screen's "Restore from seed" button only showed a
hint toast and bounced the user to Settings, dismissing the welcome with no
wallet open — it never prompted for a seed. Add a real restore step to the
welcome wizard: a seed-phrase field + optional birthday height, which calls
beginRestoreWalletAsync() (same server failover as create/open), shows
"Restoring…" progress, then completes (wallet syncs) or surfaces the error to
retry. The seed buffer is wiped on success/Back and in finish().

(The Settings -> Lite -> Restore path already prompted for a seed; this fixes
the first-run welcome path.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 02:11:19 -05:00
DanSandClaude Opus 4.8 d54c7f9e11 fix(lite): "Open data folder" points to the actual lite wallet dir
The lite SilentDragonXLite backend stores its wallet in its own directory
(dirs::data_dir()/silentdragonxlite — %APPDATA%\silentdragonxlite on Windows,
~/.silentdragonxlite on Linux, ~/Library/Application Support/silentdragonxlite on
macOS), NOT the full-node getDragonXDataDir() (…/Hush/DRAGONX). The newly added
lite "Open data folder" button opened the wrong (full-node) directory.

Add Platform::getLiteWalletDataDir() mirroring the backend's get_zcash_data_path
for the "main" chain, and point the lite button at it. The full-node button is
unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 02:01:40 -05:00
DanSandClaude Opus 4.8 b3251e9244 feat(lite): mirror errors into the lite Console (copyable), not just toasts
Lite send/shield, unlock, and key-import failures were shown only as transient
toasts — impossible to copy. Route them through liteLog() so they also appear in
the lite Console (which has a Copy button), alongside the lifecycle/open/sync
errors the controller already logs:
- send/shield broadcast failures (App broadcast-result delivery)
- wallet unlock failure
- key import failure (controller; logs the error text only, never the key)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 22:59:34 -05:00
DanSandClaude Opus 4.8 c40f4d5815 feat(settings): add an "Open data folder" button (wallet + block data)
Add an explicit button in Settings that opens the wallet/blockchain data
directory (getDragonXDataDir()) in the OS file manager via the existing
Platform::openFolder(). Placed in the full-node connection section (next to the
data-dir path, which was only a subtle clickable link) and in the lite section
(always available). i18n strings added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:14:52 -05:00
DanSandClaude Opus 4.8 5547ab1cac feat(lite): add "Redownload blocks" (rescan from lite server) to Settings
Add a maintenance option for the lite wallet to re-download and re-scan every
block from the lite server — useful when balances or history look wrong.

- LiteWalletController::startRescan() runs the backend `rescan` command (which
  clears the wallet's synced block cache and re-syncs from its birthday) on a
  detached thread, reusing the existing sync progress/refresh machinery: it
  resets syncDone_ so refreshModel() shows progress again and refreshes data on
  completion. No-op if no wallet is open or a scan is already running.
- scanInProgress() exposes the initial-sync-or-rescan state.
- Settings (lite, open wallet) gains a "Redownload blocks" button behind a
  confirmation modal, disabled while a scan is running. i18n strings added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 17:05:27 -05:00
DanSandClaude Opus 4.8 f0867084f3 fix(ui): stop spurious "failed to read" logo errors in the portable build
The header and coin logos load disk-first (for dev builds / theme drop-ins) and
fall back to the copies embedded in the exe. The portable single-file build has
no res/img/ folder beside it, so the disk read always failed and logged
"LoadTextureFromFile: failed to read ..." before the (successful) embedded
fallback. Guard each disk load with std::filesystem::exists() so the missing
file is skipped silently and we go straight to the embedded logo — no error
line, logos unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:58:19 -05:00
DanSandClaude Opus 4.8 5167b52cbd feat(console): add an "App" toggle to show/hide [app] log lines
The console mixed RPC traces, daemon output, and the wallet's own "[app] ..."
log lines with no way to hide the latter. Add an "App" checkbox alongside the
existing Daemon/Errors/RPC toggles. Since [app] lines share COLOR_INFO with
other info text, the filter matches them by their "[app] " prefix rather than by
color. Default on; unit test + i18n added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:52:42 -05:00
DanSandClaude Opus 4.8 e7d11f620a fix(network): populate the peer count on connect, not just on the Peers tab
On a fresh open the status-bar peer count stayed 0 until the Network tab was
opened. refreshData() — the one-shot refresh run on connect / warmup-complete /
unlock — only refreshed peers when the active tab was Peers, so on any other tab
nothing populated the count until a tab visit forced it. Refresh peers
unconditionally there so the count appears right after connecting; the periodic
20s Peers timer (all tabs) keeps it current after that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:45:50 -05:00
DanSandClaude Opus 4.8 168cae9306 feat(explorer): fuzzy search — filter the block list by partial hash/height
Add a fuzzy mode to the explorer search: a non-numeric, non-full-hash query now
filters the list to cached blocks whose hash (or height text) contains the query
substring, live as you type. Backed by a new ExplorerBlockCache::searchBlocks()
(SQLite LIKE with escaped wildcards), memoized per query so it doesn't hit the DB
every frame. Exact queries still navigate precisely: a block height re-anchors
the list, and a full 64-char hash is resolved via RPC. Row clicks still open the
detail modal. Empty results show "No matching cached blocks".

Note: fuzzy matching covers cached (browsed/prefetched) blocks only — the daemon
has no partial-hash index — while exact height/hash lookups reach any block.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:38:24 -05:00
DanSandClaude Opus 4.8 317d9028a3 fix(explorer): search re-anchors the block list live instead of opening a modal
Typing in the explorer search ran an exact lookup that popped the block-detail
modal. It now updates the recent-blocks LIST as you type: a block height
re-anchors the list to that height (offline-friendly, no RPC), and a complete
64-char hash is resolved to its height and the list jumps there (a txid still
shows the inline tx view) — all without a modal. Clearing the box returns to the
recent (tip) blocks. Row clicks still open the detail modal for an explicit
full view. Removed the now-unused fetchBlockDetailByHash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:19:47 -05:00
DanSandClaude Opus 4.8 09ab8d52c5 feat(explorer): live (debounced) search as the user types
The explorer search only ran on Enter or the Search button. Now it also fires
automatically ~350ms after the user stops typing, once the query is resolvable
— a block height (all digits) or a complete 64-char hash/txid. Partial hex is
ignored so it won't flash "invalid query" mid-type, per-keystroke RPC spam is
avoided via the debounce, and the same query isn't re-run. Enter/button still
work for an immediate search.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:07:01 -05:00
DanSandClaude Opus 4.8 101c835c46 feat(settings): confirmation modals for rescan and restart-daemon
The Rescan blockchain and Restart daemon buttons fired immediately on click —
both are disruptive (long offline rescan / connection drop) and easy to hit by
accident. Route them through confirmation modals, matching the existing
delete-blockchain / clear-ztx confirmations: the button now sets a confirm flag
and an overlay dialog performs the action only on explicit confirm. New i18n
strings added with English defaults.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 11:52:44 -05:00
DanSandClaude Opus 4.8 c71c3c3378 fix(network): keep the status-bar peer count current on every tab
The peer count in the status bar is state_.peers.size(), refreshed only by
getpeerinfo — and the peers refresh interval was 0 (disabled) on every tab
except Peers. So the count never changed until you opened the Peers/Network
tab. Give peers a slow 20s cadence on all tabs (30s on Console); the Peers tab
keeps its fast 5s for the live list. During sync this is still overridden by
kSyncProfile (peers 0) so it can't contend with block download. Test updated to
the new intervals.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 11:12:13 -05:00
DanSandClaude Opus 4.8 2e115aef39 fix(daemon): replace the embedded daemon even when the old binary is locked
When a newer wallet build embeds a newer daemon, extractEmbeddedResources()
detects the size change and tries to overwrite dragonxd.exe in the daemon dir —
but the write is a plain truncating ofstream, which fails silently if the file
is locked. A running (or just-killed, handle-not-yet-released) daemon locks the
.exe on Windows (and Linux returns ETXTBSY), so the stale binary was kept and
the wallet kept launching the old daemon version.

If the direct write fails, move the stale binary aside to "<name>.old" (renaming
a running/locked executable is permitted on both Windows and Linux — the running
process keeps the moved copy) and write the fresh one at the original path. A
best-effort pass removes leftover .old files once the old process has exited.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:51:10 -05:00
DanSandClaude Opus 4.8 574307f6ac build(setup): warn when a prebuilt daemon is older than the dragonx source
build.sh bundles whatever daemon binary already sits in prebuilt-binaries/, and
setup.sh only rebuilds a platform's daemon when its flag (--win/--mac) is passed
— so a daemon left over from an older source revision silently shipped in the
wallet (the Network tab showed dragonxd v1.0.1 while the source was v1.0.2).

Add a stale-daemon guard: compare the vX.Y.Z baked into each prebuilt daemon
against CLIENT_VERSION_* in the checked-out dragonx source. On the present/skip
and --check paths it now prints either "matches dragonx source" or a STALE
warning naming both versions and the rebuild command, plus a summary reminder at
the end of the daemon section. Version is read with grep -a (no binutils/strings
dependency); no-ops cleanly when the source or a binary is absent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:14:11 -05:00
DanSandClaude Opus 4.8 88851f5eea fix(history): unstick the unconfirmed-tx badge on confirmed shields
The History badge counts transactions with confirmations==0, iterating the raw
transaction list. Autoshield transactions have two legs sharing one txid, and
the send leg parsed from z_viewtransaction carries confirmations=0 even when the
transaction is long confirmed (the receive leg holds the real count). So the
badge counted those stale legs and stuck at a non-zero number (e.g. 7) with no
pending transactions.

Treat a txid with ANY confirmed leg as confirmed, and count UNIQUE unconfirmed
txids rather than legs — so confirmed multi-leg transactions don't inflate the
badge and genuinely pending ones still count once each.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 10:06:46 -05:00
DanSandClaude Opus 4.8 5796664b51 feat(history): add date and amount sorting to the History tab
Add a sort selector next to the type filter with four modes: Newest first
(default), Oldest first, Largest amount, Smallest amount. The mode folds into
the merged-list memoization cache key (so the list re-sorts only when the mode
changes) and the comparator branches on it, keeping txid as a deterministic
tiebreak. Changing the sort resets to page 1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 09:55:24 -05:00
DanSandClaude Opus 4.8 b71f8ae0a8 perf(history): toggle mining address without a full chain re-scan
Marking/unmarking a mining address triggered a long history reload: it called
invalidateShieldedHistoryScanProgress() + forced a transaction refresh, which
re-scans every z-address over many RPC cycles. But "mined" vs "receive" is a
pure function of the LOCAL mining-address set — the daemon knows nothing about
it — so a chain re-scan is pointless.

Relabel the affected rows in the in-memory history directly and persist just
those to the encrypted SQLite history cache. The History tab updates instantly
(its display cache rebuilds on the type change), with no daemon round-trip and
no reload. Only re-save when something actually changed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 09:37:56 -05:00
DanSandClaude Opus 4.8 555f541c84 fix(history): update tx labels immediately when a mining address is toggled
Unmarking (or marking) a mining address didn't change the history. The refresh
re-scanned the affected transaction as "receive", but appendMissingPreviousTransactions
carries over not-yet-rescanned prior transactions and dedupes by txid+TYPE — so
the stale "mined" copy was carried over right alongside the fresh "receive", and
the change never appeared.

Re-label state_.transactions in setMiningAddress() the moment the flag changes
(mined vs receive is just whether the receiving address is mining-flagged). The
History tab updates instantly, and the next refresh's carry-over now matches the
fresh scan instead of duplicating the old label. The reclassified list is also
persisted via the existing cache save.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 09:13:36 -05:00