Commit Graph
100 Commits
Author SHA1 Message Date
DanSandClaude Opus 4.8 806c9be965 fix(rpc): detect mid-session disconnects and stop blocking the UI thread
The connection state machine never tore down on a lost connection: refresh-loop
RPC errors were swallowed, rpc_->isConnected() stayed true after a daemon
crash/restart/socket drop, and the UI showed stale balances with no reconnect.
Several operations also ran synchronous curl straight from ImGui handlers.

- Add handleLostConnection(): after N consecutive cycles where BOTH core RPCs
  fail (warmup excluded, so no reconnect loop), disconnect so update()'s
  reconnect branch re-enters tryConnect().
- Move banPeer/unbanPeer/clearBans and key export/import onto the worker thread
  (import requests a rescan that could freeze the UI for the curl timeout).
- Run the block-info dialog's two chained RPCs on the worker thread (+ guard the
  getblockhash result type).
- Detect daemon warmup via the JSON-RPC -28 code (new RpcError carrying the code;
  message text preserved so 401/warmup string-matching is unaffected), and widen
  CONNECTTIMEOUT to 10s for remote/TLS hosts (2s localhost).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 14:17:17 -05:00
DanSandClaude Opus 4.8 055306fc65 fix(tx): track async operations to completion (send/shield/auto-shield)
z_sendmany returns an opid immediately; the tx is built/signed/broadcast
asynchronously afterward. The send path showed "Transaction sent successfully!"
and cleared the form on opid receipt, so a later async failure contradicted it.
Shield/merge stored the opid only in a dialog-local static (never polled), and
auto-shield ran a blocking z_shieldcoinbase on the UI thread and discarded its
opid — async failures of all three were silently lost.

- Add App::trackOperation(opid) so shield/merge/auto-shield register with the
  shared opid poller (failures surface, balances refresh on completion).
- Defer the full-node send's success/failure to the poller via per-opid callbacks
  (parseOperationStatusPoll now exposes failureByOpid); the "Sending..." spinner
  covers the finalizing window, and the form is kept until terminal status.
- Dispatch auto-shield through the worker thread and use the configured fee.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 14:16:48 -05:00
DanSandClaude Opus 4.8 9b17939669 fix(persistence): atomic + owner-only settings/address-book writes
settings.json and addressbook.json were written in place with a bare ofstream —
a crash or power loss mid-write truncated the file, and on the next launch the
parse failure silently reset every preference (hidden/favorite addresses, labels,
pool workers, language, theme, lite-server list) because the next save overwrote
the corrupt file with defaults.

Add Platform::writeFileAtomically() (temp file -> fsync -> atomic rename; dir
fsync on POSIX, MoveFileEx on Windows; optional owner-only 0600) and route both
saves through it. On a parse failure, quarantine the unreadable settings file to
settings.json.corrupt-<ts> instead of clobbering it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 14:16:10 -05:00
DanSandClaude Opus 4.8 a0ebbd68cc fix(send): pass the user-selected fee to z_sendmany
The full-node send built the recipients array and called z_sendmany with only
(fromaddress, amounts) — dropping the minconf and fee positional args. The whole
fee-tier UI (Low/Normal/High, send-max math, the confirmation fee) was collected
and shown but never sent, so the daemon silently applied its own default fee and
the Low/High tiers were cosmetic.

Pass {from, recipients, 1, fee}, with the fee formatted fixed-decimal so the
daemon's ParseFixedPoint accepts it (a small double like 0.00005 would otherwise
serialize to "5e-05" and be rejected).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 14:15:58 -05:00
DanSandClaude Opus 4.8 4de81679bf feat(lite): show connection + sync status in the Network tab
Add a status panel at the top of the Network tab driven by the live WalletState:
- Connection: a colored dot + Connected / Syncing / Not connected, with the in-use server host
  (or "Random server") and its latency on the right.
- Sync: "<pct>%  ·  <walletHeight> / <chainHeight>" while syncing (with a thin progress bar),
  "Synced · block N" when complete, or "No wallet open" when disconnected.

Reads app->state().sync (populated by the lite refresh: progress / wallet+chain height / complete)
and state().connected (= walletOpen). Advances with a Dummy so the bounds grow correctly.

Both variants build; suite passes; hygiene clean; lite GUI smoke OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:41:55 -05:00
DanSandClaude Opus 4.8 35cf423b3a fix(lite): grow the Network tab scroll region with a Dummy (ImGui layout)
Each server card advanced to the next via a bare SetCursorScreenPos, which ImGui won't use to
extend the scroll region's content height ("Code uses SetCursorPos() to extend window boundaries
... submit an item e.g. Dummy() afterwards"). Beyond the warning, this meant cards past the fold
wouldn't scroll. Advance with an ImGui::Dummy(cardW, gap) below each card so the content height
grows correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:34:26 -05:00
DanSandClaude Opus 4.8 592ad4fef6 feat(lite): ObsidianDragonLite Network tab — server browser
A lite-wallet-only "Network" tab (full-node keeps the Peers tab; exactly one shows per variant)
to manage lightwalletd servers, replacing the basic selector that was in Settings.

- Card list of servers with per-server latency + status dot, DNS host + resolved IP, and an
  Official/Custom pill. Official DragonX servers get a glowing outline.
- Pick a server (Sticky) by clicking its card, or toggle "use a random server" (Random mode);
  selection applies immediately (App::rebuildLiteWallet(force=true) tears down + rebuilds the
  controller against the new server and resyncs — its dtor detaches the uninterruptible sync
  thread, so this doesn't block).
- Add custom servers; hide/unhide servers (persisted set, revealed by a "Show hidden" toggle).
- Latency/IP come from a new background probe (util/LiteServerProbe): libcurl CONNECT_ONLY does
  the TCP+TLS handshake (works for gRPC lightwalletd, no HTTP response needed), recording
  APPCONNECT_TIME as latency and CURLINFO_PRIMARY_IP. Auto-runs on tab open + a Refresh button.

Wiring: WalletUiSurface::LiteNetwork (gated !fullNodePagesAvailable) + NavPage::LiteNetwork in
the sidebar + app.cpp dispatch; settings gains a hidden-servers set; isOfficialLiteServer() added
to lite_connection_service. The Settings page lite-server selector + its plumbing are removed
(single source of truth = the tab).

Reuses the existing server model (LiteServerPreference, Sticky/Random, selectLiteServer) and UI
primitives (DrawGlassPanel, ThemeEffects glow, peers-tab ping-dot idiom). Unit-tested
(liteServerHost, isOfficialLiteServer) + an env-gated live probe (verified vs lite.dragonx.is:
online, latency, IP). Both variants + lite-backend build; suite passes; hygiene clean; GUI
smoke-launched without crash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 11:09:27 -05:00
DanSandClaude Opus 4.8 89fd00801d fix(build): don't clobber the other variant's release artifacts
The linux/windows release packaging did `rm -rf "$out"` on the whole output dir, so building
ObsidianDragonLite into release/<os>/ wiped the ObsidianDragon artifacts already there (both
variants share release/linux and release/windows). Remove only the CURRENT variant's prior
artifacts (by APP_BASENAME, which can't cross-match — "ObsidianDragon-*" excludes
"ObsidianDragonLite-*"), so full-node and lite releases coexist.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 10:17:37 -05:00
DanSandClaude Opus 4.8 301af3a8e1 feat(mining): move "Update miner" into the benchmark row, showing latest + current version
Relocate the miner-update control from a standalone full-width button into the mining-control
header row, immediately left of the benchmark button:
- The button now shows the latest available version ("Update <tag>"), with the current installed
  version as text to its left ("Current: <tag>" / "none").
- A one-shot background version check (util::XmrigUpdater::startCheck) runs the first time the pool
  section is shown, so the latest tag can be displayed; until it arrives the button reads
  "Update miner…". Clicking opens the existing dialog; disabled (greyed, with tooltip) while the
  miner is running.
- New i18n keys: xmrig_update_short, xmrig_current, xmrig_none.

Both variants build; suite passes; GUI smoke-launched without crash.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 10:13:18 -05:00
DanSandClaude Opus 4.8 3b28eccb22 build: bump full-node to 1.3.0 + give ObsidianDragonLite an independent version (1.0.0)
The full-node app and ObsidianDragonLite are now versioned separately:
- project() VERSION -> 1.3.0 (suffix cleared); DRAGONX_LITE_VERSION -> 1.0.0.
- A DRAGONX_APP_VERSION* set (resolved per variant in the lite/full block) feeds the generated
  header (version.h.in), the Windows VERSIONINFO/.rc + manifest, and the build summary — so each
  variant reports its own version. The .rc/manifest name fields also follow DRAGONX_APP_NAME so a
  lite .exe's properties read "ObsidianDragonLite".
- build.sh resolves the release-filename version per variant by parsing CMakeLists (single source
  of truth) instead of a hardcoded string.

Also fixes a latent variant-bleed: build.sh now passes DRAGONX_BUILD_LITE and
DRAGONX_ENABLE_LITE_BACKEND explicitly (ON *and* OFF), so switching variants in a shared build dir
can't reuse a stale cached value (a prior --lite build was making a subsequent full-node build
produce the lite name/version).

Both variants build + report the right version (full 1.3.0, lite 1.0.0); suite passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 09:51:28 -05:00
DanSandClaude Opus 4.8 59d59a2056 docs: document the xmrig miner updater + release-signing requirement
Add a "Miner updater (xmrig)" section to CLAUDE.md: the update flow + verification
(TLS + archive SHA-256 + enforced ed25519 signature against a pinned key), and the
release-process consequence — every drg-xmrig release must be signed
(scripts/sign-xmrig-release.sh) with the .sig uploaded per archive, or the in-app
updater refuses it; the signing secret key stays offline (gitignored), only the base64
public key is pinned in source.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 09:35:39 -05:00
DanSandClaude Opus 4.8 3c4ce4f324 i18n(mining): route xmrig updater strings through TR()
Replace the English string literals in the miner-update dialog + the "Update miner…" mining-tab
button/tooltip with TR() keys, and register their English text in i18n.cpp's loadBuiltinEnglish()
(the in-code English fallback that non-English locales overlay). Reuses the existing cancel/close/
retry keys. Labeled values use a "%s %s" literal format with a TR'd label (no -Wformat-security
risk). Non-English locales fall back to English for the new xmrig_* keys until translations are
added to res/lang/*.json.

Both variants build; suite passes; hygiene clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 09:34:14 -05:00
DanSandClaude Opus 4.8 341f67c83a feat(mining): enforce xmrig signatures + fix multi-platform checksum/asset bugs
Now that the release publishes a valid .sig per archive (verified against the pinned key for
linux/win/macOS), enable enforcement and fix two bugs that the newer multi-platform release
(v6.25.3, which added a macOS build) exposed:

- kXmrigRequireSignature = true: refuse any install whose release doesn't publish a valid
  ed25519 signature over the archive. Verified live end-to-end against the signed v6.25.3
  (archive SHA-256 + signature -> install).
- Drop the redundant inner-binary SHA-256 check. It keyed on the inner filename, but both the
  linux and macOS archives contain a binary literally named "xmrig", so the two "xmrig (…)"
  checksum lines collided in the map and the linux install compared against the macOS hash ->
  spurious "could not verify" failure. The whole archive is already verified (SHA-256 +
  signature), so every extracted member is authentic by transitivity — the per-member check
  added nothing but ambiguity.
- Fix the macOS platform token: the asset is named "...-macos-x86_64.zip", not "...-macos-x64",
  so selectXmrigAsset never matched it. currentXmrigPlatformToken() now returns "macos-x86_64"
  on Intel macs (arm64 has no build -> Unavailable). Added a matcher test for the macOS naming.

Both variants build; suite stable (0 failures / multiple runs); live require-mode install verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 09:29:37 -05:00
DanSandClaude Opus 4.8 3db4be1a18 feat(mining): pin xmrig release-signing key + fix raw-signature parsing bug
- Pin the ed25519 public key in xmrig_updater.h, activating signature verification in soft mode
  (kXmrigRequireSignature=false): a release's ".sig" asset is verified when present, but an
  unsigned release still installs on TLS + SHA-256. Verified live against the current release
  (v6.25.2, which ships no .sig yet) — still installs.
- gitignore *.ed25519.key / *.ed25519.pub.b64 so a signing secret key can never be committed.
- Add a unit test that the pinned key decodes to a valid 32-byte ed25519 key (a malformed paste
  fails the build, not silently disabling verification).

Bug fix (found via a flaky test): verifyXmrigSignature trimmed trailing whitespace BEFORE the
raw-64-byte check, so a raw signature whose last byte equals '\n'/'\r'/space/tab (~1.6% of
signatures) was corrupted and rejected. Now base64 is tried first (safe to trim) and the raw
path uses the exact untrimmed bytes. Added a deterministic regression test that forces a
whitespace-terminated raw signature. Suite is stable (0 failures in 10 runs; was ~3/8).

Also de-brittled the live integration test: it no longer pins a release-specific binary hash
(reaching Done already means the worker verified the binary against the release's own checksum).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 00:44:53 -05:00
DanSandClaude Opus 4.8 98ea6a67b8 chore(mining): make xmrig release-signing script OpenSSL-based (no PyNaCl)
Rewrite scripts/sign-xmrig-release.sh to use OpenSSL (>= 1.1.1) instead of PyNaCl, so signing
needs no Python deps. OpenSSL's ed25519 is PureEdDSA (RFC 8032) — interop-verified against the
wallet's libsodium crypto_sign_verify_detached (script-produced .sig -> VERIFY-OK; tamper ->
VERIFY-FAIL). keygen/pubkey/sign subcommands; emits base64 raw-64-byte signatures as <file>.sig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 00:31:25 -05:00
DanSandClaude Opus 4.8 734589936b feat(mining): opt-in ed25519 signature verification for the xmrig updater (#1)
Closes the supply-chain gap the review flagged: today the archive and its SHA-256 share one
trust root (the release body), so a compromised/edited release can ship an arbitrary binary
that still "verifies". This adds authenticity via a detached ed25519 signature checked against
a public key PINNED IN THE BINARY (not fetched), using libsodium's crypto_sign_verify_detached.

Opt-in / soft rollout:
- kXmrigSignaturePublicKeyBase64 in xmrig_updater.h is EMPTY by default -> signatures are not
  checked and behavior is unchanged (TLS + SHA-256 only). Paste the base64 public key to enable.
- Once a key is pinned, an install verifies a "<archive>.sig" asset (base64/raw 64-byte ed25519
  signature over the archive bytes) when present; kXmrigRequireSignature=true additionally
  refuses installs that publish no signature.
- The check runs after the SHA-256 check, over the same already-read archive bytes; refuses on
  a missing key-but-required, unreachable .sig, or invalid signature.

- verifyXmrigSignature + selectXmrigSignatureAsset are pure (libsodium only) and unit-tested:
  valid base64 + raw-64-byte signatures verify; tampered data, wrong key, and malformed/empty
  inputs all fail closed. Cross-tool interop verified (Python stdlib base64 == sodium base64).
- scripts/sign-xmrig-release.sh: keygen / sign / pubkey helper (PyNaCl = same libsodium ed25519)
  to produce the .sig assets and the public key to pin.

No behavior change until a key is pinned. Both variants build; suite passes; live worker
re-verified (signatures off by default).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 20:29:49 -05:00
DanSandClaude Opus 4.8 fadad1b3b7 fix(mining): harden xmrig updater per adversarial review
Addresses confirmed findings from the multi-lens review of the updater:

- Cancelable + live progress (was: download uncancelable, progress stuck at 0%, closing
  the dialog mid-download blocked the UI thread on the worker join). Wire a libcurl
  CURLOPT_XFERINFOFUNCTION that publishes byte counts and returns abort when cancel() is
  requested; add a Cancel button. The dialog's destructor now aborts the transfer promptly,
  so closing mid-download no longer freezes the UI.
- Graceful "unavailable" instead of a red error on platforms with no published build
  (macOS / ARM): new terminal State::Unavailable rendered neutrally, not as a failure.
- Install-time running guard (TOCTOU): App::isPoolMinerRunning() re-checked in the dialog
  before each install, so a dialog opened before mining started can't replace a live binary.
- Size caps: CURLOPT_MAXFILESIZE on the download and a per-archive-member ceiling before
  decomphressing into memory, to bound an attacker-controlled archive.
- Distinguish a local read failure of the downloaded archive from a checksum mismatch
  (was reported misleadingly as "possible tampering").
- Reword the dialog's verification note to "checked against the release's published SHA-256
  checksum" (integrity, not authenticity — see the signing note below).

Not fixed here (needs your input): WinRing0x64.sys has no per-file hash published, but it is
covered by the verified archive checksum (it is inside the verified zip); and the release is
not cryptographically signed — checksums and binary share one trust root. Adding a pinned-key
ed25519/minisign signature is the real supply-chain hardening and needs an offline signing key
+ a release-process change.

Both variants build; suite passes; live worker re-verified end-to-end on linux-x64.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:35:17 -05:00
DanSandClaude Opus 4.8 47665d052b feat(mining): "Update miner" button + dialog wiring the xmrig updater
Wires util::XmrigUpdater into the GUI:

- ui/windows/xmrig_download_dialog.h: a modal (mirrors BootstrapDownloadDialog) that drives
  the updater — Checking -> Up-to-date/Update-available -> Downloading/Verifying/Extracting ->
  Done/Failed, with a progress bar and a "verified against its published checksum" note. On
  success it persists the installed release tag to settings. Rendered each frame from App::render.
- mining_tab: an "Update miner…" button in the pool section, disabled (with a tooltip) while
  xmrig is running so a live binary is never replaced.
- settings: persist the installed DRG-XMRig tag (xmrig_version) for update detection.

Both variants build; suite passes; GUI smoke-launched without crashing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:16:28 -05:00
DanSandClaude Opus 4.8 2fbb5b3238 feat(mining): xmrig updater service — fetch/verify/install the latest miner from Gitea
Adds util/XmrigUpdater: a background-thread service (mirrors util/Bootstrap) that pulls
the latest DRG-XMRig release from the project's Gitea, verifies it, and installs the miner
binary into the daemon directory. Service layer only; the mining-tab UI hook comes next.

Flow: GET /api/v1/repos/DragonX/drg-xmrig/releases/latest -> pick the asset matching this
platform (…-linux-x64.zip / …-win-x64.zip; no macOS build -> graceful "unavailable") ->
download (libcurl, TLS verified) -> verify the archive SHA-256 -> extract with miniz,
flattening the versioned subdir the archive nests the binary in -> verify the extracted
binary's SHA-256 in memory before writing it -> atomic install (+chmod +x on POSIX). On
Windows also extracts WinRing0x64.sys; config.json/README.md are skipped.

Security (download-and-execute): TLS is verified, and BOTH the archive and the inner binary
are checked against the SHA-256 checksums published in the release body (parsed as
"<hex>  <name>" lines) — install is refused on a missing or mismatched checksum.

Split into a pure core (xmrig_updater_core.cpp: release parse, asset/platform match, checksum
parse, SHA-256) and the curl/miniz worker (xmrig_updater.cpp). The core is unit-tested against
a real captured release fixture (tests/fixtures/xmrig/release_latest.json); an env-gated
(DRAGONX_TEST_NETWORK=1) integration test exercises the worker live and was verified end-to-end
on linux-x64 (inner binary SHA-256 matches the published value). Both variants build; suite passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:07:46 -05:00
DanSandClaude Opus 4.8 94879a48c5 build(lite): wire macOS --lite packaging in build.sh (M5b)
The mac-release path was mostly ObsidianDragon-hardcoded, so `--lite --mac-release`
would produce a broken bundle. Make it variant-aware, mirroring the linux/win lite
handling that already keys off APP_BASENAME + should_bundle_full_node_assets:

- SDL3 rpath fix, the launcher script + its .bin pair, and CFBundleExecutable now
  follow ${APP_BASENAME} (ObsidianDragonLite), so the bundle's executable resolves.
- Lite variant gets its own CFBundleName/CFBundleDisplayName ("DragonX Wallet Lite"),
  CFBundleIdentifier (is.hush.dragonx.lite), DMG filename (DragonX_Wallet_Lite-…)
  and volume name, so it can coexist with the full-node app.
- Full-node assets (daemon, Sapling params, asmap) were already gated out for lite;
  the lite backend artifact is auto-selected for the macos platform by the existing
  --lite-backend logic, and CMAKE_LITE_ARGS already reaches the mac configure.

Authored + validated on Linux (bash -n; launcher heredoc, plist, and DMG naming
render correctly for the lite variant) but NOT yet built/run — that needs macOS or
osxcross, neither available here. CLAUDE.md updated to reflect the wired-but-unverified
status; remaining M5b is verifying it on a Mac plus CI backend-artifact build + signing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 12:09:41 -05:00
DanSandClaude Opus 4.8 c60904baa5 feat(lite): runtime kill-switch + staged-rollout gate (M5b)
Adds a fail-open, local-only gate that decides whether the lite wallet may run,
so a post-release issue can disable it and rollout can be staged — without any
phone-home (privacy posture: no runtime network fetch; the per-install rollout
bucket is a hashed, never-transmitted local id).

- wallet/lite_rollout_policy.{h,cpp}: a pure decision core. Order — emergency env
  kill-switch (absolute) -> local override -> manifest gates (global enable /
  version floor-ceiling / blocklist / staged-rollout permille) -> fail-open allow.
  Plus a JSON manifest loader (missing/invalid -> fail-open) and FNV-1a bucketing.
- Threads the decision through LiteWalletController -> LiteWalletLifecycleService:
  new availability() reason RolloutDisabled blocks create/open/restore and surfaces
  the gate's user-facing message via the lifecycle status.
- App::rebuildLiteWallet() resolves it from: DRAGONX_LITE_KILL_SWITCH (env), the
  lite_rollout setting (auto/force_on/force_off), and a locally-cached manifest at
  <config-dir>/lite_rollout.json. install id generated once via libsodium.
- Settings: persist lite_rollout override + the install id.

A signed remote fetcher can populate the manifest cache later without touching the
policy. Unit-tested (version compare, bucketing, override/env precedence, manifest
gates, staged rollout, loader fail-open, controller integration) and runtime-verified
on Linux (env kill-switch, manifest disable, control sync). Both variants build;
full suite passes; hygiene clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 12:01:08 -05:00
DanSandClaude Opus 4.8 b664bf52c3 refactor(ui): remove abandoned Material-Design component library + screens layer
~9,988 lines of header-only UI code that no compiled translation unit reached,
verified by transitive include-reachability from every .cpp plus a symbol sweep
(all 28 component classes — Snackbar, Ripple, NavDrawerSpec, TabBarSpec,
TransitionManager, … — had zero references in live code):

- src/ui/material/ component library: the material.h umbrella, components/*
  (app_bar, cards, chips, dialogs, inputs, lists, nav_drawer, progress, slider,
  snackbar, tabs, text_fields), and the animation system (elevation, motion,
  ripple, transitions, app_layout) — 19 headers. Kept the live helpers the app
  actually uses directly: color_theme, colors, type/typography, draw_helpers,
  layout, project_icons, and components/buttons (included by mining_tab).
- src/ui/screens/ layer: main_layout, home_screen, send_screen, etc. — the
  original screen stack and the only consumer of the dead component library.
  The live UI runs through ui/windows/ (34 .cpp) + ui/pages/.
- src/embedded/resources.h: a superseded dragonx::embedded::Resources duplicate;
  the app uses src/resources/embedded_resources.h.

None were in CMakeLists or included by live code, so the build is unaffected.
Both variants build; full test suite passes; source-hygiene check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 11:31:06 -05:00
DanSandClaude Opus 4.8 6186389986 refactor(lite): remove dead backend artifact-contract/resolver scaffold
lite_backend_artifact_{contract,resolver}.{cpp,h} (~1,960 lines) were
app-linked but never invoked: all 14 public entry points
(evaluateLiteBackendArtifactContract/Resolver, evaluateLiteBackendActivation-
Readiness, the resolve*/...Name helpers) had zero callers in the app, the
lite_smoke tool, build scripts, or surviving tests. The real backend load
path (LiteClientBridge::linkedSdxl) uses direct litelib_* externs, and the
DRAGONX_ENABLE_LITE_BACKEND symbol check is done in CMake against the symbols
inventory (FATAL_ERROR on a missing symbol) — not via these C++ files. The
files were saturated with churn markers (disabled / dry-dispatch / scaffold).

- Delete the four artifact files and their 8 CMakeLists references.
- Drop the orphaned test cruft in test_phase4.cpp: the contract include,
  5 type aliases, and 3 never-called helpers (heapConstructPlanResult,
  makeReadyLiteBackendArtifactProvenance, liteBackendArtifactContractHasIssue)
  left over from the already-removed bridge-runtime tests.
- Correct the CLAUDE.md lite-wallet description (it credited these files with
  backend validation that CMake actually performs) and drop the stale
  lite_bridge_runtime mention.

Both variants build; full test suite passes; source-hygiene check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 11:05:38 -05:00
DanSandClaude Opus 4.8 720cf91e72 refactor(lite): extract owned-string core, drop dead bridge-runtime scaffold
lite_bridge_runtime.{cpp,h} was ~25k lines of dry-dispatch / dynamic-loader
scaffolding that the shipping wallet never used: 0 of its 122 public types
reached the app binary. The only live code on the bridge path was the
owned-string memory-safety helper — LiteClientBridge::linkedSdxl() already
loads the backend via direct litelib_* externs in lite_client_bridge.cpp.

- Extract LiteBridgeOwnedString + liteBridgeRuntimeTakeOwnedString into
  src/wallet/lite_owned_string.{h,cpp} (the copy-before-free / free-once /
  wipe / "Error:"-classify boundary), with the runtime-friend coupling removed.
- Point lite_client_bridge.cpp at the new header.
- Delete lite_bridge_runtime.{cpp,h} and the 16 runtime-only tests +
  their fixtures/aliases in test_phase4.cpp; keep the 5 owned-string tests
  (retargeted) and restore testGeneratedResourceBehavior, which had been
  caught in the runtime-test line range.
- Swap the CMake source/header references.

Both variants build; full test suite passes; source-hygiene check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 08:56:55 -05:00
DanSandClaude Opus 4.8 e9bfce85ea docs(lite): consolidate v2 plan status into CLAUDE.md, archive the plan
The lite-wallet v2 plan was the last tracked lite doc. Fold its still-live
content — current status, remaining M5b work (macOS/CI/signing/rollout), and the
push plan — into a concise "Lite wallet status" section in CLAUDE.md (the
canonical project doc), then move the full milestone plan to docs/_archive/
(untracked) alongside the other lite design docs.

Result: docs/ has no tracked markdown; tracked .md is now just repo essentials
(README, CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, CLAUDE.md). No dangling links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 21:00:48 -05:00
DanSandClaude Opus 4.8 b257ede784 chore: remove dead UI header files (scroll_fade_fbo.h, gpu_mask.h)
Both are header-only, in no CMake target, #included nowhere, and their only
symbols (ScrollFadeRT, DrawScrollFadeMask) are referenced nowhere:
- src/ui/effects/scroll_fade_fbo.h — superseded by the shader-based
  scroll_fade_shader.h (the implementation actually used by settings_page).
- src/ui/material/gpu_mask.h — a GPU blend-mask helper never integrated.

App + test build clean after removal; tests pass; hygiene clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 20:35:18 -05:00
DanSandClaude Opus 4.8 c2d689bc69 docs: archive stale/dormant-feature markdown out of git tracking
Move 8 dated-snapshot / dormant-feature docs to docs/_archive/ (git-ignored,
kept locally), leaving only repo essentials + the active lite plan tracked:
- docs/codebase-audit-2026-04-27.md, docs/codebase-overview.md — "current as of
  2026-04-27" snapshots, superseded by CLAUDE.md and the v2 plan.
- docs/ui-static-state.md — Phase-9-era UI static-state review snapshot.
- docs/chat-port-feasibility-2026-05-06.md, docs/chat-protocol-spec-2026-05-06.md
  — superseded/old-"Batch"-framing docs for the dormant, gated-OFF chat module.
- tests/fixtures/hushchat/{README,CAPTURE_MANIFEST,IMPORT_CHECKLIST}.md -> docs/
  _archive/hushchat/ — human docs (not tool input) for the dormant chat fixtures;
  the .json fixtures the HushChatFixtureCheck tool globs remain tracked.

These docs only cross-referenced each other (no code/CMake/script refs); no
dangling tracked links remain. Tracked .md (non-libs): 14 -> 6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 20:26:22 -05:00
DanSandClaude Opus 4.8 2c8f050976 docs(lite): archive superseded lite design/planning docs out of git tracking
Consolidate the lite-wallet documentation down to the single active plan
(lite-wallet-implementation-plan-v2-2026-06-04.md). The 8 prior design/planning
docs — the superseded v1 plan, its runtime-promotion-matrix, the two phase2
runtime-bridge plans, and the four backend artifact/signing design docs — are
moved to docs/_archive/ (added to .gitignore), preserving them locally as
reference while decluttering the tracked tree.

The v2 plan's References section is rewritten to be self-contained: it points to
docs/_archive/ for the historical design docs and to the actual shipping
mechanisms (scripts/build-lite-backend-artifact.sh, lite_backend_artifact_*,
lite_bridge_runtime.cpp) so there are no dangling tracked links. No code,
CMake, or scripts referenced these docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 20:14:56 -05:00
DanSandClaude Opus 4.8 dd44369cf7 docs(lite): record end-of-session implementation status
Summarize the 2026-06-05 session in the v2 plan: M1–M5a + encryption complete,
GUI wired with lite wording, ~3.2k lines cleanup, Linux+Windows packaging
verified, both variants build clean, runtime-verified on Linux. Notes the
remaining M5b infra (macOS/CI/signing/rollout) and the push plan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 19:32:50 -05:00
DanSandClaude Opus 4.8 3b6859c2f3 feat(lite): startup unlock prompt + real-backend encryption verification
Startup lock screen (soft): once the first refresh reveals the auto-opened wallet
is encrypted+locked, show the unlock modal on launch (reusing renderLiteUnlockPrompt,
one-shot per session). Soft by design — balances stay viewable via viewing keys
while locked, so the user may dismiss and browse read-only; only spending needs
the passphrase.

Real-backend verification: add `lite_smoke --encrypt` (create -> encryptionstatus
-> encrypt -> lock -> unlock, checking flags; passphrase never printed). Running it
against the real SDXL backend showed encrypt LOCKS immediately
(after encrypt: encrypted=1, locked=1) — the backend removes spending keys right
after encrypting. The controller already relays encryptionstatus faithfully (UI is
state-driven, so unaffected), but the fake modeled encrypt->unlocked; corrected the
fake (encrypt -> encrypted+locked) and the test sequence (encrypt -> unlock -> lock
-> decrypt) to match real behavior.

Builds clean, tests pass, hygiene clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 18:53:35 -05:00
DanSandClaude Opus 4.8 8d85190625 feat(lite): send-time unlock prompt for locked encrypted wallets
When the user confirms a send on a locked encrypted lite wallet, show an unlock
modal (passphrase -> unlockWallet) instead of letting the backend reject it with
"Wallet is locked". After unlocking, the user re-confirms the send (the form is
preserved). Balances remain viewable while locked; only spending needs unlock.

- send_tab: the Confirm-and-send button routes to App::requestLiteUnlock() when
  getWalletState().isLocked(), else sends as before.
- App::renderLiteUnlockPrompt(): centered modal, passphrase (Enter submits),
  Unlock/Cancel; the passphrase buffer is sodium-zeroed after every path.

Full-node unaffected (gated on liteWallet()/isLocked()). Builds clean, launches
clean, tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 18:22:15 -05:00
DanSandClaude Opus 4.8 76441d7d51 feat(lite): encryption UI — encrypt/unlock/lock/decrypt in Settings
Add a "Security" subsection to Settings → Backup & keys (open wallet only) that
wires the encryption controller methods to the UI:

- Unencrypted wallet: passphrase field + "Encrypt wallet".
- Encrypted + locked: "Unlock" (passphrase) ; Encrypted + unlocked: "Lock now".
- Encrypted: passphrase + "Remove encryption" (decrypt).
- Status line reflects the result; state shown from WalletState.isEncrypted()/
  isLocked() (kept current by the controller's encryptionstatus refresh poll).

Secret hygiene: the passphrase inputs (lite_enc_pass / lite_dec_pass) are
sodium-zeroed immediately after each action and when the wallet closes while the
section was open.

Runtime-checked: app auto-opens a wallet and the new encryptionstatus worker poll
runs clean (no errors); tests pass; hygiene clean.

Follow-ups (not yet): a send-time unlock prompt and a startup lock-screen overlay
for an encrypted+locked wallet (today: unlock via Settings; balances remain
viewable while locked).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:55:09 -05:00
DanSandClaude Opus 4.8 938aeeec87 feat(lite): wallet encryption controller layer (encrypt/unlock/lock/decrypt)
Wire the backend passphrase-encryption commands into LiteWalletController:

- encryptWallet / decryptWallet (take passphrase by value, securely wipe it,
  save after), unlockWallet / lockWallet (bring spending keys into/out of
  memory), and encryptionStatus() -> {encrypted, locked}. All return
  failure-safe results; errors arrive as {"error":..} or "Error:" (handled).
- Fold encryptionstatus into refreshModel() (polled every cycle, available even
  mid-sync since it reads local wallet state) and apply it in
  applyLiteRefreshModelToWalletState, so WalletState.isEncrypted()/isLocked()
  track the backend — which gates the existing locked/auto-lock UI.

Backend contracts verified against the SDXL source: encrypt/unlock/decrypt take
the passphrase as the single arg; lock takes none; encryptionstatus returns
{"encrypted","locked"}; ops return {"result":"success"} / {"error":..}.

Tests: testLiteWalletControllerEncryption drives encrypt -> lock -> unlock ->
decrypt via encryptionStatus(), checks empty-passphrase + closed-wallet rejection,
and that the status folds into WalletState. Fake models the state machine.

GUI wiring (encrypt in Settings, unlock prompt / lock action) is the follow-up;
the backend create flow remains unencrypted by default until encrypt is run.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:50:53 -05:00
DanSandClaude Opus 4.8 71a764be68 feat(lite): first-run welcome prompt (create / restore)
Replace the bare "land on main UI with a No-wallet overlay" first-run with a
lite welcome modal, shown when no wallet file exists yet (lite_wallet_ present,
not open, walletExists() false):

- "Create new wallet" — one-click createWallet({}); on success, notifies the user
  to back up their recovery phrase and navigates to Settings (Backup & keys),
  where the seed can be revealed/copied via the existing backup UI.
- "Restore from seed" — navigates to Settings (Lite wallet request → Restore).
- "Later" — dismiss for the session.

Routes to the already-built + verified create/restore/backup flows rather than
re-implementing seed display in the modal (no new secret-handling surface).
Dismissed once an action is chosen; never shown again once a wallet exists.
Full-node is unaffected (renderLiteFirstRunPrompt() returns early when
lite_wallet_ is null). English i18n built-ins added.

Verified: fresh-HOME lite launch shows the prompt, clean run + shutdown, no
crash/RPC noise; tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:38:32 -05:00
DanSandClaude Opus 4.8 faec1fb971 fix(lite): skip the full-node first-run wizard in lite builds
isFirstRun() keys off the full-node `blocks/` data dir, which never exists in
lite — so the daemon/blockchain setup wizard (download node, extract blockchain,
daemon status) fired in lite, where none of it applies and it has zero
lite-awareness. Gate the wizard on !isLiteBuild(); lite goes straight to the main
UI, where the "No wallet open — create or open one in Settings" prompt guides new
users to the lite create/open flow. Full-node behavior is unchanged
(isFirstRun() && !isLiteBuild() == isFirstRun() there).

Completes the lite daemon-wording sweep: the other full-node surfaces are already
lite-gated — daemon settings via supportsFullNodeLifecycleActions(), RPC settings
in the isLiteBuild() else-branch, and Console/Peers/Explorer hidden via
isUiSurfaceAvailable.

Verified: true first-run in lite (fresh HOME) no longer starts the wizard; clean
launch + shutdown, no daemon noise. tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:31:27 -05:00
DanSandClaude Opus 4.8 e585833103 polish(lite): lite-appropriate wording for no-wallet/connection states
In lite builds there is no daemon, and isConnected() now tracks the lite wallet,
so the full-node "not connected / waiting for daemon" wording was misleading when
no wallet is open. Add two strings (lite_no_wallet, lite_no_wallet_short; English
built-ins, so other languages fall back until translated) and use them in lite:

- receive/send address preview + receive empty-state overlay + send "can't send"
  tooltip + transactions empty state -> "No wallet open [— create or open one in
  Settings]" instead of daemon wording.
- Status bar: the red indicator shows "No wallet open" (not "Disconnected") in
  lite; the P2P peer count is skipped (lite has no peers); and the redundant
  full-node connection-detail line is suppressed (connection_status_ set to
  "Connected"/"" from the lite wallet state).

Full-node wording unchanged (all gated on isLiteBuild()). Build + run clean
(no RPC noise), tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:11:41 -05:00
DanSandClaude Opus 4.8 7a3601c271 feat(lite): auto-open existing wallet on startup + gate full-node RPC refreshes
Auto-open: on the first update() tick (kept off init() so a slow
initialize_existing network call can't freeze startup before the window), if a
wallet file exists, open it. initialize_existing needs no passphrase — it loads
the file; a previously-synced + saved wallet resumes from its height (fast)
instead of rescanning from the checkpoint. Adds LiteWalletController::walletExists()
(bridge.walletExists on the connection's chain) + a chainName_ member.

RPC-refresh gating: the earlier connected=walletOpen() fix (so the wallet UI is
enabled in lite) had a side effect — the full-node periodic + per-page RPC
refreshes (mining/balance/peers/txs, and setCurrentPage's immediate refresh)
gate on state_.connected, so they began firing in lite and failing
("X error: Not connected"). Re-gate those on ACTUAL RPC connectivity
(rpc_ && rpc_->isConnected()) instead of the lite proxy. Full-node is unchanged
(state_.connected ⟺ rpc connected there); lite no longer issues any RPC.

Runtime-verified in WSLg with a pre-seeded wallet: app auto-opens (Starting
Mempool + sync begins), and "Not connected" / getMiningInfo / RPC-connect noise
all drop to 0 — a fully clean lite run. tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:29:27 -05:00
DanSandClaude Opus 4.8 2ac6af99e3 fix(lite): don't run the full-node RPC loop in lite; drive isConnected() from the wallet
Runtime monitoring of ObsidianDragonLite (WSLg) showed the full-node RPC connect
state machine running in the lite build — `tryConnect()` fired every ~5s and
failed ("Couldn't connect to server / no daemon"). It's called unconditionally
from the main loop with no lite guard.

Worse than noise: `state_.connected` (App::isConnected()) was therefore ALWAYS
false in lite, and it gates the wallet UI — receive_tab disables the new-address
button + shows "not connected", send_tab disables send, transactions_tab shows
not-connected. So the M3/M4 GUI wiring was effectively unreachable: a lite user
could never generate an address or send, even with an open, synced wallet.

Fix:
- tryConnect() no-ops in lite builds (isLiteBuild()), so no RPC attempts.
- App::update() derives state_.connected from lite_wallet_->walletOpen() each
  frame — a non-blocking proxy for "lite backend operational" (a wallet opens
  only after a successful backend init against the lite server). This enables the
  wallet UI once a wallet is open.

Full-node is unaffected (both branches are runtime-gated: isLiteBuild() is false
and lite_wallet_ is null there).

Verified by re-running the app: RPC connection attempts dropped from 7/30s to 0;
clean launch (GL 4.2) + clean shutdown; tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:11:58 -05:00
DanSandClaude Opus 4.8 0c0e5e059d build(lite): enable Windows lite-backend cross-compile (.exe verified)
`build.sh --lite-backend --win-release` now cross-compiles a working
ObsidianDragonLite.exe with the real SDXL backend:

- Artifact platform follows the cross target: when only --win-release is
  requested, auto-select build/lite-backend/windows/ (previously always the host
  artifact, which would link a Linux .a into a Windows .exe).
- Link the Win32 system libs a Rust x86_64-pc-windows-gnu staticlib pulls in
  (rustls/schannel, ring, dirs, std) via DRAGONX_LITE_BACKEND_EXTRA_LIBS. The set
  is rustc's `--print native-static-libs` for the backend (winapi_* shims mapped
  to real mingw import libs); all 21 exist in mingw-w64.

Verified end to end on Linux:
- scripts/build-lite-backend-artifact.sh --platform windows cross-builds the
  backend to x86_64-pc-windows-gnu (~105 MB .a); rustls/ring cross-compile clean
  (no openssl blocker); all required litelib_* symbols present.
- build.sh --lite-backend --win-release -> release/windows/ObsidianDragonLite-
  <ver>.exe (PE32+ GUI x86-64, INCBIN-embedded, ~170 MB) + zip, with the same
  full-node-asset exclusion as Linux.

Not yet done: running the .exe on real Windows (cross-compiled only). Plan
updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 15:45:48 -05:00
DanSandClaude Opus 4.8 b3bfa59a60 docs(lite): record verified Linux lite release packaging (M5b)
`./build.sh --lite-backend --linux-release` produces a working
ObsidianDragonLite zip + AppImage (SDXL backend linked statically). Verified the
lite bundle excludes all full-node assets (dragonxd, dragonx-cli, sapling
params, asmap.dat) and includes res/ + xmrig (pool mining works in lite). CMake
falls back to FetchContent SDL3 when system SDL3 is absent, so the release build
has no system-SDL3 prerequisite. release/ is gitignored.

Remaining M5b (Windows/macOS packaging, CI artifact build + signing,
kill-switch/rollout) is infra/CI, not locally verifiable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 14:23:51 -05:00
DanSandClaude Opus 4.8 f3fa11a7b7 test(lite): smoke-check M4/M5 command shapes against the real backend
Add `lite_smoke --keys`: create a fresh wallet and exercise the M4/M5
spend/backup commands (new-address, export, seed, save) against the real linked
SDXL backend, verifying each response's JSON shape with nlohmann. SECRET-SAFE:
seed and private-key VALUES are never printed — only field presence/shape and
counts (no send/shield, which would broadcast).

Verified live (isolated HOME, throwaway wallet shredded after):
  new z      shape_ok=1            new t      shape_ok=1
  seed       has_seed=1 has_birthday=1 (REDACTED)
  export     is_array=1 count=4 has_private_key=1 (REDACTED)
  save       result_success=1

Confirms the controller's newAddress / exportSeed / exportPrivateKeys / save
parsing matches real backend output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 14:00:05 -05:00
DanSandClaude Opus 4.8 c32cf8414d feat(lite): backup & keys UI — export seed/keys + import (Settings)
Add a "Backup & keys" section to the lite Settings page, shown only for an open
wallet, wiring the M4 controller backup/import surface into the GUI:

- "Show seed" / "Show private keys" -> exportSeed() / exportPrivateKeys();
  the revealed secret is displayed read-only (TextWrapped, no extra copies) with
  Copy and "Hide & wipe" controls.
- "Import key" (password input) -> importKey() (auto-detects WIF vs shielded);
  do_import_sk just records the key + saves (no synchronous rescan), so this is
  safe on the UI thread — history appears after the next sync.

Secret hygiene: the revealed-backup buffer is sodium-wiped via
secureWipeLiteSecret on hide, on a new export (overwrite), and if the wallet
closes while revealed; each export also wipes the controller's result copy; the
import input buffer is zeroed immediately after submission.

Lite app + full-node variant build/link clean; controller methods already
covered by testLiteWalletControllerM4; hygiene clean. GUI behavior itself isn't
auto-verifiable here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 13:40:17 -05:00
DanSandClaude Opus 4.8 5440a8cec0 feat(lite): wire send + new-address GUI to the lite controller (M3/M4)
Route the existing receive/balance/send UI to the lite controller in lite builds,
with no per-tab UI changes — the existing buttons just work:

- App::createNewZAddress / createNewTAddress: lite branch calls
  lite_wallet_->newAddress() (synchronous local key derivation), injects the new
  address into WalletState so the UI selects it next frame, and invokes the
  receive-tab callback. Placed before the full-node !connected guard.
- App::sendTransaction: lite branch builds a LiteSendRequest (DRGX -> zatoshis,
  memo; `from`/`fee` ignored since the backend selects inputs and adds the fee),
  fires the controller's async broadcast, and stashes the send_tab callback.
- App::update: drains takeBroadcastResult() and delivers txid/error to the stored
  callback, so the send_tab's existing "sending.../sent" flow works unchanged.

All branches guard on lite_wallet_ (null in full-node). Verified: lite app +
test suite + full-node variant all build/link clean; hygiene clean.

Backup/import UI (export seed/keys, import) is deferred — it needs new
secret-display UI rather than an existing button.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 13:10:56 -05:00
DanSandClaude Opus 4.8 b14f6c322c feat(lite): M5a — wallet persistence after sync/send/shield
Verified against the SDXL Rust source that the backend auto-saves only on
new-address / import / rescan; it does NOT save after sync, send, or shield, and
litelib_shutdown merely sets a flag. So without intervention a first sync
(~30 min) and any sent transaction are lost on restart.

The controller now triggers the backend `save` at exactly the right points:
- after the detached `sync` completes — and BEFORE syncDone_ is set, so a
  syncComplete() observer always sees a fully persisted wallet;
- after a successful send / shield (the doSend/doShield cores; skipped on
  failure so a failed broadcast doesn't write);
- a guarded best-effort flush in the destructor, only when syncDone_ and no
  broadcast is in flight, so shutdown never blocks on the wallet lock held by an
  uninterruptible scan or in-progress proving;
- plus a public saveWallet() for explicit/periodic saves.

Wallet-file crash recovery (.dat / .dat.bak rotation) is already handled inside
the backend.

Tests: testLiteWalletControllerM5Persistence proves saves fire after
sync/send/shield and explicit saveWallet(), and do NOT fire on a failed send or
with no wallet open (fake gains a save counter). Plan doc updated; M5b
(packaging/CI/signing/rollout) remains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:48:44 -05:00
DanSandClaude Opus 4.8 298ae52528 refactor(lite): drop 4 unused OOP wrapper classes over free functions
Each of these classes wrapped an existing free function with a one-line
delegating method and was never instantiated anywhere (verified: no references
outside their own translation unit, not even within their own .cpp beyond the
definition) — the redundant "wrapper layer" pattern CLAUDE.md warns against:

- LiteWalletLifecycleUiExecutionAdapter      -> executeLiteWalletLifecycleUiRequest
- LiteWalletServerSelectionUiExecutionAdapter -> executeLiteWalletServerSelectionUi
- LiteWalletServerLifecycleReadinessPlanner   -> evaluateLiteWalletServerLifecycleReadiness
- LiteBackendActivationReadinessAdapter       -> evaluateLiteBackendActivationReadiness

The live free functions (the actual entry points used by the UI/runtime) are
unchanged. Both targets build, test suite passes, source-hygiene clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:40:26 -05:00
DanSandClaude Opus 4.8 dddb54b8bc refactor(lite): remove dead parallel refresh/readiness scaffolding (~3.1k lines)
The lite-wallet tree carried a second, unused refresh+readiness architecture
that never reached the shipping binary — exactly the churn CLAUDE.md warns
against. The live refresh path is controller -> gateway.refresh ->
mapLiteWalletRefreshResult -> applyLiteRefreshModelToWalletState; this parallel
stack was dead weight.

Verified unused (their public types/functions are referenced only within the
cluster), then deleted (8 files / 16 incl. headers):
- lite_wallet_refresh_service            (LiteWalletRefreshService + gateway adapters)
- lite_wallet_app_refresh_coordinator
- lite_wallet_app_refresh_orchestrator
- lite_wallet_refresh_readiness_policy
- lite_wallet_state_apply_plan
- lite_wallet_state_apply_executor
- lite_wallet_sync_app_refresh_integration
- lite_wallet_sync_execution_readiness

Severed three thin couplings into the cluster from live files:
- state_mapper: dropped the dead mapLiteWalletRefreshServiceResult and switched
  its include from refresh_service.h to gateway.h (where the live
  LiteWalletRefreshResult/Bundle DTOs actually live).
- server_lifecycle_readiness: dropped the unused syncLifecycleInput member +
  converter and the sync_app_refresh_integration include.
- artifact_resolver: relocated the three LIVE artifact-input structs
  (LiteWalletSdxlArtifact{Symbols,}Input, LiteWalletLinkedBackendReadinessInput)
  out of sync_execution_readiness.h — their only real consumers — into
  artifact_resolver.h, then dropped the include.

Also removed the dead DRAGONX_LONG_LITE_BATCH CMake machinery (its source var
was empty; on Windows it generated a broken lite_batch90_receipt_plan.cpp that
#included an empty path) and the stale .cpp/.h entries in CMakeLists.

Lite source files: 44 -> 30. Lite + full-node configure, both targets build,
test suite passes, source-hygiene clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:25:02 -05:00
DanSandClaude Opus 4.8 85a7db0b5e feat(lite): M4 — send/shield/import/export/seed via controller + bridge
Add the spend & backup surface to LiteWalletController, with the real SDXL
backend contracts verified against the Rust source:

- send / shield: ASYNC (detached broadcast thread + takeBroadcastResult() slot,
  mirroring the sync thread's shared-lifetime pattern, since sapling proving can
  take seconds), plus synchronous *Blocking cores for tests. send uses the
  JSON-array form ([{address,amount,memo}]) because litelib_execute passes the
  whole args string as ONE argument (no whitespace split) — the space-separated
  CLI form would never parse. send/shield report failure via {"error":..} in the
  body (NOT an "Error:" prefix), so the result is derived from the parsed JSON.
- importKey: auto-detects transparent WIF (U/5/K/L -> timport) vs shielded key
  (-> import); takes the key by value and securely wipes it before returning.
- exportPrivateKeys / exportSeed: synchronous local reads returning SECRET
  material (flagged: no logging; caller wipes after the user saves the backup).
- broadcast thread is detached in the dtor (captures shared bridge + flag + slot,
  never `this`), so it is safe to outlive the controller.

Tests: testLiteWalletControllerM4 drives send (success / no-recipients /
{"error":..} / async-slot delivery / pre-open rejection), shield, export, seed,
and import (shielded + WIF + pre-open). Fake backend returns the real command
shapes + a g_liteFakeSendFails error toggle.

GUI wiring (send_tab button, backup/import UI) is deferred like the M3 UI hop
(GUI-unverifiable here). Plan doc updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:06:19 -05:00
DanSandClaude Opus 4.8 b2999f491b fix(lite): rebuild controller on lite-server change (stale-settings audit HIGH)
The LiteWalletController was constructed once at App::init() with the lite
connection settings known at startup; changing the lite server in Settings
persisted to disk but never reached the live controller, so the new server had
no effect until the next launch.

Factor the construction into App::rebuildLiteWallet() and call it after a
successful server-selection save. The rebuild deliberately preserves a live
session: if a wallet is already open (and possibly mid-sync), it no-ops and the
new selection applies on the next controller build, rather than discarding the
open wallet and its uninterruptible in-flight sync.

Closes the last remaining HIGH from the session audit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 11:32:54 -05:00
DanSandClaude Opus 4.8 ad174c13ac fix(lite): address adversarial audit findings in session's lite work
Re-audited this session's lite-wallet changes (originally written at medium
effort) and fixed the genuine issues found:

- walletReady (open path): litelib_initialize_existing returns the bare string
  "OK", which is NOT valid JSON, so the previous `json::accept(value)` check
  marked a *successful* open as not-ready. Key off a non-empty success response
  instead (the bridge already maps "Error:"/null to failure). Drops the now
  unused nlohmann include.
- sync progress: while the detached sync thread is still running, syncDone_ is
  authoritative — don't surface the backend's transient idle syncstatus
  ({"syncing":"false"} -> parser progress=1.0/complete=true) as a misleading
  100%/done. Force complete=false and zero the bogus 1.0 in the progress model.
- per-address balance: also exclude `pending` outputs (notes/utxos from an
  unconfirmed received tx) so per-address figures match confirmed/available.
- secret wiping: the settings page left the page-local request copies
  (input.request.*Request.{passphrase,seedPhrase}) unwiped, and the
  validation-only fallback path wiped nothing. Replace the single-path memzero
  with an RAII scrubber that wipes both the UI char buffers and the request
  string copies on every return path.
- concurrency: document that concurrent bridge->execute() is intentionally
  unguarded — litelib serializes wallet access internally via
  Arc<RwLock<LightWallet>>, so a C++ mutex is unnecessary and would defeat the
  sync/syncstatus concurrency the design relies on. syncLaunched_ -> atomic.

Tests: fake backend now returns the real init shapes (seed object for
create/restore, bare "OK" for open) and a new open-path case guards the
walletReady regression. Removed an unreliable alloc==freed leak assert from the
thread-bearing controller test (kept in the thread-free bridge test). Also fixed
a stray CMake indent and removed ~220MB of untracked build/debug scratch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 11:28:37 -05:00
DanSandClaude Opus 4.8 63c386a885 feat(lite): M3 — new-address generation + sync-indicator confirmation
- LiteWalletController::newAddress(shielded) runs the backend "new" command ("zs"/"R" ->
  do_new_address), parses the ["addr"] response, and returns the new address; the next
  refresh lists it. Fast (local derivation), safe on the UI thread.
- fake_lite_backend returns ["zs1fakenew"]/["R1fakenew"] for "new" by args.
- testLiteWalletControllerNewAddress covers shielded/transparent + no-wallet error.

Also confirmed (no code needed): the sync-progress indicator already works for lite —
balance_tab reads state.sync.* which M2b-3 populates. Per-address balances landed in M2.

Remaining M3 is pure UI wiring (receive_tab button -> newAddress, loading/empty states),
which isn't verifiable without a GUI session.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:36:00 -05:00
DanSandClaude Opus 4.8 6e40d54f7a feat(lite): per-address balances from unspent notes/utxos
applyLiteRefreshModelToWalletState now derives each address's balance by summing its unspent
notes/utxos (excluding spent and unconfirmed-spent outputs) instead of the aggregate-only
zeros, so the Receive/Balance UI shows per-address amounts. The notes parser shape is
confirmed against do_list_notes in the backend source.

testLitePerAddressBalances covers the summing + spent-exclusion. Completes M2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 10:24:36 -05:00
DanSandClaude Opus 4.8 8f7603f13b test(lite): real-backend shape verification of refresh parsers
lite_smoke: add --restore-recent (restore a throwaway wallet at birthday≈tip) and factor
the data-shape checks (non-blocking commands first). Finding: the backend downloads from a
fixed checkpoint regardless of birthday, so first sync is ~30 min and balance/list block
until synced — a full live data run is impractical.

Verified all refresh parsers against the real backend without a full sync:
- live run: info/addresses/syncstatus parse_ok=1 (addresses z=1/t=6 on a restored wallet).
- via the authoritative Rust source (commands.rs / lightclient.rs):
  - balance do_balance fields match parseLiteBalanceResponse.
  - list do_list_transactions: sends use outgoing_metadata (no top-level address), receives
    use address+amount; parseTransactionRecord already branches correctly.
  - syncstatus was the only mismatch (fixed in the prior commit).

No parser changes needed beyond syncstatus. M2 refresh path verified end-to-end at the
shape level.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 09:13:54 -05:00
DanSandClaude Opus 4.8 9666b5df8a fix(lite): gateway refresh degrades gracefully on a failed command
LiteWalletGateway::refresh() aborted the entire refresh on the first command whose bridge
call or parse failed — which turned a single real-backend shape mismatch (e.g. syncstatus)
into a total, empty-everything refresh. Since the balance/addresses/list real shapes are
still unverified and we've already hit shape drift twice, make refresh resilient:

- Run every planned command; assembleLiteWalletRefreshBundle already skips failed results.
- result.ok = any usable data came back (bundle.complete still reflects all-succeeded).
- One command's failure now degrades gracefully — the other sections still populate.

testLiteWalletGatewayRefreshSkipsFailedCommand (fake balance returns invalid JSON) asserts
the refresh still succeeds with addresses/transactions/info populated and balance skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 06:57:10 -05:00
DanSandClaude Opus 4.8 523b668926 fix(lite): non-blocking, non-hanging sync (Finding B)
The backend `sync` command is a blocking, uninterruptible full chain scan (do_sync(true);
does not honor the shutdown flag), and balance/list block until synced. Previously
startSync() ran on the main thread (would freeze wallet creation) and the worker could
block, making the destructor join() hang at shutdown.

Redesign:
- bridge is now std::shared_ptr<LiteClientBridge>, shared with a detached sync thread so
  detaching is safe and litelib_shutdown isn't called while a running sync still holds the
  bridge; the controller's own ref prevents premature shutdown during normal operation.
- startSync() launches the blocking `sync` on a detached thread (non-blocking; never joined).
- refreshModel() gates on syncDone_: while syncing it publishes syncstatus progress only;
  once synced it does the full balance/addresses/list refresh (now fast).
- destructor joins only the fast poll worker and detaches the sync thread -> no hang.
- syncComplete() accessor added.

Tests (deterministic, via a blocking-sync fake; counters made atomic for the detached
thread): testLiteWalletControllerShutdownDoesNotHangDuringSync (destructor returns <1.5s
with sync blocked); refresh/worker tests wait for syncComplete()/a balance-bearing model.
Stable across repeated runs; lite+backend and full-node apps build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 06:35:26 -05:00
DanSandClaude Opus 4.8 4c6384ebff fix(lite): parse real syncstatus shapes (idle vs in-progress)
The real backend returns syncstatus as idle {"syncing":"false"} (string) or in-progress
{"syncing":"true","synced_blocks":N,"total_blocks":M} (commands.rs:83-87), but
parseLiteSyncStatusResponse hard-required the block fields and failed whenever the wallet
wasn't actively syncing — so sync/progress never updated in the real app.

- Read "syncing" as a string; require synced_blocks/total_blocks only when syncing=true;
  idle => complete, synced/total 0.
- fake_lite_backend syncstatus now uses the real "syncing":"true" shape.
- testLiteSyncStatusParserRealShapes covers idle, in-progress, and missing-counts-while-syncing.
- Verified against the live backend via lite_smoke --refresh (syncstatus parse_ok=1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:52:42 -05:00
DanSandClaude Opus 4.8 83801ddb83 feat(lite): M2b-3 — background refresh worker + App::update hook
- LiteWalletController owns a background std::thread worker that, once a wallet is ready,
  refreshes every ~4s and publishes a copyable LiteWalletAppRefreshModel under a mutex.
  Worker auto-starts on lifecycle-ready and is stopped+joined in the destructor. status_
  is written only on the main thread; walletOpen_/syncStarted_ are atomic.
- App::update() calls takeRefreshedModel() and applies it into state_ on the main thread
  (WalletState is non-copyable, so the model crosses the thread boundary, not the state),
  so the existing Balance/Receive/Transactions tabs populate from lite data.
- refreshWalletState() refactored onto refreshModel() (pure, worker-safe).
- testLiteWalletControllerWorkerProducesModel verifies the worker publishes a populated
  model (stable across repeated runs). Builds clean in all configs.

Real-backend smoke (lite_smoke --refresh now runs real output through the parsers) found
two integration bugs, documented in the plan for follow-up:
- syncstatus parser requires synced_blocks/total_blocks but the real idle response is
  {"syncing":"false"} (string), so it fails to parse when not actively syncing.
- the first data query (balance/list) blocks on a full chain sync, which would hang the
  worker's shutdown join — needs a cancel/timeout path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:38:34 -05:00
DanSandClaude Opus 4.8 fb481dee81 feat(lite): M2b-1/2 — shared-bridge refactor + sync/refresh into WalletState
Shared-bridge refactor (litelib is a global singleton; every LiteClientBridge calls
litelib_shutdown() on destruction, so services must not each own one):
- LiteWalletLifecycleService, LiteWalletGateway, LiteSyncService now take a non-owning
  LiteClientBridge*; LiteWalletController owns the single bridge and passes &bridge_.

Sync + controller refresh:
- LiteSyncService::startSync executes the real "sync" command (was a stub).
- LiteWalletController: startSync() (auto-fires when a wallet becomes ready) and
  refreshWalletState(WalletState&) — polls syncstatus, runs gateway.refresh(), maps the
  bundle, applies balances/addresses/transactions/sync into WalletState.

Tests:
- fake_lite_backend.h returns command-shaped JSON (per tests/fixtures/lite/result_parsers.json).
- testLiteWalletControllerRefreshPopulatesState drives the full path against the fake.
- Surfaced + worked around a real integration issue: parseLiteInfoResponse requires
  latest_block_height and the gateway aborts the whole refresh on the first command's
  parse failure (fragile vs partial backend responses; hardening tracked for M2b-3).

Verified: ctest green; lite+backend, full-node, lite-no-backend apps + lite_smoke build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 22:24:18 -05:00
DanSandClaude Opus 4.8 5737778678 feat(lite): real backend integration — controller, M0-M2a wiring, smoke tool, tests
- LiteWalletController (src/wallet/lite_wallet_controller.*): App-owned; runs real
  create/open/restore via the linked SDXL bridge with allowBridgeCalls=true; wipes
  seed/passphrase with sodium_memzero; persists on a ready wallet. M2a:
  applyLiteRefreshModelToWalletState maps a parsed refresh bundle into WalletState
  (zatoshi->DRGX, z/t split, tx typing + confirmations, sync progress).
- App wiring: liteWallet() accessor + init() construction when supportsLiteBackend();
  persist -> settings save.
- settings_page: "Validate" reroutes to the controller for real execution (validation-
  only fallback otherwise); wipes UI secret buffers after submit.
- chain name default -> "main" with load-time migration of legacy "DRAGONX"
  (settings.cpp), preventing the backend "Unknown chain" panic.
- M0: build.sh --lite-backend flag; lite_smoke real-backend tool + CMake targets;
  tests/fake_lite_backend.h deterministic harness.
- Tests (test_phase4): injectable-fake bridge, controller lifecycle, chain-name
  migration, refresh->WalletState mapping; plus the lite test-suite churn-cleanup rewrite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:15:44 -05:00
DanSandClaude Opus 4.8 5a27a13628 feat(lite): lite wallet foundation (inherited working-tree state)
Preserve the previously-uncommitted lite wallet implementation and related dev WIP
under version control:
- src/wallet/ lite services: client bridge, bridge runtime, connection, lifecycle,
  sync, gateway, result parsers, state mapper, artifact contract/resolver, refresh
  services, UI adapters, wallet_backend/capabilities. (Includes two small M1 fixes:
  lifecycle walletReady now parses the response; default chain name -> "main".)
- src/chat/ chat protocol; tests/fixtures/ (lite + hushchat); tools/hushchat_fixture_check.cpp;
  scripts/build-lite-backend-artifact.sh.
- Pre-existing modified app_network/security/wizard, network_refresh_service, sidebar,
  mining_tab, bootstrap dialog, and version headers captured as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:15:28 -05:00
DanSandClaude Opus 4.8 981592f7d3 docs(lite): add v2 implementation plan, source-hygiene guard, and CLAUDE.md
- docs/lite-wallet-implementation-plan-v2-2026-06-04.md: vertical-slice plan that
  supersedes the v1 plan (now banner-marked); carries over the inherited artifact/
  signing/phase-2 design docs for reference.
- scripts/check-source-hygiene.sh: pre-commit/CI guard rejecting >80-char filenames
  and chained churn-token names, to stop the deleted "_plan"/"_batch" scaffolding
  from regrowing.
- CLAUDE.md: repository guidance for future sessions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:15:11 -05:00
DanS 4bfb0095e8 feat(lite): add ObsidianDragonLite build mode and gate full-node features
Add --lite build flow and ObsidianDragonLite target naming, hide full-node pages/features in lite mode, enforce pool-only mining in lite, and include chat port feasibility audit documentation.
2026-05-06 03:42:05 -05:00
DanS 08f5704c45 feat(wallet): persist history and surface pending sends
Add an encrypted SQLite transaction history cache with cached tip metadata and
per-address shielded scan progress so startup and full refreshes avoid
re-scanning every z-address while still invalidating on wallet/address/rescan
changes.

Improve wallet history loading by paging transparent transactions, preserving
cached shielded and sent rows, keeping recent/unconfirmed activity visible, and
classifying mining-address receives. Show z_sendmany opid sends immediately in
History and Overview, pin pending rows through refreshes, and apply optimistic
address/balance debits until opids resolve.

Add timestamped RPC console tracing by source/method without logging params or
results, reduce redundant refresh/RPC calls, and cache Explorer recent block
summaries in SQLite.

Expand focused tests for transaction cache encryption, scan-progress
persistence/invalidation, history preservation, operation-status parsing,
pending send visibility, and Explorer/RPC refresh behavior.
2026-05-05 03:22:14 -05:00
DanS e970006298 fix(history): keep wallet-created sends visible
Replay cached outgoing viewtransaction entries during transaction refresh so shielded sends created from the wallet remain in the History tab after send tracking is cleared.

Keep incomplete tracked sends retryable, preserve cached send timestamp/confirmation metadata, and emit a send placeholder from gettransaction metadata when viewtransaction enrichment is not yet available.

Add regression coverage for cached sends, retryable empty entries, placeholder sends, and send txid cleanup behavior.
2026-04-30 14:57:37 -05:00
DanS 8f7409d99a Refactor app services and stabilize refresh/UI flows
- Add refresh scheduler and network refresh service boundaries for typed
  refresh results, ordered RPC collectors, applicators, and price parsing.
- Add daemon lifecycle and wallet security workflow helpers while preserving
  App-owned command RPC, decrypt, cancellation, and UI handoff behavior.
- Split balance, console, mining, amount formatting, and async task logic into
  focused modules with expanded Phase 4 test coverage.
- Fix market price loading by triggering price refresh immediately, avoiding
  queue-pressure drops, tracking loading/error state, and adding translations.
- Polish send, explorer, peers, settings, theme/schema, and related tab UI.
- Replace checked-in generated language headers with build-generated resources.
- Document the cleanup audit, UI static-state guidance, and architecture updates.
2026-04-29 12:47:57 -05:00
DanS 6658e8725b feat(addresses): improve address labeling and view-only handling
- Add expanded address icon picker with search, bottom-aligned actions, and improved modal sizing
- Embed a pickaxe icon font subset and wire it into typography/address icon rendering
- Track view-only shielded addresses and prevent sends from non-spendable z-addresses
- Improve address transfer dialog sizing, max amount handling, and text clipping
- Tune main header layout values in ui.toml
- Update README, codebase overview, and third-party license documentation
2026-04-27 13:54:28 -05:00
DanS fd6deb796a fix: drag-to-transfer drop not triggering transfer dialog
s_dropTargetIdx was reset to -1 unconditionally each frame, including
the release frame. Since drop target detection runs in PASS 2 (after
the drop handler), the target was always -1 when checked. Only reset
while mouse button is held so the previous frame's value is preserved.

Also bump version to 1.2.0-rc1 and add release notes.
2026-04-12 19:07:41 -05:00
DanS a572268ca1 fix: sidebar nav text overflow for long translations
- Add text scaling for section labels (TOOLS, ADVANCED) in sidebar
- Separate explorer_section key from explorer nav label to fix ALL CAPS
- Shorten long sidebar translations: es/pt settings, pt overview, ru tools/advanced
- Fix explorer translations from ALL CAPS to proper case in all languages
2026-04-12 18:45:48 -05:00
DanS 07504e4bd5 Add bootstrap download dialog and fix 100 missing translation keys
- New BootstrapDownloadDialog accessible from Settings page
  - Stops daemon before download, prevents auto-restart during bootstrap
  - Confirm/Downloading/Done/Failed states with progress display
  - Mirror support (bootstrap2.dragonx.is)
- Add bootstrap_downloading_ flag to prevent tryConnect() auto-reconnect
- Right-align Download Bootstrap + Setup Wizard buttons in settings
- Add 100 missing i18n keys to all 8 language files (de/es/fr/ja/ko/pt/ru/zh)
  - Includes bootstrap, explorer, mining benchmark, transfer, delete blockchain,
    force quit, address label, and settings section translations
- Update add_missing_translations.py with new translation batch
2026-04-12 18:19:01 -05:00
DanS 0191419cf0 feat: modernize address list with drag-transfer, labels, and UX polish
- Rewrite RenderSharedAddressList with two-pass layout architecture
- Add drag-to-transfer: drag address onto another to open transfer dialog
- Add AddressLabelDialog with custom label text and 20-icon picker
- Add AddressTransferDialog with amount input, fee, and balance preview
- Add AddressMeta persistence (label, icon, sortOrder) in settings.json
- Gold favorite border inset 2dp from container edge
- Show hide button on all addresses, not just zero-balance
- Smaller star/hide buttons to clear favorite border
- Semi-transparent dragged row with context-aware tooltip
- Copy-to-clipboard deferred to mouse-up (no copy on drag)
- Themed colors via resolveColor() with CSS variable fallbacks
- Keyboard nav (Up/Down/J/K, Enter to copy, F2 to edit label)
- Add i18n keys for all new UI strings
2026-04-12 17:29:56 -05:00
DanS 6d00408b06 refactor: rewrite sidebar layout with two-pass architecture
Replace fragile Dummy()-based cursor flow with a deterministic two-pass
layout system:
- Pass 1: compute exact Y positions for all elements (pure math)
- Pass 2: render at computed positions using SetCursorScreenPos + draw list

Eliminates the dual-coordinate mismatch that caused persistent centering
and overflow bugs. Height is computed once, not estimated then measured.

Also tune sidebar spacing via ui.toml:
- button-spacing: 4 → 6
- section-gap: 4 → 8
- Add section-label-pad-bottom (4px) below category labels
- bottom-padding: 0 → 4
2026-04-12 16:34:31 -05:00
DanS 2e2b454250 fix: accurate sync speed display, add missing i18n keys, native language names
- Fix blk/s calculation that was inflated ~10x due to resetting the
  time baseline every frame instead of only when blocks advanced
- Add decay when no new blocks arrive for 10s so rate doesn't stay stale
- Add 7 missing translation keys (timeout_off/1min/5min/15min/30min/1hour,
  slider_off) to all 8 language files so settings dropdowns translate
- Show language names in native script (中文, Русский, 日本語, 한국어)
2026-04-12 15:12:36 -05:00
DanS 27e66983fe feat: non-blocking warmup — connect during daemon initialization
Instead of blocking the entire UI with "Activating best chain..." until
the daemon finishes warmup, treat warmup responses as a successful
connection. The wallet now:

- Sets connected=true + warming_up=true when daemon returns RPC -28
- Shows warmup status with block progress in the loading overlay
- Polls getinfo every few seconds to detect warmup completion
- Allows Console, Peers, Settings tabs during warmup
- Shows orange status indicator with warmup message in status bar
- Skips balance/tx/address refresh until warmup completes
- Triggers full data refresh once daemon is ready

Also: fix curl handle/header leak on reconnect, fill in empty
externalDetected error branch, bump version to v1.2.0 in build scripts.
2026-04-12 14:32:57 -05:00
DanS 79fc409b2d fix: auto-refresh peers list, show warmup status during daemon startup
- Fix peer timer calling refreshEncryptionState() instead of
  refreshPeerInfo(), so the Network tab now auto-updates every 5s
- Reorder RPC error handling so warmup messages (Loading block index,
  Verifying blocks, etc.) display in the status bar instead of being
  masked by the generic "Waiting for dragonxd" message
2026-04-12 13:43:45 -05:00
DanS 290352e26d feat: use DragonX DNS seed nodes, pass -maxconnections to daemon, show sync speed
- Replace hardcoded IP addnodes with node.dragonx.is, node1–4.dragonx.is
  in both daemon launch params and auto-generated DRAGONX.conf
- Add max_connections setting (persisted, default 0 = daemon default);
  passed as -maxconnections= flag to dragonxd on startup
- Show blocks/sec in status bar during sync with exponential smoothing
  (e.g. "Syncing 45.2% (12340 left, 85 blk/s)")
2026-04-12 13:22:22 -05:00
DanS c4e5a65b6b feat: CJK font rendering, force quit confirmation, settings i18n
- Rebuild CJK font subset (1421 glyphs) and convert CFF→TTF for
  stb_truetype compatibility, fixing Chinese/Japanese/Korean rendering
- Add force quit confirmation dialog with cancel/confirm actions
- Show force quit tooltip immediately on hover (no delay)
- Translate hardcoded English strings in settings dropdowns
  (auto-lock timeouts, slider "Off" labels)
- Fix mojibake en-dashes in 7 translation JSON files
- Add helper scripts: build_cjk_subset, convert_cjk_to_ttf,
  check_font_coverage, fix_mojibake
2026-04-12 10:32:58 -05:00
DanS dab7f72a81 Redesign benchmark to measure sustained (thermally throttled) hashrate
instead of initial burst performance. Previously the benchmark used a
fixed 20s warmup + 10s peak measurement, which reported inflated
results on thermally constrained hardware (e.g. 179 H/s vs actual
sustained 117 H/s on a MacBook Pro).

- Adaptive warmup with stability detection: mine for at least 90s,
  then compare rolling 10s hashrate windows. Require 3 consecutive
  windows within 5% before declaring thermal equilibrium (cap 300s)
- Average-based measurement: record mean hashrate over 30s instead
  of peak, reflecting real sustained throughput
- Start candidates at half the system cores — lower thread counts
  are rarely optimal and waste time warming up
- Add CoolingDown phase: 5s idle pause between tests so each starts
  from a similar thermal baseline
- Adaptive time estimates: use observed warmup durations from
  completed tests to predict remaining time
- UI shows Stabilizing when waiting for thermal equilibrium past
  the minimum warmup, Cooling during idle pauses"
2026-04-06 13:51:56 -05:00
DanS 60ec8b91ae v1.2.0: UX audit — security fixes, accessibility, and polish
Security (P0):
- Fix sidebar remaining interactive behind lock screen
- Extend auto-lock idle detection to include active widget interactions
- Distinguish missing PIN vault from wrong PIN; auto-switch to passphrase

Blocking UX (P1):
- Add 15s timeout for encryption state check to prevent indefinite loading
- Show restart reason in loading overlay after wallet encryption
- Add Force Quit button on shutdown screen after 10s
- Warn user if embedded daemon fails to start during wizard completion

Polish (P2):
- Use configured explorer URL in Receive tab instead of hardcoded URL
- Increase request memo buffer from 256 to 512 bytes to match Send tab
- Extend notification duration to 5s for critical operations (tx sent,
  wallet encrypted, key import, backup, export)
- Add Reduce Motion accessibility setting (disables page fade + balance lerp)
- Show estimated remaining time during mining thread benchmark
- Add staleness indicator to market price data (warning after 5 min)

New i18n keys: incorrect_pin, incorrect_passphrase, pin_not_set,
restarting_after_encryption, force_quit, reduce_motion, tt_reduce_motion,
ago, wizard_daemon_start_failed
2026-04-04 19:10:58 -05:00
DanS 65537b67fe refactor: tab-aware prioritized refresh system
Split monolithic refreshData() into independent sub-functions
(refreshCoreData, refreshAddressData, refreshTransactionData,
refreshEncryptionState) each with its own timer and atomic guard.

Per-category timers replace the single 5s refresh_timer_:
- core_timer_: balance + blockchain info (5s default)
- transaction_timer_: tx list + enrichment (10s default)
- address_timer_: z/t address lists (15s default)
- peer_timer_: encryption state (10s default)

Tab-switching via setCurrentPage() adjusts active intervals so
the current tab's data refreshes faster (e.g. 3s core on Overview,
5s transactions on History) while background categories slow down.

Use fast_worker_ for core data on Overview tab to avoid blocking
behind the main refresh batch.

Bump version to 1.1.2.
2026-04-04 13:05:00 -05:00
DanS abb5ae1d65 refactor: extract AI/agent files into separate repo
ObsidianDragon-agent/ is now a standalone git repo (future submodule)
so AI configuration files are not pushed to the main repository.

- Remove copilot-instructions.md and ARCHITECTURE.md from main tracking
- Remove symlinks from .github/ and docs/
- Add ObsidianDragon-agent/ and .github/ to .gitignore
2026-04-04 11:36:04 -05:00
DanS 4833e37ee2 refactor: move AI/agent files into ObsidianDragon-agent/
- copilot-instructions.md → ObsidianDragon-agent/copilot-instructions.md
- ARCHITECTURE.md → ObsidianDragon-agent/ARCHITECTURE.md
- Symlinks at original locations preserve Copilot auto-discovery
2026-04-04 11:29:12 -05:00
DanS dde027b364 docs: add ARCHITECTURE.md with project overview
Covers directory layout, threading model, RPC architecture,
connection lifecycle, UI system, build system, and key conventions.
2026-04-04 11:17:21 -05:00
DanS c054110193 docs: add copilot-instructions.md and file-level comments
- Create .github/copilot-instructions.md with project coding standards,
  architecture overview, threading model, and key rules for AI sessions
- Add module description comments to app.cpp, rpc_client.cpp, rpc_worker.cpp,
  embedded_daemon.cpp, xmrig_manager.cpp, console_tab.cpp, settings.cpp
- Add ASCII connection state diagram to app_network.cpp
- Remove /.github/ from .gitignore so instructions file is tracked
2026-04-04 11:14:31 -05:00
DanS 24fa921f8c fix: console not connected when fast-lane RPC still connecting
The console tab was passed fast_rpc_ even before its async connection
completed, causing 'Not connected to daemon' errors despite the main
RPC being connected and sync data flowing. Fall back to the main
rpc_/worker_ until fast_rpc_ reports isConnected().
2026-04-03 11:34:32 -05:00
DanS b25723c739 build: macOS universal binary (arm64+x86_64) with deployment target 11.0
- Set CMAKE_OSX_DEPLOYMENT_TARGET and CMAKE_OSX_ARCHITECTURES before
  project() so they propagate to all FetchContent dependencies (SDL3, etc.)
- build.sh: native mac release builds universal binary, detects and
  rebuilds single-arch libsodium, verifies with lipo, exports
  MACOSX_DEPLOYMENT_TARGET; dev build uses correct build/mac directory
- fetch-libsodium.sh: build arm64 and x86_64 separately then merge with
  lipo on native macOS; fix sha256sum unavailable on macOS (use shasum)
2026-04-03 10:55:07 -05:00
DanS 6951e6113e feat: thread benchmark, GPU-aware idle mining, thread scaling fix
- Add pool mining thread benchmark: cycles through thread counts with
  20s warmup + 10s measurement to find optimal setting for CPU
- Add GPU-aware idle detection: GPU utilization >= 10% (video, games)
  treats system as active; toggle in mining tab header (default: on)
  Supports AMD sysfs, NVIDIA nvidia-smi, Intel freq ratio; -1 on macOS
- Fix idle thread scaling: use getRequestedThreads() for immediate
  thread count instead of xmrig API threads_active which lags on restart
- Apply active thread count on initial mining start when user is active
- Skip idle mining adjustments while benchmark is running
- Disable thread grid drag-to-select during benchmark
- Add idle_gpu_aware setting with JSON persistence (default: true)
- Add 7 i18n English strings for benchmark and GPU-aware tooltips
2026-04-01 17:06:05 -05:00
DanS bc1f7aa13a update build output filenames to include version info 2026-03-25 11:24:21 -05:00
DanS dcd6564f7d update hardcoded version for mac dmg build 2026-03-25 11:18:03 -05:00
DanS 74691fa255 feat: track shielded send txids via z_viewtransaction
Extract txids from completed z_sendmany operations and store in
send_txids_ so pure shielded sends are discoverable. The network
thread includes them in the enrichment set, calls z_viewtransaction,
caches results in viewtx_cache_, and removes them from send_txids_.
2026-03-25 11:06:09 -05:00
DanS e130ebc6da fix: macOS block index corruption, dbcache auto-sizing, import key rescan height
- Shutdown: 3-phase stop (wait for RPC stop → SIGTERM → SIGKILL) prevents
  LevelDB flush interruption on macOS/APFS that caused full re-sync on restart
- dbcache: auto-detect RAM and set -dbcache to 12.5% (clamped 450-4096 MB)
  on macOS (sysctl), Linux (sysconf), and Windows (GlobalMemoryStatusEx)
- Import key: pass user-entered start height to z_importkey and trigger
  rescanblockchain from that height for t-key imports
- Bump version to 1.1.1
2026-03-25 11:00:14 -05:00
DanS 7a37f68e9b update mac icons 2026-03-19 14:46:33 -05:00
DanS d79d013060 feat: sync thread grid during idle scaling, skip lock screen while pool mining, add paste preview to import key dialog
- Mining tab: sync s_selected_threads with actual thread count when idle
  thread scaling adjusts threads (solo via genproclimit, pool via
  threads_active), skipping sync during user drag
- Auto-lock: bypass lock screen overlay when xmrig pool mining is active
  so the mining UI remains accessible
- Import key dialog: add clipboard hover preview with transparent overlay
  on the input field, inline key type validation next to title (matching
  send tab paste button pattern), configurable via ui.toml
2026-03-19 06:10:46 -05:00
DanS 474250bb50 v1.1.0: explorer tab, bootstrap fixes, full theme overlay merge
Explorer tab:
- New block explorer tab with search, chain stats, mempool info,
  recent blocks table, block detail modal with tx expansion
- Sidebar nav entry, i18n strings, ui.toml layout values

Bootstrap fixes:
- Move wizard Done handler into render() — was dead code, preventing
  startEmbeddedDaemon() and tryConnect() from firing post-wizard
- Stop deleting BDB database/ dir during cleanup — caused LSN mismatch
  that salvaged wallet.dat into wallet.{timestamp}.bak
- Add banlist.dat, db.log, .lock to cleanup file list
- Fatal extraction failure for blocks/ and chainstate/ files
- Verification progress: split SHA-256 (0-50%) and MD5 (50-100%)

Theme system:
- Expand overlay merge to apply ALL sections (tabs, dialogs, components,
  screens, flat sections), not just theme+backdrop+effects
- Add screens and security section parsing to UISchema
- Build-time theme expansion via expand_themes.py (CMake + build.sh)

Other:
- Version bump to 1.1.0
- WalletState::clear() resets all fields (sync, daemon info, etc.)
- Sidebar item-height 42 → 36
2026-03-17 18:49:46 -05:00
DanS 40dd6d45b2 daemon version check, idle mining control, bootstrap mirror, import key paste, and cleanup
- Add startup binary version checking for dragonxd/xmrig
- Display daemon version in UI
- Add idle mining thread count adjustment
- Add bootstrap mirror option (bootstrap2.dragonx.is) in setup wizard
- Add paste button to import private key dialog with address validation
- Add z-address generation UI feedback (loading indicator)
- Add option to delete blockchain data while preserving wallet.dat
- Add font scale slider hotkey tooltip (Ctrl+Plus/Ctrl+Minus)
- Fix Windows RPC auth: trim \r from config values, add .cookie fallback
- Fix connection status message during block index loading
- Improve application shutdown to prevent lingering background process
2026-03-17 14:57:12 -05:00
DanS 1b97476a54 update version to v1.0.2 2026-03-12 02:29:08 -05:00
DanS d7bc5c638a fix: remove D3D11 debug layer flag that prevented startup on user machines
DRAGONX_DEBUG was defined unconditionally, causing D3D11CreateDevice() to
request the debug layer via D3D11_CREATE_DEVICE_DEBUG. This layer is only
available on machines with the Windows SDK or Graphics Tools installed,
so the call fails with DXGI_ERROR_SDK_COMPONENT_MISSING on regular user
machines — causing the app to silently exit.
2026-03-12 00:13:27 -05:00
DanS 39f193a264 fix xmrig bundling issues 2026-03-11 21:14:03 -05:00
DanS 4023af9466 fix scrolling bug 2026-03-11 03:15:31 -05:00
DanS 853e3e0f17 build: Linux release outputs binaries zip + AppImage, bundle sapling params
- Linux --linux-release now produces both ObsidianDragon-Linux-x64.zip
  (raw binaries) and ObsidianDragon.AppImage (single-file)
- Windows --win-release keeps standalone exe alongside zip with binaries
- Bundle sapling-spend.params and sapling-output.params in Linux release
2026-03-11 01:38:59 -05:00
DanS 1bcfcd1c73 ui: add idle delay combo to mining tab
Add inline combo box (30s/1m/2m/5m/10m) next to the idle mining
toggle so users can choose how long to wait before idle mining starts.
2026-03-11 01:38:48 -05:00
DanS 6bb35bd3e1 ui: reorganize settings page with collapsible sections
- Rename APPEARANCE section to THEME & LANGUAGE
- Move font scale slider out of effects into main section
- Collapse visual effects into "Advanced Effects..." toggle
- Collapse wallet tools into "Tools & Actions..." toggle
- Remove redundant Tools & Actions divider/section from wallet card
- Add i18n strings: theme_language, advanced_effects, tools_actions
2026-03-11 01:38:40 -05:00