Commit Graph
493 Commits
Author SHA1 Message Date
DanSandClaude Opus 4.8 d27f387d6d feat(ui): in-app FAQ, DPI-scaling audit fixes, mining/stratum polish + localize strings
Bundles the session's UI work (the touched files carry several of these
changes together, so they are committed as one coherent UI batch):

- FAQ: new RenderFaqDialog + data-driven faq_content, opened from a
  status-bar "?" (and the Windows title bar), styled like the Wallets
  modal with search, Wallet/Daemon tabs, and smooth scroll.
- DPI/font-scale audit: multiply hand-drawn absolute geometry by
  Layout::dpiScale() across ~30 files so nothing renders native-size at
  HiDPI / font_scale 1.5 (verified with a full sweep at 1.5x).
- Mining: chart now fills the horizontal space; thread stepper +/- buttons
  match the input-box height; move the stratum-host toggle into
  Node & Security (v1.3.0+).
- Settings: fix the auto-shield status text overlapping the grid.
- Sidebar: drop the peer-count badge on the Network button.
- i18n: wrap 193 hardcoded literals with TR() (keys/translations added in
  the preceding i18n commit), so the security/PIN/lock flow, seed-backup
  wizard, and witness-rebuild dialog localize.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-01 19:37:16 -05:00
DanSandClaude Opus 4.8 aec996a9ce feat(mining): host a RandomX stratum pool from the node (v1.3.0+)
Add an opt-in "Host a mining pool (stratum)" toggle so a v1.3.0+ node can run its native
RandomX stratum server for other miners to point at. Settings toggle + optional allow-IP/CIDR;
passes -stratum (+ -stratumallowip) to the daemon launch args, mirroring the -maxconnections
plumbing (EmbeddedDaemon::setStratumHosting <- DaemonController::syncSettings <- Settings).

Backwards compatible / safe by default:
- UI gated on daemon_version >= 1030000, so it's never offered where it would do nothing.
- The launch flag is harmless on older daemons (they ignore unknown args), and the toggle can
  only be enabled while connected to a v1.3.0+ node anyway.
- Blank allow-IP => the daemon serves loopback only (its safe default); entering a subnet opens
  it to that LAN, with an explicit exposure warning in the UI.
- Takes effect on the next daemon start/restart.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-31 23:19:48 -05:00
DanSandClaude Opus 4.8 90e02b1ddd feat(ui): surface daemon DEGRADED mode + v1.3.0 auto-shield status
Rec3 — v1.3.0+ opens a wallet that lost its hdchain in DEGRADED mode (existing funds spendable,
but no new HD-key derivation) instead of aborting. Add a daemon-log classifier
(walletOpenedDegraded) + detectWalletDegraded(), warned once per session. Pre-1.3.0 daemons
never emit that line, so it's a no-op there.

O1 — probe z_autoshieldstatus once per connection (now decoupled from our own toggle/balance) and,
in Settings, show whether the node handles auto-shield itself (+ its destination, or the daemon's
disabled_reason). The checkbox now governs only the wallet's fallback shielder, which defers to the
node. Nothing renders on pre-1.3.0 daemons (no such RPC), so behaviour is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-31 22:48:46 -05:00
DanSandClaude Opus 4.8 08cfeb0e08 feat(ui): rework the consolidate/merge modal
Make Merge to Address actually serve wallet-bloat consolidation and be far less
opaque. New ShieldDialog::showConsolidate() preset (used by the large-wallet
Settings banner + alert action) frames it as "Consolidate funds" and targets
shielded notes — the bloat the nudge warns about.

- Source selector: consolidate shielded notes (ANY_SAPLING), transparent
  (ANY_TADDR), or both (*) — previously hardcoded to ANY_TADDR, which never
  reduced the shielded-witness bloat. Batch limit now applies to the right side.
- Scope: on open, count spendable UTXOs + notes (listunspent / z_listunspent)
  and show "N transparent + M shielded · ~X DRGX"; warn "repeat to finish" when
  the set exceeds one batch.
- Destination auto-selects the best spendable z-address (button enabled by
  default); empty wallets get an inline "Create shielded address" (z_getnewaddress).
- Advanced disclosure hides Fee + "Max inputs per batch" (renamed from the "UTXO
  Limit" jargon) with sane defaults.
- Inline confirm step before the fund-moving call (amount + input count + dest).
- Live progress: self-polls z_getoperationstatus to show Consolidating… →
  Done/Failed, replacing the raw opid + manual "Check status" button.

All three merge entry points now use the typed showMerge()/showConsolidate()
(no stale-static leaks from direct show(MergeToAddress)). Shield-coinbase mode
keeps working. New i18n keys fall back to English.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-20 22:33:59 -05:00
DanSandClaude Opus 4.8 5daf2d83b6 feat(ui): large-wallet nudge as a one-time toast + clickable alert
Extend the wallet-bloat warning beyond the Settings banner: when wallet.dat
first crosses 500 MB (full-node, synced), fire a one-time warning toast plus a
clickable "Consolidate notes…" entry in the bell/alerts panel that opens Merge
to Address. The persisted large_wallet_warned flag keeps it once-only and
re-arms if the file later shrinks back under the threshold.

- AlertRecord gains an optional onClick + actionHint; Notifications::action()
  pushes a toast and a clickable history entry. renderAlertHistoryPanel() now
  renders the accent action link (under the message) and measures true content
  height so wrapped messages + the link aren't clipped.
- App::maybeWarnLargeWallet() (mirrors maybeRemindSeedBackup) runs once per
  launch from update(); reuses the existing wallet_size_warn/consolidate strings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-20 18:09:36 -05:00
DanSandClaude Opus 4.8 6d26ccd0ed feat(ui): large-wallet nudge in Node & Security
The BDB wallet.dat bloats with shielded-note witness data and never shrinks
in place, so a mining/shielded wallet can grow past 500 MB. Below the Wallet
Size row, show a one-line amber hint once wallet.dat crosses 500 MB with a
"Consolidate notes…" shortcut that opens the Merge to Address (z_mergetoaddress)
dialog. Full-node only (lite has no wallet.dat here); threshold is a single
named constant. i18n keys fall back to English for non-English locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-20 17:41:25 -05:00
DanSandClaude Opus 4.8 a7514becbc feat(ui): credit The DragonX Developers in the About tab
Add "The DragonX Developers" to the About-tab credits (after The Hush
Developers), acknowledging the DragonX chain/daemon this wallet drives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-20 17:17:03 -05:00
DanSandClaude Opus 4.8 d0bd55b9c1 feat(ui): settings polish — button retune, daemon card, RPC 2-row, chat preview
Settings tabs brought closer to the approved mockup:
- ActionButton/renderCardButton retune (settings-scoped): 7px radius, 9px
  padX, Primary → accent-outline chip, Secondary/card buttons more defined.
- Daemon-binary card: compact status right-aligned on the DAEMON BINARY
  heading (Up to date / Version differs / Not installed), filled/rounded
  status box, neutral danger divider (was alarming red), roomier spacing.
- RPC Connection: two-row column-aligned layout (Host | Port, then
  Username | Password) so the password no longer clips off the card edge.
- Chat settings tab: live conversation preview below the Appearance /
  Messaging cards; "Focus input on open" checkbox reflowed onto the console
  color-toggle row.
- Debug Options: "Current theme only" toggle restricts either screenshot
  sweep to the active theme instead of cycling every skin.
- Tabs fill the full content width (content-max-width cap disabled) and the
  sidebar nav panel centers within the true visible area.
- i18n: new keys for the above (untranslated keys fall back to English).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-20 16:59:03 -05:00
DanSandClaude Opus 4.8 b37d3d97b6 fix(send/receive): unify card width, justify receive footer, fix recipient-row button height/clip/glyph
Send and Receive are now consistent in layout, and the Send recipient row's buttons
render correctly.

Card envelope (Send ⇄ Receive consistency):
- Add Layout::mainComposeCardBox(availW) — a single shared source for the compose card's
  width + centering (fill the available column up to content-max-width, then center). Both
  tabs derive their card from it, so they can't drift again. Previously Send capped at
  760dp and Receive at 860dp, so the Send card rendered ~150px narrower on any window wider
  than ~860dp; now they fill available width identically.

Receive:
- Justify the footer buttons edge-to-edge (equal shares over the live count) instead of
  left-clustering with dead space, matching Send's full-width footer rhythm.
- Build the address-dropdown preview to the combo's real pixel width so the trailing
  balance ("— 12.00000000 DRGX") no longer hard-clips at 150% (was char-count truncation).

Send recipient row (input | Paste | contacts-icon):
- Pin the contacts icon button to the frame height so the larger iconMed font doesn't
  auto-size it taller than Paste/the input.
- Reserve the real ItemSpacing.x gaps (not the smaller spacingSm token) so the row no
  longer overshoots the card and clips the icon's right border.

draw_helpers (root cause, app-wide):
- TactileButton's icon path measured/drew the label INCLUDING the "##id" suffix (which
  CalcTextSizeA/AddText don't strip the way ImGui's text render does), shoving the glyph
  off-center-left. Strip at "##" before measuring/drawing. Corrects any icon button that
  passes an explicit size and a "##id" label; no-op for labels without "##".

Verified via headless sweeps at 1.0x and 1.5x, plus a real 3800px-wide render (both cards
byte-identical at L=1174/R=2773). ctest 1/1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-19 16:10:02 -05:00
DanSandClaude Opus 4.8 8778398d31 feat(ui): layout polish — fill dead space, pair/box/collapse, warning weight, dialog glass
Implements the layout-improvement suggestions from the layout audit (visual arrangement
only; no functionality added or removed):

- Send/Receive: the recent-activity list now grows to fill the space below the fixed
  compose/receive card (more history visible), with a centered empty-state when there is
  none — instead of leaving dead canvas.
- Shield/Merge: pair the Fee and UTXO-Limit fields on one row to tighten vertical rhythm.
- Market: extend + frame the portfolio group-list as one contained panel (with a bottom
  edge) and center its empty-state, closing the previously un-anchored gap.
- Overlay dialogs: raise the card glass fill/border alpha (35/50 -> 60/90 of 255) so the
  dialog card reads as a distinct surface over busy backdrops (global, all overlays).
- Wallets: size the list height to the actual wallet count instead of always reserving 7
  rows, removing the large gap before the scan/create prompts (still scrolls when many).
- Contacts: width-aware address truncation shows more of the address on wide rows.
- Transfer Funds: give the "sends the full balance" warning a warning icon + color so the
  stakes stand out from the neutral result-preview lines.
- First-run wizard: collapse a completed Step 1 (Appearance) to the compact pill like
  Step 2, so a finished step is no longer taller than the active one.
- Explorer: distribute the Chain card's two stats to match the density of the sibling
  metrics grid.
- Validate Address: a "Results will appear here" caption fills the pre-interaction blank.
- Change Passphrase: add the warning banner its sibling security dialogs have.
- Migration ShowSeed: box the 24-word mnemonic grid (a GlassSectionScope behind the
  existing RenderSeedWordGrid) so the critical secret reads as a distinct artifact —
  purely visual, no seed/logic/state change.

Verified at 1280 across full-node + Lite + Windows (ctest green) and an adversarial diff
review (clean). New i18n key backfilled into all 8 languages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-19 13:56:13 -05:00
DanSandClaude Opus 4.8 d5c30237d2 refactor(ui): consolidate dialog footers + shared empty-state, unify buttons/rounding
Addresses the cross-screen inconsistencies from the layout audit by routing screens onto
the design system's own (previously under-used) shared helpers:

- Dialog footers: migrate ~9 overlay dialogs off hand-rolled placement onto the shared
  helpers — DialogActionFooter (primary+Close), DialogConfirmFooter, or
  BeginOverlayDialogFooter for custom/multi-button rows — so footers share one centered
  treatment. All footer/action buttons now use TactileButton (glass press) instead of the
  bare StyledButton some dialogs used.
- Empty states: add a shared material::DrawEmptyState(icon, title, hint) (centered icon +
  title + wrapped hint) and adopt it in Peers, Transactions, and Market-portfolio, which
  previously showed a bare left-aligned caption.
- Security dialogs: add the missing Cancel to Change Passphrase and Set PIN so the whole
  security family shares a two-button footer (Cancel dismisses without applying).
- Transactions pager: shared TactileButton helpers (matching Explorer).
- Frosted-pane rounding: Contacts/Chat use Layout::glassRounding() instead of hardcoded
  12/10/8px literals, matching Peers.
- "Set Label..." title loses its stray trailing ellipsis.

Preserves every button's label and action; the transfer footer's order becomes
[Confirm][Cancel] to match the shared helper's primary-first convention. Verified at 1280
across full-node + Lite + Windows (ctest green) and an adversarial diff review (clean).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-19 11:23:49 -05:00
DanSandClaude Opus 4.8 a598217975 fix(ui): cut-off/clipping — recent-tx collisions, updater note wrap, request-payment URI, console filter, icon-grid scroll
Fixes the cut-off/clipping bugs from the layout audit (all visible at the default
1280/1024 window sizes):

- Receive "Recent Received" rows: the amount collided with the relative-time
  ("+15.7500 DRGX14 days ago") and the type label touched the address at narrow
  widths. Use the shared short time format (formatTimeAgoShort, matching Overview),
  chain the amount's right edge off the measured time width, and start the address
  after the measured type-label width — so neither pair can collide.
- Daemon & xmrig updater verify-note: drawn unwrapped and clipped at the card's right
  edge; wrap it (PushTextWrapPos) within the already-reserved height.
- Request Payment: the three footer buttons shared one fixed width (clipping "Copy
  Full Address"); size each to its own label. The Payment URI overflowed a plain
  field; render it in a bordered read-only box (bounded, un-chunked).
- Console: the filter input shrank below its own placeholder (gone entirely at 1024);
  give it a min width >= the placeholder and drop the "N lines" count when the row
  can't fit both.
- Address-label "Choose Icon" grid: had NoScrollbar hiding most of the catalog with
  no cue; give it a real scrollbar.
- Overview "Recent Transactions": drop the 4th row at 1024 (it clipped off-screen) by
  capping to rows that fully fit the reserved height.
- Sidebar: reserve the unread-badge width in the nav-label centering so History/Chat
  labels no longer collide with their badge.

Verified at 1024 and 1280 across full-node + Lite + Windows (ctest green) and an
adversarial diff review (clean). Skipped the legacy settings_window overlay footer
(dead code / removal candidate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-19 10:11:55 -05:00
DanSandClaude Opus 4.8 9205addf55 feat(ui): width-responsiveness — content max-width cap + per-surface form/input clamps
Wide/ultrawide (1440-3440px) responsiveness was unhealthy: no page/content-level
max-width cap existed, and every card/form/table derived width from raw
GetContentRegionAvail().x with floor-only clamps, so surfaces stretched edge-to-edge
(2000-3000px inputs, ballooning cards, giant grid cells, 2000px+ dead row-voids).

Root cause: adopt the (previously dead-code) clamp helpers.
- New Layout::kContentMaxWidth() (~1600dp, tunable via ui.toml [layout]
  content-max-width; <=0 disables). Cap ##ContentArea to it and center the column in
  wider windows — every tab derives from this child, so one change tames the app at
  wide widths. No-op below the cap (fills as before), so 1080p/1440p are unaffected.

Per-surface upper-clamps (std::min(cap*dp, expr), floors preserved) where a single
element is still too wide even within the capped column:
- Settings: Theme/Layout/Language combos, the font-scale slider (~3000px -> 360dp),
  the effect sliders, Explorer URL and RPC credential fields.
- Send / Receive: cap the compose / receive cards to a readable form width and center
  them (Indent(pad+offset) so the auto-layout fields align with the hand-drawn card);
  the recent-tx lists below keep the full column width.
- Chat message bubbles + composer, mining pool URL/payout inputs + stats left/right
  split, contacts search, and the lite-network add-server row / server cards / status
  panel (capped + centered).
- Wizard: vertically center the cards when they fit (was top-anchored, leaving a void
  on tall monitors), compensating the content-height measurement so it can't oscillate.

The 1600 cap also subsumes the fixed-4-column balance grids (~400px cards) and the
right-anchored row dead-gaps (voids shrink from ~2700px to ~800px), so those are left
to the cap rather than blind column/row redesigns.

Verified at 1024/1280 (and via a temporary 900dp cap to exercise the cap+center path,
since the test display clamps to 1280) across full-node + Lite + Windows (ctest green)
and an adversarial diff review (clean). The true wide/ultrawide look and the 1600dp cap
value still want eyes on a real wide monitor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 23:15:39 -05:00
DanSandClaude Opus 4.8 ce8c7696d4 fix(ui): finish HiDPI pass — cosmetic ×dpiScale, narrow-width reflow, recent-list reserves
The tail of the DPI/font-scale/responsiveness audit — ~26 remaining findings.

Container / recent-list (Theme-1 leftovers):
- Send: drop NoScrollbar|NoScrollWithMouse on ##SendFormScroll so Recent Sends is
  reachable at font_scale 1.5 (parity with receive).
- Receive: cap the QR/form card via std::min(mainCardTargetH, availH - recentReserve)
  so RECENT RECEIVED stays on-screen (identity at 1.0x).
- Wallets dialog: size the capped-mode list to whole rows so it no longer clips a
  partial row / crowds "Create a new wallet".

Narrow-width (1024px) reflow:
- Console toolbar reserves space for ALL trailing controls (both icon toggles + zoom
  buttons) so the +/- zoom no longer runs off-window.
- History sort combo sized to its measured widest localized label ("Newest first").
- Settings Theme/Layout/Language row: scale the wide→stacked breakpoint by dpiScale so
  it drops to full-width stacked combos at 1.5x (Consolidated Card no longer clips).
- Recent-tx type label: derive the address column X from the measured label width so it
  can't collide at narrow widths.
- Mining Recent Pool Payouts: floor the panel height to fit the empty-state caption.

Cosmetic ×dpiScale() on absolute geometry (no-ops at 1.0x): mining SOLO|POOL toggle &
idle combos, market pair-chips, password/PIN strength bars, receive/send currency
toggles, explorer search bar/rows/rounding, About-card logo, chat empty-state wrap,
recent-list address/time offsets, address-toolbar & two-row action buttons, console
line-gap/status-dot/pane rounding.

Verified at font_scale 1.5 and at 1024px across full-node + Lite + Windows (ctest
green) and an adversarial diff review (one over-reserve regression fixed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 21:29:10 -05:00
DanSandClaude Opus 4.8 99d1e73676 fix(ui): scale unscaled geometry at HiDPI — inputs, send progress cards, chat/pool overlaps, peer rows
At font_scale 1.5 ~13 sites read absolute geometry (schema .size/.width) straight
into ImGui without ×dpiScale(), so they stayed native-size while the font grew and
overlapped/clipped real text or money:

- Shield/Merge fee + UTXO inputs, Request Payment amount, Block Info height input:
  ×dpiScale() so the value no longer clips (e.g. 0.00010000 -> 0.00010).
- Send: the confirm-popup Amount Details divider (floored the row step at the scaled
  caption height so it no longer strikes the Fee row), the tx-progress error and
  sending/success cards, and the zero-balance CTA button — all ×dp.
- Mining pool row: ellipsis-truncate the hostname so it can't collide with the
  right-aligned hashrate.
- Chat conversation list: scale the pane-width clamp AND clip the peer name to the
  column left of the timestamp (measure-then-clip) so name and time never overlap.
- Explorer block-detail label column, Peers row offsets, and DialogConfirmFooter
  button height — ×dp.

transaction_details keeps its negative fill-sentinel widths unscaled (a content
margin, not raw px). Verified at font_scale 1.5 across full-node + Lite + Windows
(ctest green) and an adversarial diff review (three wrong-scale regressions fixed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 20:53:29 -05:00
DanSandClaude Opus 4.8 755cf22ad0 fix(ui): make HiDPI-overflowing containers scrollable — wizard, overlay dialogs, balance recent-tx
At font_scale 1.5 (dpiScale 1.5) three fixed, non-scrolling containers clipped
content off the bottom with no scroll escape:

- First-run wizard: the hand-drawn cards grow ~1.5x past the fixed window,
  pushing Continue / Encrypt & Continue / Skip off-screen (a setup blocker).
  Inject a wheel-driven scroll offset into the layout seed + a scroll indicator;
  gate the wheel on !IsPopupOpen + NoPopupHierarchy so an open combo popup does
  not scroll the wizard behind it. No-op at 1.0x.
- Overlay dialogs (BeginOverlayDialog): auto-height cards taller than the
  viewport (About, Request Payment) ran their footer off the bottom. Add a
  sticky per-open overflow flag that clamps the card to the viewport and makes
  the content child scrollable; short dialogs still center unchanged. Give the
  nested settings clear-history confirm its own idSuffix so it can't inherit the
  parent dialog's overflow state or collide on the child window id.
- Balance Recent Transactions: the dp-scaled address card evicted the recent-tx
  list off the non-scrolling tab host. Cap the card inside RenderSharedAddressList
  against the space that actually remains (minus a caller-provided reserve) so
  the section below stays on-screen — covers all 10 balance layouts.

Verified at font_scale 1.5 across full-node + Lite + Windows (ctest green) and an
adversarial diff review (two low-severity regressions found + fixed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 20:12:01 -05:00
DanSandClaude Opus 4.8 e24ca015d1 feat(ui): implement UI/UX audit — i18n, HiDPI, theme, destructive-action & overflow fixes
Implements all 26 confirmed UI/UX audit findings plus the 4 dashboard/timeline
tile labels. Verified across full-node + Lite + Windows builds (ctest green) and
an adversarial diff review (one market-tab delete regression caught + fixed).

i18n coverage:
- Balance hero/quick-actions/toasts + dashboard & timeline tiles (Total Balance,
  Shielded, Transparent, Quick Send, Quick Receive, Click to open, Market)
- Send: Review Send / Cancel / Paste / view-only tooltip / memo byte counter
- Settings: Lite lifecycle errors, plaintext-RPC security warning, 8 toasts
- Mining pool-payout tooltip
- Resolve daemon_update_title double-assignment collision (new daemon_update_prompt_title)
- 23 new keys translated into all 8 locales; CJK subset font rebuilt

HiDPI: DPI-scale the Send amount bar, mining thread-tile clamp bounds, receive
loading skeleton, and sidebar notification-badge insets.

Light theme: theme-aware material::SurfaceOverlay() for balance bar tracks and
row-hover highlights; contacts active-pill foreground uses OnPrimary().

Destructive actions: arm/confirm for portfolio-group delete, saved pool/worker
remove, and avatar-image delete.

Interaction/overflow: route balance star/eye buttons and the console fold-toggle
through popup-safe guards; clip peer addr/subver; truncate the balance custom
label; measure Settings tool-button widths; mining stepper disabled-state
feedback; +/- stepper buttons restyled to match the thread tiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 17:54:15 -05:00
DanSandClaude Opus 4.8 0de44569b7 feat(mining): thread-count stepper, adaptive tiles, dropdown click-through fix, xmrig version state
Mining-tab UI improvements:
- Thread selector: a centered [-] N [+] stepper (top-aligned in the header) to pick an exact
  thread count — number centered, -/+ step by one (clamped to [1, cores]), still typeable
  (commits on Enter so it doesn't restart the miner mid-typing).
- Thread tiles now render at an adaptive step (1/2/4/8 by core count) plus 1 and the max, so a
  high-core CPU (e.g. a 192-thread EPYC) shows ~25 tiles instead of one-per-thread and no longer
  overflows the card. Unchanged for <=24-core machines.
- Fix: clicking the X (or a row) in an open saved-pools / payout-address dropdown no longer bleeds
  through to the thread tiles / Mine button — the custom drawlist hit-tests now gate on
  IsPopupOpen(AnyPopup).
- xmrig update button: shows "xmrig releases" when installed >= latest (numeric version compare),
  else "Update <latest>"; the "Current: <ver>" text and the button are subtle green when up to date
  and subtle orange when an update is available (neutral when either version is unknown).

New i18n keys back-filled across all 8 languages; CJK subset font rebuilt for the new glyphs.
Verified across full-node, lite, and Windows builds; the stepper layout confirmed via the UI sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 15:39:43 -05:00
DanSandClaude Opus 4.8 06afbee4f8 fix(mining): remediate mining-tab audit (22 findings) — crash-safety, async control, validation, math
Fixes all 22 confirmed findings from the mining-tab audit (10 Medium, 12 Low; 0 Critical/High),
adversarially reviewed (6 follow-ups found + fixed, incl. the review-caught idle-auto-start bypass
and a wrong benchmark-restore condition).

Crash-safety & lifecycle:
- M-04: join a stale/finished monitor thread in XmrigManager::start() and ~XmrigManager so an xmrig
  crash-then-restart (or quit) no longer std::terminate()s the wallet.
- L-03/L-10: surface an unexpected miner exit once and clear the stale running flag.

UI never blocks (M-03/L-06/L-08/L-09/L-13): pool start/stop now run on a dedicated serialized FIFO
mining-control thread (joined before teardown), so the ~13 call sites don't block the render thread on
stop()'s SIGTERM->SIGKILL->join; the spawn result marshals back to the UI.

Miner-process / pool trust boundary:
- M-01: validate the payout address (util::isValidRecipientAddress) at EVERY start path — the UI gate
  AND App::startPoolMining() (idle auto-start / thread scaling) — so a stale/wrong-chain address can't
  silently lose rewards.
- M-09: SSRF guard skips the background pool-stats GET for loopback/private/link-local/single-label hosts.
- M-02/L-02: cap the pool-stats + xmrig-API HTTP response bodies.
- L-01: write the xmrig config 0600 at creation (POSIX open with mode) — no world/group-readable window.
- M-10: reject shell-metacharacter binary paths before the version popen (excluding '()' so Program Files
  (x86) still works).

Solo mining: M-06/M-08 clamp thread count to [1, cores] at the setgenerate/xmrig boundary; M-07 notify +
don't lie on stop failure.

Correctness: L-05 block-time constant 75->150s (chainparams); M-05 discloses pool-mode "Est. Daily" as a
rough solo-equivalent; L-04/L-11/L-12 benchmark lifecycle (cancel on nav-away / mode-switch with restore,
skip rebalance mid-benchmark); L-07 honor cancel mid-extract in both the xmrig and daemon updaters.

Two new i18n keys back-filled across all 8 languages; CJK subset font rebuilt for the new glyphs.
Verified across full-node, lite, and Windows builds; tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 14:53:46 -05:00
DanSandClaude Opus 4.8 6ee81a5abe fix: security-audit remediation (15 findings), empty-wallet warning, and send/chat/console/shutdown UX
Security audit remediation (15 confirmed findings from the codebase audit):
- H-02: scrub+delete the decrypt-flow plaintext key export on ALL exit paths
  (RAII guard) and purge stale obsidiandecryptexport* files at startup.
- M-01/L-03/L-04/L-05/L-07: sodium_memzero the Set-PIN and encrypt-PIN worker
  passphrase/PIN copies, the RPC Basic-auth string (auth_), the exported/imported
  key buffers (App::wipeSecrets, called from ~App and before main's _Exit), and
  the first-run wizard "Skip" buffers.
- M-03/M-04/M-05/L-06: return locked COPIES from XmrigManager/EmbeddedDaemon
  getters (dedicated error_mutex_; DaemonController::lastError now by value),
  route xmrig last_error_ writes through a locked setter, and wrap
  shutdown_status_/wizard_stop_status_ in a locking GuardedStatus
  (wizard_stopping_external_ -> std::atomic).
- M-02: persist after a console send/shield/import in the lite backend.
- L-01: require the confirm click for z_shieldcoinbase/z_mergetoaddress.
- L-02: quote/escape each Windows daemon argv per the MSDN CommandLineToArgvW rules.
- L-08: pin json/tomlplusplus/libwebp FetchContent to immutable commit SHAs.
- I-01: extract updater archives from the already-verified in-memory buffer
  (no disk re-read TOCTOU).

Feature: warn once (full-node) when the active wallet loads empty while a sibling
wallet file in the datadir holds keys. A funded salvage wallet.<ts>.bak routes to
the recovery/Restore flow; a funded sibling .dat routes to the wallet manager.
Per-wallet-file dismissal; gated on synced + address-list-loaded to avoid false
positives on warm reconnect / spent-down wallets.

UX fixes:
- send: show the TOTAL balance (with a spendable "available" note) in the source
  dropdown and keep pending-change addresses visible.
- chat: insert emoji at the cursor position; restrict new-chat recipients to
  shielded (z) addresses.
- console: optional auto-focus of the command input on tab open (off by default).
- shutdown: when "stop external daemon" is on, keep the shutdown screen up until
  the external node actually exits, showing live status.

Adversarially reviewed; verified across full-node, lite, and Windows builds; tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-18 13:18:49 -05:00
DanSandClaude Opus 4.8 ea26c0cbbb fix(balance): stop the displayed balance cratering during a pending shielded send
Sending a small amount from an address holding a large balance made the displayed
balance collapse to ~0 until the tx confirmed. A shielded spend consumes the whole
source note; the change returns as a 0-confirmation note, and every balance query
used the default minconf=1 — so the spent note dropped out and the change wasn't
counted yet.

Split every balance into two views:

- DISPLAY (balance / privateBalance / transparentBalance / totalBalance) — now
  queried at minconf=0, so it INCLUDES the user's own pending change and no longer
  craters. This is what the Overview, balance tab, market portfolio and receive
  tab show. unconfirmedBalance is now populated (= total - spendable).
- SPENDABLE (new spendableBalance / spendable*Balance) — confirmed (minconf>=1),
  what z_sendmany (run at minconf=1) can actually spend. The Send form's available/
  Max/validation, the from-address selection, the drag-to-transfer dialog cap, the
  chat pay-from and the auto-shield gate all size off these, so they never offer
  0-conf change the daemon would reject.

Implementation: a single z_listunspent(0)/listunspent(0), partitioned per-note by
"confirmations">=1; z_gettotalbalance called at minconf 0 (display) and 1
(spendable); the z_getbalance fallback queries both. applyPendingSendDelta (the
optimistic post-send debit) now touches ONLY the spendable fields — debiting the
display too would re-crater it on top of the honest minconf=0 RPC. Lite mirrors
spendableBalance = balance (its per-address balance is already confirmed) so lite
sends aren't zeroed. The confirmed-only gates (seed-migration/sweep z_gettotalbalance,
sweep z_getbalance(addr,1), z_sendmany's minconf arg) are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-13 16:40:14 -05:00
DanSandClaude Opus 4.8 001e85ac1a fix(ui): prevent text/button cutoff and scale hand-drawn geometry at HiDPI
Findings from a UI-cutoff audit — each is a spot where an in-tree helper
(truncateMiddle / TruncateToWidth / measured button width / the *dpiScale/*hs
factors) was bypassed:

- notifications: the toast-pill height/padding/icon-gap were raw logical px while
  the icon/text drawn inside are DPI-baked, so they clipped the pill at HiDPI.
  Scale the geometry by dpiScale (not the already-scaled glyph metrics).
- settings: in the two-column NODE & SECURITY layout the data-directory path could
  overrun into the Daemon-binary column (shared draw list, no clip rect between
  them). Middle-ellipsize it to the column width; the full path stays in the
  tooltip + click-to-open + copy.
- send: the "Confirm & Send" button width came straight from the schema and was
  never measured against the label, clipping the Russian translation on the
  pre-broadcast dialog. Size to max(schema width, measured label + padding).
- balance: the recent-tx address-column offset missed the `* hs` DPI factor its
  sibling (amount-right-margin) uses, overlapping the type label at HiDPI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-10 22:11:07 -05:00
DanSandClaude Opus 4.8 f88304fed2 fix(wallets): stop a legacy wallet showing as a seed-phrase wallet when linked
The wallets list badged the active/linked row from the runtime seed status
(activeWalletSeedBadge → wallet_seed_status_, reset only on disconnect) in
preference to the offline on-disk probe. Two issues let a genuinely legacy
wallet render as "seed phrase":

- The offline probe's budget-fallback branch dropped the fMnemonicSeed flag:
  res.mnemonic was set only in the (parsed && complete) branch. The probe shares
  a 768 MB budget across all wallet files, so a large wallet (e.g. a 194 MB one)
  probed after the budget is spent falls into the fallback, loses its seed/legacy
  classification (mnemonic → 0), and the row defers to the runtime badge.
- With mnemonic == 0, the code used the runtime badge, which can still carry a
  HasMnemonic from a previously-active mnemonic wallet — mislabelling the legacy
  wallet.

Fix:
- Carry the definitive positives (fMnemonicSeed/hdSeed/mkey) from a cap-truncated
  btree walk — a found marker is authoritative even when the scan didn't finish.
- Make the on-disk fMnemonicSeed read take precedence: it's the SAME flag the
  daemon's IsMnemonicSeed()/z_exportmnemonic consult, so a definitive read wins;
  the runtime badge is used only when the probe genuinely couldn't decide, and
  never overrides a definitive on-disk classification.

Verified the wallet in question is truly legacy (fMnemonicSeed=false on disk,
matching the daemon's CHDChain serialization + IsMnemonicSeed). The flag reader
(hdChainMnemonicFlag) is already unit-tested; suite green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-09 15:29:23 -05:00
DanSandClaude Opus 4.8 a1d3964e34 fix(lite): require exactly 24 words on first-run restore (crash on valid seed)
The lite first-run restore wizard enabled Restore for {12,15,18,21,24}-word
phrases, but the SDXL backend only accepts 24-word / 32-byte-entropy seeds:
LightWallet::new does copy_from_slice(&phrase.entropy()) into a [u8;32]
(lightwallet.rs:231), which panics on 16/20/24/28-byte entropy. Mnemonic::
from_phrase accepts the shorter valid phrase, and the restore FFI
litelib_initialize_new_from_phrase (lib.rs:127) has no catch_unwind (unlike
litelib_execute), so the panic unwinds across extern "C" -> process abort
(UB on the pinned rustc 1.63). A user restoring a legitimate 12-word seed
from another wallet crashed the app.

The Settings restore gate was already tightened to == 24 (6ff1fda) but the
first-run wizard gate (df14533) was never updated — same restore path, two
verdicts, crash only via the more-common first-run path.

Add shared util/seed_phrase.{h,cpp} as the single source of truth:
- normalizeSeedPhrase: fold NBSP/en/em/ideographic/narrow spaces to ASCII,
  strip zero-width marks, collapse+trim (word bytes untouched)
- seedPhraseWordCount
- isCompleteRecoveryPhrase(int) == 24  (the sole SDXL contract)

Both restore gates now count via the normalizer and gate via
isCompleteRecoveryPhrase, and both submit the normalized phrase. This closes
the crash, reconciles the two gates so they can't drift again, and — because
tiny-bip39 splits on literal ASCII space with no NFKD — makes an NBSP-pasted
24-word seed (common from PDFs/note apps) restore correctly instead of being
undercounted and rejected.

Adds testSeedPhraseHelpers. Suite green (1/1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-09 01:32:40 -05:00
DanSandClaude Opus 4.8 c3e81a5fa6 fix(send): accept P2SH/multisig recipients in the send + URI address gates
Same defect class as the import-key fix: a hardcoded prefix/length pre-filter
layered over the checksum validators rejected valid addresses before the daemon
saw them. The send-screen recipient gate required a[0]=='R', and the payment-URI
parser accepted only 'R'/'t' with rigid length bands — so every valid P2SH /
multisig address (DragonX SCRIPT_ADDRESS=85 → 'b…') was silently refused, leaving
the Send button disabled with no usable recipient.

Centralize recipient recognition in util/address_validation:
- isTransparentAddress: Base58Check with a 21-byte version+hash160 payload —
  covers P2PKH ('R…', v60) AND P2SH ('b…', v85) on every network, rejects WIF
  keys / typos by real checksum.
- isShieldedAddress: Bech32 + a Sapling payment-address HRP (zs / ztestsapling /
  zregtestsapling), distinguishing a payment address from a viewing key.
- isValidRecipientAddress: either of the above.

send_tab's two validity helpers (the single choke point for all 5 call sites) and
the payment-URI format check now route through these. The URI parser now
checksum-validates the recipient (fail-fast on transcription errors) rather than
being prefix/length-only.

Tests use real checksummed vectors (P2PKH/P2SH/shielded, WIF- and typo-rejection);
testPaymentUri updated off its old fake fixed-char addresses. Suite green (1/1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-08-09 00:12:17 -05:00
DanSandClaude Opus 4.8 4b3f0fa92b feat(diagnostics): persistent alert history with a status-bar bell (Foundation QoL)
Toasts fade in 1-4s, so anything that scrolled past was gone. Notifications now retains
every pushed alert in a capped (100) ring buffer with a wall-clock epoch (AlertRecord) —
separate from the 5-item live-toast deque — plus a monotonic total_pushed_ counter.

A bell in the status-bar right cluster opens an upward popup listing recent alerts
newest-first: severity icon + colour (reusing the toast palette), the message, and a
relative age (formatTimeAgoShort), with a Clear-all action. An unread dot on the bell,
coloured by the most-severe unseen alert, marks alerts that arrived since the panel was
last opened — driven by totalPushed() deltas so it survives capping/clearing.

Thread note: every push is on the UI thread (RPC results run as main-thread MainCb
callbacks), matching this class's existing lock-free model; documented as a
no-raw-worker-thread invariant.

New i18n keys (alerts_*). Build-clean; ctest 1/1 (adds testNotificationHistory: retention,
order, cap, monotonic counter, clear). Closes the QoL bundle and the Foundation tier.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 20:57:02 -05:00
DanSandClaude Opus 4.8 c7c3440a7b feat(diagnostics): refresh-staleness badge on the Total Balance card (W6-2)
When the wallet is connected but the balance has quietly stopped refreshing — a busy
daemon can fail z_gettotalbalance without dropping the whole connection (only *both*
core RPCs failing 3x triggers a disconnect) — the old number sits on screen while the
node-status banner stays hidden. The Total Balance card now shows a small pill on its
status line ("Updated 2m ago", amber, escalating to red past 3 min) so the stale value
isn't silently trusted; hovering explains it and points at the node connection.

No refresh-path changes: WalletState::last_balance_update is already stamped only on a
successful fetch (network_refresh_service.cpp), so the badge reads it and computes age
against the same std::time clock via util::formatTimeAgoShort. The decision is a pure,
unit-tested helper (ui/staleness_badge.h::evaluateStalenessBadge, 45s/180s thresholds)
gated on connected so it never contradicts the banner.

Closes P2 (5/5). Build-clean; ctest 1/1 (adds testStalenessBadge).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 20:48:56 -05:00
DanSandClaude Opus 4.8 e779ded2e8 feat(diagnostics): persistent node/RPC error banner at top of content (Foundation QoL)
A persistent horizontal strip now appears at the top of the content column whenever the
wallet can't reach its node — unlike the transient toasts it stays up for as long as the
fault persists, so an offline wallet is never silently mistaken for a working one.

The show/severity/action decision is a pure, unit-tested function
(ui/node_status_banner.h::evaluateNodeStatusBanner) fed a state snapshot by the new
App::renderNodeStatusBanner(). Three cases:
  - full-node offline        -> amber, "Reconnect"    (App::tryConnect)
  - embedded daemon crashed
    & auto-restart gave up    -> red,   "Restart node" (App::restartDaemon)
  - lite wallet open failed   -> red,   message-only

Suppressed during the wizard / wallet-switch / daemon-restart / screenshot-sweep / shutdown,
and while an expected startup phase (warmup / init / connect-in-progress) already owns the
screen. Banner height lives in res/themes/ui.toml (banners.node-status); colours come from the
material semantic palette; the detail text is ellipsis-clipped so it can't push the action
button off-screen. Drawn before the content edge-fade vertex capture so it stays fully opaque.

New i18n keys (node_banner_*). Build-clean both variants; ctest 1/1 (adds testNodeStatusBanner).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 20:21:05 -05:00
DanSandClaude Opus 4.8 940dd21464 feat(diagnostics): add "Copy diagnostics" + "Open log folder" actions (Foundation QoL)
Settings (logging section) gains two support-friendly actions, now that the logging
foundation actually produces logs (W7-2):

- Open log folder: opens the config dir (Platform::openFolder) so users can find
  dragonx-debug.log / dragonx-crash.log.

- Copy diagnostics: copies a plaintext support snapshot to the clipboard via the new
  App::buildDiagnosticsReport() — version, build variant, platform, connection status,
  active wallet path + existence + size, encryption/lock state, sync heights, and (full-
  node) daemon status/running/crash-count/lastError, plus the log paths. No secrets.

Build-clean; ctest 1/1. Remaining QoL: persistent alert history, a daemon/RPC error
banner, and the W6-2 refresh-staleness badge. See docs/wallet-hardening.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 15:37:52 -05:00
DanSandClaude Opus 4.8 da0e9f5915 fix(console): redact secret-bearing commands from the console echo and history (W7-1)
The RPC console echoed and stored typed commands verbatim, so `walletpassphrase
<secret>`, `z_importkey <key>`, `encryptwallet <pass>` etc. left the secret in the
visible log AND the 100-entry recall history (copyable). Adds a pure, unit-testable
RedactConsoleCommand()/ConsoleCommandCarriesSecret() (allowlist of 13 secret-bearing
first-tokens) in console_tab_helpers; submitConsoleCommand() now echoes and stores
`> walletpassphrase ****` while still executing the real command unredacted. Bare
secret commands and non-secret commands pass through unchanged.

Output-secret commands (dumpprivkey / z_exportkey / z_exportmnemonic) — whose secret is
in the RESULT — are a separate redaction concern, tracked as a follow-up.

First fix in the wallet-hardening P0-A cluster (see docs/wallet-hardening.md). New
testConsoleSecretRedaction (11 assertions); ctest 1/1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-02 13:57:52 -05:00
DanSandClaude Opus 4.8 45b652f514 feat(mining): live pool fee, saved/custom pool rows, and payout-address fix
Several related mining-tab pool improvements:

- Report the default pool fee correctly: pool.dragonx.is is 1%, not 0%.
  The registry constant was hardcoded to 0. It now also fetches the live
  poolFee from the pool's /api/stats alongside hashrate (no extra
  request), so the displayed fee self-corrects and falls back to the
  compile-time value only when the fetch hasn't landed.

- Show fractional fees: new FormatFeePercent trims trailing zeros so
  whole fees read "1%" and fractional ones keep their decimals ("1.5%").

- Surface saved + custom pools in the pool list card: the list is now
  the union of the official pools, the user's saved favorites, and the
  currently-mined pool (effectivePools), each a selectable, endpoint-
  deduped row. Previously the card only showed the hardcoded knownPools().

- Fix the xmrig "user" field: the "Payout Address" field now drives the
  pool login rewards are credited to (resolveMiningUserAddress), instead
  of being written only to "pass" while "user" was auto-derived from the
  wallet's own first z-address -- which silently ignored a configured
  payout address and could route rewards to the wrong address.

Unit tests cover parsePoolFee, FormatFeePercent, effectivePools, and
resolveMiningUserAddress; full app + ObsidianDragonTests build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 03:48:05 -05:00
DanSandClaude Fable 5 02554d523d fix(ui): align contacts table-view footer with cards/list views
In table view the table is inset into its glass panel by tVpad and its
outer_size is listH-2*tVpad, so the post-table cursor ended tVpad (~10px) above
where the cards/list views leave it, pulling the "N address saved" footer up.
Land the cursor at the glass-panel bottom (tpMin.y + listH) so the footer lines
up across all three view styles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 23:45:14 -05:00
DanSandClaude Fable 5 c53b7f771e fix(ui): overview/mining polish + default-pool cleanup
- balance: inset the address-row favorite (star) button by the card's inner
  padding so it mirrors the left margin instead of hugging the card edge.
- mining: remove pool.dragonx.cc from the built-in default pools (pool.dragonx.is
  is now the sole default); update the pool-registry test accordingly.
- mining: middle-truncate saved pool-URL and payout-address dropdown rows (new
  shared material::TruncateToWidth helper) so a full z-address no longer runs
  under the trailing delete (X) button.
- mining: fix the thread-grid cells overflowing the card at >100% display
  scaling — the reserved Mine-button width used a raw clamp that didn't scale;
  scale it by dp so cols is estimated correctly (no-op at 100%).

Full-node build + test suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 16:01:51 -05:00
DanSandClaude Fable 5 bcee4bfe72 fix(security): apply audit remediations to dev (ported + dev-only)
Dev-branch audit (docs/_archive/security-audit-dev-2026-07-22.md) re-found the
master issues (absent on dev) plus new ones in dev-only code. Applied here;
full-node build + test suite green; the subtle fixes were adversarially re-verified.

Ported from the master remediation (adapted to dev's code):
- bootstrap: reject zip-slip / path-traversal archive members (isSafeArchivePath)
  before writing (S2-1). (dev already fail-closes on a missing checksum.)
- xmrig updater: fail closed when a signature is required but no key is pinned (F1-1).
- http_download.httpGetString: 16 MiB hard cap + MAXFILESIZE on the shared
  metadata/price fetch (F1-2 / caps the updater + exchange paths at one site).
- rpc_client: explicit SSL_VERIFYPEER/VERIFYHOST (F4-1) and a 256 MiB response
  cap in WriteCallback (F4-3).
- lite_connection_service: reject remote http:// lite servers, loopback only (L1-1).
  Loopback is matched by a strict dotted-decimal 127.0.0.0/8 check (not a
  startsWith("127.") prefix, which would wrongly accept 127.0.0.1.evil.com), with
  userinfo/fragment stripping.
- lite controller: propagate encrypt/decrypt save() failure instead of reporting
  success (F7-1).
- xmrig_manager: chmod(0600) the pool config before writing secrets (F5-2).
- app: clear the copied secret from the OS clipboard on shutdown (F3b-1).
- export_transactions: neutralize CSV/spreadsheet formula injection (F13-1).
- build pipeline: build-from-source lite backend + remove the self-attested
  CMake signature gate (F15-1); pinned+verified appimagetool (F15-3/4);
  verified Sapling params in setup.sh (F15-6); build.sh exits 0 on success.
  (F14-1 empty-quoted-arg and F8-2 NUL-termination were already fixed on dev.)

Dev-only findings:
- rpc_client.callRaw: scrub the raw buffer + parsed tree (templated scrubJsonSecrets
  for ordered_json) so console dumpprivkey/z_exportkey keys don't linger in freed
  heap (N1-1).
- seed_wallet_creator: wipe the exported mnemonic on the failure path so a discarded
  failed result never carries a live seed (W1-2).
- export_all_keys: write the plaintext key dump 0600 + atomically via
  writeFileAtomically (U1-2).
- chat_database: restrict chat_messages.sqlite and its WAL/SHM sidecars to owner-only (C3-1).

Not done (need a decision, documented in the report):
- Chat header metadata (cid/z/p) rides outside the AEAD (C1/C2) — binding it is a
  wire-protocol change requiring SilentDragonXLite interop review.
- Bootstrap lacks an offline-rooted signature (S2-2 residual) — needs signing infra.
- Console scrollback retains console-typed key-export output in plaintext (N1-1
  residual) — inherent to an echoing console; would need output redaction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 12:08:24 -05:00
DanSandClaude Opus 4.8 7cf0bb8bc7 feat(settings): add hover tooltips to the settings controls that lacked them
A per-card audit found 38 interactive controls with no tooltip. Add them, with
new tt_* i18n keys (English source; per-language JSONs fall back to English
until translated):
- Chat & Contacts (8): emoji/bubble style, accent, density, text size, poll
  rate, timestamps, enter-sends — the segmented/beginRow helpers added none.
- Node & Security (26): the whole lite wallet lifecycle / backup-keys /
  encryption panel, plus the full-node RPC-details toggle and daemon Refresh.
- Debug Options (4): the screenshot-sweep / full-UI-sweep / open-folder /
  seed-demo-chat buttons.

Theme, Wallet, Explorer, About were already fully covered.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 21:31:15 -05:00
DanSandClaude Opus 4.8 541a9e1fd5 feat(settings): split Chat & Contacts into Appearance | Messaging columns
Break up the tall, left-heavy Chat & Contacts card internally: Appearance on
the left, Messaging on the right when the card is wide (fills the width, ~halves
the height). Same Indent technique as the Node & Security card; the row helpers
retarget their leftX/rowW per column so controls right-align within each. The
narrow chat-settings modal stays single-column.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 21:16:37 -05:00
DanSandClaude Opus 4.8 07b8e0b2cb revert(settings): drop the page-level card masonry
Page-level two-column masonry made the cards narrow and fought their internal
layouts. Revert the whole-page band (full-width stacked cards again) and keep
the internal NODE & SECURITY two-column — the better lever is breaking up the
large cards internally, not columning the page. The Explorer row-start-X fix is
kept (correct at full width too).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 21:13:37 -05:00
DanSandClaude Opus 4.8 de48414c65 feat(settings): hybrid masonry — full-width Theme + About, columns between
Per feedback, keep Theme & Language and About/Debug full width (they read
better wide), and put only Wallet / Node & Security (left) and Explorer /
Chat & Contacts (right) in the two-column band. The band captures its top
anchor below the Theme card and restores full width before About.

Also fix an Explorer bug the two-column band exposed: row 1 placed the Address
column with an absolute SameLine(pad + halfW + …) that ignores the right-column
Indent, so "Address URL" leaked into the left column. Anchor it to the row's
indent-inclusive start X instead (identical result at full width).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 21:01:04 -05:00
DanSandClaude Opus 4.8 f0c681b0b5 fix(settings): lower page-masonry threshold so it engages under display scaling
The 1000-logical-px threshold never engaged at 125% OS scaling: the panel's
real-px width (~1190) was below 1000×dpiScale (~1250), so the page stayed
single-column even though the internal NODE & SECURITY two-column (760×dpiScale)
did engage. Drop to 780 logical px — still requires usable (~390 logical) columns
but engages at the widths real windows actually have under display scaling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 20:50:19 -05:00
DanSandClaude Opus 4.8 fdf2502ca8 feat(settings): two-column page masonry for the section cards (A)
Render the section cards in two columns when the page is wide (>1000px):
left = Theme, Wallet, Node & Security; right = Explorer, Chat & Contacts,
About; Debug spans full width below. Driven by shadowing `availWidth` to the
column width for the whole card region and floating the right column with
Indent() (a one-shot cursor set can't hold a column across line-advances);
each card's own responsive gates collapse their internal columns at the
narrower width. Collapses to one column below the breakpoint.

Fix the half-width overflows a parallel audit surfaced (cards previously only
ever rendered full-width):
- WALLET Tools & Actions: merge-to-address was a 3rd button on a 2-button row
  (unconditional SameLine) — flip the separators so 2-per-row pairs cleanly.
- EXPLORER: wrap the Block Explorer button to its own row when the checkboxes
  + button won't fit (measured, locale-safe).
- NODE & SECURITY: wrap "Remove Encryption" below when the encrypted row's
  three buttons + status don't fit the column.
- ABOUT: 2x2 button grid instead of 1x4 so labels don't clip.
- THEME: reserve the real gaps + custom-skin "*" marker in the theme combo
  width so Refresh doesn't spill past the edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 19:15:21 -05:00
DanSandClaude Opus 4.8 e393b0d847 fix(settings): wrap the SECURITY row in the narrow two-column card
The encrypt controls + auto-lock combo + PIN hint are one SameLine chain that
fits at full width but overflowed the half-width NODE & SECURITY column,
spilling the "Encrypt wallet first to enable PIN" hint over the Daemon
column's buttons. When the section is narrow, break the auto-lock/PIN group
onto its own row at the section's left edge instead of continuing the line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 18:45:56 -05:00
DanSandClaude Opus 4.8 3b041f20c1 fix(settings): hold the right column with Indent, not a one-shot cursor set
The two-column NODE & SECURITY card collapsed both columns onto the left,
overlapping. A one-time SetCursorScreenPos() can't hold a column: ImGui
resets the cursor X to the window's left indent on every line-advance, so
the first widget in the right column (a Dummy spacer) bounced everything
after it back to the left margin.

Shift the whole right column with ImGui::Indent(colW+gap) instead, so each
line-start lands at the column X; set the first widget's X explicitly to
match, and Unindent before continuing the page. Left column already renders
at the shadowed half-width, so it stays in its lane.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 18:04:15 -05:00
DanSandClaude Opus 4.8 77a0ad64b0 feat(settings): two-column NODE & SECURITY card (B) — fill the empty right side
The NODE & SECURITY card stacked Node / RPC / Security / Daemon-binary in one
full-width column, packing everything on the left. Split it into two columns when
the card is wide enough: Node + RPC + Security on the left, Daemon binary on the
right — which fills the previously-empty right half and roughly halves the card's
height. Falls back to a single column on narrow windows (and when there's no
daemon section). Implemented by shadowing contentW + sectionOrigin per column
(renderSecuritySection already takes an explicit width/origin), so every
sub-section lays out within its column; the card auto-sizes to the taller column.
Full-node only for now; the lite Node section stays single-column.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 17:15:32 -05:00
DanSandClaude Opus 4.8 d0ca2fdf52 feat(settings): wrap crammed checkbox rows + cleanup (sweep)
Step 5. The Wallet privacy/daemon checkbox row and the Advanced-Effects checkbox
row shrank their text (SetWindowFontScale) to cram onto one line — they now wrap
to new rows at full size instead, so nothing clips on narrow windows (the lite
"Animate avatars" was being cut off). Also drop two now-unused locals left over
from the Node/Daemon rebuild. (The Explorer row, the RPC collapsible header, and
the secondary/compact effects layout are left for a later polish pass.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 16:03:36 -05:00
DanSandClaude Opus 4.8 9c07b6d33d feat(settings): converge the lite Node section styling with full-node
Step 4. Bring the Lite settings section in line with the full-node one: its
subsection headers (Backup & keys, Security, Maintenance) switch from the Body2
type style to the accent Overline used by the full-node Node/Daemon/Security
subsections, and the standalone action buttons (Open data folder, Show seed,
Show private keys, Redownload blocks) become icon ActionButtons matching the
full-node button styling. The intricate secret-reveal / encryption flow and its
label-column input layout are left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:56:23 -05:00
DanSandClaude Opus 4.8 fedfe3d60c feat(settings): rebuild Node data-dir + Daemon binary on the new components
Step 3. Node "Data Dir" is now a clean key/value block: the directory on its own
row as a click-to-open accent link with a copy button, the wallet size below, and
the folder buttons on their own row (icon ActionButtons) — no more path
font-scaling or right-align overflow math. Daemon binary shows Installed / Bundled
as key/value rows plus a Success/Warning status chip (replacing the fragile
atom-by-atom wrapped status line); "Check for updates…" is now the accent Primary
action, and the maintenance actions are a separate row with Delete Blockchain
styled Destructive (Test/Rescan/Repair Secondary). All handlers, disabled-state
predicates and tooltips are preserved; the buttons wrap instead of overflowing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:53:28 -05:00
DanSandClaude Opus 4.8 95ff9ce9ae feat(settings): shared design components + Wallet button hierarchy
Step 1: promote the polished chat-settings building blocks into reusable material
components (src/ui/material/settings_controls.h) — SettingsSubheader (accent
small-caps), SettingsRow (label left / control right), SegmentedControl (iOS
track+pill), and a tiered ActionButton (Primary=accent fill, Secondary=glass,
Tertiary=ghost, Destructive=error) with an optional leading Material icon, plus a
ButtonFlow that wraps rows of buttons.

Step 2: rebuild the Wallet BACKUP & DATA button wall on them. The 9–11 identical
buttons that were font-scaled onto one row are now tier-ordered, icon-labelled,
and wrap to new rows: the emphasized actions the user flagged — Import key, Seed
phrase, Wallets…, Download Bootstrap — cluster on top as accent Primary buttons;
common actions (viewing-key import, Backup, Migrate, Setup Wizard) are Secondary;
exports are low-emphasis Tertiary. All click handlers/tooltips and the migrate
"legacy wallet" glow are preserved. Removes the SetWindowFontScale legibility hack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:48:21 -05:00
DanSandClaude Opus 4.8 3a2be661ea feat(chat): custom DragonX emoji via the :drgx: shortcode
Add a DragonX custom emoji to chat. The emoji picker gains a DragonX tile (first
cell) that inserts the text ":drgx:"; message bodies containing it render the mark
inline via layoutChatBodyRich, which flows text words + the emoji image with word
wrap (plain bodies keep the tighter layoutChatBody path, so normal messages are
unaffected). Other clients simply show the literal ":drgx:" text — a portable
encoding with graceful degradation.

The emoji is the DragonX SVG rasterized once at fixed brand colors (crimson body,
white detail) via LoadTextureFromSvg — theme-independent so it looks identical for
sender and receiver — exposed as App::getDrgxEmojiTexture(). Emoji insertion is
factored into one insertToken() helper (space-prepend + on-chain byte cap), shared
by the DragonX tile and the Unicode emojis. Drag-to-select text is skipped on
":drgx:" messages (their inline layout doesn't match the plain-text hit-test
geometry); right-click "copy" still copies the whole message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 13:42:30 -05:00
DanSandClaude Opus 4.8 267d839d5b feat(chat): add a dragon-themed emoji set to the picker
Adds 🐉 🐲 🐍 🦎 🐊 🐾 🥚 🪺 🏰 ⚔ 🛡 🗡 to the emoji picker, with DRGX-flavoured
keywords (the dragons are searchable by "dragon"/"drgx"). All are single-codepoint
emoji verified present in both the bundled monochrome NotoEmoji subset and the
color Twemoji font, so no font rebuild is needed and they render on other clients
(standard Unicode, UTF-8 in the memo, within the 236-byte cap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 12:45:49 -05:00
DanSandClaude Opus 4.8 987d9b93c7 feat(history): badge chat transactions + add a Chat filter
Transactions that carried a chat message (sent or received, messages + contact
requests) now show a "Message" badge in the History tab, and a new "Chat" option
in the type-filter combo shows only those transactions.

Detection reuses what the chat store already tracks: ChatStore gains an inline
chatTxids() returning the set of on-chain txids that carried a chat message; the
tab builds it once per frame (cheap — O(chat messages)) and tests txid membership
for the badge and the filter. Empty when chat is disabled or the wallet has no
chat identity. Both variants populate the chat store before this tab renders, so
the same code serves full-node and lite with no separate handling.

The "Message" badge reuses the stacked top-pill slot (chat txs are send/receive,
not the autoshield "shield" type, so it and the "Shielded" badge are mutually
exclusive; chat takes precedence) in a distinct Secondary() colour. Guards the
summary-card accent idx_map so the new filter value (4) can't index it OOB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 01:43:55 -05:00