Commit Graph

327 Commits

Author SHA1 Message Date
8c2b0521a6 feat(market): combine portfolio address filter + selection onto one row
Merge the two address-section rows into one: the All/Shielded/Transparent
segmented type filter sits on the left and Select all / Clear / Funded are
right-aligned on the same line, both vertically centered. Select-all is deferred
to a flag applied after the filtered set is built, so it still targets the
current filter (and stays idempotent via PortfolioEntryAdd's dedup).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:37:10 -05:00
4411cb45e2 feat(market): Esc / outside-click discard portfolio edits; only Close saves
Complete the explicit-save model: leaving the Manage-portfolio modal via Esc or
an outside-click now discards the current group's uncommitted edits (matching
the discard-on-switch behavior), while the Close button still commits. Only the
Save and Close buttons persist; every other exit path is a cancel.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:27:16 -05:00
7db6564a0e feat(market): discard uncommitted group edits when switching away
Switching to another group in the Manage-portfolio list (or adding/deleting a
group) now discards the current group's uncommitted working edits instead of
auto-saving them — only the Save button (and closing the modal) persists. This
matches the explicit-save model signalled by the Revert/Save buttons and the
unsaved-changes dot: navigating between drafts is ephemeral until saved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:24:52 -05:00
faf8e4e430 feat(market): rework portfolio-modal actions, addresses row, price & unsaved state
- Move the group-scoped Revert/Save buttons into the detail container (sized to
  the Add-entry height) so they read as lower-hierarchy than the modal-level
  Close, which now stands alone in the footer. The detail body is wrapped in a
  child that reserves the bottom row.
- Add an amber "unsaved changes" dot on the selected group row while its working
  copy differs from the stored entry; also gate the detail Revert/Save on that
  dirty state.
- Addresses: narrow the All/Shielded/Transparent segmented control (360px) and
  right-align Select all / Clear / Funded on one line; give the Select all /
  Clear pills more text padding.
- Appearance: shrink the icon search field and right-align it on the heading
  line, with a "Search icons…" hint.
- Price: move the sparkline toggle onto the 24h line; the interval radios stay
  below, enabled only when the sparkline is shown.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:14:40 -05:00
1affea0273 feat(market): icon-picker search + Settings-style scroll for portfolio modal
Rework the Manage-portfolio detail pane's Appearance and Addresses sections:

- Icon picker: search field inline with the "Icon" heading (filters by name,
  hides the "None" slot while searching); grid now always fits 12 icons per
  row, scaling cell size to width and centering the block.
- Both the icon grid and the address list scroll via a shared helper
  (pfBeginScrollChild/pfEndScrollChild): smooth scrolling + the Settings-tab
  edge-fade shader + a thicker, rounded scrollbar inset from the container edge
  (bordered outer frame wrapping an inner scrolling child). Smaller container
  radius. Fade gates off in low-spec and while the acrylic backdrop is being
  captured — latched per-frame so the last capture frame no longer clashes with
  the render-state reset.
- Addresses: All/Shielded/Transparent segmented control moved to its own row,
  capped at 580px; Select all / Clear pills moved left of the Funded checkbox
  on one left-aligned row (Funded vertically centered against the pills).
- Custom colour "+" swatch inset a few px so its selection outline no longer
  clips at the modal's right edge.
- Remove the divider above the footer Revert/Save/Close buttons.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:50:26 -05:00
32d8d5663a feat(market): portfolio detail polish — bigger icons, inline filter+funded, pill buttons, single-row colors
- Appearance: icon-picker cells doubled (56px, iconXL glyphs); color swatches back to a
  single row that hides overflow but always keeps the custom "+" cell visible at the right.
- Price: basis radios are now horizontal, matching the sparkline-interval radios.
- Addresses: All/Shielded/Transparent segmented control shrinks to sit on one line with
  the Funded toggle to its right; Select all / Clear are rounded (pill) buttons with
  padding, right-aligned on their own row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 22:15:50 -05:00
3568b12dbf feat(market): portfolio detail — radio price options, segmented address filter, +17 colors
- Price: the basis and sparkline-interval dropdowns become radio groups (all options
  visible), now that the segmented layout gives the section full height.
- Addresses: the All/Shielded/Transparent chips become a rounded segmented control
  matching the top switcher. "Select shown" renamed to "Select all"; Select all + Clear
  moved onto the Funded row, right-aligned. Address list gets inner WindowPadding.
- Appearance: 17 more preset colors (25 total), and the swatches now wrap into a grid
  (with the custom-color cell) instead of a single overflowing row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:42:08 -05:00
e74989ec2c feat(market): replace portfolio detail accordion with a segmented control
Swap the collapsible Appearance/Price/Addresses dropdown sections for a single
segmented pill control (one section shown at a time) — no dropdown headers. The
active section now owns the full detail height, so the icon grid and address list
fill the available space instead of fixed heights. State: s_pf_section (0/1/2)
replaces the three accordion open-flags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:24:17 -05:00
fe18628216 feat(market): bump the portfolio backdrop blur radius (64) via sole-consumer isolation
Raising the backdrop radius alone would thrash the shared radius-keyed blur cache (the
glass panels blur at ~30). Instead, while a full-window blur overlay is active, glass
panels use their opaque fallback (IsFullWindowBlurOverlayActive) — they're covered by the
backdrop anyway — so the backdrop is the SOLE applyBlur caller and can use a strong custom
radius (64) with the cache staying coherent and frozen. The flag is toggled alongside the
theme-effect suppression by a RAII guard in App::render().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:15:12 -05:00
4dc6b2ef8f perf(market): capture the portfolio backdrop once on open (+resize), not every frame
Switch the live-blur behind the modal from a per-frame capture (which flickered from
per-frame re-blur non-determinism) to capture-once-on-open, re-captured only on resize.

- Insert the acrylic live-capture callback into the overlay draw list ONLY for a few
  frames on open and whenever the viewport size changes; other frames reuse the frozen
  blurred snapshot (blurCacheValid_), so there's no per-frame capture/re-blur cost. State:
  s_pf_was_open (reset on close), s_pf_capture_frames, s_pf_capture_w/h.

Two fixes from an adversarial review of the timing/pipeline interaction:
- Do NOT override the backdrop blurRadius to 40. applyBlur caches one blurred buffer
  keyed on radius, shared with every glass panel (market cards + the modal preview card,
  radius ~30). A different radius thrashed that cache every frame and made the backdrop
  sample the wrong blur, defeating the frozen-blur guarantee. Using the theme card's
  radius keeps one blur serving all surfaces, frozen.
- Skip the blur on the overlay's first frame (allowBlur=false) — the live content isn't
  captured until that frame's render, so blurring then would show stale/background pixels
  for one frame; draw the plain opaque scrim instead.

Compiles on OpenGL (Linux) + DX11 (Windows). Needs on-device visual verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 21:05:16 -05:00
5afb339ba7 fix(market): portfolio modal — hover assert, cut-off footer, plain heading, FX bleed, bigger delete icon
- Fix the ImGui error on hovering a group row: the per-row delete was an
  overlapping InvisibleButton + SetCursorScreenPos over the row Selectable. Replace
  it with a manual IsMouseHoveringRect / IsMouseClicked hit-test (no overlapping
  ImGui item, no cursor manipulation).
- Delete icon is larger (iconMed) and inset from the row edge (spacingMd).
- Footer no longer clipped: reserve 56px for the 40px buttons + separator.
- Manage-portfolio heading is now plain text (h6) with no title-bar background or
  divider — content floats on the backdrop.
- Sidebar theme-effect borders no longer bleed through the overlay: suppress panel
  theme-effects frame-wide (RAII guard in App::render via ui::PortfolioEditorActive)
  while the modal is open, instead of only during the market render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:47:33 -05:00
0e8816e220 feat(effects): overlay-time live-framebuffer blur behind the portfolio modal
The acrylic system only blurred a frozen snapshot of the static app background. Add
an overlay-time re-capture so the Manage-portfolio modal blurs the LIVE tab content
behind it.

- AcrylicMaterial::captureLiveFramebuffer() (GL blit of FB 0 / DX11 CopyResource of the
  backbuffer + no-backend stub) mirrors captureBackgroundDirect but bypasses the
  dirtyFrames_ gate so it refreshes every frame the overlay is open, forcing a re-blur.
- ImGuiAcrylic::GetLiveCaptureCallback() returns a draw callback for it.
- market_tab: the modal inserts that callback (+ ImDrawCallback_ResetRenderState) at the
  START of its overlay draw list, before the backdrop, so the capture holds the app UI
  drawn below and the backdrop blurs it. On close, InvalidateCapture() re-captures the
  background so other glass panels don't keep blurring the stale live capture.
- Panel theme-effects are suppressed during the market render while the modal is open
  (their foreground-draw-list borders draw above all windows and would otherwise bleed
  over the overlay). Market tab body is rendered again (no longer skipped).
- Backdrop draws the acrylic at full strength (fallbackColor.w=1) over an opaque base so
  the blurred content reads clearly; lighter dim.

Design mapped via an Explore investigation of the capture/blur pipeline; compiles on
both OpenGL (Linux) and DX11 (Windows cross-build). Needs on-device visual verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:26:11 -05:00
2035f71c65 fix(market): stop portfolio group rows overlapping; center modal button text
- The per-row delete icon's SetCursorScreenPos left the ImGui cursor at the icon,
  so the next group row started too high and rows overlapped. Capture the row's
  end cursor after the Selectable and restore it after drawing the delete icon.
- Force centered button-label alignment for the modal (ButtonTextAlign 0.5,0.5)
  so the footer/Add buttons read consistently.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:12:24 -05:00
d007f3de39 feat(market): portfolio modal — per-row delete, footer Close, instant Add, no bleed-through
- Delete moved from the footer to a per-row trash icon on each group (shown on
  hover/selected), with deferred deletion + selection fix-up.
- Removed the title-bar X; the footer is now right-aligned Revert / Save / Close
  (larger buttons), Close commits + dismisses. Esc and outside-click still close.
- Add entry now immediately creates a persisted "Untitled" group and selects it
  (no more invisible draft state); the -2 draft path is gone.
- Backdrop bleed-through fixed: when the modal is open the Market tab body is not
  rendered, so its content and foreground theme-effect borders no longer show over
  the overlay — the modal floats on the app's blurred/opaque backdrop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 20:02:25 -05:00
1fdb6656aa feat(market): fix portfolio Add button clipping; add accordion summaries
- Add button can no longer be cut off: it's now a normal widget below the group
  list (in a group with the scroll child) instead of nested inside the list child.
- Stop the modal's 28x20 WindowPadding from cascading into the nested master/detail/
  list children (pop it right after the card child begins) — that double-padding
  was squeezing the master column and the Add button.
- Right pane navigability: collapsed accordion sections now show a compact summary
  on the right (Appearance -> icon, Price -> basis, Addresses -> N selected), so a
  group's state is readable without expanding. Section titles are consistent
  title-case ("Appearance"/"Price"/"Addresses").

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 19:13:35 -05:00
3305be6318 feat(market): drop the portfolio modal card panel — float content on the backdrop
Remove the centered glass card behind the Manage-portfolio content so it floats
directly on the blurred/opaque full-window backdrop. The little live-preview card
keeps its panel (it previews the actual group card).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:57:28 -05:00
19482214f1 fix(market): draw the portfolio overlay above the tab content
Drop ImGuiWindowFlags_NoBringToFrontOnFocus on the overlay window — it stopped
the per-frame SetNextWindowFocus() from raising the modal, so it rendered behind
the market chart/cards. Now matches the proven BeginOverlayDialog behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:52:06 -05:00
4aed72c73b feat(market): rework Manage-Portfolio as a full-window master-detail modal
Combine the old list/edit modes into one master-detail view rendered as a
full-window overlay instead of a floating dialog.

- Backdrop: new material::DrawFullWindowBlurBackdrop draws an opaque base first
  (so the sheet is opaque regardless of the UI-opacity slider — the acrylic path
  multiplies its alpha by GetUIOpacity), then a strong full-window blur of the
  backdrop, gated exactly like DrawGlassPanel so low-performance mode skips the
  blur entirely (just the opaque scrim).
- Frame: bespoke full-window overlay (not BeginOverlayDialog) that mirrors the
  scrim plumbing and preserves both overlay popup gotchas verbatim (no focus-steal
  and no outside-click dismiss while a combo / color picker is open). Title-bar X,
  Esc, and outside-click all dismiss.
- Left (master): selectable group list with accent bar + icon + name + value line,
  and a pinned "Add group" button.
- Right (detail): live preview + label header, then a single-open accordion
  (Appearance = color + outline opacity + icon; Price; Addresses) so only one
  control group shows at a time — far less cluttered than the old two-column form.
- Footer: Delete / Revert / Save. Editing auto-commits named groups on navigation
  and on close (preserving grid geometry); unnamed drafts are dropped.
- s_pf_editing tri-state replaced by s_pf_sel (+ accordion open-state); the Manage
  button and grid-card click open with a valid selection.

Design + adversarial review done via multi-agent workflows (understand/design, then
a 3-lens ImGui-lifecycle / state-safety / backdrop review; zero confirmed defects).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:46:46 -05:00
3e6987d72f fix(market): remove the chart divider between interval buttons and the plot
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 18:01:44 -05:00
42c511b13a fix(market): tighten blank space above interval buttons; pad below the divider
- Drop the extra gap the hero header reserved below the price row, so the interval
  buttons sit closer to the top of the chart area.
- Place the divider just below the buttons with a comfortable gap (spacingMd)
  between it and the plot, instead of centering it in the strip gap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 17:59:16 -05:00
ef9141c248 feat(market): default chart to 1M, divider below interval buttons, drop chart dots
- Default the price chart to the 1M range on open (was 1D).
- Move the hero divider from below the price row to below the interval buttons
  (centered in the gap between the buttons and the plot).
- Remove the dot markers on the chart: no per-point dots, no current-price dot,
  and no high/low dot anchors — just the clean line + area fill (the high/low
  price labels and the hover crosshair remain).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 17:25:26 -05:00
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
b0ded528b1 fix(dialogs): let Combo dropdowns / popups work inside overlay dialogs
BeginOverlayDialog called ImGui::SetNextWindowFocus() every frame to keep the
dialog on top. That forced the scrim back to the front each frame and closed
any popup opened from inside the dialog — so the portfolio editor's price-basis
Combo and the custom color-picker popup flashed open then vanished, and clicking
a dropdown item that overhung the card could trip the outside-click-dismiss and
close the whole dialog.

Make the overlay popup-aware: compute overlayPopupOpen (IsPopupOpen with
AnyPopupId|AnyPopupLevel) and (1) skip SetNextWindowFocus while a popup is
showing so it stays focused/on-top, and (2) skip the outside-click-dismiss while
a popup is open so clicks inside it don't close the dialog. No change when no
popup is open, so existing dialogs are unaffected.

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 01:02:18 -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
af2db4366c refactor(audit): de-duplicate the main.cpp backdrop draw + DPI transition
Two render-loop de-duplications, zero behavior change (verified byte-equivalent
on both the GL and DX11 backends via the mingw Windows cross-build):

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 19:17:44 -05:00