Portfolio group cards:
- Always show the secondary DRGX amount, including on 2-cell cards.
- Larger group icon + label (subtitle1); value stays subtitle1 for consistency.
- Minimum group width is now 8 cells (default width 8).
Price chart:
- Area fill is now a flat translucent fill matching the group-card sparklines
(dropped the vertical gradient + its primitive-API helper).
- Interval buttons sit near the top of the chart area (removed the empty gap
above them) with a comfortable gap below before the plot.
- The change badge's suffix now matches the SELECTED range (1H->1h, 1D->24h,
1W->7d, 1M->30d) instead of the raw data span (which read 23h / 6d).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Chart interval buttons are now time RANGES, not bucket sizes: 1H = last hour,
1D = last 24h (5-min intraday), 1W = last 7 days, 1M = last 30 days (daily).
pfChartSeries filters the timestamped series to the window; X-axis "ago" labels
and the period-change span now derive from the real timestamps.
- Portfolio group cards redesigned: name (+ secondary DRGX) on the left; the value
with its % change stacked in the top-right corner; the sparkline fills the card
body. The sparkline now renders on 2-cell cards too (down to ~8px of body), with
tighter padding on short cards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Builds on the timestamped historical series (pfChartSeries returns [time,price]):
- Gradient area fill: replace the flat translucent fill with a true vertical
gradient (solid at the line, fading to transparent) via the draw-list primitive
API (pfFillGradientArea).
- Period-aware change: the price badge now reports the change over the DISPLAYED
chart period with a span label derived from real timestamps (e.g. "+42% 1.0y"),
falling back to 24h for Live. Line/fill/marker colors follow that direction.
- Timestamped hover: the crosshair tooltip shows the real date/time at the hovered
point (time for Live/1H, date for day+ intervals) alongside the price.
- High/low markers: hollow dot + price label at the range's extremes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Portfolio group minimum height is now 2 cells (was 3). Added a 2-cell card tier
that also drops the sparkline (not just the footer) so a ~58px card keeps the
header + hero value legible.
- Price chart: stop drawing a dot on every point — for a dense historical series
(>40 points) just the clean line + fill, and always mark the latest point
(current price) with a dot + ring.
- Y-axis labels use the shared adaptive price formatter (e.g. $0.0143) instead of
a fixed 6 decimals, so they're cleaner and narrower.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Chart:
- The price chart now plots the historical CoinGecko series with a Live/1H/1D/1W/1M
interval selector (top-left of the chart). Live = in-session buffer; 1H uses the
intraday 5-min series, 1D/1W/1M the daily series (via pfSparklineSeries). X-axis
labels are interval-aware ("~5h"/"~3d"/"~2w"/"~4mo"). Market-tab open kicks the
self-throttled market_chart fetch so history populates promptly.
- 24H volume + market cap moved from their own row onto the chart's top strip
(right side, next to the refresh button), reclaiming the empty band above the
chart; the hero header now holds just the price row + separator.
- More bottom padding under the plot so the time labels and the DRGX/USDT pair
buttons aren't crowded against the card edges.
Portfolio groups:
- Accent-outline opacity is now configurable per group (0-100%, default 25%) via a
slider in the editor's Appearance column; applied to the card + preview outline.
- Resize grip enlarged (14->20px) and a live "W x H" size readout is drawn while
resizing, so shrinking a group to the 4x3 minimum is reliable and visible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Portfolio groups now default to the 3-cell minimum height (was 4). The card
design adapts: below ~112px it drops the redundant DRGX footer and uses the
compact hero font so a 3-cell card stays legible.
- Group accent color is now a colored outline around the whole card (and the
editor's live preview) instead of a stripe on the left edge; the text no longer
reserves the old left-stripe offset.
- Price chart: trim the empty band between the 24H VOLUME stats and the chart
(smaller hero-header bottom pad), and keep the Y-axis price labels inside the
card on narrow windows (clamp to the card's left edge + bump min y-axis padding
40->54) so they no longer spill onto the tab background.
- Market tab no longer draws a scrollbar (NoScrollbar); the portfolio grid still
scrolls via the mouse wheel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
While rearranging, the dot grid only reached two rows below the lowest card, so
the empty space beneath the cards showed no dots even though a card can be
dropped there. Fill the grid down to the bottom of the visible scroll viewport
(dl clip rect), keeping the two-row buffer as a minimum. The drop-target row is
already unclamped, so dropping into that lower area places the card and grows the
scrollable content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Portfolio-group sparklines previously resampled the ~24-minute in-session price
buffer, so the hour/day/week/month intervals could never fill. Fetch real
historical USD series from CoinGecko /market_chart and back each interval with
appropriately-grained data.
- MarketInfo gains two timestamped series: price_chart_intraday (days=1, 5-min)
and price_chart_daily (days=365, daily), plus a fetch timestamp.
- App::refreshMarketChart() fetches both on the RPC worker via the TLS-verifying
util::httpGetString helper, self-throttled to ~30 min (historical data moves
slowly). Gated by getFetchPrices(); triggered on connect and each price tick.
- Pure NetworkRefreshService::parseCoinGeckoMarketChart() parses {"prices":
[[ms,price],...]} into (unix-seconds, price); malformed rows skipped. Unit-tested.
- market_tab pfSparklineSeries() maps interval -> series+bucket: minute = live
buffer; hour = intraday bucketed to 1h; day/week/month = daily bucketed to
1d/7d/30d. Falls back to the in-session buffer until the fetch populates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the fill-to-height portfolio dashboard with a fine gapless grid of
~32px square cells whose column count scales to fill the tab width. The grid
extends past the window and the Market tab scrolls when cards are placed lower.
- Cells are a fixed square edge (>= ~32px), gapless; cards inset by 3dp so
neighbours don't touch. Column count = availWidth / 32dp (min pfMinW cols).
- Card minimum span is pfMinW x pfMinH (~128x96); layout/resize clamps to it.
New groups default to 6x4 cells.
- Content height reserves two extra rows below the lowest card so a card can be
dragged past the current bottom; the scroll child grows to match.
- Dot grid (shown while rearranging) softened and extended into the buffer rows.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Recommendation #1 + fill-to-fit:
- Group cards are now stat tiles instead of top-heavy rows: a header (icon +
name, with a 24h badge right), the value as a large hero centered in the
middle band, a footer DRGX amount, and a dedicated sparkline strip along the
bottom (soft fill under the line). Uses the taller square cells properly.
- The grid now fills the available height too: cell height is computed at render
time so the rows reach the bottom of the tab (no dead space), and the dot grid
extends to the bottom edge while rearranging. Cell height = availableHeight /
rows, so adding a row shrinks cells to keep filling. The hero font drops from
h4 to subtitle1 on short cells so it always fits.
Full-node + Lite build clean; ctest 1/1; hygiene clean. Interactive/visual —
needs a screenshot + drag/resize check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Grid cells are now square (cellH = cellW); column count is responsive with a
2-column minimum, and a group's minimum size is 2x1 cells. New groups default
to 2x1; drag/resize/auto-place all enforce the 2-wide minimum.
- Fix: editing a group no longer resets its grid placement/size. The editor's
Save now starts from the existing entry and only overwrites the form fields,
so grid_col/row/w/h (and anything else off-form) are preserved.
- Add a per-group sparkline interval (Minute/Hour/Day/Week/Month) next to the
Sparkline toggle. The price history (~1 sample/min) is resampled by averaging
each interval's worth of minute-samples into one point.
PortfolioEntry gains sparklineInterval and defaults gridW=2. Persisted in
settings.json. Full-node + Lite build clean; ctest 1/1; hygiene clean.
Note: the app only accumulates in-session minute samples, so Hour/Day/Month
show points only once enough data has been collected; true long-range history
(instant day/month charts) would need a CoinGecko market_chart fetch, which can
be added separately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turn the portfolio group area into a dashboard grid:
- Responsive column count (1-4 by width); each 1x1 cell is one card.
- Drag a card to move it to another cell; drag its bottom-right corner to
resize (span multiple cells). Both snap to the grid.
- A subtle dot grid appears while rearranging (dragging or resizing), with a
highlighted drop-target / resize footprint.
- Click (no drag past a 4px threshold) still opens the group editor.
- Grid placement (col/row) and span (w/h) persist per group in settings.json;
positions are resolved each frame — stored placement when it fits, else
auto-placed row-major into free cells. Dropping onto an occupied cell swaps.
PortfolioEntry gains grid_col/grid_row/grid_w/grid_h. The card render was
refactored into a drawCard() lambda reused for the in-grid, dragged, and
resizing states.
Full-node + Lite build clean; ctest 1/1; hygiene clean. This is an interactive
feature I can't verify without the GUI — needs drag/resize testing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a per-group "Sparkline" toggle (live-market bases only, like 24h). When on,
the group card draws a faint price-trend line in its lower band — the group's
value tracks the DRGX price, so it plots the market price_history (normalized),
colored green/red by overall direction. Behind the text so amounts stay
readable. The editor's live preview reflects it too.
Persisted as show_sparkline in settings.json; wired through PortfolioEntry +
the editor working state + the pfDrawSparkline() helper shared by card+preview.
Full-node + Lite build clean; ctest 1/1; hygiene clean. Rendering change —
needs a screenshot check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each portfolio group can now configure how it is valued and which price
fields its card shows:
- Price basis: Market · USD, Market · BTC, DRGX only, or Manual (a custom
price-per-DRGX + currency label). Manual mode values the group independently
of the live DRGX market price.
- Field toggles: show DRGX amount / show value / show 24h change (24h enabled
only for the live-market bases; value disabled for DRGX-only).
- The editor's left "Appearance" column gains a Price section (basis combo,
manual price + currency inputs, field checkboxes); the live preview and the
group cards both render through one pfBuildDisplay() helper so they stay in
sync. Defaults preserve the prior behavior (DRGX + USD).
Persisted in settings.json (price_basis / manual_price / manual_currency /
show_drgx / show_value / show_24h).
Note: "price source per exchange/pair" is limited to USD/BTC/Manual — the app
only fetches the aggregate DRGX price (USD + BTC); ExchangePair carries no
price, so valuing against an arbitrary pair's live price would need a new
market fetch (can be added separately).
Full-node + Lite build clean; ctest 1/1; hygiene clean. Rendering change —
needs a screenshot check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Reorder the Appearance column so Color sits above the icon grid, and let the
icon grid fill the remaining height of the left column (via
GetContentRegionAvail().y). Fixes the large empty space that sat below the
color row and gives the icon picker much more room.
- Add a custom-color circle after the preset swatches: it shows the current
color when it isn't a preset (else a "+"), and clicking it opens a
ColorPicker3 popup. The picked RGB packs straight into the group's color.
New i18n key portfolio_custom_color. Full-node + Lite build clean; ctest 1/1;
hygiene clean. Rendering change — needs a screenshot check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Full redesign of the group edit view (list mode unchanged):
- Two columns in a wider (780px) dialog:
* LEFT "Appearance": a live preview of the group card (icon + color + name +
summed DRGX/USD), then Label, the icon picker, and the color picker.
* RIGHT "Addresses": a search box, Shielded/Transparent/All type filters, a
"Funded" toggle, Select-shown / Clear, and the address list.
- Reworked address rows: a custom checkbox, a Z/T type chip, the address's own
label + icon (from the address book), a middle-truncated address, and the
balance right-aligned. Selected rows sort to the top (then by balance); the
whole row toggles selection. Replaces the old wall of raw address strings.
- Search filters by address or label; empty-state hint when nothing matches.
New i18n keys (portfolio_appearance/addresses_hdr/search/funded/select_all/
select_shown/group_name/no_addr_match). Full-node + Lite build clean; ctest 1/1;
hygiene clean. Rendering change — needs a screenshot check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename the portfolio heading "MY DRGX" -> "PORTFOLIO".
- Group cards are now clickable: clicking one opens the editor directly on that
group (edit mode), with a hover highlight + hand cursor.
- PortfolioEntry gains an `icon` (project_icons wallet-icon name) and a `color`
(packed IM_COL32 accent); both are persisted in settings.json.
- Editor edit-mode gains an icon picker (the same wallet icon set as the overview
tab's address-label dialog, via material::project_icons) with a "None" option,
and a color picker (preset accent palette + a default/no-color swatch).
- Group cards render the chosen icon to the left of the name and, when a color
is set, a left accent bar + the icon tinted in that color.
Full-node + Lite build clean; ctest 1/1; hygiene clean. Rendering change — needs
a screenshot check of the Market portfolio + the editor.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hero-card header height was derived from an inflated schema value
(hero-card-height * vs), reserving far more vertical space than the price row +
separator + stats row actually use. Since the chart is drawn at the bottom of
that header, it started well below the "24H VOLUME"/"Market Cap" row, leaving an
empty band. Size heroHeaderH to the actual content so the chart begins right
after the stats.
Full-node build clean; ctest 1/1; hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follow-up to the Market tab rework, per feedback:
- Chart is more compact: a modest responsive height (~22% of the tab, floored
at 110px) instead of stretching to fill.
- Chart now has a time axis. The price series is one sample per ~60s market
refresh (RefreshScheduler::kPrice), so the x-axis is labelled in approximate
minutes-ago ("~45m … ~15m … now") instead of only "Now" — you can now read
the interval/span of each plot point.
- Portfolio: removed the top-level card background — the summary (fiat hero +
24h change + BTC, DRGX balance + Z/T, ratio bar) now floats directly under the
MY DRGX header, and the "GROUPS" heading is gone.
- Group cards redesigned: a max width (280px, shrinks on narrow tabs) so they
float rather than stretch; the group name is larger (subtitle font) top-left;
the DRGX + fiat amounts moved to the card's top-right corner; and more inner
padding around the content.
Removed the now-unused portfolio_groups i18n key. Full-node + Lite build clean;
ctest 1/1; hygiene clean. Rendering change — needs a screenshot check.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>