Commit Graph

684 Commits

Author SHA1 Message Date
9c07b6d33d feat(settings): converge the lite Node section styling with full-node
Step 4. Bring the Lite settings section in line with the full-node one: its
subsection headers (Backup & keys, Security, Maintenance) switch from the Body2
type style to the accent Overline used by the full-node Node/Daemon/Security
subsections, and the standalone action buttons (Open data folder, Show seed,
Show private keys, Redownload blocks) become icon ActionButtons matching the
full-node button styling. The intricate secret-reveal / encryption flow and its
label-column input layout are left untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:56:23 -05:00
fedfe3d60c feat(settings): rebuild Node data-dir + Daemon binary on the new components
Step 3. Node "Data Dir" is now a clean key/value block: the directory on its own
row as a click-to-open accent link with a copy button, the wallet size below, and
the folder buttons on their own row (icon ActionButtons) — no more path
font-scaling or right-align overflow math. Daemon binary shows Installed / Bundled
as key/value rows plus a Success/Warning status chip (replacing the fragile
atom-by-atom wrapped status line); "Check for updates…" is now the accent Primary
action, and the maintenance actions are a separate row with Delete Blockchain
styled Destructive (Test/Rescan/Repair Secondary). All handlers, disabled-state
predicates and tooltips are preserved; the buttons wrap instead of overflowing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:53:28 -05:00
95ff9ce9ae feat(settings): shared design components + Wallet button hierarchy
Step 1: promote the polished chat-settings building blocks into reusable material
components (src/ui/material/settings_controls.h) — SettingsSubheader (accent
small-caps), SettingsRow (label left / control right), SegmentedControl (iOS
track+pill), and a tiered ActionButton (Primary=accent fill, Secondary=glass,
Tertiary=ghost, Destructive=error) with an optional leading Material icon, plus a
ButtonFlow that wraps rows of buttons.

Step 2: rebuild the Wallet BACKUP & DATA button wall on them. The 9–11 identical
buttons that were font-scaled onto one row are now tier-ordered, icon-labelled,
and wrap to new rows: the emphasized actions the user flagged — Import key, Seed
phrase, Wallets…, Download Bootstrap — cluster on top as accent Primary buttons;
common actions (viewing-key import, Backup, Migrate, Setup Wizard) are Secondary;
exports are low-emphasis Tertiary. All click handlers/tooltips and the migrate
"legacy wallet" glow are preserved. Removes the SetWindowFontScale legibility hack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 15:48:21 -05:00
203967411a feat(chat): theme the :drgx: emoji to the accent (was fixed brand colors)
The custom DragonX chat emoji now recolors to the theme like the logo — body =
accent, detail = white on dark skins / on-surface (dark) on light skins — and
re-rasterizes on a theme/dark-light change (moved out of the one-time fixed-color
load into ensureLogoTexture's re-render block). The detail highlights keep it
legible even on accent-tinted outgoing bubbles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 14:12:44 -05:00
3d0305a9ca fix(ui): darken the logo SVG detail on light skins
The SVG's white highlight (detail) washed out against a light card/background on
light skins — the dragon's wings/detail nearly vanished, leaving only the accent
body. Make the detail colour theme-aware: white on dark skins (unchanged), the
theme's on-surface colour (dark) on light skins, so the full mark reads on both.
Applies to the header logo and the balance coin icon; re-rasterizes on the
dark↔light flip (already tracked by the logo guard). The fixed-brand ":drgx:"
emoji is unaffected (its crimson body carries white detail fine on any bubble).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 14:09:03 -05:00
e7f38c2a45 fix(ui): theme the balance-card coin icon per skin too (not just the header)
The prominent DragonX mark on the Overview (next to the balance) is the coin /
currency icon (coin_logo_tex_), a separate texture from the header logo — and it
was still loaded from the fixed logo_dragonx_128.png, so it never recolored when
switching themes. Route it through the same themed SVG: ensureLogoTexture() now
rasterizes BOTH the header logo and the coin icon from logo_dragonx.svg recolored
to the theme accent, re-rasterizing both together on a skin / dark-light change
(coin done first, since the header branch early-returns on success). The old
one-shot PNG coin-logo load in render() is removed (superseded), the PNG remains
a fallback, and the coin texture is now freed on theme switch (was leaked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 14:02:13 -05:00
3a2be661ea feat(chat): custom DragonX emoji via the :drgx: shortcode
Add a DragonX custom emoji to chat. The emoji picker gains a DragonX tile (first
cell) that inserts the text ":drgx:"; message bodies containing it render the mark
inline via layoutChatBodyRich, which flows text words + the emoji image with word
wrap (plain bodies keep the tighter layoutChatBody path, so normal messages are
unaffected). Other clients simply show the literal ":drgx:" text — a portable
encoding with graceful degradation.

The emoji is the DragonX SVG rasterized once at fixed brand colors (crimson body,
white detail) via LoadTextureFromSvg — theme-independent so it looks identical for
sender and receiver — exposed as App::getDrgxEmojiTexture(). Emoji insertion is
factored into one insertToken() helper (space-prepend + on-chain byte cap), shared
by the DragonX tile and the Unicode emojis. Drag-to-select text is skipped on
":drgx:" messages (their inline layout doesn't match the plain-text hit-test
geometry); right-click "copy" still copies the whole message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 13:42:30 -05:00
17525003c5 feat(ui): themed DragonX logo — runtime-rasterized SVG recolored per skin
Replace the shared app logo (Overview header, first-run wizard, lock screen) with
the DragonX mark rendered from res/img/logos/logo_dragonx.svg, recolored to each
theme: the dragon body takes the theme accent (Primary()), the inner detail stays
light. Vendor nanosvg (memononen, zlib/public-domain) in libs/nanosvg/ and add
util::LoadTextureFromSvg (parse a copy, recolor shapes by luminance, rasterize to
RGBA, upload via the existing CreateRawTexture — GL + DX11). The SVG is embedded as
a string (src/embedded/logo_dragonx_svg.h) so it's available in every build.

The load moves into App::ensureLogoTexture(), called at the top of render() BEFORE
the wizard/lock early-returns (so those screens get the logo too, which they never
did before), and re-rasterizes when the accent or the dark/light variant changes.
The per-skin PNG remains a fallback if rasterization ever fails; logo-texture
lifetime is now freed on replace + on theme switch (was leaked).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 13:28:51 -05:00
267d839d5b feat(chat): add a dragon-themed emoji set to the picker
Adds 🐉 🐲 🐍 🦎 🐊 🐾 🥚 🪺 🏰 ⚔ 🛡 🗡 to the emoji picker, with DRGX-flavoured
keywords (the dragons are searchable by "dragon"/"drgx"). All are single-codepoint
emoji verified present in both the bundled monochrome NotoEmoji subset and the
color Twemoji font, so no font rebuild is needed and they render on other clients
(standard Unicode, UTF-8 in the memo, within the 236-byte cap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 12:45:49 -05:00
987d9b93c7 feat(history): badge chat transactions + add a Chat filter
Transactions that carried a chat message (sent or received, messages + contact
requests) now show a "Message" badge in the History tab, and a new "Chat" option
in the type-filter combo shows only those transactions.

Detection reuses what the chat store already tracks: ChatStore gains an inline
chatTxids() returning the set of on-chain txids that carried a chat message; the
tab builds it once per frame (cheap — O(chat messages)) and tests txid membership
for the badge and the filter. Empty when chat is disabled or the wallet has no
chat identity. Both variants populate the chat store before this tab renders, so
the same code serves full-node and lite with no separate handling.

The "Message" badge reuses the stacked top-pill slot (chat txs are send/receive,
not the autoshield "shield" type, so it and the "Shielded" badge are mutually
exclusive; chat takes precedence) in a distinct Secondary() colour. Guards the
summary-card accent idx_map so the new filter value (4) can't index it OOB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 01:43:55 -05:00
4c43f2e082 fix(chat): draw the jump-to-latest pill on top of the messages
The pill was drawn on the parent window's draw list after EndChild, but ImGui
renders a child window ON TOP of its parent — so the message text covered it.
Draw it instead on the thread child's own draw list, after the message loop
(later draw commands render on top), with a PushClipRect so the child's content
padding / scrollbar doesn't clip it. Clickability is unchanged (hand-drawn +
IsMouseHoveringRect / IsMouseClicked test the cursor directly), and it stays
pinned to the visible bottom-right corner via absolute screen coordinates.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-21 01:23:23 -05:00
57fa6470b7 feat(chat): thread UX — auto-scroll, selectable text, clickable Latest, emoji polish
Five message-thread improvements:
- Auto-scroll like the Console tab: new messages pin to the bottom only while
  the reader is already there; a wheel-up detaches (re-armed on return to the
  bottom, after a cooldown). A direct mouse-in-rect test (not IsWindowHovered,
  which is false while the composer holds focus) matches ApplySmoothScroll so
  scrolling up to read history while typing still detaches.
- The "Latest" jump pill is now hand-drawn (rect + IsMouseHoveringRect /
  IsMouseClicked) instead of ImGui::Button: it overlaps the thread child which
  owns mouse-hover there, so a real widget on the parent never got the click.
  Clicking it re-arms auto-scroll; its geometry is shared with the deselect
  guard so a pill click no longer drops an active selection.
- Sending a message closes the emoji picker (it takes over the conversation
  list) so the list reappears, and clears its search filter.
- Inserting an emoji prepends a space when the draft doesn't already end in
  whitespace (still respecting the on-chain byte cap).
- Message text is selectable by click-drag (chatBodyLines / chatBodyHitTest
  mirror layoutChatBody's wrap so highlight + hit-test align with the drawn
  text); while a range is active a copy button shows at the bubble's top on the
  opposite side and copies the selected substring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 23:04:26 -05:00
c68889d276 i18n(chat): translate chat note-buffer status strings (8 languages)
The status-bar chat-buffer indicator was hardcoded English. Add semantic keys
(chat_buffer_sending[_one]/preparing/loading/ready) to the English source and
route chatBufferStatusText() through TR()+snprintf like the other counted
strings — a singular/plural pair for the send count, %d/%d for the buffer
fill. Translate all five into de/es/fr/ja/ko/pt/ru/zh (additive JSON edits,
format specifiers preserved so the runtime overlay accepts them), and rebuild
the CJK subset font for the two new glyphs (버퍼's 퍼 U+D37C, 缓冲's 冲 U+51B2).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 22:22:59 -05:00
b0a4333cdf feat(chat): pre-split note buffer for rapid sends + status + console logging
Each chat message is a shielded tx that spends a verified note, so a burst of
messages hit "insufficient verified funds" once the single note is spent. Add a
per-frame note-buffer coordinator (both variants) that keeps ~10 verified
spendable notes: it serializes sends through the one broadcast channel, counts
verified notes by block depth (lite) or a rate-limited z_listunspent scan
(full node), self-splits in the background to refill toward the target, and
queues overflow to drain as change matures — with honest Sent/Failed status
instead of the prior optimistic "Sent". Guardrails: single split in flight with
a watchdog, cooldown, and session-generation guards so a wallet switch can't
drain another wallet's queue. Surface a "Chat buffer: N/10 ready" indicator in
the status bar while the Chat tab is active, and route chat diagnostics through
the console on both variants (the full-node console now drains the shared ring).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 22:00:00 -05:00
4471f54842 feat(chat): stamp sender compose-time in message header (clock-clamped)
Carry the sender's compose time as an optional "ts" (Unix seconds) in the
plaintext header JSON that rides outside the AEAD, and prefer it as the
displayed message time so both ends show the same send time regardless of when
the tx confirms. Parse "ts" leniently. On ingest, clamp: reject a "ts"
implausibly in the future vs the receive/block time (1h skew tolerated) so a
wrong/ahead peer clock can't pin messages to the bottom of a thread; a past
compose time is fine (the note buffer may broadcast a queued message later, and
a confirmed tx's block time is always >= compose time). Tests cover the
round-trip and the future-clock clamp.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:59:46 -05:00
e08121af2b feat(chat): redesign message composer — radial byte gauge, autogrow, wrap
Replace the plain "N/236" byte counter with a radial wheel drawn inside the
input, right-aligned, filling green→amber→red as the message approaches the
236-byte memo cap. Vertically center the text with left padding (FramePadding),
hard-cap typing at the cap (no silent overflow), add Shift+Enter for a newline
(via an always-callback, since ImGui 1.92's Enter shortcut needs exact mods)
while plain Enter still sends, animate the input growing taller as lines are
added, and word-wrap the display instead of overflowing left. Give a hard
newline a touch more space above than a soft wrap in the message bubble.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 21:59:37 -05:00
31ebfc782e style(theme): make Jade's gold vein follow rounded corners
Jade's edge-trace hero hand-walked the panel perimeter as straight line
segments, so rounded corners chamfered into flat polygons. Retire it in
favour of the gradient-border effect, which draws via AddRect and hugs
the real corner arcs.

- theme_effects: drawGradientBorderShift gains phaseOffset + alphaMul;
  drawPanelEffects now draws it on every glass panel (position-phased so
  panels drift like veins at different depths, 0.6x alpha vs the active
  nav button), gated behind a new opt-in gradient-border-panels flag so
  button-only themes (Obsidian) are unaffected.
- jade.toml: edge-trace off; gradient-border-panels on, speed 0.10,
  jade->gold; jade motes + viewport wash/vignette kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 00:24:36 -05:00
f3776bcbe5 feat(theme): add the Jade skin background asset
The 1024x1024 jade marble texture referenced by res/themes/jade.toml,
committed on request so the Jade skin ships with its background instead
of falling back to the programmatic gradient. Sits alongside the other
tracked theme backgrounds in res/img/backgrounds/texture/.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 19:34:25 -05:00
38f2aa2f8f fix(ui): frost the chat + contacts panes with acrylic blur + padding
The chat and contacts panes drew flat ImGui fills (ChildBg / default
WindowBg / NoBackground), so they never sampled the acrylic blur and
showed the raw backdrop texture. Route them through DrawGlassPanel --
the pattern the peers_tab sibling and every other tab already use:

- chat: frost the conversation-list + thread panes and the composer
  input box, and add inner padding so content doesn't hug the glass edges
- contacts: frost the card/list container AND the table view, padding both

Padding needed ImGuiChildFlags_AlwaysUseWindowPadding -- a borderless
child silently ignores WindowPadding otherwise (documented gotcha in
daemon_download_dialog.h). BeginTable can't take that flag, so the table
view is inset within its glass panel instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 19:32:11 -05:00
40f8425d94 feat(theme): add the Jade skin (dark jade-green + gold veins)
A new bundled skin driven by the existing jade_bg.png background: deep
green surfaces, jade-green primary accents, and a soft gold secondary
echoing the stone's veins, plus a jade specular-glare + jade-to-gold
sidebar-border effect. SkinManager auto-discovers it (no C++/CMake
changes) and it appears in Settings -> Appearance as "Jade".

The background asset (res/img/backgrounds/texture/jade_bg.png) is a
binary handled separately; until it lands in the repo the skin falls
back to the programmatic gradient on other machines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 19:32:02 -05:00
2157a30192 fix(tests): guard updater asset-selection index reads against OOB
testXmrigAssetSelection / testDaemonAssetSelection index rel.assets[i]
right after EXPECT_TRUE(i >= 0), but select*Asset returns -1 on no match
and this harness's EXPECT doesn't abort -- so a fixture/parser regression
would read rel.assets[-1] and SIGSEGV the whole suite instead of
reporting the failed EXPECT. Gate each index read behind `if (i >= 0)`
(same pattern as the AddressBook config-dir fix). No behavior change on
the valid checked-in fixtures; ctest passes 100%.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 13:39:04 -05:00
bef3c0c47d fix(tests): use the per-variant config dir so the lite ctest doesn't segfault
testAddressBookScope hardcoded ".config/ObsidianDragon" for its
pre-scoping addressbook.json, but AddressBook::load() reads the
per-variant config dir (Lite -> ObsidianDragonLite/). In a --lite build
the write and read diverged, so load() found nothing, size()==0, and the
following entries()[0] read out of bounds -> SIGSEGV, taking the whole
suite down.

Write to dragonx::util::Platform::getConfigDir() (the same path load()
uses, resolved under the test's temp HOME) and guard the entries()[0]
access so a non-aborting EXPECT can never SIGSEGV the suite. Verified:
ctest now passes 100% in BOTH the full-node and --lite builds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 12:28:04 -05:00
5c570613c8 feat(console): lite backend command-reference modal
Give the lite console the parity analog of the full-node RPC command
reference: the same searchable two-pane modal (browse/search, detail
pane, examples, Insert / Insert & run, destructive confirm), driven by
the lite backend's own command set instead of daemon RPC.

- ConsoleCommandExecutor::commandReference() returns the category table
  (full node = consoleCommandCategories(); lite = new
  liteConsoleCommandCategories() -- 25 backend verbs in 5 categories).
  The shared renderCommandsPopup reads the table from the executor.
- The Commands button now shows for both variants (gated on
  commandReference()!=nullptr); title/tooltip/arg-quoting branch on
  hasRpcReference() (clarified to mean "speaks JSON-RPC / full node").
- Lite args are bare tokens (the backend takes one unsplit arg string
  and does not strip quotes), so the param-builder's JSON string
  auto-quoting is disabled for lite -- Insert & run emits runnable bare
  commands. send uses the JSON-array form (its positional form is
  unreachable via the single-arg transport); new uses zs/R; import
  takes just the key (the backend hardcodes birthday=0).

Adds 7 i18n keys across all 8 languages (additive). Full-node behavior
is unchanged. Adversarially reviewed (data vs the backend registry,
plumbing/regression, i18n) with all findings fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 12:27:51 -05:00
567732206e fix(console): localize the lite console + drop full-node-only wording
The lite console tab lagged the full-node one on i18n: its toolbar status,
status lines, help, and error/warning strings were hard-coded English or
worded for a full node (a "daemon" the lite wallet doesn't have).

- toolbarStatus()/statusLines(): route through TR() (reusing the existing
  lite_net_* keys where they already map)
- printHelp(): enumerate the 25 pass-through backend verbs for discoverability,
  since the C++ tab intercepts `help` before the backend's own HelpCommand runs
- gate the destructive 'stop' confirmation to the full node (hasRpcReference);
  lite has no node, so it lets 'stop' fall through to the backend instead of
  showing a phantom "shut down the node" warning behind a dead gate
- word the not-connected error per variant (daemon vs "no wallet open")
- TR() the "(no output)" command-result fallback

Adds 7 i18n keys across all 8 languages (additive); rebuilds the CJK subset
for the one new glyph (U+C5D4 for the Korean "backend"). Build + ctest +
source-hygiene green; changes adversarially verified (no logic/i18n defects).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 20:10:17 -05:00
0dcc09fc5f fix(console): DPI-scale the toolbar status-dot offsets
The status indicator's left inset (+2) and dot-to-text reservation (+6) were raw pixels while the dot radius is DPI-scaled, so the dot shifted a couple native px at HiDPI. Multiply both by Layout::dpiScale() per the project's hand-drawn-geometry rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 18:03:11 -05:00
82d3178119 fix: use reentrant localtime_r/localtime_s off the worker thread
std::localtime shares a process-wide static tm; both sites are reachable from background threads (the RPC price-parse on the worker thread, and Logger::write from worker/monitor threads), so a concurrent localtime call could clobber the struct between the call and the read. Copy into a local std::tm via localtime_r / localtime_s, matching the codebase's existing convention (console_tab rpcTraceTimestamp, app_network, etc.). From the console-tab audit; benign (garbled/stale timestamp only), no crash or state impact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 18:03:10 -05:00
7d47c6e14e fix(console): balance colour-toggle push/pop so it stops drawing a red error rect
The accent + text-colour toggle buttons pushed/popped ImGuiCol_Text guarded by a flag the TactileButton flips in between, so a click left the colour stack unbalanced for that frame — and ImGui's error recovery drew a red rectangle around the console window (imgui.cpp:11727). Capture the flag into a local before the button and guard both the push and the pop with it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 13:39:42 -05:00
973bc0d338 feat(market): trend sparkline in all portfolio styles + inline chart/style pickers
Render the portfolio trend sparkline in every row style, each with a layout that features it:
- Table: a dedicated header-labelled TREND column (48dp rows), aligned across rows.
- Cards: a left-aligned info stack (icon + label / value+delta chip / DRGX) with a full-height sparkline filling the rest of the width (content-driven, so short groups give the chart more room).
- Spotlight: a big-value-over-a-full-width-chart-band hero tile (92dp).
Default sparkline interval is now MONTH (a real curve from the daily series, not the young minute buffer). Removed the per-row container accent line (redundant with the icon/sparkline colour) and the per-group shielded/transparent bar (private-by-default makes it near-always 100%, and dropped its now-dead SumPortfolioSplit helper). DPI-scale the sparkline stroke. Adds the "Trend" column string across 9 languages.

Relocated the Market controls out of a removed settings modal + gear: the chart line/candle picker now sits top-right of the chart (left of the trade button, candle-capable ranges only), and the Table/Cards/Spotlight picker sits left of Manage. Dropped the market fetch-prices toggle (still in the general Settings page).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 13:39:42 -05:00
e4c9b98ca2 feat(market): trend sparkline in all portfolio styles + fix candlestick availability
Two coupled Market-tab changes (same file):
- Sparkline in every style: render the per-entry trend sparkline in Table (a reserved trend column, aligned under the column header, reserved only when at least one group opts in so the dense grid isn't padded otherwise) and Cards (a filled strip on the right, above the full-width Z/T bar), not just Spotlight — so the per-entry 'show sparkline' setting is honored in all three views. DPI-scale the sparkline stroke (1.2f/1.4f * dp), from the sparkline audit's L1.
- Fix the candlestick toggle intermittently missing: the Market tab never triggered the per-exchange OHLC fetch on its own (only a pair-chip / refresh click did), so candlesticks were unavailable on a fresh tab open. Call refreshExchangeChart() every frame (self-throttled + in-flight-guarded — its own doc comment already says 'safe to call every frame'), which also fixes the new pair's candles never loading if you switch pairs mid-fetch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 00:56:06 -05:00
1c38f68781 fix(market): HiDPI-scale hand-drawn geometry + dedup row/column helpers
Market-tab audit follow-up (all 9 confirmed findings):
- Scale the unscaled corner radii on the Cards/Spotlight row rects, the address-preview glass panel, the chart Y-axis labels, and both chart hover tooltips by dpiScale() — they mismatched the coincident already-scaled rects on HiDPI (the CLAUDE.md hand-drawn-geometry rule).
- Share the portfolio row-height/gap formula (pfRowHeight/pfRowGapFor) and the TABLE column widths (kPfValColW/kPfDrgxColW) between the draw loop and the scroll-region height budget so they can't drift and clip.
- Derive the portfolio total from the shielded/transparent split (one address scan instead of two in Cards).
- Build the address-picker selected-set once (unordered_set) instead of a linear PortfolioEntryContains per sort-compare and per row.
No correctness/security/threading defects were found in the audit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 23:00:02 -05:00
818c29fca7 feat(market): settings gear, options modal & distinct portfolio styles
Add a settings-gear modal (portfolio layout / chart style / fetch prices) and persist the portfolio style. Redesign the three row styles into distinct purposes: Table (borderless grid + column header + % pill), Cards (glass card + muted label/shadow value + shielded/transparent split bar), Spotlight (heavy tile + enlarged direction-coloured hero value + delta chip). Dim empty groups. Add data::SumPortfolioSplit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 22:35:03 -05:00
0a43742897 feat(chat): side-by-side settings modal with live conversation preview
Widen the chat settings modal and split it into a live message preview (canned bubbles rendered with the real style/accent/density/font/emoji/timestamp code) beside the controls; size the Done button to its text.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 22:35:03 -05:00
defe14d913 feat(contacts): avatar shapes, list scale & live settings preview
Add a settings-gear modal with a segmented avatar-shape picker (circle / rounded square / full-row left tab), a list-scale slider, and a live two-row preview. Smooth-scroll the address list and fix a latent wheel double-scroll on the outer scroll child. (settings.h also refreshes the portfolio-style doc comment.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 22:35:03 -05:00
8b55a90102 i18n: contacts/market settings strings + portfolio-style rename
Add contacts_shape_tab, the market settings-modal + column-header keys; rename the portfolio-style labels to Table / Cards / Spotlight (same keys, new values). Rebuild the CJK subset for the new glyphs. All 8 languages, additive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 22:35:02 -05:00
0fd3d214a4 fix(ui): clip & left-align overflowing SegmentedControl labels
material::SegmentedControl drew each label centred with no clip, so long non-English labels (Russian/German, etc.) overflowed their cell and overlapped neighbours. Left-align on overflow and clip per cell (mirroring the chat modal's segmented helper); the fits-case is visually unchanged. Benefits every caller (contacts, receive, wallets, market).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 22:35:02 -05:00
996e10ed02 fix(ui): stop forward-wheel double-scroll on peers & console tabs
The inner scroll child (##PeersList / ConsoleOutput) uses NoScrollWithMouse + ApplySmoothScroll; ImGui forwards its wheel up to the outer scroll container, so the wheel scrolled both. Add NoScrollWithMouse to the outer containers (safe: both fill the remaining height and never overflow), matching the contacts/explorer fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 22:35:02 -05:00
2c909d35ea feat(chat): message-list rework, customization surface, smooth scroll
Chat tab overhaul:
- Header/layout: single-row header (name + key-lock + compact click-to-copy address +
  right-aligned icon toolbar with a settings "notch"), composer moved below the message
  box (emoji toggle left of a bottom-flush input), tightened list-pane controls.
- Message list: per-day date separators (Today/Yesterday/date), time-only group headers,
  tight same-sender grouping with iMessage-style merged corners, per-run peer avatar,
  delivery status (clock -> check), hover-reveal per-message time. Message base ~18px.
- Customization: a settings "notch" gear opens a modal (also under Settings ->
  Chat & Contacts) with segmented controls for emoji style / bubble style / density /
  timestamps, sliders for poll rate + text size, a bubble-accent dropdown, Enter-to-send.
  Bubble style/accent/density/text-size/timestamps all applied live in the message loop.
- Settings: app-wide clock-format control lives in Settings -> General; chat keeps a
  per-tab override. Both chat panes now use the app's smooth wheel-scroll.
- i18n: new strings across all 8 languages; CJK subset rebuilt for the added glyphs.

Inline contact rename, hide/mute, 0-conf fast-scan and the export path are carried through.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:45:19 -05:00
3b5db02e09 feat(app): font-atlas rebuild hook + clock/emoji sync
- requestFontRebuild() + preFrame handling so toggling color emoji swaps the atlas live;
  request a rebuild at startup when the saved setting wants color.
- preFrame syncs Typography's color-emoji flag and the util clock flag from settings.
- Chat 0-conf fast-scan cadence now reads the user's poll-rate setting (was a hardcoded 2.5s).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:44:55 -05:00
8d8cd337cf feat(util): app-wide 24h/12h clock format
Add util::formatClockDateTime/formatClockTime driven by a process-wide flag (setClock12h,
synced from the time_format setting each frame). Switch the primary user-facing timestamp
displays to it — the transaction list, wallet-state tx + banned-peer times, the explorer
block time, and the block-info dialog — so one preference drives every clock.

Log files, export filenames/content, console line prefixes, the market chart axis, and the
worker-thread "last updated" string intentionally stay fixed 24h.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:44:44 -05:00
acde8c0833 feat(config): chat-customization + app-wide clock settings
Persisted (additive JSON, clamped on load) preferences backing the new chat settings
surface and the global clock:

- chat: emoji style (color default), poll rate, bubble style + accent, message density,
  text scale, per-tab timestamp override, Enter-to-send.
- app-wide time_format (0=24h, 1=12h) that the Chat tab falls back to and every other
  user-facing timestamp now follows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:44:29 -05:00
a78b44246e build(freetype): color-emoji rendering via a cross-platform FreeType backend
The chat "Emoji style: Color" option renders the merged emoji in color (COLR/CPAL
Twemoji) instead of the monochrome NotoEmoji subset. This needs FreeType, which the
default stb_truetype rasterizer can't do for color glyphs.

- Vendor imgui_freetype (matches the bundled 1.92 ImFontLoader API) and embed a 1.4 MB
  COLRv0 Twemoji font (no libpng/harfbuzz needed).
- CMake gains an optional FreeType path: native Linux/macOS use the system FreeType via
  find_package; the mingw-w64 cross-compile has none, so build.sh --win-release now
  cross-builds a minimal static FreeType (scripts/build-freetype-mingw.sh) and passes it
  in. Absent FreeType => graceful monochrome fallback, so no build breaks.
- Typography selects the FreeType loader + the color font (LoadColor) when color emoji is
  on, else the stb loader + mono subset; toggling reloads the atlas. Both the DX11 and
  OpenGL backends already support the 1.92 RGBA dynamic atlas, so color glyphs render.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 18:44:19 -05:00
63ed31d2ec feat(chat): polish thread header, emoji picker, and pane gutter
Tier A + B chat-tab visual pass:
- Widen the list<->thread gutter (1px -> 10px) for breathing room.
- Redesign the thread header: avatar + name on the left with a
  right-aligned frameless icon toolbar (add-contact/export/mute/hide,
  each with a tooltip); the name is clipped to the toolbar's left edge
  so a long contact label can't overrun the icons.
- Replace the "Copy Full Address" button with a click-to-copy shortened
  address (copies the full z-addr), preceded by a key-verify lock whose
  tooltip shows a comparable identity-key fingerprint.
- Show a "Waiting for reply" chip in the header when the peer's identity
  key isn't known yet.
- Emoji picker: frameless grid with tight cells (glyphs fill the cell)
  and leftover width spread into the column gaps so it stays edge-to-edge.
- Larger 30px composer emoji toggle that lights up while the picker is
  open; footer height + byte-counter centering adjusted to match.

New i18n keys (chat_copy_address_tip / chat_verify_key / chat_awaiting_key)
added additively to all 8 languages; CJK subset font rebuilt for the new
zh glyph.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:14:44 -05:00
64b27db2ff feat(chat): emoji picker as an in-pane overlay with keyword search
Reworked the emoji picker from a floating popup (which overlapped the message
area) into an overlay that takes over the conversation-list pane while open:
a Cancel button + a keyword search box at the top, then a responsive grid below
that wraps to the pane width.

Each emoji now carries search keywords (grin/heart/fire/…) so the search box
filters the ~150-emoji set. The 🙂 composer button toggles the overlay;
selecting an emoji appends it to the composer (byte-cap respected) and the
overlay stays open for multiple picks.

+1 CJK glyph (絵) for the ja "Search emoji" string.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 08:06:37 -05:00
03fe6e077c feat(chat): emoji picker + show-hidden toggle
- Emoji picker: a 🙂 button on the composer row opens a scrollable grid of ~150
  common single-codepoint emoji (all verified present in the bundled NotoEmoji
  subset); clicking appends to the composer, respecting the byte cap. ImGui does
  no shaping, so the set is single-codepoint only (ZWJ sequences / flags omitted).
- Show hidden: when there are hidden conversations, a "Show hidden (N)" toggle
  appears in the list; toggling it reveals them (dimmed) and the thread header's
  Hide button becomes Unhide. Off by default, and resets when nothing is hidden.

8-language strings; no new CJK glyphs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 01:08:35 -05:00
125ffa8863 feat(chat): dedicated ~2.5s poll for the 0-conf fast-scan
The fast-scan was hung on the page's Transactions timer, which is 10–15s on most
pages (15s on Chat), so incoming messages still took up to ~15s to appear. Give it
its own ~2.5s accumulator (delta-time based, independent of the page cadence) so
messages land in a few seconds — network propagation then dominates, which is as
fast as 0-conf gets. Still gated behind the warmup/rescan block and self-gated in
fastScanChatMemos; the in-flight guard prevents overlapping RPCs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 00:58:42 -05:00
3465acc57b fix(chat): session-guard the fast-scan callback (stale-wallet ingest)
Review of 30bd0d9 found the 0-conf fast-scan MainCb was the one async chat
callback missing the chat_session_generation_ guard the broadcast + identity-fetch
callbacks use. worker_ survives a wallet switch/lock, so a fast-scan posted under
wallet A could drain after resetChatSession() and ingest A's metadata (or toast)
against wallet B's freshly-provisioned store.

- Capture scanGen at post time and drop the result if it changed by drain time.
- Clear chat_fast_scan_in_flight_ in resetChatSession() so a switch immediately
  re-enables the fast path; the stale callback returns WITHOUT clearing the flag so
  it can't clobber the new session's own in-flight scan (generation is bumped only
  in resetChatSession, which already reset the flag).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 00:50:58 -05:00
30bd0d99ff feat(chat): 0-conf fast-scan so incoming messages show before a block
The receive harvest gates each z-address behind scannedAtTip — it only re-scans
when a new block advances the tip — so incoming chat waited ~1 confirmation even
though the daemon already exposes mempool notes (FindMySaplingNotes runs on
mempool txs; z_listreceivedbyaddress(addr,0) returns them).

Add App::fastScanChatMemos(): every transaction-refresh cycle, re-scan JUST the
chat reply address (where peers send) at minconf=0, extract chat metadata, and
ingest — so messages surface at mempool speed (a few seconds) instead of waiting
for a block. Full-node only (lite has its own harvest). An in-flight guard avoids
stacking RPCs; the store dedups on txid+position, so the confirmed harvest never
double-inserts.

Hidden conversations are deliberately skipped by the fast path — they don't get
the mempool speed-up and still come back through the normal confirmed harvest
(which un-hides on a new message). New non-muted messages toast off-tab as usual.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 00:38:41 -05:00
29fbe46cce fix(chat): un-hide on lite receives + scope the contact picker per wallet
Review of fc414ee found two issues:

- HIGH: the lite variant harvests chat via ingestLiteChatMemos, which called
  ingest() without the newIncomingCids out-param — so the un-hide never ran and a
  hidden conversation stayed hidden PERMANENTLY on new messages (chat is default-ON
  and fully supported in Lite), breaking the "a new message brings it back"
  invariant. Wire the same un-hide + off-tab toast into the lite path.

- LOW: the new-conversation contact picker listed every z-address contact,
  ignoring the per-wallet scope the Contacts tab enforces — leaking another
  wallet's scoped contact into this wallet's picker. Apply the same scope test
  (global + legacy fail open; "w:" scopes match the active wallet).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 23:36:14 -05:00
fc414eeed4 feat(chat): hide conversations + contact address picker for new conversation
Hide conversations:
- A "Hide" action in the thread header drops a conversation from the list. The
  messages stay in the seed-encrypted store (on-chain history can't be deleted);
  a new INCOMING message un-hides it (you can't un-receive), so nothing is lost.
- Hidden cids persist in settings (mirrors the mute list) and are skipped by both
  the conversation list and the unread badge.

New-conversation address picker:
- A "Choose from contacts" dropdown lists the address book's shielded (z-address)
  contacts and fills the recipient field on selection; manual paste still works.

8-language strings + CJK subset (+1 glyph 届).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 23:26:04 -05:00
fce873936e fix(chat): demo identity must not clobber a real one or be a shared constant
Root cause of the reported "my own messages come back as replies": seedChatDemoData
(the Settings "Seed demo chat" debug button) set the chat identity to a FIXED
secret ("obsidian-dragon-demo-chat") and, because maybeProvisionChatIdentity
no-ops once any identity exists (app_network.cpp:2771), that demo identity stuck
and overrode the wallet's real seed-derived one. Clicking it on two different-seed
wallets gave BOTH the same constant identity — so they were cryptographically the
same person, and a wallet's own outgoing memo (harvested) decrypted back as an
incoming message.

Two rules now:
- Only fabricate a demo identity when there is NO real one (never clobber a
  provisioned wallet identity).
- Derive it from a RANDOM per-run secret, so it can never be a constant shared
  across installs/wallets.

Complements 7351d8a (which removed the self-harvest path); together they close the
loopback both at the harvest and at the identity source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 22:41:43 -05:00