- Bootstrap: Retry now reuses the source the user actually started (was always
the default), and a "Try mirror" button offers the alternate server on
failure (s_lastUrl remembered on start, cleared on a fresh Confirm).
- Validate Address: classification + checksum are local, so the dialog no
longer requires a node connection — offline/lite now validate and show the
type, with ownership reported as "Unknown — node offline / not checkable in
the lite wallet" instead of a dead "not connected" screen.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- A mining start/stop/pool failure now sets a persistent App::mining_last_error_
(cleared on the next attempt), rendered as a red caption with "Press Mine to
try again" below the Mine button (plus a Dismiss, and a Windows-Defender
"How to fix" on an AV block), so a crashed/blocked/failed miner is no longer
just a button that "did nothing" once its toast faded. Two more hardcoded
English strings (pool-payout-invalid, AV-blocked) moved to i18n.
- Pool connection status is now a persistent "Status" row in the pool stats
card (PoolMiningState.running_since / last_error): it flips from "Connecting…"
to "Can't reach pool — check the URL" after a 30s timeout instead of sitting
on red forever, and no longer vanishes once logs arrive. The old transient
banner in mining_tab.cpp was removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The shield/merge status line was amber only while in-flight and uncoloured for
every terminal state (success, failure, uncertain). It now tracks an OpOutcome
(display-only) and colours the terminal line Success/Error/Warning accordingly,
shows a LoadingDots spinner while the 120s scan runs, and maps the raw daemon
error through friendlyRpcError. The actual shield/merge submit logic is
unchanged (no "Try again" that could double-submit a fund move).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The address-transfer dialog (own-address moves) enabled Confirm on amount alone,
so a disconnected / syncing / locked wallet failed only at submit. It now
mirrors the Send tab's guards (strictly more restrictive — the transfer logic is
unchanged) and re-checks live state before broadcast, shows an inline reason
when blocked (enter amount / not connected / syncing / locked), and labels the
result as a "Transaction ID" (not "Operation ID") with a copy button.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Warmup/daemon status no longer appends a hardcoded English " (Block N)" — it
uses a translated warmup_block_fmt key.
- Recent-transaction type labels (Sent/Received/Mined) are now translated in the
UI consumer (recentTypeLabel in balance_recent_tx.cpp) with a generic
"Transaction" fallback, instead of the data layer's raw English getTypeDisplay().
- Mining: a disconnected solo miner hovering the dead Mine button now gets a
"not connected to the node" tooltip instead of a misleading "Start Mining"
(the tooltip chain had no branch for the solo !isConnected() disable).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
HiDPI geometry debt from the audit: 15 security-dialog buttons used a raw 40px
height (undersized vs the scaled label at 1.5x), the market "Updated" caption
used a raw SameLine(0,12), and the QR popup's 28px overlay-inset budget wasn't
scaled (wrapping the address at HiDPI). All now go through ui::Layout::dpiScale().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Contacts: a "Message" context-menu item for shielded (z) contacts hands the
address to the Chat tab (new OpenChatNewConversation entry point) and switches
there with the New-conversation dialog prefilled; disabled with a reason for
transparent-only contacts. (Kept to the safe context-menu + picker path, not
the hand-drawn 4-slot row action cluster, to avoid mis-wiring the destructive
Delete slot.)
- Chat: the New-conversation picker no longer silently hides transparent
contacts — they're shown disabled with a "needs a shielded address to chat"
tooltip instead of vanishing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The conversation header only copies a PEER's address, so a user had no way to
hand out their own chat address for others to message them. Add a "Copy my chat
address" link in the left pane (public App::chatMyAddress() forwards the stable
seed-derived reply z-address; copied on click only, with an "address copied"
toast and a warning if none exists yet).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The aggregate price is deliberately kept on a failed or old refresh (it's never
cleared), so the hero number could be hours out of date while still drawn in the
"live" green. It now turns amber with an "as of N min/h ago" caption and a hover
tooltip carrying the friendly fetch error, when the last refresh failed or the
aggregate is >=5 min old (only when the hero is the aggregate, not an exchange
pair with its own feed). The hardcoded "Price fetch failed" default is now i18n.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three next-batch UX-audit fixes:
- Chat delivery: the sending/sent glyph has higher contrast and a tooltip
("Sent to the network — arrives after it confirms", so a check isn't mistaken
for "delivered"); a failed send now carries the real reason (ChatMessage::
fail_reason, set from onChatBroadcastResult via friendlyRpcError, in-memory
only) in the "not sent" hover tooltip.
- Explorer: a block height beyond the tip says so ("Block N is beyond the chain
tip (M)") instead of silently snapping to the top; the empty block list shows
Not connected / Syncing (N behind) / Loading instead of an endless "Loading".
- Settings: the misleading "Reset to Defaults" button (which only reloads the
last-saved values) is relabeled "Discard unsaved changes" with accurate copy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- The network fee and running total are now shown on the compose form (once an
amount is entered), instead of only being revealed in the confirm popup.
- When the pre-broadcast re-validation fails (balance dropped, sync restarted,
fee pushed total over available), the confirm popup now closes and surfaces a
warning toast — previously the reason was drawn on the form behind the still-
open popup, so the user was left re-clicking a button that did nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Seed-backup dialog: a failed "Save to file" was drawn in the success (green)
colour; it's now red on failure, green on success (flag reset on close).
- Encrypt-wallet dialog: the disabled Encrypt button now says why once the user
starts typing ("Use at least 8 characters." / "The passphrases don't match."),
and a "Show passphrase" checkbox reveals both fields.
- Key-export dialog: the raw RPC exception (e.g. "...walletpassphrase first")
is routed through util::friendlyRpcError(), so a locked wallet reads
"Unlock your wallet first, then try again." instead of daemon jargon.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New-user mining cluster from the UX audit:
- Solo/Pool toggle: hover tooltips on each half explaining the trade-off
(solo = no fees but long waits on a small CPU; pool = frequent small payouts,
needs the miner + a shielded payout address).
- Payout field: when it's empty (new wallets have no z-address, and a pool
payout must be shielded) the bookmark button is replaced by a one-click
"Generate a shielded payout address" button (createNewZAddress → fills the
field, persists, toasts), instead of only a hover hint to the Receive tab.
- Miner updater pill: when no miner is installed it becomes a prominent accent
"Download miner" call-to-action with a "pool mining needs DRG-XMRig" tooltip,
instead of a subtle grey "Update" pill that's easy to miss.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A coinbase/transparent→shielded move (often auto-shield) was drawn with a red
"Sent" label and send arrow yet a green "+X" amount — an internally
contradictory row that reads as "money leaving" to anyone who auto-shielded. It
now renders as a neutral "Shielded" row (shield icon + the shielded accent
colour for label and amount), and since a shield is your own funds going
private — not a spend — it's excluded from the Sent card total and the Sent
filter (it merges into one row visible under "All").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two of the systemic error-surfacing foundations from the UX audit:
- util::friendlyRpcError() maps the common raw daemon/JSON-RPC errors
(warming up / -28, locked / -13, method-not-found / -32601, insufficient
funds, unreachable node) to short translated guidance with a next step,
falling back to the trimmed raw text. Wired into the send (money) path: the
toast shows the friendly cause, the overlay keeps the raw text below it for
diagnostics. The helper is reusable for the remaining raw-error sites.
- Toasts now pause their auto-dismiss while hovered and ellipsize over-long
text with the full message in a hover tooltip, instead of hard-clipping it
mid-word (durations were already lengthened earlier).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A syncing node skips the balance RPC, so the balance card paints a bare
0.00000000 / $-.-- that reads as lost funds after an update (a documented
support trap). Add one shared notice at the top of the Balance tab — above
every layout — shown only for the normal catching-up case (connected, network
tip known, still verifying, not a rescan/witness rebuild). Offline, no-peers,
a busy node and rescans stay the node-status banner's job, so the two never
stack.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Seed backup: the one-time 12s toast that marked itself "reminded" the instant
it fired (so a funded, unbacked wallet was never nudged again) is replaced by
an actionable alert with a "Back up now" link that opens the backup dialog and
persists in the bell panel. "Reminded" now means the user actually viewed the
phrase (set from the backup dialog), so the nudge re-fires each session until
they do, and escalates to a warning once a balance exists.
- Console: fund-moving / key-revealing commands (dumpprivkey, z_exportkey,
z_sendmany, z_mergetoaddress, …) now require a confirming re-entry when typed
or PASTED, honoring the command reference's `destructive` flag that was
previously enforced only by the reference dialog's Run button. (Output
masking of key dumps in the scrollback is still a follow-up.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Chat tab no longer shows "Unlock your wallet" for every missing-identity
case. A new App::chatIdentityStatus() distinguishes Locked (real),
Preparing (unlocked, deriving the identity) and Unavailable (view-only or a
legacy wallet with no exportable secret — correctly latched). Each gets its
own icon + message, and the Unavailable prompt points at Back up / Migrate
instead of telling the user to unlock an already-unlocked wallet.
- A chat message that sits queued ("Sending") because the wallet is offline or
has no shielded funds for the fee now surfaces the reason (throttled, after a
short dwell), wiring the ready-made chat_toast_not_connected /
chat_toast_need_funds strings that were never shown. The send stays queued
and goes out when the condition clears.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- send confirm: the FROM/TO cards now show the FULL address (wrapped) instead
of a middle-truncated preview, and the saved-contact label when the address
matches the address book ("TO · Alice") — the strongest human check against
a swapped recipient on the one screen meant to catch it. Cards auto-grow.
- peers tab: the "No connected peers" empty state now carries the same
actionable hint as the node-status banner instead of being a bare dead-end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Batch of safe, isolated fixes from the 10-surface UX audit:
- settings: Auto-shield / Save-z-transactions toggles now persist on change
(they discarded CB()'s return and relied on a Save button that only renders
on the About tab, so the choice was silently lost on relaunch).
- exports: show the resolved Platform::getConfigDir() instead of a hardcoded
~/.config path (wrong on Windows/macOS) in both the all-keys and CSV
dialogs; all-keys also reveals the file path on a partial export.
- notifications: error/warning toasts last 9s/7s (were 4s/3.5s) so failures
are readable.
- qr popup / tx-details: copy-to-clipboard now confirms with a toast.
- about: Escape-to-close + readable heading contrast via OnSurfaceMedium().
- sidebar: suppress single-key nav (J/K/arrows/[) while an overlay dialog or
ImGui popup is open, so it no longer switches the tab behind a modal.
- i18n: strip the stale hardcoded path out of file_save_location in i18n.cpp
and all eight res/lang/*.json locales (additive, one key each).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#4 wired the total-earnings figure into the compact hero and the classic
layout only. This adds it to the remaining layouts in each one's native
style: donut / shield / timeline / minimal beside the USD line, consolidated
to the right of the USD, vertical-stack left of the Total row's amount, and
vertical-2x2 as a second line under the Total cell (dashboard and two-row
already show it via RenderCompactHero). All go through FormatTotalEarnings,
which returns nothing unless the global earnings toggle is on, so every
layout's default appearance is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Overview earnings controls (interval 1H/1D/1M/1Y, received/net mode, and the
global show-all toggle) were ad-hoc ImGui buttons. They now use
material::SegmentedControl -- the same rounded-track pill the Receive tab's
All/Shielded/Mining filter uses -- so the interval is a text segmented control,
the mode is two icon segments with per-segment tooltips, and the toggle is a
single matching segment (highlighted when on). Verified in a headless GL capture.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Surfaces the period-earnings aggregator in the Balance tab:
- Toolbar (shared address list): interval pills (1H/1D/1M/1Y), a received-
vs-net mode toggle, and a global show-all toggle, placed right of "Hide 0
Balances". The New Z-Address / New mining address buttons move to a footer
row across from "Showing X of Y" to make room.
- Per address: the row draws its earnings on line 2, left of the $USD value
(green positive, red negative in net mode), shown when the global toggle
is on or the address is opted in via a new "Show/Hide earnings" right-click
item. Works in every layout via the shared list.
- Total: FormatTotalEarnings (gated on the global toggle, so the default
appearance is unchanged) is wired into the compact hero (dashboard/minimal)
and the classic layout, left of / beside their USD figure. The remaining
layouts' totals are not wired yet.
- Settings > Appearance: an "Earnings Display Format" selector (Coin /
Coin + fiat / Fiat).
New i18n keys are translated into all eight locales; the CJK subset font is
rebuilt for the new glyphs. Builds + test suites pass on both variants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mining "Est. Daily" was a solo expected-value (your hashrate share x
blocks/day x a fixed block reward) rendered in the same green as the real
Today / Yesterday / All-Time earnings, off an instantaneous hashrate and
gross of pool fees. It read like a dependable daily income when it is a
long-run statistical average with huge (Poisson) variance.
Framing:
- The estimate is now drawn in a neutral colour, not the green "money
earned" colour (the actual mined totals stay green).
- A sub-label in BOTH modes marks it as an average ("avg", or "avg, net"
in pool mode when the fee is applied), replacing the pool-only
"before pool fee" note.
- A hover tooltip over the column explains the variance ("often nothing
for long stretches, then a lump - NOT a steady daily income") and the
fee status (after the N% fee / before the fee when unknown / nothing for
solo and 0%-fee pools).
Accuracy:
- Uses a trailing-average hashrate instead of the instantaneous / 10s
snapshot: pool prefers hashrate_15m (then 60s, then 10s); solo uses the
mean of hashrate_history once there are >=10 samples, else the instant
rate. This stops a spike or a momentary dip from swinging the figure.
- Subtracts the known pool fee so pool-mode figures are net; synthetic or
unmatched pools stay gross and the tooltip says the fee is unknown.
The static block reward and true pool-reported earnings are intentionally
out of scope (the latter needs a new pool-API field). New i18n keys are
translated into all eight locales; the CJK subset font is rebuilt for the
three new glyphs. Builds + test suites pass on both variants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a "View transactions" action that opens the History tab pre-filtered
to a chosen address. SetTransactionsAddressFilter() stores a pending filter
that RenderTransactionsTab consumes on its next frame — pre-filling the
search box and resetting the type filter to All — then the caller navigates
to NavPage::History. This reuses the existing "prefill + navigate" pattern
(cf. SetSendFromAddress) and all of History's rendering, detail and export.
Triggers:
- Contacts: a context-menu item and a 4th per-row action icon (copy /
view-transactions / edit / delete; the trailing width is widened to fit).
- Balance address list: a per-address context-menu item (own z/t addresses).
- Receive tab: the recent-received rows now carry their address into the
filter instead of jumping to History unfiltered.
The History search also now matches from_address, so filtering by one of your
own addresses catches sends FROM it, not only receives INTO it.
Scoping is honest, not exhaustive: transparent is well covered, but shielded
counterparties are private and the transparent sender of received funds is
never recorded on-chain, so this shows transactions *involving* the address.
On Lite, from_address is thin, so own-address send filtering is weaker.
New i18n keys view_transactions / tt_view_transactions translated into all
eight locales (no new glyphs). Builds + test suites pass on both variants.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a "Favorite Highlight Opacity" slider (15-100%, default 100%) to
Settings > Appearance > Scale & Effects. It scales the alpha of the four
favorite gold tokens (fill/border/icon/outline) at their resolve site in
the Balance address list via material::ScaleAlpha, so the gold row outline
and the star button fade together. The multiplier is skin-agnostic (it
scales whatever alpha the theme resolved) and touches only favorited rows;
non-favorite rows and the white selection highlight are unchanged, and the
100% default is byte-identical to the previous look.
The preference lives in Settings (favorite_opacity, clamped 0.15-1.0 in both
the setter and on load), persists across restarts, updates live, and is not
low-spec gated (it's a pure color-alpha preference, independent of the
transparency sliders). New i18n keys favorite_opacity / tt_favorite_opacity
are translated into all eight locales; the CJK subset font is rebuilt to add
the two new glyphs the zh string needs.
Verified: full + lite builds clean, full + lite test suites pass, hygiene OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The portfolio-editor address list drew every row every frame inside the
fade-scroll child. It now runs through an ImGuiListClipper; the fixed
step (the InvisibleButton height plus item spacing) keeps each row's
cursor exactly where the unclipped loop put it, so the scroll extent for
the fade shader, the check marks, the selection and the "selected rows
float to the top" ordering are all unchanged.
Verified on DX11 (Windows) at normal and large window sizes and at
font scale 1.5, including skin switch and resize with the dialog open.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The contacts Cards/List row loop submitted every contact every frame.
It now runs through an ImGuiListClipper, so only the on-screen rows are
built; the fixed step (row height plus the item spacing pushed above)
matches the per-row cursor exactly, so the scroll extent, fade, hover
actions, selection and keyboard navigation are unchanged. Table view is
left unclipped (its rows can wrap).
Verified on DX11 (Windows) at normal and large window sizes and at
font scale 1.5, in both Cards and List views.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adding a lite server from the network tab restarted the background
server probe immediately. Starting a probe while one is still running
joins the previous worker thread, and that join happened on the UI
thread, so the frame could stall until the outstanding probe finished.
- Adding a server now sets a dirty flag instead of restarting the probe
inline; the next frame starts a fresh probe once the current pass has
finished, so the UI thread never blocks on a join.
- The probe's libcurl transfers honor the cancel flag through a progress
callback and abort cleanly, so a probe in flight stops promptly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hashrate sparkline guessed the age of its oldest sample from the
sample count, labelling the left edge "5m ago", "1m ago" or "start"
regardless of how fast samples actually arrived. Solo and pool samples
come in at different intervals, and a slow or stalled poll made those
labels wrong.
- Each hashrate sample now carries a wall-clock timestamp, kept in
lockstep with the value as the history fills and rolls over, for both
the solo and pool histories.
- The chart's left-edge label is derived from the oldest sample's actual
timestamp, falling back to "start" only when no stamp is available.
Tests: a mining refresh records a timestamp per sample, and the
timestamp history stays the same length as the value history once the
history is capped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The global keyboard shortcuts tested keys without disabling key-repeat,
so holding one down fired it every frame: the theme cycle, balance and
market layout cycles, the effects and background toggles, the settings
and sidebar-collapse keys all re-triggered on a held key. Cycling themes
also stepped onto skins that had failed to load and wrote the broken
pick into settings.
- Every one-shot shortcut now checks for a fresh key press only, so a
held key fires once.
- Theme cycling skips over invalid skins and only applies, persists, and
shows the toast when a skin actually loads; if none can load, settings
are left untouched and no toast appears.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The transaction export streamed rows straight into an ofstream. A write
that failed partway, on a full disk or removable media, left a truncated
CSV on disk behind a success toast. The CSV formatting also lived inline
in the dialog with no test coverage.
- The CSV is now built in memory and written atomically (temp file plus
rename), so a write failure is reported with an error toast and leaves
no partial file.
- Formatting moves into a reusable helper, and the timestamp conversions
use the thread-safe localtime variants and tolerate a conversion
failure rather than dereferencing a null result.
Tests: field escaping neutralises formula-injection prefixes and quotes
special characters, and the full CSV has the expected header and rows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Renaming or adding a contact from the chat tab called the address-book
mutator and then saved again, even though the mutator already persists.
It also reported success unconditionally, so a failed rename still
showed a success toast.
- The redundant explicit save is removed; the mutator's own save stands.
- A rename now shows success only when the mutator succeeds and an error
toast when it does not.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pinning a lite server while a connection to it was already being
established tore the in-flight connect down and restarted it, slowing
the very connection the user was waiting on.
- Pinning is now skipped while a connect is already underway, so the
existing attempt is left to finish.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The request-payment dialog kept its generated payment URI across opens
and closes. A dialog opened for an address with no payable shielded
address could still show the previous request's URI, and the string
lingered after the dialog closed.
- The payment URI is cleared when the dialog opens, when it closes, and
on the close cleanup path.
- The URI field is shown only when there is both a URI and a real
address to pay.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The send tab read the OS clipboard every frame the paste button was
hovered, to preview a pasted address. SDL's clipboard read is a
synchronous round-trip that can stall, and the preview can only ever
latch when the recipient field is empty, so most of those reads were
wasted work.
- The clipboard is now read only while the button is hovered and the
recipient field is empty, polled once on hover entry and then at most
twice a second.
- Trimming the clipboard text moves to a shared helper that also bounds
the copy so a pathological multi-megabyte clipboard is ignored.
Tests: the trim helper handles null, surrounding whitespace, an
all-whitespace string, and the length bound.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The chart fetch and the Market tab both resolved the saved exchange and
pair names into the registry, with their own near-identical scans and
slightly different fallbacks. Keeping two copies risked the chart and
the chips disagreeing about which pair was selected.
- A single resolver maps the saved names to a registry index, matching
the exchange name (any exchange when none is saved) then the pair
display name, and falling back to the registry's first pair, the UI
default. The chart fetch and the Market tab both call it.
- The Market tab now derives its selection indices from the saved names
against the current registry every frame, so the indices stay correct
when the live exchange list replaces the fallback; a steady-state fast
path skips the scan once the indices already name the saved selection.
Tests: the resolver returns the right index for a matched exchange/pair,
an empty-exchange match, and the first-pair fallback on misses.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The portfolio address picker's sort comparator looked up each address in
the selected-address set on every comparison, two hash lookups per call,
and the per-row draw repeated the lookup again. On a long address list
that is a lot of redundant hashing every frame.
- Each filtered candidate now carries its selected flag, looked up once
when the list is built. The comparator reads the stored flag, so the
sort produces the same ordering with no lookups of its own.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The contacts list carried its own lowercase-and-compare sort, its own
lowercase substring search, and an inline linear middle-truncation loop
for fitting an address to a column width. These duplicated logic that
belongs in the shared text helpers.
- A new allocation-free case-insensitive compare replaces the per-sort
lowercasing; it lowercases ASCII per byte and leaves bytes >= 0x80
alone, so UTF-8 labels sort exactly as before.
- Search now uses the shared case-insensitive contains helper.
- Middle-truncation to a pixel width moves to a shared template that
binary-searches the fit (about log2(n) width measurements instead of
up to n) while producing the same result as the old linear shrink.
Tests: the compare matches lowercase-then-compare across ASCII, UTF-8
bytes and length ties, and the fit helper matches the old linear loop
across lengths and widths.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The thread benchmark state machine was advanced from the Mining tab's
render path, so it only progressed while that tab was visible and the
window was on screen. Hiding the tab or minimizing the window froze the
benchmark mid-sweep.
- The benchmark now advances from the main update loop, ungated by
connectivity, so it keeps running when the tab is hidden or the window
is minimized.
- Its clock is driven from a steady wall-clock reading rather than the
render delta, and each step is clamped like the other logic timers so
a suspend/resume cannot leap a benchmark phase.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cancelling a thread benchmark always stopped and restarted the pool
miner at its pre-benchmark thread count, even when the user had not been
mining before the benchmark began. That silently started the miner on a
plain cancel or a switch to solo mode.
- A cancel now always drops the candidate-thread miner, and restarts the
pool miner only when the user was mining before the benchmark, read
from a snapshot taken at the start rather than the live running state
(the benchmark sweep transiently stops the miner between candidates,
so a live check would drop the restore).
- Callers that must not restore the miner (leaving solo mode) pass the
new restore flag as false.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The filled sparklines and the market price chart built their area fill
with PathFillConcave, which runs a per-scanline pass over the whole
filled region. For a simple area under an x-monotone curve that work is
unnecessary.
- A new helper fills the area between an x-monotone polyline and a
horizontal baseline directly as a triangle strip, in one pass over the
points. The market sparklines, the market price chart and the mining
hashrate sparkline all use it. The fill is flat and non-anti-aliased;
the anti-aliased curve drawn on top hides the stepped top edge, and
the faint side and bottom edges are imperceptible at these alphas.
Tests: the strip's vertex and index counts, index bounds, and that its
triangle area matches the trapezoid area under the polyline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each recent block was fetched with two round trips (getblockhash then
getblock), and a height that failed, such as one briefly above the tip
during a reorg, was refetched on every frame.
- getblock is now called once with the height directly, falling back to
the getblockhash plus getblock pair only when an older daemon answers
"not found" for the short string; any other error is a real answer.
- A failed fetch backs off with a doubling wait (2 s up to 32 s) before
any new fetch is scheduled, and a successful fetch resets the backoff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The inline detail panel was tracked by the filtered list index. The
display list is rebuilt and re-sorted on every data change, sort switch
or filter edit, so a stored index pointed at a different transaction
after the rebuild, and a page reset was triggered by any change in the
row count, bouncing the view back to page 1.
- The open row is now tracked by a stable content key (txid, display
type, address and the amount's exact bits) so the right row stays open
across rebuilds.
- The page resets and the open row collapses only when a filter input
changes (type, sort or the full search text). A new or removed row is
data and now just clamps the current page.
- The open row is also cleared on page navigation and when the list
empties, so a wallet switch does not resurrect an old expansion.
Adds pure helpers rowKey/filterInputsChanged to
transactions_tab_helpers.h.
Tests: rowKey distinguishes each field and separates +0.0 from -0.0, and
filterInputsChanged latches each input change but not the first frame.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The History tab built a txid-to-indices map twice when its inputs
changed, once for the summary-card totals and once for the display list,
each a full pass over every transaction with string-keyed hashing.
- The rows are now grouped by txid a single time whenever the
transaction list changes, into an index-only structure (first/next
chains with no string copies), and both the summary pass and the
display pass reuse it. A sort-combo change re-sorts without regrouping.
Adds a pure, unit-testable groupByTxid helper in
transactions_tab_helpers.h.
Tests: the chains match a reference map over a random id stream, and the
empty and single-row cases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The image picker laid out and drew every cell in the directory and image
grids, including the many cells scrolled out of view.
- A cell whose rectangle is not visible now reserves the same space with
a dummy of the cell size, so the scroll extent is unchanged, and skips
the hit-test and draw.
No visible change; only off-screen cells are skipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The block detail modal drew every transaction row of a block, running
the hit-test, the draw and a hash truncation for each one, even when the
row sat far outside the scroll viewport.
- A row entirely outside the scroll clip now keeps its layout slot (so
the scroll extent is unchanged) but skips the hit-test, draw and hash
truncation. The expanded row is always laid out so its inline detail
panel still appears.
- The old "showing first 100 of N" footer is dropped, since every row is
now cheap to lay out regardless of how many a block has.
No visible change; only off-screen rows are skipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
JSON folding in the console was bypassed in any filtered view, including
when only a channel toggle (daemon, RPC or app) was active. A collapsed
block silently expanded the moment a channel was toggled.
- Folding is now kept active unless the view is text-filtered or
errors-only, which are flat so that every match stays reachable.
Channel toggles keep folding, since JSON blocks are atomic per channel.
- The fold triangles and the collapsed " ... }" summary are drawn only
in the fold-aware view, so the glyph and the rendered block can never
disagree.
User-visible: JSON results stay foldable while channel filters are
applied.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A long wrapped console line could extend far past the viewport, yet
every sub-row was still measured and drawn.
- Sub-rows that fall entirely outside the clip rect are now skipped. The
per-row ordinal still advances for a culled row, so scanline parity is
unchanged.
No visible change; only off-screen rows are skipped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A large RPC result was fully split into lines and copied before being
handed to the console, even though only the last few lines could survive
the model's line cap.
- The formatter now takes a line cap and keeps only the last N lines,
located by a backward newline scan, so a huge result is never fully
split or copied. JSON classification still keys off the first
character of the whole result.
- It works over a string view and moves line text through, and fold
spans are computed over the formatted lines directly instead of a
separate vector of copies.
- getline semantics are preserved: a trailing newline adds no empty
line and an empty result yields no lines.
Tests: getline parity for empty, trailing-newline and blank-line inputs,
the backward tail cap, and JSON classification and fold spans over a
capped result.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>