Commit Graph

86 Commits

Author SHA1 Message Date
0e1957c5f9 feat(market): explain why the portfolio Save button is disabled
Hovering the disabled Save button now shows which requirement is missing
(name / at least one address / a manual price above 0), mirroring the
send-tab disabled-submit tooltip idiom (IsItemHovered(AllowWhenDisabled)).
The hint only appears for a validation gap — not the self-evident "nothing
changed" case.

Adds portfolio_save_need_{name,address,price} to the English source and all
eight translations; rebuilds the CJK subset font for the new JA/KO/ZH glyphs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:22:56 -05:00
62c92cc862 fix(market): scope the portfolio editor to the active wallet
The Market summary filters custom groups by wallet scope, but the editor did
not: its master list showed and edited *every* wallet's groups, and "Manage…"
opened raw storage index 0 — which could belong to a different wallet.
Separately, a group could be persisted with an empty scope (a placeholder
added before the wallet identity resolved, then filled in and saved), which the
filter treats as "legacy" and shows in *every* wallet's list.

- The editor master list now filters to the active wallet's groups (plus
  legacy/unscoped), mirroring the summary. Storage indices are preserved so
  selection/delete still target the right entry.
- "Manage…" opens the first in-scope group (or the empty state), never index 0.
- The selection clamp resets to the first visible group if the current one is
  out of range or out of scope.
- On commit, a group with no scope is claimed for the active wallet when an
  identity is available, so it can't linger in the global bucket.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:08:28 -05:00
3aeb847657 fix(market): make portfolio-editor dismiss gestures consistently auto-save
The Close button auto-committed the current group, but Escape, outside-click,
switching groups, adding a group, and deleting another group all *discarded*
uncommitted edits — so whether your work was kept depended on how you happened
to leave, and the prominent unsaved-dot / Save / Revert affordances implied a
save model the exits didn't honor.

Align every dismiss/switch gesture with the Close button's intent (auto-save):
- Escape and outside-click now commit before closing (outside-click is caught
  right after BeginOverlayDialog, which clears open mid-frame).
- Switching to another group, "Add entry", and deleting a *different* group
  commit the current group first. Deleting the selected group still discards
  its own working state.

Save remains "persist now", Revert "undo to last saved", and the dot marks an
in-progress group. Ordering is safe against the placeholder GC: that GC runs
earlier in the frame (mktDrawPortfolio, before RenderPortfolioEditor) and only
while the editor is closed, so it never shifts entry indices under a commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 15:03:17 -05:00
2a9d32e78b fix(market): close portfolio-editor gaps found in re-audit
Adversarial re-audit of the group editor surfaced follow-ups to the prior
hardening pass:

- Dirty-check regression: the empty-currency->"USD" coercion was applied on
  save but not in the dirty comparison, so clearing the currency field left a
  group perpetually "dirty" (unsaved dot + Revert/Save never settling, a
  redundant persist on every Close). Both sides now share pfEditLabel() /
  pfEditCurrency() so they can't disagree — this also trims the label, so a
  trailing space no longer wedges the dirty state.
- Whitespace-only labels ("   ") passed validation and persisted as an
  invisible group; the trimmed label is now required by pfWorkingValid.
- "Add entry" persists an empty placeholder up front; abandoning it (Close/
  Esc/outside-click without adding an address) left a phantom "$0 · 0" group.
  The summary now prunes address-less groups while the editor is closed.
- Manual price accepted inf/NaN (neither is < 0); sanitize with std::isfinite.
- Escape closed the whole editor even when it was only meant to dismiss the
  open custom-color popup, discarding edits; guard on !IsPopupOpen.
- HiDPI: scale the Z/T chip pill's vertical padding and the balance-breakdown
  baseline nudge that the prior pass missed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 14:51:32 -05:00
97a54f0e0c fix(market): harden portfolio group editor + HiDPI scaling
Portfolio audit fixes:
- Guard the BTC value line behind price_btc > 0 so an empty BTC price no
  longer renders a misleading "≈ 0.00000000 BTC".
- Validate the working group before commit/save (pfWorkingValid: needs a
  label, at least one address, and a positive manual price when the manual
  price basis is selected); the Save button disables to match, so a
  half-filled editor no longer persists a broken group on close/switch.
- Default an empty manual currency to "USD" instead of an empty string.
- Reload the editor after clamping a stale selection index so it can't
  point past the end of the group list.
- Scale hand-drawn geometry by dpiScale(): address-row rounding, chip
  y-offset, preview border, chart toggle buttons, and the shielded/
  transparent ratio-bar corner radii now render correctly on HiDPI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 14:14:45 -05:00
e82514f46a feat(market): persist chart range + style across sessions
The chart interval (Live/1H/1D/1W/1M) and the line/candle style were
session-only statics. Persist both like the selected exchange/pair: new
chart_interval / chart_style settings (defaults 1M / candlestick),
loaded into the market view on first show and saved on each interval
click / style toggle.

Verified: writing chart_interval=2, chart_style=0 to settings.json and
launching restores them (not reset to defaults) and re-saves them intact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 12:05:36 -05:00
76708e9191 feat(market): line/candle toggle + ~50% taller chart
- Add a chart-style toggle (line vs candlestick) next to the interval buttons,
  shown only when the selected range has per-exchange candles (the aggregate /
  Live view is line-only). It flips s_mkt.chartStyle; candles draw when OHLC
  exists AND the user hasn't switched to the line. Icon reflects the current
  style; tooltip says what a click switches to. Two i18n keys across 8 languages.
- Make the price chart ~50% taller: scale the height floor / desired / viewport
  cap (110->165, x1.5 desired, 0.22->0.33 of available) for a roomier plot.

Verified via a forced-OHLC render: the taller chart, the toggle button, and
candles with a correct week/day x-axis.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:54:08 -05:00
a767702ff6 feat(market): OHLC hover readout on the candlestick chart
Candles had no hover (the line's close tooltip is gated off for them). Add a
candle-aware readout: the candle under the cursor gets a column highlight +
crosshair, and a small box shows its date and open/high/low/close (colored green
up / red down). Verified via a forced-hover render (date + O/H/L/C over the
highlighted candle). Line charts keep their existing single-price tooltip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:46:26 -05:00
40e8128a30 feat(market): candlestick chart for per-exchange OHLC
The exchange candle APIs return full OHLC but we only kept the close (a line).
Now the per-exchange chart draws real candlesticks; the CoinGecko aggregate stays
a line (it's close-only).

- Adapter keeps OHLC: parseExchangeOHLC() returns open/high/low/close candles
  (parseExchangeCandles is now a close-only wrapper over it). New data/candle.h
  holds the dependency-free Candle + bucketOHLC (5-min -> hourly for the 1D view).
- Model stores exchange_ohlc_intraday/daily alongside the close series;
  refreshExchangeChart populates both. market_series::chartCandles() returns the
  bucketed OHLC for the range, empty unless the per-exchange series is active.
- The chart renders wick (low..high) + body (open..close), green up / red down,
  with a low..high y-range; the line-only bits (fill, hi/lo labels, hover tooltip)
  are gated off for candles. Falls back to the line for the aggregate / Live view.

Verified: OHLC parsers + bucketOHLC + chartCandles unit-tested; a forced-state
render shows correct candlesticks; the aggregate still draws a clean line.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 11:16:44 -05:00
326192e75e feat(market): per-exchange 24h volume in the hero
The hero's "24H Vol" showed CoinGecko's cross-exchange aggregate even after the
chart/price went per-venue. Capture the per-exchange converted_volume.usd from
the tickers (previously discarded, alongside converted_last) and show the
SELECTED exchange's own 24h volume; fall back to the aggregate when unknown.
Market cap stays aggregate (it's coin-wide, not per-venue).

This is a big real difference — e.g. Ourbit ~$14.3K vs NonKYC ~$253 for DRGX/USDT
— so the header now reflects the venue you're actually looking at.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 10:58:50 -05:00
a107fda9b8 feat(market): loading spinner while the price chart fetches (pair switches)
Switching exchange pairs (or first load) briefly leaves the chart with no series
while the venue's candles fetch, which showed the bare "No price history
available" empty state. When a chart fetch is in flight — App::isMarketChartLoading()
(the CoinGecko aggregate OR the per-exchange fetch) — draw a spinner + animated
"Loading price history…" in the plot area instead; the "no history" text only
shows when genuinely empty and idle. New i18n key market_chart_loading across all
8 languages. Verified via a forced-state render (spinner + label centered).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 09:38:44 -05:00
d1ff58c374 feat(market): per-exchange price chart from each venue's own candle API
The market chart was hardwired to CoinGecko's cross-exchange USD aggregate
(vs_currency=usd), so selecting a trading pair only changed the "Trade" link —
never the chart or price. Now the SELECTED exchange drives both.

CoinGecko gives us which venues list DRGX (the ticker `market.identifier`) but
not their APIs, so add data/exchange_candles.h: a hand-maintained map from that
identifier to each venue's public candle endpoint, with two adapters verified
against the live APIs — Ourbit (MEXC-style /api/v3/klines, array format) and
NonKYC (TradingView-UDF /market/candles, `bars`). Unmapped venues return no URL,
so the chart falls back to the CoinGecko aggregate and nothing regresses.

- App::refreshExchangeChart() resolves the selected pair, fetches its intraday
  (5-min) + daily candles via the TLS-verified httpGetString on the worker, and
  stores them on MarketInfo (exchange_chart_intraday/daily + exchange_chart_active).
  Re-fetches on pair change; self-throttled to ~30 min otherwise; falls back to
  aggregate on any failure.
- chartSeries() draws the per-exchange series when active (portfolio sparklines
  stay on the aggregate). The hero shows the selected venue's real price
  (converted_last) — Ourbit vs NonKYC differ ~7%. parseCoinGeckoTickers now
  captures the identifier + per-exchange USD price (previously discarded).

Adapters unit-tested against real captured responses (URL builder + both parsers
+ the chartSeries switch) and validated against the full live feeds (Ourbit 20d,
NonKYC 370d, ascending, correct closes). Build + ctest + hygiene green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 09:19:55 -05:00
b70f1ae643 feat(market): refine the three portfolio row styles
Act on the design review of the row layouts:
- Fixed right-aligned columns (DRGX | value | 24h | sparkline) so the numbers
  line up across rows and the value no longer jumps rightward when a group has
  no sparkline/24h.
- Emphasize the fiat value (larger + brighter than the muted DRGX amount) — the
  number users actually scan for.
- Graceful fallback: on-but-absent fields (e.g. price not loaded yet) show a
  muted em-dash instead of a hole; deliberately-hidden fields stay blank.
- Detailed style reworked into a 2x2 grid (label/value on top, DRGX/24h below)
  with a tall sparkline strip spanning both lines, so its extra height earns its
  keep; when a group has no sparkline the value/DRGX fill the space.
- Featured hero value is now neutral bold (OnSurface) instead of the accent
  color, so it can't collide with the green 24h; DRGX right-aligns to fill the
  right when there is no sparkline.
- Stronger row separation (more card fill) and REMOVED the left accent strip —
  group identity now reads from the tinted icon + a subtle accent border.

Verified via the headless sweep across all three styles on dark + light.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 21:07:40 -05:00
76a2ef51f3 fix(market): stop the last portfolio row from clipping
Inside the portfolio row child each row emits two items (the InvisibleButton
hit target + a gap Dummy), and ImGui inserts its default ItemSpacing.y between
every item — so the real content ran ~4x ItemSpacing taller than the child's
height budget, clipping the bottom row (e.g. "Cold storage" lost its second
line) and widening the inter-row gaps.

Zero ItemSpacing inside the row child (the inter-row gap is set explicitly by
the Dummy) and size the child to the visible rows plus one gap of bottom
breathing room, so the last row never sits on the clip edge. Scale-independent
(rowH/gap both scale by dpiScale). Verified via the headless sweep across all
three row styles — every row now renders fully and the gaps tighten to rowGap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 20:37:43 -05:00
5db7941fbc feat(market): portfolio row-style redesign + per-wallet scope
Replace the drag/resize dashboard grid on the Market tab's portfolio with
three full-width row styles — compact (single-line), detailed (two-line),
and value-hero — cycled with Left/Right arrows like the Overview layouts.
Rows live in an internal scroll region bounded to the portfolio's height
budget, so the section stays put as groups are added.

- pfDrawRow renders each group in the selected style, reusing the icon /
  value / DRGX / 24h / sparkline pieces; per-entry field toggles honored.
- portfolio_style setting (0/1/2) persisted; arrow-key switcher + toast.
- Portfolio groups gain a per-wallet scope: new groups default to the
  active wallet's identity hash; legacy groups (empty scope) stay visible
  in every wallet. Scope filter drives both the height budget and the rows.
- Remove the now-dead grid machinery (mktDrawCard, pfComputeGridLayout,
  PfCell, PfGridDrag, MktCardCtx + MktCtx grid fields) — net code drop.
- Full UI sweep: seed demo portfolio groups + wavy price history and add
  market-rows-{compact,detailed,featured} surfaces so the styles are
  captured per skin (snapshot/restore the real portfolio + market data).
- i18n: portfolio_style_{label,compact,detailed,featured} + 8 langs; CJK
  subset rebuilt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 19:03:07 -05:00
ee6903ae56 fix(theme): mop-up per-skin nits — ratio bar + light-skin muted text
Lower-severity polish from the per-skin review:
- Market portfolio ratio bar: was full-saturation Success/Warning at a208 over a
  white a10 track — the green read as "neon" on dark skins and the track was
  invisible on light skins. Make it theme-aware: muted fills (a165) on dark, a
  dark track on light so the bar is defined on white/pastel surfaces.
- Light-skin muted text: bump --on-surface-disabled 0.52 -> 0.62 on light/
  color-pop-light/dune/marble/iridescent so secondary text (addresses, category
  labels) clears the pastel/gradient backgrounds (analog of the dark-skin bump).

Remaining review nits are the "white-overlay-assumes-dark" track/hover pattern
(a broader separate sweep) and per-skin aesthetic trade-offs; deferred.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 00:46:53 -05:00
da56bb73a0 refactor(ui): consolidate raw ImGui buttons onto material::TactileButton
UI-standardization audit: ~66 call sites still used raw ImGui::Button /
ImGui::SmallButton instead of the app's canonical TactileButton, so those
buttons missed the standard glass fill + rim + tactile overlay (and the
light-theme flat treatment). Convert 59 plain action-button sites across the
wizard, security dialogs, settings, market/console/explorer tabs, and the
address dialogs to material::TactileButton / TactileSmallButton (drop-in:
same signature/return, args preserved verbatim). Danger buttons keep their
PushStyleColor wrappers — Tactile renders through them then overlays.

Deliberately left raw (not plain buttons): InvisibleButton hit-targets, the
frameless transparent-bg collapsible-header toggles (RPC/Debug), the icon-only
pagination chevrons, and the receive All/Z/T segmented filter — a glass rim
would clash with those intentionally borderless/segmented looks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 19:00:14 -05:00
ad59d62b82 refactor(ui): extract shared ProgressBar / CopyableTextOverlay / DrawPill helpers
UI-standardization audit: three draw patterns were copy-pasted across tabs and
dialogs. Promote each to a material:: helper and adopt at every verbatim site.
Output is pixel-identical (same colors/geometry threaded through as params).

- material::DrawProgressBar / ProgressBar — replaces 3 byte-identical rounded
  fill bars in the daemon/xmrig/bootstrap download dialogs.
- material::CopyableTextOverlay — the click-to-copy affordance (hit button +
  hand cursor + tooltip + hover underline + clipboard) shared by the peers
  best-block hash and the 3 mining stats (difficulty/block/address). Returns
  "copied this frame" so callers keep their own toast (no ui-layer dependency).
- material::DrawPill / PillSize — rounded bg + optional border + inset text
  badge, shared by the explorer status pill, the market Z/T chip, and the
  network official/custom tag.

Divergent one-offs left in place (transactions status pills use schema rounding
around pre-positioned multi-line text; the mining filter-pill is a button bg).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 18:48:08 -05:00
ebf01bd858 feat(market): migrate portfolio modal onto the shared blur-overlay framework (Phase 1)
Dogfood the Phase 0 framework: RenderPortfolioEditor now opens via
material::BeginOverlayDialog(BlurFloat) + EndOverlayDialog instead of its own
inline overlay scaffold, and uses the promoted material:: SegmentedControl /
RightAlignX / BeginFadeScrollChild helpers (overlay_scroll.h). Delete the
portfolio's inline backdrop/capture-once state machine + its PfEditState capture
statics + the four local helper copies. App::render's ModalRenderGuard is now
driven by the modal-agnostic AnyBlurOverlayActiveLastFrame() (timing-equivalent),
and LatchBlurOverlayActive() at frame end handles the acrylic re-capture on close.

Framework tweak: floating cards pop the card WindowPadding after BeginChild (so
nested children don't inherit it) and center button labels, keeping the net
style-var count at 2 so EndOverlayDialog is unchanged and GlassCard stays
byte-identical. RenderPortfolioEditor: 357 -> 282 lines. Needs GUI verification
that the portfolio modal is visually/behaviourally unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 02:33:15 -05:00
69cd8ba048 i18n(market): route chart interval labels + "Updated" through TR()
Add market_iv_1h/1d/1w/1m and market_updated ("· Updated %s") keys and use them
in the chart interval strip (previously hardcoded "1H"/"1D"/"1W"/"1M") and the
pair-selector attribution line (previously a literal "· Updated %s"). Closes the
audit's i18n gaps in the market tab.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 01:19:30 -05:00
4543cdc495 refactor(market): extract portfolio section into mktDrawPortfolio
Move the balance summary (fiat/DRGX/BTC + shielded ratio bar) and the
draggable/resizable custom-group card grid (drag/resize gestures, dot grid,
click-to-edit) out of RenderMarketTab into mktDrawPortfolio(MktCtx); the local
x-coord `cx` renamed `cx0` (ctx param is `cx`). Drop the now-unused
gap/dl/glassSpec/buf locals. RenderMarketTab is now ~150 lines (from 962) — a
thin orchestrator: precompute -> build MktCtx -> hero/chart/pair/portfolio.
Verbatim bodies. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 01:02:11 -05:00
71fb81670f refactor(market): extract price chart into mktDrawPriceChart
Move the ~250-line chart block (interval strip + 24h stats + refresh, plotted
curve with grid/area-fill/hi-lo+time labels/hover crosshair+tooltip, empty
state) out of RenderMarketTab into mktDrawPriceChart(MktCtx), recomputing cheap
locals and reading the precomputed series via the ctx. Drop the now-unused
hs/pad locals. Verbatim body. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 00:57:59 -05:00
a092097141 refactor(market): extract MktCtx + price hero into mktDrawPriceHero
Introduce a MktCtx struct holding RenderMarketTab's precomputed geometry/series,
built once after the precompute, and extract the combined hero card (glass panel
spanning header + chart, price/ticker/period-badge/trade button) into
mktDrawPriceHero(MktCtx). Cheap locals (dl/fonts/dp/market) recomputed in the
helper; the hero's local x-coord `cx` renamed `cx0` (the ctx param is `cx`).
Verbatim body. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 00:53:27 -05:00
50873d1112 refactor(market): extract pair selector into mktDrawPairSelector
Move the exchange/pair chip grid (wrapping flat chips + click-to-switch +
attribution line) out of RenderMarketTab into mktDrawPairSelector(app, registry,
availWidth), recomputing its own schema/fonts/market. Drop the now-unused ovFont
local. Verbatim body. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 00:43:43 -05:00
7e8628f94c refactor(market): extract drawCard lambda to mktDrawCard function
Lift the ~90-line [&]-capturing drawCard closure out of RenderMarketTab's grid
code into a file-static mktDrawCard(MktCardCtx, min, max, entry, hov). The
captured locals (dl/state/market/mktDp/pfInset/pfCardPad/sub1/capFont) are
bundled into a MktCardCtx bound once per frame. Verbatim body. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 00:40:56 -05:00
0ae52ee095 refactor(market): extract pure portfolio grid-layout into pfComputeGridLayout
Pull the ~30-line row-major cell placement (honor stored placement else
auto-place) out of RenderMarketTab into a pure, deterministic helper taking
(entries, cols, minW, minH). Also bump the grid resize-readout buffer 16->32 to
silence a worst-case -Wformat-truncation the extraction surfaced. No behavior
change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 00:38:08 -05:00
529a74f6a7 refactor(market): extract portfolio detail sections into helpers
Pull the three detail-pane section blocks out of RenderPortfolioEditor into
pfDrawAppearanceSection / pfDrawPriceSection / pfDrawAddressSection (each
recomputes its own dp/fonts/state; the section dispatch is a 3-line switch).
Verbatim move — same widgets, IDs and drawing. RenderPortfolioEditor drops from
~760 to ~357 lines. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 00:20:25 -05:00
88f1415bcc refactor(market): lift portfolio-editor lambdas to file-static functions
Convert the four [&]-capturing lambdas (persist / buildWorking / workingMatches /
commitIfNeeded) into file-static pfPersist / pfBuildWorking / pfWorkingMatches /
pfCommitIfNeeded that operate on the s_pfEdit struct + take Settings*. Removes
the per-frame closure setup and lets the upcoming section helpers reuse them.
No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 00:13:42 -05:00
b8cfc1c79c refactor(market): group ~35 file-scope statics into three structs
Collapse the loose market_tab.cpp statics into MarketViewState (s_mkt),
PfEditState (s_pfEdit) and PfGridDrag (s_grid). Pure mechanical rename
(compile-verified, no behavior change) that shrinks the global surface and gives
the upcoming function decompositions a single state handle per concern instead
of ~35 free names.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 00:10:59 -05:00
a0fd9aff82 refactor(market): extract pure price-series math to data/market_series.h + tests
Move the no-I/O, no-ImGui series helpers (resampleHistory, bucketBySeconds,
sparklineSeries, chartSeries) out of market_tab.cpp into an inline header under
data/ (mirroring data/portfolio.h) and cover them with a testMarketSeries()
group in test_phase4.cpp (resample block averaging + partial blocks, window
bucketing + guards, sparkline fallback, chart time-window filtering + live
timestamp synthesis). market_tab now calls data::sparklineSeries/chartSeries.
No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 00:03:19 -05:00
14b08d1358 refactor(market): extract pfSegmentedControl helper
Collapse the two identical equal-width segmented controls (the detail section
switcher Appearance/Price/Addresses and the address type filter
All/Shielded/Transparent) into one pfSegmentedControl(dl, origin, w, h, labels,
count, selected, font, idBase, dp) that returns the clicked index. Same track /
active-pill / centered-label drawing and per-cell hit-test as before; the
variable-width chart-interval strip is left as-is. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:56:51 -05:00
d689b02de9 refactor(market): extract pfProjectSeries + pfRightAlignX helpers
Dedup the identical min/max→rect projection shared by pfDrawSparkline and
pfDrawSparklineFilled into pfProjectSeries, and the repeated right-align cursor
math (footer Close, detail Revert/Save, summary Manage button) into
pfRightAlignX. No behavior change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:54:10 -05:00
84498b1312 refactor(market): drop dead statics, fix BTC preview/card precision mismatch
Audit cleanup of market_tab.cpp:
- Remove the write-only statics s_history_initialized and s_last_refresh_time
  (assigned in several places, never read) and their now-dead assignments.
- Align the portfolio grid card's BTC value to %.8f to match the editor preview
  (pfBuildDisplay) and the summary rows; it was the lone %.6f, so the same
  balance rendered with different precision in the preview vs the card.
- Fix the stale s_pf_sel comment (no "-2 = unsaved draft" state exists).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-03 23:49:09 -05:00
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
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