The Market tab is non-scrolling, but an oversized fixed-height chart ate ~65%
of the height and squeezed the portfolio into a cramped strip, with custom
groups rendered as tiny afterthought rows at the very bottom.
- Precompute section geometry (hero header + portfolio card height) up front and
let the price chart ABSORB the remaining vertical space, capped at its schema
height and floored at 130px*vs so it stays useful. The chart now shrinks to
make room instead of dominating. Replaces the barely-used SectionBudget.
- Portfolio summary refined: fiat value stays the hero with the 24h change beside
it and the BTC value right-aligned; DRGX balance + Z/T breakdown on the next
row; a full-width shielded/transparent ratio bar + % label.
- Custom groups now render as a wrapping grid of mini glass cards (label + DRGX +
fiat each), sized to the card width, instead of a cramped one-line list. A
"GROUPS" subheader separates them from the all-funds summary. The card grows to
fit the grid.
New i18n key portfolio_groups ("GROUPS"). Full-node + Lite build clean; ctest
1/1; hygiene clean. Rendering change — needs a screenshot check of the Market tab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Manage portfolio" editor renders its address checklist inside the
overlay dialog's content child, which is ImGuiChildFlags_AutoResizeY (the
dialog sizes to its content). The checklist used a negative "fill remaining
space" height (-GetFrameHeightWithSpacing()*1.5f), which is meaningless in an
auto-resizing parent — there is no fixed height to subtract from — so ImGui
collapsed the child to ~0 and no addresses were visible, even with
state.addresses fully populated.
Size the list to its rows instead, capped at 40% of the viewport height so a
large wallet scrolls rather than running off-screen. Pre-existing bug from
when the editor was added; distinct from the earlier state.addresses
population fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
- 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
The Sent summary card showed 0 while selecting the Sent filter listed
transactions. The card counted only plain "send" rows and deliberately excluded
both legs of an autoshield pair as an "internal move", but the list shows the
merged "shield" row under the Sent filter. With only shielding transactions and
no plain sends, the card read 0 against a non-empty Sent list.
Count each shield pair toward the Sent card (with the shielded receive-leg
amount, which is what the merged row displays), so the card and the filter agree.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
History looked unsorted because every merged "shield" row carried
confirmations=0, and the list sorts 0-conf (pending) transactions to the very
top. So long-confirmed shielding transactions floated above newer ones — and
when the type filter was switched off "All" they vanished (shield rows only
match the "Sent" filter), which read as "transactions disappear when sorting".
Root cause: the autoshield merge set the row's confirmations to
min(send, recv). Both legs are the SAME transaction (one real confirmation
count), but the send leg (parsed from z_viewtransaction) routinely arrives with
confirmations=0, so min() picked 0. Use max() to take the populated value.
Also give the sort a txid tiebreak so same-block transactions keep a stable
order instead of reshuffling every time a new block bumps confirmations.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three layout fixes in the export-key modal, all symptoms of widths/heights
authored as raw pixels while text scales with the user's font setting:
- "Copy to Clipboard" no longer clips — the Show/Hide · Copy · QR buttons are
auto-width (size 0) so they always fit their label;
- those buttons now share one font, so Show/Hide matches Copy (was a smaller
toggle-button font);
- the read-only address and key fields are sized to the wrapped text instead
of a fixed 60/80px, removing the empty space below their value.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
From a screenshot at a non-default font scale: the red WARNING box clipped its text
("...balance, but" cut off) because it used a fixed 80px child height while the text inside
scales with the font. Make the warning box auto-size to its content (ImGuiChildFlags_AutoResizeY)
so it never clips at any scale, and scale the address / key read-only field heights by
Layout::dpiScale() for the same reason. Complements the card-width scaling fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Auto-clear: the Copy button now routes through App::copySecretToClipboard, so a copied
private/viewing key is wiped from the clipboard after ~45s (same protection as the seed) with
a "auto-clears" notice — instead of the raw SetClipboardText that left it indefinitely.
- QR: once the key is revealed, a Show/Hide QR toggle renders the key's QR inline (via the same
GenerateQRTexture/RenderQRCode widget the Receive tab uses) for scanning into another wallet.
The QR texture is cached, regenerated on key change, and freed on hide/close/dismiss; hiding the
key also hides its QR.
- Actions row tightened to Show/Hide · Copy · QR, and the key + QR texture are now cleared on any
dismissal (Close button, scrim click, Esc), not just the Close button.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The address list supported two drag gestures that collided: dragging a row onto another
transferred funds, dragging into a gap reordered. Since rows are contiguous, a reorder-drag was
almost always over another row, so it triggered a fund transfer instead of reordering.
Disambiguate by WHERE on the target row the drag is released (user's suggestion): the top/bottom
~30% edge bands = reorder (an insertion line is shown), the centre = transfer (the row highlights).
A zero-balance row or an off-row drop always reorders. Tooltip and i18n hint updated to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First slice of decomposing balance_tab.cpp (3449 lines). The five rendering helpers used by every
balance layout — UpdateBalanceLerp, RenderCompactHero, RenderSharedAddressList (599 lines, the
drag-reorderable address list), RenderSharedRecentTx, RenderSyncBar — are moved verbatim into
balance_components.cpp. balance_tab.cpp is now 2680 lines.
Clean extraction: the helpers' interactive statics (drag/copy/hide/show) are function-local and
move WITH them; the only file-scope state they share is the balance-lerp animation values
(s_dispTotal/Shielded/Transparent/Unconfirmed) and s_generating_z_address, now non-static and
declared `extern` in balance_components.h (defined once in balance_tab.cpp, so both TUs share the
same objects). RenderCompactHero's default arg moved to the header declaration. The layouts (still
in balance_tab.cpp) call the helpers via the new header.
Verified: full-node + Windows + lite build (links cleanly -> extern state resolves), tests,
hygiene. This touches every layout's address list / recent-tx / hero / sync bar, so needs a
hands-on pass across the balance layouts before the next slice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Final slice of decomposing mining_tab.cpp. The ~529-line "Mode toggle" section (SOLO | POOL
segmented control + pool URL/worker inputs) is moved verbatim into RenderMiningModeToggle().
mining_tab.cpp is now 311 lines (was 2628) — just the tab dispatch, thread-sync glue, benchmark
advance, section-budget setup, and four card calls.
State the toggle mutates is passed BY REFERENCE so behaviour is identical: the pool-mode flag,
the settings-dirty flag, and the pool URL / worker char[256] buffers (the text inputs write into
them) — passed as char(&)[256] references and named with their original identifiers so the body
stays byte-identical.
Verified: full-node + Windows + lite build, tests, hygiene. Audit #10 complete: the 2628-line
monolith is now five focused files (earnings, stats, controls, mode-toggle + the 311-line shell).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Third and largest slice of decomposing mining_tab.cpp. The ~843-line "Controls" card (CPU-core
grid + drag-to-select, mining start/stop button, benchmark + miner-update controls) is moved
verbatim into RenderMiningControls(). mining_tab.cpp is now 839 lines (was 2628 originally).
The most coupled section, so mutated state is passed BY REFERENCE — the benchmark
(ThreadBenchmark&), selected thread count (int&), and drag state (bool&/int&) — with local
reference aliases so the body stays byte-identical and interactions (drag, benchmark, start/stop)
behave exactly as before. Read-only context is passed by value/const; the compiler verified
const-correctness. Local statics inside the block moved with it.
Verified: full-node + Windows + lite build, tests, hygiene, no startup crash.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second slice of decomposing mining_tab.cpp. The ~313-line "Hashrate + Stats" card (stat values +
hashrate chart / live-log view) is moved verbatim into RenderMiningStats(); mining_tab.cpp is now
1680 lines (was 1992 after slice 1, 2628 originally). Body byte-identical apart from a s_pool_mode
alias; the chart/log toggle statics (s_show_pool_log/s_show_solo_log) moved with the card, and the
log buffer was already a function-local static. No App dependency in this section.
Verified: full-node + Windows + lite build, tests, hygiene, clean smoke start. Pending hands-on
visual check before the next slice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>