Animate contact avatars end to end:
- texture_loader gains LoadAnimatedRGBA: decodes an image into a downscaled
RGBA frame sequence + per-frame durations — animated GIF via stb
(stbi_load_gif_from_memory) and animated WebP via libwebp's WebPAnimDecoder;
stills (and APNG, which stb reads as one image) return a single frame. Frames
are box-downscaled (smaller cap for animations) to bound VRAM, capped at 300.
- The contacts avatar cache now holds a frame sequence; currentAvatarFrame()
advances animated avatars by the ImGui clock and is used everywhere avatars
draw (list, cards, table, grid, preview). When a live animated frame is drawn
it flags the render loop (ConsumeContactsAvatarAnimation, clear-on-read) so
main.cpp keeps producing frames while animation plays and idles when it stops
or the contacts view is hidden.
- New animate_avatars setting (default on) + a Settings appearance toggle
("Animate avatars"); off shows the first frame only. currentAvatarFrame
honors it. +i18n (8 langs, CJK subset rebuilt for 帧/播/첫).
Verified: a 3-frame GIF and a 3-frame animated WebP both decode to 3 frames
with correct 120ms delays through the exact libwebp/stb calls used here.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Broaden avatar/image support:
- Add libwebp (FetchContent, static, decode-only) so WebP loads. Built from
source for Linux / mingw-Windows / macOS-osxcross identically — the cross
sysroots have no webp, so vendoring from source is the one portable path;
encode/tool builds are disabled to avoid pulling in libpng/zlib. Linked as
webp + webpdemux (the latter for animated WebP, wired next).
- texture_loader routes all decode through DecodeImageRGBA: sniffs the RIFF/
WEBP header and uses libwebp (WebPDecodeRGBAInto into a free()-able buffer),
else stb — so every existing caller (avatars, QR, thumbnails) gains WebP for
free with no allocator mismatch.
- Enable stb's TGA / PSD / PNM / PIC decoders and add .webp/.tga/.psd/.pnm/
.ppm/.pgm/.pic to the image-picker + avatar-library extension lists.
Verified: libwebp builds static and a real .webp decodes to correct RGBA.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the adversarial review of the image-library grid:
- MED: the delete badge was an InvisibleButton positioned via SetCursorScreenPos,
which left CursorPosPrevLine at the badge corner — the next cell's SameLine
reads that, so a hovered/selected row's trailing cells jittered ~3px and their
hit-rects overlapped. Hit-test the badge MANUALLY (no layout item, no cursor
moves); it still takes click priority over selecting the thumbnail.
- MED: avatar textures were uploaded at full native resolution and cached for the
session with no cap — a library of large photos could cost GBs of VRAM, and the
contact list decoded every image avatar at once on tab open. Box-downscale to
<=256px (an avatar renders at most ~112px) and budget decodes to a few per
frame, so large libraries fill in progressively instead of stalling.
(A third finding — a symlink edge case in the delete guard — was reviewed and
judged below the bar; the weakly_canonical + parent-path guard already holds.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Redesign the Image avatar tab from a single-image chooser into a grid over a
persistent image library (mirrors the Icon tab):
- Images the user adds live in <config>/contact-avatars/ and PERSIST as a
reusable, portable set — they travel with the wallet data dir, so avatars
survive moving to another machine without remembering source paths.
- The grid's first cell is always the "+ add image" button (opens the picker,
decode-verifies, copies into the library, auto-selects the new image).
- Each library image is a selectable thumbnail (Primary ring when selected)
with a delete badge (top-right, red on hover) to remove it from the library;
the delete is deferred past the grid loop and clears the selection if it
pointed at the removed file. Contacts still referencing a deleted image fall
back to their Z/T badge.
- Removes the previous auto-prune of "unused" images on edit/delete — images
are only removed by the explicit delete badge now.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Compare the file's parent path to the managed dir instead of a string prefix,
so a sibling dir like contact-avatars-x can't false-match. Our avatar copies
always live directly in the dir, so this is both correct and safer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Table view now shows the same avatar (image / icon / Z-T badge) before each
contact's label, drawn after the row Selectable so its highlight doesn't
paint over it — visual parity with Cards/List.
- When a contact's custom image avatar is replaced (edit) or the contact is
deleted, its now-unused file in <config>/contact-avatars/ is removed
(pruneOrphanAvatar) — but only if no other contact still references it and
the path is inside our managed dir, and its cached texture is dropped too.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From an adversarial review of the avatar edit-dialog + image picker:
- HIGH: the per-row Delete icon called doDelete() (which erases from
book.entries()) INSIDE the loop iterating that same vector — out-of-bounds
reads / wrong rows on the confirming click. Defer it until after the loop.
- HIGH: the decode stack was compiled PNG-only (STBI_ONLY_PNG) while the image
picker accepts .jpg/.jpeg/.bmp/.gif, so picking a JPEG (the common photo
case) silently produced a non-loading avatar + an orphaned copy on disk.
Enable JPEG/BMP/GIF decoders, and guard the pick: verify the source decodes
before copying/committing (new contact_avatar_bad_image string, 8 langs).
- MED: the fixed, non-scrolling edit card floored bodyH at 260*dp, which could
push Save/Cancel below the card on short windows — floor lowered so the
footer always stays inside.
- LOW: the live-preview panel rounding is now dp-scaled (10*dp) to match the
real list card it mirrors.
- LOW: re-picking the same source path after its contents changed showed a
stale cached texture — evict the avatar texture cache entry on re-pick.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fix the thumbnail grid to a constant 6 columns whose square cells scale to the
available width, instead of fixed 96px cells that left dead space on the right.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Smooth (lerped) wheel scrolling via ApplySmoothScroll on the list, matching
the app's other modal lists.
- The list is now a bordered/rounded outer frame whose 6px padding insets the
scrollbar so it clears the card's rounded corners; the inner scroll child is
transparent (the frame draws the single background — no more box-in-a-box
from the ChildBg being left on the stack across both BeginChild calls).
- Folders render as a 2-column grid of thin rounded rectangles (folder icon +
name) instead of full-width rows, so more folders are visible at a glance.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Wrap the picker body in a padded inner child so the filled directory list and
thumbnail grid keep a clear margin from the card's rounded corners instead of
running edge-to-edge past them.
- Center the Use image / Cancel buttons and drop the stray separator line above
them (the faint artifact at the footer's left edge).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address five reported issues:
- Address field is now full width with a centered Paste button beneath it
(was a narrow field with Paste crammed alongside).
- The pinned "Show in every wallet" checkbox no longer clips at the column
bottom — the Notes fill reserves a clear margin for it.
- Image mode: more spacing between the preview circle and the Choose/Remove
row so the button isn't crowding the avatar.
- Contact list: hovering a row now un-collapses the address to its full form
inline (clipped to the text column) instead of popping a tooltip.
- Per-row copy/edit/delete icons fire on the first click on an unselected row:
the action lambdas validate s_selected_index freshly (via selValid())
instead of the frame-top has_selection bool, which was stale in the same
frame the icon set the selection.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The dialog was a squat auto-height box with a big dead zone below and an icon
grid that clipped at ~3 rows. Make it a fixed, tall card (up to 84% of the
viewport) whose body flexes to fill the height:
- The icon grid grows into the space — ~8–9 rows visible instead of 3.
- Notes expands to fill the left column above the now bottom-pinned Global
checkbox, so the left side uses the height too.
- The image-mode preview circle is bigger (r44 → r56) and vertically centered,
with glyphs scaled to it.
- Footer pins to the bottom; Save/Add is now accent-colored so the primary
action reads above Cancel.
Notes/actionButton schema lookups that only fed the old fixed heights are gone.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Four refinements now that the two-column layout has room:
1. Image mode shows a large circular preview of the chosen picture (or a
placeholder circle with an add-photo glyph, or a broken-image glyph if the
file went missing), the filename, and centered Choose/Remove buttons —
instead of a bare button.
2. The live-preview avatar is larger (r20 → r26) and the address is now
middle-truncated (head + tail) so both ends read, like the real list row.
3. Badge mode shows the two actual chips — Z (shielded) and T (transparent) —
with labels, making clear the badge is auto-picked from the address type,
rather than a line of text.
4. The Badge/Icon/Image segmented control gains glyphs (badge / palette /
image) beside its labels via a small two-font inline control (the shared
SegmentedControl helper is single-font).
Adds contact_avatar_shielded / _transparent keys and rewords the badge hint
(the chips now carry the Z/T meaning); +8-language translations, all within
the existing CJK subset.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The edit dialog was a tall, narrow single column that wasted ~half the
horizontal space and cramped the icon grid into ~2.5 clipped rows crowding
the footer. Rework it into the portfolio-editor two-column shape:
- Card widened 560 → 880 logical.
- Full-width live preview stays on top (now shows more of the address).
- Body is two fixed-height columns: form (label / address+paste / notes /
global) on the left, avatar picker (segmented + icon grid / image / badge
hint) on the right, each filling its column so the grid gets ~6 columns and
~4 rows instead of clipping mid-row.
- Overall modal is much shorter, so Save/Cancel no longer crowd the grid.
- Badge-mode hint re-centered relative to the current cursor (it now sits
below the segmented control inside the shared column, not a fresh child).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The revamped edit dialog derives its input widths from the card content
width, so the address-input schema config is no longer read.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rebuild the add/edit contact dialog on the portfolio-editor design language:
- A live preview card at the top shows the contact exactly as it renders in the
list (avatar + name + address), updating as you type and pick an avatar.
- An avatar picker (Badge / Icon / Image segmented control) lets you keep the
default Z/T type badge, choose a Material wallet icon from a searchable grid,
or set a custom image. The picker area is fixed-height so the modal doesn't
jump when switching modes.
- Custom images go through a new in-app ImagePicker (image_picker.h): a
Material overlay that browses the filesystem starting at the user's Pictures
folder, shows a thumbnail grid (decoded to raw pixels, box-downscaled to a
small texture, cached per directory and freed on navigate/close, budgeted a
few decodes per frame so large folders don't hitch), and returns the chosen
path. The chosen image is copied into <config>/contact-avatars/ (named by an
FNV hash of the source path, so re-picking is idempotent) and stored as
"img:<path>". Like FolderPicker, it takes over the modal surface while open.
- Paste is now available on both add and edit; buttons are content-sized.
Adds three sweep surfaces (contacts-edit-{icon,badge,image}) that open the
dialog on a seeded contact in each avatar mode, plus i18n keys (+ 8-language
translations; reworded two zh/ja strings to stay within the existing CJK
subset, so no font rebuild).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an `avatar` field to AddressBookEntry ("" = default Z/T type badge,
"icon:<name>" = a Material wallet-icon, "img:<path>" = a custom image),
serialized additively in addressbook.json (only written when non-empty, so
existing books are untouched).
Render it in the Cards/List views via a new drawContactAvatar helper: custom
images are loaded once through a path-keyed texture cache and drawn
circular-cropped (centre-cropped UVs + a thin border ring); icons reuse the
project-icon set (incl. the special pickaxe font path) in a tinted circle;
everything else falls back to the existing Z/T badge (also the fallback when
an image fails to load or an icon name is unknown).
Seed one sweep contact with an icon avatar to exercise the icon-badge path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three interaction refinements to the address list:
- The globe badge now stays pinned far-right; the per-row copy/edit/delete actions
appear to its LEFT on hover/selection instead of replacing it.
- A left-click on empty space in the Cards/List area clears the current selection
(no row/action hovered -> deselect).
- Right-clicking a row (any view) selects it and opens a shared context menu
(Copy address / Edit / Delete), rendered once after the list.
Build + hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two follow-ups from the contacts audit:
- Per-row actions: in the Cards/List views, copy/edit/delete icon buttons now appear
on the right of a row on hover or selection (the globe badge shows otherwise). The
row Selectable uses SetNextItemAllowOverlap so the action InvisibleButtons take
click priority; whole-row hover (IsMouseHoveringRect) drives the highlight so it
survives hovering an icon; the delete icon turns red while armed (two-click confirm);
each has a tooltip. Trailing space is reserved so the text never reflows on hover,
and the cursor is restored after the manual action layout.
- Add/edit modal HiDPI: the Layout::kDialog* helpers fold dpiScale() (physical px)
while raw schema widths are logical, so the schema-path formW/actionW/actionGap/
notesH were unscaled vs their scaled fallbacks. Scale the schema-path values; and
since BeginOverlayDialog re-applies dpiScale to cardWidth, divide the already-scaled
dialogW back out (it was double-scaled). No visible change at 100%; correct at 150%.
Build + ctest + hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a persisted view toggle so the contacts address list can be rendered three
ways, moving it away from a bare data-grid toward a Material look:
- Cards: tactile rounded cards with a circular Z/T type-avatar, label over a muted
truncated address, trailing globe badge, primary-tint + outline selection, hover
fill, and a centred Material empty state.
- List: borderless two-line rows (same avatar + label/address) with hover tint and a
thin per-row divider — denser than cards.
- Table: the previous 3-column table, material-ized (grid borders dropped, row
backgrounds + interactive sort kept).
The toggle is an icon SegmentedControl (view-agenda / view-list / table-rows)
right-aligned on the toolbar; the choice persists via a new contacts_view_mode_
setting (0 cards / 1 list / 2 table, mirroring portfolio_style_). Cards/List sort by
label; Table keeps its sortable headers. All geometry is dpiScale()-aware; the globe
badge is drawn in the icon font in every mode. Default is Cards.
Build + ctest + hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address the confirmed findings from a workflow audit of the Contacts tab body:
- Label clip: the Label column was WidthFixed 150px and clipped long labels
mid-word ("drgx pool payout a…"). Make all three columns WidthStretch (label 1.5,
address 2.6, notes 1.0) so the label grows with the tab and the notes column no
longer reserves a fixed empty block on the right (notes = low weight, per request).
- Globe badge tofu: the global-contact ICON_MD_PUBLIC badge was drawn with the text
font (no Material glyphs) → rendered as "?". Push Type().iconSmall() around it.
- Plural: "1 addresses saved" -> add address_book_count_one ("%zu address saved",
8 langs, %zu kept so the format signature matches) and branch on count == 1.
- DPI: the SameLine badge gaps (6/8px) and the table-height floor (120px) are now
* Layout::dpiScale(); the WidthStretch columns are relative so need no scaling.
- Toolbar: give the four actions leading Material icons (person-add / edit / delete /
content-copy) and accent the primary "Add New" — via a small local icon+label
tactile-button helper (ImGui has no two-font button), icons inherit the
disabled-aware text alpha so they gray out with BeginDisabled().
Build + ctest + hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The QR popup used the same window-relative card width the Export-Key dialog just
moved away from (its comment even said "Match the key-export modal: 85% of the
window width"). On a large monitor that left the centered AddressCopyField floating
far from its left-aligned "Address:" label and inflated the QR's empty margins.
Derive the card width from the address field's own natural (chunked) box width
(mirroring the key-export fix, incl. the overlay card's 28px content inset so the
address stays on one line), bounded to 85% of the window as an upper limit. Also
scale the responsive-QR size literals (280 fallback / 420 cap) by dpiScale() so the
QR renders at the intended size on HiDPI/font-scale. No change to the QR texture /
clipboard logic.
Found via a workflow audit of all 27 redesigned modals for the same anti-pattern —
the QR popup was the only other occurrence (0 false positives).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Export-Key dialog sized its card to a fixed 85% of the window width. On a large
monitor that's ~1570px, but the content is ~900px, and AddressCopyField centers its
text-clamped box within the available width — so the address/key fields floated far
from their left-aligned "Address:"/"Viewing Key:" labels and read as off-center
(more visible after the warning box became a left-aligned DialogWarningHeader).
Derive the card width from the address field's own natural (chunked) box width —
the widest deterministic element before the key is revealed — mirroring
AddressCopyField's boxW math, plus the overlay card's 28px-per-side content inset so
the address sits on one line under its label. Adapts to address type (z vs t) and
font scale with no magic width; bounded to 85% of the window as an upper safety
limit; the revealed key+QR layout adapts to whatever width results. Width-only
change — the key fetch/wipe/mask/close logic is untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Register sweep surfaces for the redesigned Wave-2 modals so they get captured for
visual review: modal-shield + modal-merge (ShieldDialog's two modes),
modal-transfer (a z->t transfer so the converted deshielding DialogWarningHeader
renders), and modal-key-export (KeyExportDialog — named distinctly from the
settings modal-export-key). Each setup opens the dialog through its public show()
without clicking any button, so no async RPC fires; teardown closes it.
Adds a static hide() to ShieldDialog and KeyExportDialog for clean sweep teardown
(AddressTransferDialog already had close()). KeyExportDialog::hide() also clears the
revealed key + frees its QR, mirroring the dialog's own close/dismiss secret-wipe.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Presentation-only migration of KeyExportDialog::render (reveals a private or
viewing key) — a secret-handling modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth cardW,
idSuffix keyexport}) replacing the legacy positional overload
- replace the bespoke red-bordered "WARNING" box with material::DialogWarningHeader
(user-approved) so the key-export warning matches the import-key reference; passes
the correct per-key-type body key (private vs viewing)
- remove the two dividers (after the warning, above the Close footer)
- 4 StyledButton -> TactileButton (retry / reveal / show-hide / close)
The secret path is byte-for-byte unchanged: the z_exportkey/dumpprivkey/
z_exportviewingkey (and lite exportPrivateKeys) fetch + their secureWipeLiteSecret
calls, the "*"-masking when hidden, the Show/Hide toggle, copySecretToClipboard
(auto-clearing), the QR generate/cache/releaseQr, and the secret-clear on BOTH exit
paths (Close button + the post-EndOverlayDialog dismiss teardown).
Verified: build + ctest + hygiene clean; a 2-lens adversarial review
(secret-hygiene / presentation-fund) returned zero findings, proving every
secret-handling path diffs byte-for-byte identical to HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Presentation-only migration of AddressTransferDialog::render (drag-one-address-
onto-another transfer) — a fund-moving modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth 620,
idSuffix addrtransfer}) replacing the legacy positional overload
- the top-of-dialog deshielding warning -> material::DialogWarningHeader; the green
shielding notice stays colored text (it's a positive notice, not a warning)
- remove the footer divider and drop its "+1.0f" footerH reserve term (keep the
form->result-preview divider)
- reuse the existing shield_operation_id key for the "Operation ID: %s" label (no
new i18n key)
The fund path is byte-for-byte unchanged: the amountValid guard, totalDeduct/
newFromBal/newToBal math, maxSendableAmount, the Max button, s_app->sendTransaction
+ its ok/fail callback, and the BeginDisabled(!amountValid || s_sending) gate.
Verified: build + ctest + hygiene clean; a 2-lens adversarial review returned zero
findings, proving the fund path diffs byte-for-byte identical to HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Presentation-only migration of ShieldDialog::render (Shield coinbase / Merge funds)
— a fund-moving modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth win.width,
idSuffix shielddialog}) replacing the legacy positional overload
- drop the description divider and the form->footer divider (keep the post-submit
operation-id section divider)
- 3 StyledButton -> TactileButton (submit / cancel / check-status)
- TR() the hardcoded "No shielded (z) address yet …" note -> new shield_no_zaddr_hint
key, 8 langs (em-dash preserved); the "DRGX" ticker stays hardcoded per convention
The fund path is byte-for-byte unchanged: the can_submit guard, fee/utxo clamps, the
"ANY_TADDR" setup, the z_shieldcoinbase / z_mergetoaddress worker->post calls + their
opid/status/Notifications/trackOperation callbacks, and the BeginDisabled/tooltip logic.
Verified: build + ctest + hygiene clean; a 2-lens adversarial review
(fund-path-untouched / presentation-i18n) returned zero findings, proving the fund
path diffs byte-for-byte identical to HEAD via isolation diffs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Presentation-only migration of RenderSendConfirmPopup (the confirm-before-broadcast
popup) — a fund-moving modal:
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth popupW,
idSuffix confirmsend}) replacing the legacy positional overload; p_open stays
nullptr (the popup keeps its own Escape + Cancel handling)
- TR() the one hardcoded status string in the synchronous live-revalidation guard
("Cannot send now …") -> new send_cannot_send_now key, 8 langs (em-dash preserved)
The fund path is byte-for-byte unchanged: the live re-validation guard, the
app->sendTransaction call + its success/failure callback, the lite-locked
requestLiteUnlock branch, the s_sending/s_show_confirm state, the Escape handler,
and the FROM/TO cards + fee-tier/summary rendering.
Verified: build + ctest + hygiene clean; a 2-lens adversarial review
(fund-path-untouched / ui-i18n) returned zero findings, with the fund-path lens
proving the send path diffs byte-for-byte identical to HEAD.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two sweep-driven fixes:
- Validate Address: remove the input->results Separator. When no result is shown
yet it sat directly above the Close button and read as a footer divider,
inconsistent with the reference design (the description divider was already gone).
- Drop the modal-daemon-prompt sweep surface: renderDaemonUpdatePrompt is gated
behind !capture_mode_, so it never renders during a sweep (the capture showed the
Settings page behind it). Its migration stays verified via build + the shared
BlurFloat/TactileButton pattern.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Register sweep surfaces so the redesigned Wave-1 modals get captured for visual
review: modal-qr-popup, modal-request-payment, modal-validate-address,
modal-address-label, modal-daemon-prompt, and modal-antivirus (the last renders
only on Windows, where its #ifdef body compiles). Each is driven through the
dialog's public show()/flag; teardown closes it. Adds a static hide() to
RequestPaymentDialog / ValidateAddressDialog / AddressLabelDialog for clean sweep
teardown (they had no public close). The contact add/edit and explorer block-detail
modals are tab-embedded and not yet surfaced.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Presentation-only reference-design migration of three more modals:
- Validate Address: struct-form overlay (idSuffix validateaddr), 3 StyledButton ->
TactileButton, drop the description divider, and TR() the hardcoded "Error: %s"
(reuse the existing error_format key). The validateaddress RPC/worker path is untouched.
- Address label: struct-form overlay (idSuffix addrlabel); drop the footer divider and
zero its layout-reserve term (separatorH) so the fixed-height icon-picker card math
stays consistent. Buttons were already TactileButton.
- Contacts add/edit: struct-form overlay (keeps its idSuffix + BeginOverlayDialogFooter);
StyledButton -> TactileButton across the file (3 modal + 4 contact-list buttons) for a
consistent Contacts tab. The address-book add/update/notify logic is untouched.
Build + hygiene clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the two Receive-tab modals onto the settings-modal reference language
(presentation-only):
- struct-form BeginOverlayDialog(OverlayDialogSpec{BlurFloat, cardWidth, idSuffix})
replacing the legacy positional overload (idSuffix qrpopup / requestpay)
- StyledButton -> TactileButton (2 + 3 sites)
- drop Request-payment's description divider
No logic touched: the QR texture lifecycle, payment-URI builder, address combo,
and clipboard/notification calls are unchanged. The "DRGX" ticker stays hardcoded
per the existing codebase convention (7 sites, no currency TR key).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The miner and node updater dialogs were rebuilt around their own two-pane
tactile-Material version lists, which superseded release_list_view.h's
RenderReleaseList() picker. That function had zero call sites — both dialogs
include the header only for the shared ReleaseRow struct. Remove the dead
function (and the material/i18n/imgui includes it alone needed), leaving just
the ReleaseRow model, and correct CLAUDE.md's stale "shared picker" description
to reflect that the two-pane list is the browse-all UI and only the row model
is shared.
No behavior change: build + ctest + hygiene clean; grep confirms no remaining
RenderReleaseList references.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The settings-triggered BootstrapDownloadDialog wasn't captured (only the wizard's
bootstrap page was). Add BootstrapDownloadDialog::hide() + a modal-bootstrap sweep
surface (opens the confirm screen) so the migrated dialog is verifiable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1 (last low-risk modal). Replace the hand-rolled amber warning child-card
with the standard material::DialogWarningHeader (⚠ + Warning color, wrapping),
and center the Download / Mirror / Cancel button row. Already struct-form,
TactileButton, and i18n'd. Completes Phase 1.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1. Swap the 5 StyledButton actions (Open / Create / Scan folder / Reveal /
Close) to TactileButton, and remove the ImGui::Separator above the footer (the
reference has no divider). Already struct-form + fully i18n'd. Deferred: R6
glass-panel grouping of the scanned-folders section (it interacts with the
dialog's pre-computed cardHeight — a separate, more careful change).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1. DialogWarningHeader care header (was a hand-rolled red icon+text);
centered DialogActionFooter (Export + Close) replacing the StyledButtons + two
separators; on full success the saved path shows in a copy field with a green
check, partial/error render inline; export progress uses LoadingDots. Full i18n:
the previously-hardcoded notification + status strings now use TR (8 new
export_keys_* keys in en + 8 langs). Added hide() + a modal-export-all-keys sweep
surface. Also fixes a latent Begin/EndOverlayDialog imbalance on the 0-address
early return. No change to the export RPC logic.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 1 (first modal). Bring the CSV export dialog onto the key-import reference:
- Struct-form OverlayDialogSpec / BlurFloat (was the legacy positional overload).
- material::DialogActionFooter (centered TactileButton pair) replacing the
StyledButtons + the two ImGui::Separators.
- Full i18n: TR("close") and the success/error status now via TR (were hardcoded
English); on success the saved path shows in a widgets::AddressCopyField with a
green check.
- Add ExportTransactionsDialog::hide() + a modal-export-transactions sweep surface
so the dialog is captured (it wasn't before).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two issues from the audit:
- Cursor: hovering the modal showed the console's text-select cursor. The
output's mouse-interaction guard only suppressed itself under ImGui popups,
but this modal is a BeginOverlayDialog (not a popup), so the console kept
setting the text cursor underneath. Suppress output interaction while the
reference modal is open (!show_commands_popup_).
- Design: the two panes used hard 1px child borders and floated unpolished.
Restyle to match the portfolio/wallets modals — each pane now sits on a soft
DrawGlassPanel surface (borderless child + inner padding), category headers
are subtle accent labels (no heavy filled bars), and the selected/hovered row
gets a rounded Material fill instead of the sharp Selectable highlight. The
glass panes are theme-adaptive (light on light skins), so the command-name
blue and the mono example green are floored to readable contrast (FloorLight)
and the detail heading uses theme Primary().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The builder's "omit trailing optionals" loop stopped at the FIRST empty
optional, which discarded any later field the user had filled — e.g. setban
with bantime empty but absolute=true built `setban "ip" "add"`, dropping the
typed `true`, and (worse) left Insert & run enabled so it ran a command that
ignored the input.
Include fields up to the last one that is filled or required; only genuinely
trailing empty optionals are dropped. A gap (an empty field before a later
filled one) keeps its placeholder and marks the command incomplete, so Insert
& run disables and nothing typed is lost. Found by an adversarial pass over all
82 param templates.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The detail pane's Parameters section is now an editable form: each parameter is
a type-hinted input field (string / number / json, optional marked), and a live
"Builds" preview shows the command being assembled. String values are
auto-quoted; a required field left empty keeps its placeholder and disables
"Insert & run"; trailing empty optionals are omitted.
Insert and Insert & run now use the assembled command (not just the template),
so Insert & run works for parameterized commands once the required fields are
filled — destructive commands still confirm first. Param fields reset when the
selected command changes or the modal opens. Keyboard nav (Up/Down/Enter) is
scoped to the search box so typing in a field doesn't move the selection.
A best-effort parseParamSpecs turns the human-readable param templates into
fields; JSON-array params (z_sendmany, createrawtransaction) surface as a single
"json" field the user pastes into. Adds the console_ref_builds label + 8
translations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add plain-language details, a concrete example, and intent keywords to the
50 commands that Phase 1 left with only the terse description — so every entry
in the command explorer now explains itself and is searchable by task, not just
by name. Destructive flags are preserved.
Content was authored per-category and reviewed for technical accuracy (correct
RPC semantics, valid console example syntax, realistic DragonX placeholders).
Data-only change; command docs stay English (technical), consistent with the
existing policy.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the flat, terse command-reference table with a guided two-pane
explorer built for users who don't know the RPC commands:
- Search by name OR task: keyword synonyms let "balance" find getbalance,
"send money" find sendtoaddress; results are ranked (name-prefix > name >
keyword > desc).
- Two panes (mirrors the portfolio editor): a category-grouped master list
(mono names, a warning dot on consequential commands) + a rich detail pane
with a plain-language explanation, a per-parameter type breakdown (string /
number / json, optional dimmed), and a concrete example.
- Insert into console (fills the input with a template to review + run) and,
for no-parameter commands, Insert & run — with a confirm for destructive
ones. Deferred via pending_submit_ so the modal needs no executor.
- Keyboard: type to filter, Up/Down to move, Enter to insert; auto-focus and
auto-select the top result.
Data model: ConsoleCommandEntry gains details/example/keywords/destructive
(C++17 defaults, so the ~60 un-enriched entries are untouched); ~20 common
commands enriched and 18 consequential ones flagged. Command docs stay English
(technical); the UI chrome + 7 category names are translated into all 8
languages (CJK subset rebuilt, +3 glyphs).
Verified: two-pane renders on dark + light skins and at font_scale 1.5; detail
pane shows explanation/params/example; danger dots + safety badge on
destructive commands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Clicking a command now actually closes the modal. It called
ImGui::CloseCurrentPopup(), a no-op here (BeginOverlayDialog is Begin/
BeginChild, not an ImGui popup), so the picker stayed open covering the input
and the insert looked like it did nothing. Set show_commands_popup_=false.
- Search filter is now a member cleared when the modal opens, so it isn't stale
on reopen after an outside-click / command-click dismiss (was reset only by
the Close button).
- Esc dismisses the modal (this overlay has no built-in Esc handling), and the
search box auto-focuses on open so the user can type immediately.
- Scale the 2-vs-3 column breakpoint (cmd-min-width) by dpiScale() — it was a
logical-px threshold compared against a physical-px available width.
- Give each category's table a unique ImGui id (##cmdsN) instead of sharing
"##cmds", and translate the 7 category names (Control/Network/... — command
descriptions stay English) into all 8 languages; CJK subset rebuilt (+2).
(Investigated but rejected: the command-name blue was flagged as low-contrast
on light skins, but the modal card is a dark glass panel on every theme, so the
original color is correct — verified by rendering marble/light/color-pop-light.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- D2: rpcTraceTimestamp used std::localtime (process-wide static tm) guarded by
a private mutex that can't stop another thread's localtime from clobbering the
shared buffer. Use localtime_r / localtime_s into a local tm (the codebase
pattern) and drop the now-useless mutex. Runs on RPC worker threads.
- C1: the toolbar's "<N> matches" label read filter_match_count_ before
renderOutput recomputed it, so it lagged one frame. Compute the visible/
filtered set once at the top of render() (before the toolbar) and reuse it in
renderOutput, so the count and the output share one consistent computation.
- C4: the "N new lines" indicator counted the raw drain count, inflating it with
lines the active filter hides. Count only newly-added lines that pass the
current filter, so the badge matches what the user sees on jumping to bottom.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BuildConsoleRpcCall pushed any bare token that "looked numeric" as a JSON
number, and lost the tokenizer's quoting decision — so a genuinely-string
argument that happened to be all digits (e.g. a label) was sent with the wrong
type. std::stoll also silently truncated "1e999" to 1 (stops at the first
non-digit) and std::stod could yield inf/subnormal.
- ParseConsoleCommandArgsTagged returns {text, quoted}; quoted tokens are sent
verbatim as JSON strings, never coerced. (ParseConsoleCommandArgs kept as a
text-only wrapper so existing callers/tests are unchanged.)
- Bare tokens become a number only when the WHOLE token parses to a finite
int/double; otherwise they're sent as a string.
Adds test coverage for quoted-numeric-string, bare-number, 1e999, and 123abc.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the console-tab audit:
- HiDPI: the "New output" pill (140x24 + margin/rounding/border) and the JSON
indent-guide thickness were hand-drawn in raw px; scale them by dpiScale().
- Fold triangle: size it from the DPI/density-scaled gutter width (not the
zoomed font) and center it in the gutter band so glyph and clickable cell
stay aligned; only draw/handle it in the unfiltered view (folding_active_) —
in a filtered flat view a click silently flipped the collapsed flag with no
visible effect and the glyph disagreed with the rendered block.
- Selection: ignore left-clicks in the gutter (< output_origin_.x) so toggling
a fold no longer clears the user's active text selection.
- 'stop' confirmation: promote the function-local static to a member and reset
it in clear(), so a toolbar/context-menu clear between the two 'stop's can't
leave a stale arm that skips the shutdown warning.
- RPC-trace callback: dereference the console pointer under the mutex (not
after releasing it) so ~ConsoleTab can't destroy the object mid-call —
closing a latent use-after-free on shutdown (fires on RPC worker threads).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Persist the two console output display preferences and add a second toolbar
toggle for monochrome text.
- Persistence: new Settings fields console_line_accents / console_text_color
(both default true, matching the ConsoleTab statics so an upgrade re-save
can't flip visible behavior). Restored into the statics at startup in the App
constructor, and saved from a diff-and-save after the console renders (only
when a value actually changed — save() is disk I/O). The startup restore also
fixes a pre-existing bug: scanline was only synced from settings when the
Settings page or first-run wizard ran, so an existing wallet ignored a saved
scanline preference on launch.
- Monochrome text: a new toolbar toggle (FORMAT_COLOR_TEXT, dimmed when off)
next to the accent toggle. channelTextColor() early-returns the neutral
COLOR_RESULT for every channel (and JSON syntax role) when off, leaving the
left accent bars independent. COLOR_RESULT is contrast-floored per theme, so
text stays readable on light and dark terminals.
Adds console_toggle_text_color to the English source + all 8 translations.
Verified: both toggles independent, persistence round-trips across restart,
monochrome readable on light + dark skins.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a paint-bucket toggle button to the console toolbar (in the appearance
cluster next to zoom) that shows/hides the per-line left color accent bars for
a cleaner monochrome gutter. The icon reflects state (FORMAT_COLOR_FILL when on,
a dimmed FORMAT_COLOR_RESET when off) with a tooltip. Session-only, like the
console zoom factor; default on.
Adds console_toggle_accents to the English source and all eight translations
(no new CJK glyphs — the subset font already covers them).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>