Commit Graph

128 Commits

Author SHA1 Message Date
8c88afe16e feat(market): card/chart refinements — 8-cell min width, larger labels, flat fill, range-matched change
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>
2026-07-03 17:09:40 -05:00
d48c6db21c feat(market): range-based chart intervals; redesign group cards (value top-right + body sparkline)
- 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>
2026-07-03 16:52:09 -05:00
ced1773e70 feat(market): chart gradient fill, period change %, timestamped hover, high/low markers
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>
2026-07-03 15:53:41 -05:00
d76c109063 feat(market): 2-cell min group height; declutter + polish the price chart
- 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>
2026-07-03 15:45:10 -05:00
5aa3cc4d51 feat(market): historical chart intervals, stats overlay, configurable outline, easier resize
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>
2026-07-03 15:37:31 -05:00
4e15a2b1ba feat(market): 3-cell min groups, full accent outline, tighter chart, no scrollbar
- 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>
2026-07-03 15:15:17 -05:00
021ba88522 fix(market): extend portfolio drag dot-grid to the bottom of the viewport
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>
2026-07-03 15:00:07 -05:00
c1d5f79502 feat(market): real historical sparklines via CoinGecko market_chart
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>
2026-07-03 14:50:17 -05:00
db212a81c6 feat(market): fine 32px square portfolio grid that scales to width and scrolls
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>
2026-07-03 14:30:04 -05:00
67f6156030 feat(market): stat-tile group cards + grid fills available width and height
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>
2026-07-03 14:13:16 -05:00
76ed09fad0 feat(market): square grid cells (2x1 min), preserve grid on edit, sparkline interval
- 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>
2026-07-03 04:15:38 -05:00
77219c5ce7 feat(market): portfolio groups as a draggable/resizable dashboard grid
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>
2026-07-03 01:54:20 -05:00
b26aff392b feat(market): optional price-trend sparkline per portfolio group
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>
2026-07-03 01:15:23 -05:00
301b3df77e feat(market): per-group configurable price data
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>
2026-07-03 00:10:42 -05:00
ae4e6486c6 feat(market): portfolio editor — taller icon grid + custom color picker
- 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>
2026-07-02 23:53:18 -05:00
d838243caa feat(market): redesign the Manage-portfolio editor as a two-column layout
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>
2026-07-02 23:44:16 -05:00
75f33e41eb feat(market): portfolio groups — PORTFOLIO heading, clickable cards, icon + color
- 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>
2026-07-02 23:20:21 -05:00
1263058ba7 fix(market): remove empty band between the stats row and the chart
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>
2026-07-02 22:49:09 -05:00
9bcdf51895 feat(market): compact chart w/ time axis; floating portfolio + redesigned group cards
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>
2026-07-02 22:32:19 -05:00
67feaea767 feat(market): rework tab layout — responsive chart + refined portfolio + group cards
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>
2026-07-02 22:12:27 -05:00
3e1cdd15f3 fix(market): portfolio editor address checklist was collapsing to zero height
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>
2026-07-02 21:53:08 -05:00
121a79e768 refactor(audit): batch 9 — de-duplicate balance/peers/transactions UI cards
Three UI de-duplications (rendering changes; the test suite skips the GUI, so
these need a manual screenshot check of the Balance/Peers/Transactions tabs).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Full-node + lite build clean, ctest green.

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

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

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

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

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

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

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

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

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

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

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

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

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

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