8 Commits

Author SHA1 Message Date
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
23 changed files with 618 additions and 60 deletions

Binary file not shown.

View File

@@ -271,12 +271,18 @@
"console_app": "App", "console_app": "App",
"console_auto_scroll": "Automatisch scrollen", "console_auto_scroll": "Automatisch scrollen",
"console_available_commands": "Verfügbare Befehle:", "console_available_commands": "Verfügbare Befehle:",
"console_backend_reference": "Backend-Befehlsreferenz",
"console_backend_unavailable": "Kein Backend",
"console_capturing_output": "Erfasse Daemon-Ausgabe...", "console_capturing_output": "Erfasse Daemon-Ausgabe...",
"console_cat_advanced": "Erweitert",
"console_cat_blockchain": "Blockchain", "console_cat_blockchain": "Blockchain",
"console_cat_control": "Steuerung", "console_cat_control": "Steuerung",
"console_cat_keys": "Schlüssel & Sicherheit",
"console_cat_mining": "Mining", "console_cat_mining": "Mining",
"console_cat_network": "Netzwerk", "console_cat_network": "Netzwerk",
"console_cat_raw_transactions": "Rohtransaktionen", "console_cat_raw_transactions": "Rohtransaktionen",
"console_cat_send": "Senden",
"console_cat_sync": "Synchronisierung",
"console_cat_utility": "Dienstprogramme", "console_cat_utility": "Dienstprogramme",
"console_cat_wallet": "Wallet", "console_cat_wallet": "Wallet",
"console_clear": "Leeren", "console_clear": "Leeren",
@@ -310,11 +316,14 @@
"console_help_help": " help - Diese Hilfe anzeigen", "console_help_help": " help - Diese Hilfe anzeigen",
"console_help_setgenerate": " setgenerate - Mining steuern", "console_help_setgenerate": " setgenerate - Mining steuern",
"console_help_stop": " stop - Daemon stoppen", "console_help_stop": " stop - Daemon stoppen",
"console_last_error": "Letzter Fehler:",
"console_line_count": "%zu Zeilen", "console_line_count": "%zu Zeilen",
"console_matches": "Treffer", "console_matches": "Treffer",
"console_new_lines": "%d neue Zeilen", "console_new_lines": "%d neue Zeilen",
"console_no_daemon": "Kein Daemon", "console_no_daemon": "Kein Daemon",
"console_no_output": "(keine Ausgabe)",
"console_not_connected": "Fehler: Nicht mit Daemon verbunden", "console_not_connected": "Fehler: Nicht mit Daemon verbunden",
"console_not_connected_lite": "Fehler: Keine Wallet geöffnet",
"console_quit_note": "'quit'/'exit' werden hier nicht benötigt — schließen Sie einfach das Fenster.", "console_quit_note": "'quit'/'exit' werden hier nicht benötigt — schließen Sie einfach das Fenster.",
"console_ref_builds": "Ergibt", "console_ref_builds": "Ergibt",
"console_ref_cancel": "Abbrechen", "console_ref_cancel": "Abbrechen",
@@ -330,12 +339,14 @@
"console_ref_run_confirm": "%s jetzt ausführen? Dies ist ein folgenreicher Befehl.", "console_ref_run_confirm": "%s jetzt ausführen? Dies ist ein folgenreicher Befehl.",
"console_ref_search_hint": "Nach Name oder Aufgabe suchen…", "console_ref_search_hint": "Nach Name oder Aufgabe suchen…",
"console_ref_select_hint": "Wählen Sie einen Befehl, um zu sehen, was er tut.", "console_ref_select_hint": "Wählen Sie einen Befehl, um zu sehen, was er tut.",
"console_ref_value": "Wert",
"console_rpc_reference": "RPC-Befehlsreferenz", "console_rpc_reference": "RPC-Befehlsreferenz",
"console_rpc_trace": "RPC", "console_rpc_trace": "RPC",
"console_scanline": "Konsolen-Scanline", "console_scanline": "Konsolen-Scanline",
"console_search_commands": "Befehle suchen...", "console_search_commands": "Befehle suchen...",
"console_select_all": "Alles auswählen", "console_select_all": "Alles auswählen",
"console_show_app_output": "[App]-Wallet-Protokollzeilen anzeigen", "console_show_app_output": "[App]-Wallet-Protokollzeilen anzeigen",
"console_show_backend_ref": "Backend-Befehlsreferenz anzeigen",
"console_show_daemon_output": "Daemon-Ausgabe anzeigen", "console_show_daemon_output": "Daemon-Ausgabe anzeigen",
"console_show_errors_only": "Nur Fehler anzeigen", "console_show_errors_only": "Nur Fehler anzeigen",
"console_show_rpc_ref": "RPC-Befehlsreferenz anzeigen", "console_show_rpc_ref": "RPC-Befehlsreferenz anzeigen",
@@ -348,6 +359,7 @@
"console_status_stopped": "Gestoppt", "console_status_stopped": "Gestoppt",
"console_status_stopping": "Stoppt", "console_status_stopping": "Stoppt",
"console_status_unknown": "Unbekannt", "console_status_unknown": "Unbekannt",
"console_stop_confirm_node": "'stop' fährt den Node herunter und trennt die Wallet. Geben Sie zur Bestätigung erneut 'stop' ein.",
"console_tab_completion": "Tab zur Vervollständigung", "console_tab_completion": "Tab zur Vervollständigung",
"console_text_colors": "Textfarben", "console_text_colors": "Textfarben",
"console_toggle_accents": "Farbakzente der Zeilen umschalten", "console_toggle_accents": "Farbakzente der Zeilen umschalten",
@@ -626,6 +638,7 @@
"lite_birthday_backup": "Geburtstag: %llu (auch diesen sichern)", "lite_birthday_backup": "Geburtstag: %llu (auch diesen sichern)",
"lite_birthday_hint": "Blockhöhe, ab der gescannt werden soll. Bei 0 belassen, falls unbekannt (langsamerer vollständiger Scan).", "lite_birthday_hint": "Blockhöhe, ab der gescannt werden soll. Bei 0 belassen, falls unbekannt (langsamerer vollständiger Scan).",
"lite_birthday_label": "Geburtsblock", "lite_birthday_label": "Geburtsblock",
"lite_console_backend_commands": "Backend-Befehle:",
"lite_console_help_passthrough": "Jede andere Eingabe wird als Lite-Wallet-Konsolenbefehl ausgeführt.", "lite_console_help_passthrough": "Jede andere Eingabe wird als Lite-Wallet-Konsolenbefehl ausgeführt.",
"lite_copy": "Kopieren", "lite_copy": "Kopieren",
"lite_could_not_write": "Konnte nicht schreiben ", "lite_could_not_write": "Konnte nicht schreiben ",
@@ -643,6 +656,7 @@
"lite_net_add_url_hint": "https://ihr-lite-server", "lite_net_add_url_hint": "https://ihr-lite-server",
"lite_net_checking": "wird geprüft…", "lite_net_checking": "wird geprüft…",
"lite_net_connected": "Verbunden", "lite_net_connected": "Verbunden",
"lite_net_connecting": "Verbinde…",
"lite_net_custom": "Benutzerdefiniert", "lite_net_custom": "Benutzerdefiniert",
"lite_net_disconnected": "Nicht verbunden", "lite_net_disconnected": "Nicht verbunden",
"lite_net_hidden_section": "Ausgeblendete Server", "lite_net_hidden_section": "Ausgeblendete Server",

View File

@@ -271,12 +271,18 @@
"console_app": "App", "console_app": "App",
"console_auto_scroll": "Auto-desplazamiento", "console_auto_scroll": "Auto-desplazamiento",
"console_available_commands": "Comandos disponibles:", "console_available_commands": "Comandos disponibles:",
"console_backend_reference": "Referencia de Comandos del Backend",
"console_backend_unavailable": "Sin backend",
"console_capturing_output": "Capturando salida del daemon...", "console_capturing_output": "Capturando salida del daemon...",
"console_cat_advanced": "Avanzado",
"console_cat_blockchain": "Blockchain", "console_cat_blockchain": "Blockchain",
"console_cat_control": "Control", "console_cat_control": "Control",
"console_cat_keys": "Claves y seguridad",
"console_cat_mining": "Minería", "console_cat_mining": "Minería",
"console_cat_network": "Red", "console_cat_network": "Red",
"console_cat_raw_transactions": "Transacciones sin procesar", "console_cat_raw_transactions": "Transacciones sin procesar",
"console_cat_send": "Enviar",
"console_cat_sync": "Sincronización",
"console_cat_utility": "Utilidades", "console_cat_utility": "Utilidades",
"console_cat_wallet": "Cartera", "console_cat_wallet": "Cartera",
"console_clear": "Limpiar", "console_clear": "Limpiar",
@@ -310,11 +316,14 @@
"console_help_help": " help - Mostrar este mensaje de ayuda", "console_help_help": " help - Mostrar este mensaje de ayuda",
"console_help_setgenerate": " setgenerate - Controlar minería", "console_help_setgenerate": " setgenerate - Controlar minería",
"console_help_stop": " stop - Detener el daemon", "console_help_stop": " stop - Detener el daemon",
"console_last_error": "Último error:",
"console_line_count": "%zu líneas", "console_line_count": "%zu líneas",
"console_matches": "coincidencias", "console_matches": "coincidencias",
"console_new_lines": "%d nuevas líneas", "console_new_lines": "%d nuevas líneas",
"console_no_daemon": "Sin daemon", "console_no_daemon": "Sin daemon",
"console_no_output": "(sin salida)",
"console_not_connected": "Error: No conectado al daemon", "console_not_connected": "Error: No conectado al daemon",
"console_not_connected_lite": "Error: No hay ninguna cartera abierta",
"console_quit_note": "'quit'/'exit' no son necesarios aquí — simplemente cierra la ventana.", "console_quit_note": "'quit'/'exit' no son necesarios aquí — simplemente cierra la ventana.",
"console_ref_builds": "Genera", "console_ref_builds": "Genera",
"console_ref_cancel": "Cancelar", "console_ref_cancel": "Cancelar",
@@ -330,12 +339,14 @@
"console_ref_run_confirm": "¿Ejecutar %s ahora? Es un comando con consecuencias.", "console_ref_run_confirm": "¿Ejecutar %s ahora? Es un comando con consecuencias.",
"console_ref_search_hint": "Buscar por nombre o tarea…", "console_ref_search_hint": "Buscar por nombre o tarea…",
"console_ref_select_hint": "Selecciona un comando para ver qué hace.", "console_ref_select_hint": "Selecciona un comando para ver qué hace.",
"console_ref_value": "valor",
"console_rpc_reference": "Referencia de Comandos RPC", "console_rpc_reference": "Referencia de Comandos RPC",
"console_rpc_trace": "RPC", "console_rpc_trace": "RPC",
"console_scanline": "Líneas de consola", "console_scanline": "Líneas de consola",
"console_search_commands": "Buscar comandos...", "console_search_commands": "Buscar comandos...",
"console_select_all": "Seleccionar Todo", "console_select_all": "Seleccionar Todo",
"console_show_app_output": "Mostrar las líneas de registro de la cartera [app]", "console_show_app_output": "Mostrar las líneas de registro de la cartera [app]",
"console_show_backend_ref": "Mostrar referencia de comandos del backend",
"console_show_daemon_output": "Mostrar salida del daemon", "console_show_daemon_output": "Mostrar salida del daemon",
"console_show_errors_only": "Mostrar solo errores", "console_show_errors_only": "Mostrar solo errores",
"console_show_rpc_ref": "Mostrar referencia de comandos RPC", "console_show_rpc_ref": "Mostrar referencia de comandos RPC",
@@ -348,6 +359,7 @@
"console_status_stopped": "Detenido", "console_status_stopped": "Detenido",
"console_status_stopping": "Deteniendo", "console_status_stopping": "Deteniendo",
"console_status_unknown": "Desconocido", "console_status_unknown": "Desconocido",
"console_stop_confirm_node": "'stop' apagará el nodo y desconectará la cartera. Escribe 'stop' de nuevo para confirmar.",
"console_tab_completion": "Tab para completar", "console_tab_completion": "Tab para completar",
"console_text_colors": "Colores de texto", "console_text_colors": "Colores de texto",
"console_toggle_accents": "Alternar acentos de color de línea", "console_toggle_accents": "Alternar acentos de color de línea",
@@ -626,6 +638,7 @@
"lite_birthday_backup": "Cumpleaños: %llu (respalda esto también)", "lite_birthday_backup": "Cumpleaños: %llu (respalda esto también)",
"lite_birthday_hint": "Altura de bloque desde la que empezar a escanear. Deja 0 si se desconoce (escaneo completo más lento).", "lite_birthday_hint": "Altura de bloque desde la que empezar a escanear. Deja 0 si se desconoce (escaneo completo más lento).",
"lite_birthday_label": "Fecha de creación", "lite_birthday_label": "Fecha de creación",
"lite_console_backend_commands": "Comandos del backend:",
"lite_console_help_passthrough": "Cualquier otra entrada se ejecuta como un comando de consola de la cartera lite.", "lite_console_help_passthrough": "Cualquier otra entrada se ejecuta como un comando de consola de la cartera lite.",
"lite_copy": "Copiar", "lite_copy": "Copiar",
"lite_could_not_write": "No se pudo escribir ", "lite_could_not_write": "No se pudo escribir ",
@@ -643,6 +656,7 @@
"lite_net_add_url_hint": "https://tu-servidor-lite", "lite_net_add_url_hint": "https://tu-servidor-lite",
"lite_net_checking": "comprobando…", "lite_net_checking": "comprobando…",
"lite_net_connected": "Conectado", "lite_net_connected": "Conectado",
"lite_net_connecting": "Conectando…",
"lite_net_custom": "Personalizado", "lite_net_custom": "Personalizado",
"lite_net_disconnected": "No conectado", "lite_net_disconnected": "No conectado",
"lite_net_hidden_section": "Servidores ocultos", "lite_net_hidden_section": "Servidores ocultos",

View File

@@ -271,12 +271,18 @@
"console_app": "App", "console_app": "App",
"console_auto_scroll": "Défilement auto", "console_auto_scroll": "Défilement auto",
"console_available_commands": "Commandes disponibles :", "console_available_commands": "Commandes disponibles :",
"console_backend_reference": "Référence des commandes du backend",
"console_backend_unavailable": "Aucun backend",
"console_capturing_output": "Capture de la sortie du daemon...", "console_capturing_output": "Capture de la sortie du daemon...",
"console_cat_advanced": "Avancé",
"console_cat_blockchain": "Blockchain", "console_cat_blockchain": "Blockchain",
"console_cat_control": "Contrôle", "console_cat_control": "Contrôle",
"console_cat_keys": "Clés et sécurité",
"console_cat_mining": "Minage", "console_cat_mining": "Minage",
"console_cat_network": "Réseau", "console_cat_network": "Réseau",
"console_cat_raw_transactions": "Transactions brutes", "console_cat_raw_transactions": "Transactions brutes",
"console_cat_send": "Envoyer",
"console_cat_sync": "Synchronisation",
"console_cat_utility": "Utilitaires", "console_cat_utility": "Utilitaires",
"console_cat_wallet": "Portefeuille", "console_cat_wallet": "Portefeuille",
"console_clear": "Effacer", "console_clear": "Effacer",
@@ -310,11 +316,14 @@
"console_help_help": " help - Afficher ce message d'aide", "console_help_help": " help - Afficher ce message d'aide",
"console_help_setgenerate": " setgenerate - Contrôler le minage", "console_help_setgenerate": " setgenerate - Contrôler le minage",
"console_help_stop": " stop - Arrêter le daemon", "console_help_stop": " stop - Arrêter le daemon",
"console_last_error": "Dernière erreur :",
"console_line_count": "%zu lignes", "console_line_count": "%zu lignes",
"console_matches": "correspondances", "console_matches": "correspondances",
"console_new_lines": "%d nouvelles lignes", "console_new_lines": "%d nouvelles lignes",
"console_no_daemon": "Pas de daemon", "console_no_daemon": "Pas de daemon",
"console_no_output": "(aucune sortie)",
"console_not_connected": "Erreur : Non connecté au daemon", "console_not_connected": "Erreur : Non connecté au daemon",
"console_not_connected_lite": "Erreur : Aucun portefeuille ouvert",
"console_quit_note": "'quit'/'exit' ne sont pas nécessaires ici — fermez simplement la fenêtre.", "console_quit_note": "'quit'/'exit' ne sont pas nécessaires ici — fermez simplement la fenêtre.",
"console_ref_builds": "Génère", "console_ref_builds": "Génère",
"console_ref_cancel": "Annuler", "console_ref_cancel": "Annuler",
@@ -330,12 +339,14 @@
"console_ref_run_confirm": "Exécuter %s maintenant ? C'est une commande à conséquences.", "console_ref_run_confirm": "Exécuter %s maintenant ? C'est une commande à conséquences.",
"console_ref_search_hint": "Rechercher par nom ou tâche…", "console_ref_search_hint": "Rechercher par nom ou tâche…",
"console_ref_select_hint": "Sélectionnez une commande pour voir ce qu'elle fait.", "console_ref_select_hint": "Sélectionnez une commande pour voir ce qu'elle fait.",
"console_ref_value": "valeur",
"console_rpc_reference": "Référence des commandes RPC", "console_rpc_reference": "Référence des commandes RPC",
"console_rpc_trace": "RPC", "console_rpc_trace": "RPC",
"console_scanline": "Scanline de la console", "console_scanline": "Scanline de la console",
"console_search_commands": "Rechercher des commandes...", "console_search_commands": "Rechercher des commandes...",
"console_select_all": "Tout sélectionner", "console_select_all": "Tout sélectionner",
"console_show_app_output": "Afficher les lignes du journal du portefeuille [app]", "console_show_app_output": "Afficher les lignes du journal du portefeuille [app]",
"console_show_backend_ref": "Afficher la référence des commandes du backend",
"console_show_daemon_output": "Afficher la sortie du daemon", "console_show_daemon_output": "Afficher la sortie du daemon",
"console_show_errors_only": "Afficher uniquement les erreurs", "console_show_errors_only": "Afficher uniquement les erreurs",
"console_show_rpc_ref": "Afficher la référence des commandes RPC", "console_show_rpc_ref": "Afficher la référence des commandes RPC",
@@ -348,6 +359,7 @@
"console_status_stopped": "Arrêté", "console_status_stopped": "Arrêté",
"console_status_stopping": "Arrêt", "console_status_stopping": "Arrêt",
"console_status_unknown": "Inconnu", "console_status_unknown": "Inconnu",
"console_stop_confirm_node": "'stop' arrêtera le nœud et déconnectera le portefeuille. Tapez à nouveau 'stop' pour confirmer.",
"console_tab_completion": "Tab pour compléter", "console_tab_completion": "Tab pour compléter",
"console_text_colors": "Couleurs du texte", "console_text_colors": "Couleurs du texte",
"console_toggle_accents": "Basculer les accents de couleur des lignes", "console_toggle_accents": "Basculer les accents de couleur des lignes",
@@ -626,6 +638,7 @@
"lite_birthday_backup": "Date de création : %llu (à sauvegarder également)", "lite_birthday_backup": "Date de création : %llu (à sauvegarder également)",
"lite_birthday_hint": "Hauteur de bloc à partir de laquelle commencer l'analyse. Laissez 0 si inconnue (analyse complète plus lente).", "lite_birthday_hint": "Hauteur de bloc à partir de laquelle commencer l'analyse. Laissez 0 si inconnue (analyse complète plus lente).",
"lite_birthday_label": "Bloc de création", "lite_birthday_label": "Bloc de création",
"lite_console_backend_commands": "Commandes du backend :",
"lite_console_help_passthrough": "Toute autre entrée est exécutée comme une commande de la console du portefeuille lite.", "lite_console_help_passthrough": "Toute autre entrée est exécutée comme une commande de la console du portefeuille lite.",
"lite_copy": "Copier", "lite_copy": "Copier",
"lite_could_not_write": "Impossible d'écrire ", "lite_could_not_write": "Impossible d'écrire ",
@@ -643,6 +656,7 @@
"lite_net_add_url_hint": "https://votre-serveur-lite", "lite_net_add_url_hint": "https://votre-serveur-lite",
"lite_net_checking": "vérification…", "lite_net_checking": "vérification…",
"lite_net_connected": "Connecté", "lite_net_connected": "Connecté",
"lite_net_connecting": "Connexion…",
"lite_net_custom": "Personnalisé", "lite_net_custom": "Personnalisé",
"lite_net_disconnected": "Non connecté", "lite_net_disconnected": "Non connecté",
"lite_net_hidden_section": "Serveurs masqués", "lite_net_hidden_section": "Serveurs masqués",

View File

@@ -271,12 +271,18 @@
"console_app": "アプリ", "console_app": "アプリ",
"console_auto_scroll": "自動スクロール", "console_auto_scroll": "自動スクロール",
"console_available_commands": "利用可能なコマンド:", "console_available_commands": "利用可能なコマンド:",
"console_backend_reference": "バックエンドコマンドリファレンス",
"console_backend_unavailable": "バックエンドなし",
"console_capturing_output": "デーモン出力をキャプチャ中...", "console_capturing_output": "デーモン出力をキャプチャ中...",
"console_cat_advanced": "詳細設定",
"console_cat_blockchain": "ブロックチェーン", "console_cat_blockchain": "ブロックチェーン",
"console_cat_control": "制御", "console_cat_control": "制御",
"console_cat_keys": "鍵とセキュリティ",
"console_cat_mining": "マイニング", "console_cat_mining": "マイニング",
"console_cat_network": "ネットワーク", "console_cat_network": "ネットワーク",
"console_cat_raw_transactions": "生トランザクション", "console_cat_raw_transactions": "生トランザクション",
"console_cat_send": "送金",
"console_cat_sync": "同期",
"console_cat_utility": "ユーティリティ", "console_cat_utility": "ユーティリティ",
"console_cat_wallet": "ウォレット", "console_cat_wallet": "ウォレット",
"console_clear": "クリア", "console_clear": "クリア",
@@ -310,11 +316,14 @@
"console_help_help": " help - このヘルプを表示", "console_help_help": " help - このヘルプを表示",
"console_help_setgenerate": " setgenerate - マイニングを制御", "console_help_setgenerate": " setgenerate - マイニングを制御",
"console_help_stop": " stop - デーモンを停止", "console_help_stop": " stop - デーモンを停止",
"console_last_error": "最後のエラー:",
"console_line_count": "%zu 行", "console_line_count": "%zu 行",
"console_matches": "件一致", "console_matches": "件一致",
"console_new_lines": "%d 新しい行", "console_new_lines": "%d 新しい行",
"console_no_daemon": "デーモンなし", "console_no_daemon": "デーモンなし",
"console_no_output": "(出力なし)",
"console_not_connected": "エラー:デーモンに接続されていません", "console_not_connected": "エラー:デーモンに接続されていません",
"console_not_connected_lite": "エラー:ウォレットが開かれていません",
"console_quit_note": "ここでは 'quit''exit' は不要です — ウィンドウを閉じるだけで構いません。", "console_quit_note": "ここでは 'quit''exit' は不要です — ウィンドウを閉じるだけで構いません。",
"console_ref_builds": "生成", "console_ref_builds": "生成",
"console_ref_cancel": "キャンセル", "console_ref_cancel": "キャンセル",
@@ -330,12 +339,14 @@
"console_ref_run_confirm": "%s を今すぐ実行しますか? 影響の大きいコマンドです。", "console_ref_run_confirm": "%s を今すぐ実行しますか? 影響の大きいコマンドです。",
"console_ref_search_hint": "名前または用途で検索…", "console_ref_search_hint": "名前または用途で検索…",
"console_ref_select_hint": "コマンドを選ぶと内容が表示されます。", "console_ref_select_hint": "コマンドを選ぶと内容が表示されます。",
"console_ref_value": "値",
"console_rpc_reference": "RPCコマンドリファレンス", "console_rpc_reference": "RPCコマンドリファレンス",
"console_rpc_trace": "RPC", "console_rpc_trace": "RPC",
"console_scanline": "コンソールスキャンライン", "console_scanline": "コンソールスキャンライン",
"console_search_commands": "コマンドを検索...", "console_search_commands": "コマンドを検索...",
"console_select_all": "すべて選択", "console_select_all": "すべて選択",
"console_show_app_output": "[app] ウォレットのログ行を表示", "console_show_app_output": "[app] ウォレットのログ行を表示",
"console_show_backend_ref": "バックエンドコマンドリファレンスを表示",
"console_show_daemon_output": "デーモン出力を表示", "console_show_daemon_output": "デーモン出力を表示",
"console_show_errors_only": "エラーのみ表示", "console_show_errors_only": "エラーのみ表示",
"console_show_rpc_ref": "RPCコマンドリファレンスを表示", "console_show_rpc_ref": "RPCコマンドリファレンスを表示",
@@ -348,6 +359,7 @@
"console_status_stopped": "停止済み", "console_status_stopped": "停止済み",
"console_status_stopping": "停止中", "console_status_stopping": "停止中",
"console_status_unknown": "不明", "console_status_unknown": "不明",
"console_stop_confirm_node": "'stop' はノードを停止し、ウォレットを切断します。確認するにはもう一度 'stop' と入力してください。",
"console_tab_completion": "Tabで補完", "console_tab_completion": "Tabで補完",
"console_text_colors": "テキスト色", "console_text_colors": "テキスト色",
"console_toggle_accents": "行のカラーアクセントを切り替え", "console_toggle_accents": "行のカラーアクセントを切り替え",
@@ -626,6 +638,7 @@
"lite_birthday_backup": "誕生日:%llu (これもバックアップしてください)", "lite_birthday_backup": "誕生日:%llu (これもバックアップしてください)",
"lite_birthday_hint": "スキャンを開始するブロック高。不明な場合は0のままにしてください完全スキャンが遅くなります。", "lite_birthday_hint": "スキャンを開始するブロック高。不明な場合は0のままにしてください完全スキャンが遅くなります。",
"lite_birthday_label": "バースデー", "lite_birthday_label": "バースデー",
"lite_console_backend_commands": "バックエンドコマンド:",
"lite_console_help_passthrough": "その他の入力はライトウォレットのコンソールコマンドとして実行されます。", "lite_console_help_passthrough": "その他の入力はライトウォレットのコンソールコマンドとして実行されます。",
"lite_copy": "コピー", "lite_copy": "コピー",
"lite_could_not_write": "書き込めませんでした ", "lite_could_not_write": "書き込めませんでした ",
@@ -643,6 +656,7 @@
"lite_net_add_url_hint": "https://your-lite-server", "lite_net_add_url_hint": "https://your-lite-server",
"lite_net_checking": "確認中…", "lite_net_checking": "確認中…",
"lite_net_connected": "接続済み", "lite_net_connected": "接続済み",
"lite_net_connecting": "接続中…",
"lite_net_custom": "カスタム", "lite_net_custom": "カスタム",
"lite_net_disconnected": "未接続", "lite_net_disconnected": "未接続",
"lite_net_hidden_section": "非表示のサーバー", "lite_net_hidden_section": "非表示のサーバー",

View File

@@ -271,12 +271,18 @@
"console_app": "앱", "console_app": "앱",
"console_auto_scroll": "자동 스크롤", "console_auto_scroll": "자동 스크롤",
"console_available_commands": "사용 가능한 명령어:", "console_available_commands": "사용 가능한 명령어:",
"console_backend_reference": "백엔드 명령어 참조",
"console_backend_unavailable": "백엔드 없음",
"console_capturing_output": "데몬 출력 캡처 중...", "console_capturing_output": "데몬 출력 캡처 중...",
"console_cat_advanced": "고급",
"console_cat_blockchain": "블록체인", "console_cat_blockchain": "블록체인",
"console_cat_control": "제어", "console_cat_control": "제어",
"console_cat_keys": "키 및 보안",
"console_cat_mining": "채굴", "console_cat_mining": "채굴",
"console_cat_network": "네트워크", "console_cat_network": "네트워크",
"console_cat_raw_transactions": "원시 트랜잭션", "console_cat_raw_transactions": "원시 트랜잭션",
"console_cat_send": "보내기",
"console_cat_sync": "동기화",
"console_cat_utility": "유틸리티", "console_cat_utility": "유틸리티",
"console_cat_wallet": "지갑", "console_cat_wallet": "지갑",
"console_clear": "지우기", "console_clear": "지우기",
@@ -310,11 +316,14 @@
"console_help_help": " help - 도움말 표시", "console_help_help": " help - 도움말 표시",
"console_help_setgenerate": " setgenerate - 채굴 제어", "console_help_setgenerate": " setgenerate - 채굴 제어",
"console_help_stop": " stop - 데몬 중지", "console_help_stop": " stop - 데몬 중지",
"console_last_error": "마지막 오류:",
"console_line_count": "%zu줄", "console_line_count": "%zu줄",
"console_matches": "일치", "console_matches": "일치",
"console_new_lines": "%d 새 줄", "console_new_lines": "%d 새 줄",
"console_no_daemon": "데몬 없음", "console_no_daemon": "데몬 없음",
"console_no_output": "(출력 없음)",
"console_not_connected": "오류: 데몬에 연결되지 않았습니다", "console_not_connected": "오류: 데몬에 연결되지 않았습니다",
"console_not_connected_lite": "오류: 열린 지갑 없음",
"console_quit_note": "여기서는 'quit'/'exit'가 필요 없습니다 — 그냥 창을 닫으세요.", "console_quit_note": "여기서는 'quit'/'exit'가 필요 없습니다 — 그냥 창을 닫으세요.",
"console_ref_builds": "생성", "console_ref_builds": "생성",
"console_ref_cancel": "취소", "console_ref_cancel": "취소",
@@ -330,12 +339,14 @@
"console_ref_run_confirm": "%s 을(를) 지금 실행할까요? 영향이 큰 명령입니다.", "console_ref_run_confirm": "%s 을(를) 지금 실행할까요? 영향이 큰 명령입니다.",
"console_ref_search_hint": "이름 또는 용도로 검색…", "console_ref_search_hint": "이름 또는 용도로 검색…",
"console_ref_select_hint": "명령을 선택하면 설명이 표시됩니다.", "console_ref_select_hint": "명령을 선택하면 설명이 표시됩니다.",
"console_ref_value": "값",
"console_rpc_reference": "RPC 명령어 참조", "console_rpc_reference": "RPC 명령어 참조",
"console_rpc_trace": "RPC", "console_rpc_trace": "RPC",
"console_scanline": "콘솔 스캔라인", "console_scanline": "콘솔 스캔라인",
"console_search_commands": "명령어 검색...", "console_search_commands": "명령어 검색...",
"console_select_all": "모두 선택", "console_select_all": "모두 선택",
"console_show_app_output": "[app] 지갑 로그 줄 표시", "console_show_app_output": "[app] 지갑 로그 줄 표시",
"console_show_backend_ref": "백엔드 명령어 참조 표시",
"console_show_daemon_output": "데몬 출력 표시", "console_show_daemon_output": "데몬 출력 표시",
"console_show_errors_only": "오류만 표시", "console_show_errors_only": "오류만 표시",
"console_show_rpc_ref": "RPC 명령어 참조 표시", "console_show_rpc_ref": "RPC 명령어 참조 표시",
@@ -348,6 +359,7 @@
"console_status_stopped": "중지됨", "console_status_stopped": "중지됨",
"console_status_stopping": "중지 중", "console_status_stopping": "중지 중",
"console_status_unknown": "알 수 없음", "console_status_unknown": "알 수 없음",
"console_stop_confirm_node": "'stop'은 노드를 종료하고 지갑 연결을 끊습니다. 확인하려면 'stop'을 다시 입력하세요.",
"console_tab_completion": "Tab으로 자동 완성", "console_tab_completion": "Tab으로 자동 완성",
"console_text_colors": "텍스트 색상", "console_text_colors": "텍스트 색상",
"console_toggle_accents": "줄 색상 강조 전환", "console_toggle_accents": "줄 색상 강조 전환",
@@ -626,6 +638,7 @@
"lite_birthday_backup": "생성 블록: %llu (이 값도 백업하세요)", "lite_birthday_backup": "생성 블록: %llu (이 값도 백업하세요)",
"lite_birthday_hint": "스캔을 시작할 블록 높이입니다. 모르면 0으로 두세요(전체 스캔이 느려짐).", "lite_birthday_hint": "스캔을 시작할 블록 높이입니다. 모르면 0으로 두세요(전체 스캔이 느려짐).",
"lite_birthday_label": "생일 블록", "lite_birthday_label": "생일 블록",
"lite_console_backend_commands": "백엔드 명령:",
"lite_console_help_passthrough": "그 외 입력은 라이트 지갑 콘솔 명령으로 실행됩니다.", "lite_console_help_passthrough": "그 외 입력은 라이트 지갑 콘솔 명령으로 실행됩니다.",
"lite_copy": "복사", "lite_copy": "복사",
"lite_could_not_write": "쓸 수 없습니다: ", "lite_could_not_write": "쓸 수 없습니다: ",
@@ -643,6 +656,7 @@
"lite_net_add_url_hint": "https://your-lite-server", "lite_net_add_url_hint": "https://your-lite-server",
"lite_net_checking": "확인 중…", "lite_net_checking": "확인 중…",
"lite_net_connected": "연결됨", "lite_net_connected": "연결됨",
"lite_net_connecting": "연결 중…",
"lite_net_custom": "사용자 지정", "lite_net_custom": "사용자 지정",
"lite_net_disconnected": "연결되지 않음", "lite_net_disconnected": "연결되지 않음",
"lite_net_hidden_section": "숨겨진 서버", "lite_net_hidden_section": "숨겨진 서버",

View File

@@ -271,12 +271,18 @@
"console_app": "App", "console_app": "App",
"console_auto_scroll": "Rolagem automática", "console_auto_scroll": "Rolagem automática",
"console_available_commands": "Comandos disponíveis:", "console_available_commands": "Comandos disponíveis:",
"console_backend_reference": "Referência de Comandos do Backend",
"console_backend_unavailable": "Sem backend",
"console_capturing_output": "Capturando saída do daemon...", "console_capturing_output": "Capturando saída do daemon...",
"console_cat_advanced": "Avançado",
"console_cat_blockchain": "Blockchain", "console_cat_blockchain": "Blockchain",
"console_cat_control": "Controle", "console_cat_control": "Controle",
"console_cat_keys": "Chaves e segurança",
"console_cat_mining": "Mineração", "console_cat_mining": "Mineração",
"console_cat_network": "Rede", "console_cat_network": "Rede",
"console_cat_raw_transactions": "Transações brutas", "console_cat_raw_transactions": "Transações brutas",
"console_cat_send": "Enviar",
"console_cat_sync": "Sincronização",
"console_cat_utility": "Utilitários", "console_cat_utility": "Utilitários",
"console_cat_wallet": "Carteira", "console_cat_wallet": "Carteira",
"console_clear": "Limpar", "console_clear": "Limpar",
@@ -310,11 +316,14 @@
"console_help_help": " help - Mostrar esta mensagem de ajuda", "console_help_help": " help - Mostrar esta mensagem de ajuda",
"console_help_setgenerate": " setgenerate - Controlar mineração", "console_help_setgenerate": " setgenerate - Controlar mineração",
"console_help_stop": " stop - Parar o daemon", "console_help_stop": " stop - Parar o daemon",
"console_last_error": "Último erro:",
"console_line_count": "%zu linhas", "console_line_count": "%zu linhas",
"console_matches": "correspondências", "console_matches": "correspondências",
"console_new_lines": "%d novas linhas", "console_new_lines": "%d novas linhas",
"console_no_daemon": "Sem daemon", "console_no_daemon": "Sem daemon",
"console_no_output": "(sem saída)",
"console_not_connected": "Erro: Não conectado ao daemon", "console_not_connected": "Erro: Não conectado ao daemon",
"console_not_connected_lite": "Erro: Nenhuma carteira aberta",
"console_quit_note": "'quit'/'exit' não são necessários aqui — basta fechar a janela.", "console_quit_note": "'quit'/'exit' não são necessários aqui — basta fechar a janela.",
"console_ref_builds": "Gera", "console_ref_builds": "Gera",
"console_ref_cancel": "Cancelar", "console_ref_cancel": "Cancelar",
@@ -330,12 +339,14 @@
"console_ref_run_confirm": "Executar %s agora? Este é um comando com consequências.", "console_ref_run_confirm": "Executar %s agora? Este é um comando com consequências.",
"console_ref_search_hint": "Pesquisar por nome ou tarefa…", "console_ref_search_hint": "Pesquisar por nome ou tarefa…",
"console_ref_select_hint": "Selecione um comando para ver o que ele faz.", "console_ref_select_hint": "Selecione um comando para ver o que ele faz.",
"console_ref_value": "valor",
"console_rpc_reference": "Referência de Comandos RPC", "console_rpc_reference": "Referência de Comandos RPC",
"console_rpc_trace": "RPC", "console_rpc_trace": "RPC",
"console_scanline": "Scanline do console", "console_scanline": "Scanline do console",
"console_search_commands": "Pesquisar comandos...", "console_search_commands": "Pesquisar comandos...",
"console_select_all": "Selecionar Tudo", "console_select_all": "Selecionar Tudo",
"console_show_app_output": "Mostrar linhas do log da carteira [app]", "console_show_app_output": "Mostrar linhas do log da carteira [app]",
"console_show_backend_ref": "Mostrar referência de comandos do backend",
"console_show_daemon_output": "Mostrar saída do daemon", "console_show_daemon_output": "Mostrar saída do daemon",
"console_show_errors_only": "Mostrar apenas erros", "console_show_errors_only": "Mostrar apenas erros",
"console_show_rpc_ref": "Mostrar referência de comandos RPC", "console_show_rpc_ref": "Mostrar referência de comandos RPC",
@@ -348,6 +359,7 @@
"console_status_stopped": "Parado", "console_status_stopped": "Parado",
"console_status_stopping": "Parando", "console_status_stopping": "Parando",
"console_status_unknown": "Desconhecido", "console_status_unknown": "Desconhecido",
"console_stop_confirm_node": "'stop' irá desligar o nó e desconectar a carteira. Digite 'stop' novamente para confirmar.",
"console_tab_completion": "Tab para completar", "console_tab_completion": "Tab para completar",
"console_text_colors": "Cores do texto", "console_text_colors": "Cores do texto",
"console_toggle_accents": "Alternar destaques de cor das linhas", "console_toggle_accents": "Alternar destaques de cor das linhas",
@@ -626,6 +638,7 @@
"lite_birthday_backup": "Aniversário: %llu (faça o backup disto também)", "lite_birthday_backup": "Aniversário: %llu (faça o backup disto também)",
"lite_birthday_hint": "Altura do bloco a partir da qual começar a escanear. Deixe 0 se desconhecida (escaneamento completo mais lento).", "lite_birthday_hint": "Altura do bloco a partir da qual começar a escanear. Deixe 0 se desconhecida (escaneamento completo mais lento).",
"lite_birthday_label": "Data de nascimento", "lite_birthday_label": "Data de nascimento",
"lite_console_backend_commands": "Comandos do backend:",
"lite_console_help_passthrough": "Qualquer outra entrada é executada como um comando de console da carteira leve.", "lite_console_help_passthrough": "Qualquer outra entrada é executada como um comando de console da carteira leve.",
"lite_copy": "Copiar", "lite_copy": "Copiar",
"lite_could_not_write": "Não foi possível gravar ", "lite_could_not_write": "Não foi possível gravar ",
@@ -643,6 +656,7 @@
"lite_net_add_url_hint": "https://seu-servidor-lite", "lite_net_add_url_hint": "https://seu-servidor-lite",
"lite_net_checking": "verificando…", "lite_net_checking": "verificando…",
"lite_net_connected": "Conectado", "lite_net_connected": "Conectado",
"lite_net_connecting": "Conectando…",
"lite_net_custom": "Personalizado", "lite_net_custom": "Personalizado",
"lite_net_disconnected": "Não conectado", "lite_net_disconnected": "Não conectado",
"lite_net_hidden_section": "Servidores ocultos", "lite_net_hidden_section": "Servidores ocultos",

View File

@@ -271,12 +271,18 @@
"console_app": "Прил.", "console_app": "Прил.",
"console_auto_scroll": "Авто-прокрутка", "console_auto_scroll": "Авто-прокрутка",
"console_available_commands": "Доступные команды:", "console_available_commands": "Доступные команды:",
"console_backend_reference": "Справочник команд бэкенда",
"console_backend_unavailable": "Нет бэкенда",
"console_capturing_output": "Захват вывода daemon...", "console_capturing_output": "Захват вывода daemon...",
"console_cat_advanced": "Дополнительно",
"console_cat_blockchain": "Блокчейн", "console_cat_blockchain": "Блокчейн",
"console_cat_control": "Управление", "console_cat_control": "Управление",
"console_cat_keys": "Ключи и безопасность",
"console_cat_mining": "Майнинг", "console_cat_mining": "Майнинг",
"console_cat_network": "Сеть", "console_cat_network": "Сеть",
"console_cat_raw_transactions": "Сырые транзакции", "console_cat_raw_transactions": "Сырые транзакции",
"console_cat_send": "Отправка",
"console_cat_sync": "Синхронизация",
"console_cat_utility": "Утилиты", "console_cat_utility": "Утилиты",
"console_cat_wallet": "Кошелёк", "console_cat_wallet": "Кошелёк",
"console_clear": "Очистить", "console_clear": "Очистить",
@@ -310,11 +316,14 @@
"console_help_help": " help - Показать эту справку", "console_help_help": " help - Показать эту справку",
"console_help_setgenerate": " setgenerate - Управление майнингом", "console_help_setgenerate": " setgenerate - Управление майнингом",
"console_help_stop": " stop - Остановить daemon", "console_help_stop": " stop - Остановить daemon",
"console_last_error": "Последняя ошибка:",
"console_line_count": "%zu строк", "console_line_count": "%zu строк",
"console_matches": "совпадений", "console_matches": "совпадений",
"console_new_lines": "%d новых строк", "console_new_lines": "%d новых строк",
"console_no_daemon": "Нет daemon", "console_no_daemon": "Нет daemon",
"console_no_output": "(нет вывода)",
"console_not_connected": "Ошибка: Не подключено к daemon", "console_not_connected": "Ошибка: Не подключено к daemon",
"console_not_connected_lite": "Ошибка: Нет открытого кошелька",
"console_quit_note": "Здесь не нужны 'quit'/'exit' — просто закройте окно.", "console_quit_note": "Здесь не нужны 'quit'/'exit' — просто закройте окно.",
"console_ref_builds": "Формирует", "console_ref_builds": "Формирует",
"console_ref_cancel": "Отмена", "console_ref_cancel": "Отмена",
@@ -330,12 +339,14 @@
"console_ref_run_confirm": "Выполнить %s сейчас? Это ответственная команда.", "console_ref_run_confirm": "Выполнить %s сейчас? Это ответственная команда.",
"console_ref_search_hint": "Поиск по названию или задаче…", "console_ref_search_hint": "Поиск по названию или задаче…",
"console_ref_select_hint": "Выберите команду, чтобы увидеть, что она делает.", "console_ref_select_hint": "Выберите команду, чтобы увидеть, что она делает.",
"console_ref_value": "значение",
"console_rpc_reference": "Справочник RPC-команд", "console_rpc_reference": "Справочник RPC-команд",
"console_rpc_trace": "RPC", "console_rpc_trace": "RPC",
"console_scanline": "Скан-линия консоли", "console_scanline": "Скан-линия консоли",
"console_search_commands": "Поиск команд...", "console_search_commands": "Поиск команд...",
"console_select_all": "Выбрать всё", "console_select_all": "Выбрать всё",
"console_show_app_output": "Показать строки журнала кошелька [app]", "console_show_app_output": "Показать строки журнала кошелька [app]",
"console_show_backend_ref": "Показать справочник команд бэкенда",
"console_show_daemon_output": "Показать вывод daemon", "console_show_daemon_output": "Показать вывод daemon",
"console_show_errors_only": "Показать только ошибки", "console_show_errors_only": "Показать только ошибки",
"console_show_rpc_ref": "Показать справочник RPC-команд", "console_show_rpc_ref": "Показать справочник RPC-команд",
@@ -348,6 +359,7 @@
"console_status_stopped": "Остановлен", "console_status_stopped": "Остановлен",
"console_status_stopping": "Остановка", "console_status_stopping": "Остановка",
"console_status_unknown": "Неизвестно", "console_status_unknown": "Неизвестно",
"console_stop_confirm_node": "'stop' остановит узел и отключит кошелёк. Введите 'stop' ещё раз для подтверждения.",
"console_tab_completion": "Tab для дополнения", "console_tab_completion": "Tab для дополнения",
"console_text_colors": "Цвета текста", "console_text_colors": "Цвета текста",
"console_toggle_accents": "Переключить цветовые акценты строк", "console_toggle_accents": "Переключить цветовые акценты строк",
@@ -626,6 +638,7 @@
"lite_birthday_backup": "Дата рождения: %llu (сохраните её тоже)", "lite_birthday_backup": "Дата рождения: %llu (сохраните её тоже)",
"lite_birthday_hint": "Высота блока, с которой начинать сканирование. Оставьте 0, если неизвестно (медленное полное сканирование).", "lite_birthday_hint": "Высота блока, с которой начинать сканирование. Оставьте 0, если неизвестно (медленное полное сканирование).",
"lite_birthday_label": "Дата рождения", "lite_birthday_label": "Дата рождения",
"lite_console_backend_commands": "Команды бэкенда:",
"lite_console_help_passthrough": "Любой другой ввод выполняется как команда консоли лайт-кошелька.", "lite_console_help_passthrough": "Любой другой ввод выполняется как команда консоли лайт-кошелька.",
"lite_copy": "Копировать", "lite_copy": "Копировать",
"lite_could_not_write": "Не удалось записать ", "lite_could_not_write": "Не удалось записать ",
@@ -643,6 +656,7 @@
"lite_net_add_url_hint": "https://your-lite-server", "lite_net_add_url_hint": "https://your-lite-server",
"lite_net_checking": "проверка…", "lite_net_checking": "проверка…",
"lite_net_connected": "Подключено", "lite_net_connected": "Подключено",
"lite_net_connecting": "Подключение…",
"lite_net_custom": "Свой", "lite_net_custom": "Свой",
"lite_net_disconnected": "Не подключено", "lite_net_disconnected": "Не подключено",
"lite_net_hidden_section": "Скрытые серверы", "lite_net_hidden_section": "Скрытые серверы",

View File

@@ -271,12 +271,18 @@
"console_app": "应用", "console_app": "应用",
"console_auto_scroll": "自动滚动", "console_auto_scroll": "自动滚动",
"console_available_commands": "可用命令:", "console_available_commands": "可用命令:",
"console_backend_reference": "后端命令参考",
"console_backend_unavailable": "无后端",
"console_capturing_output": "正在捕获守护进程输出...", "console_capturing_output": "正在捕获守护进程输出...",
"console_cat_advanced": "高级",
"console_cat_blockchain": "区块链", "console_cat_blockchain": "区块链",
"console_cat_control": "控制", "console_cat_control": "控制",
"console_cat_keys": "密钥与安全",
"console_cat_mining": "挖矿", "console_cat_mining": "挖矿",
"console_cat_network": "网络", "console_cat_network": "网络",
"console_cat_raw_transactions": "原始交易", "console_cat_raw_transactions": "原始交易",
"console_cat_send": "发送",
"console_cat_sync": "同步",
"console_cat_utility": "实用工具", "console_cat_utility": "实用工具",
"console_cat_wallet": "钱包", "console_cat_wallet": "钱包",
"console_clear": "清除", "console_clear": "清除",
@@ -310,11 +316,14 @@
"console_help_help": " help - 显示此帮助信息", "console_help_help": " help - 显示此帮助信息",
"console_help_setgenerate": " setgenerate - 控制挖矿", "console_help_setgenerate": " setgenerate - 控制挖矿",
"console_help_stop": " stop - 停止守护进程", "console_help_stop": " stop - 停止守护进程",
"console_last_error": "上次错误:",
"console_line_count": "%zu 行", "console_line_count": "%zu 行",
"console_matches": "个匹配", "console_matches": "个匹配",
"console_new_lines": "%d 新行", "console_new_lines": "%d 新行",
"console_no_daemon": "无守护进程", "console_no_daemon": "无守护进程",
"console_no_output": "(无输出)",
"console_not_connected": "错误:未连接到守护进程", "console_not_connected": "错误:未连接到守护进程",
"console_not_connected_lite": "错误:没有打开的钱包",
"console_quit_note": "这里不需要 'quit'/'exit'——直接关闭窗口即可。", "console_quit_note": "这里不需要 'quit'/'exit'——直接关闭窗口即可。",
"console_ref_builds": "生成", "console_ref_builds": "生成",
"console_ref_cancel": "取消", "console_ref_cancel": "取消",
@@ -330,12 +339,14 @@
"console_ref_run_confirm": "立即运行 %s这是一个有重大影响的命令。", "console_ref_run_confirm": "立即运行 %s这是一个有重大影响的命令。",
"console_ref_search_hint": "按名称或用途搜索…", "console_ref_search_hint": "按名称或用途搜索…",
"console_ref_select_hint": "选择一个命令以查看其功能。", "console_ref_select_hint": "选择一个命令以查看其功能。",
"console_ref_value": "值",
"console_rpc_reference": "RPC 命令参考", "console_rpc_reference": "RPC 命令参考",
"console_rpc_trace": "RPC", "console_rpc_trace": "RPC",
"console_scanline": "控制台扫描线", "console_scanline": "控制台扫描线",
"console_search_commands": "搜索命令...", "console_search_commands": "搜索命令...",
"console_select_all": "全选", "console_select_all": "全选",
"console_show_app_output": "显示[应用]钱包日志行", "console_show_app_output": "显示[应用]钱包日志行",
"console_show_backend_ref": "显示后端命令参考",
"console_show_daemon_output": "显示守护进程输出", "console_show_daemon_output": "显示守护进程输出",
"console_show_errors_only": "仅显示错误", "console_show_errors_only": "仅显示错误",
"console_show_rpc_ref": "显示 RPC 命令参考", "console_show_rpc_ref": "显示 RPC 命令参考",
@@ -348,6 +359,7 @@
"console_status_stopped": "已停止", "console_status_stopped": "已停止",
"console_status_stopping": "停止中", "console_status_stopping": "停止中",
"console_status_unknown": "未知", "console_status_unknown": "未知",
"console_stop_confirm_node": "'stop' 将关闭节点并断开钱包连接。再次输入 'stop' 以确认。",
"console_tab_completion": "Tab 补全", "console_tab_completion": "Tab 补全",
"console_text_colors": "文本颜色", "console_text_colors": "文本颜色",
"console_toggle_accents": "切换行颜色强调", "console_toggle_accents": "切换行颜色强调",
@@ -626,6 +638,7 @@
"lite_birthday_backup": "生日区块:%llu (也请一并备份)", "lite_birthday_backup": "生日区块:%llu (也请一并备份)",
"lite_birthday_hint": "开始扫描的区块高度。如未知请保留 0完整扫描更慢。", "lite_birthday_hint": "开始扫描的区块高度。如未知请保留 0完整扫描更慢。",
"lite_birthday_label": "诞生区块", "lite_birthday_label": "诞生区块",
"lite_console_backend_commands": "后端命令:",
"lite_console_help_passthrough": "其他任何输入都将作为轻钱包控制台命令运行。", "lite_console_help_passthrough": "其他任何输入都将作为轻钱包控制台命令运行。",
"lite_copy": "复制", "lite_copy": "复制",
"lite_could_not_write": "无法写入 ", "lite_could_not_write": "无法写入 ",
@@ -643,6 +656,7 @@
"lite_net_add_url_hint": "https://your-lite-server", "lite_net_add_url_hint": "https://your-lite-server",
"lite_net_checking": "检查中…", "lite_net_checking": "检查中…",
"lite_net_connected": "已连接", "lite_net_connected": "已连接",
"lite_net_connecting": "连接中…",
"lite_net_custom": "自定义", "lite_net_custom": "自定义",
"lite_net_disconnected": "未连接", "lite_net_disconnected": "未连接",
"lite_net_hidden_section": "隐藏的服务器", "lite_net_hidden_section": "隐藏的服务器",

167
res/themes/jade.toml Normal file
View File

@@ -0,0 +1,167 @@
[theme]
name = "Jade"
author = "The Hush Developers"
dark = true
elevation = { --elevation-0 = "#071210", --elevation-1 = "#0C1A16", --elevation-2 = "#16261F", --elevation-3 = "#1D3128", --elevation-4 = "#243B30" }
images = { background_image = "backgrounds/texture/jade_bg.png", logo = "logos/logo_ObsidianDragon_dark.png" }
[theme.palette]
--primary = "#2FA07A"
--primary-variant = "#1E7357"
--primary-light = "#7FD1B5"
--secondary = "#C9A24E"
--secondary-variant = "#A8842F"
--secondary-light = "#E0C583"
--background = "#071210"
--surface = "#0C1A16"
--surface-variant = "#16261F"
--on-primary = "#FFFFFF"
--on-secondary = "#000000"
--on-background = "#DCEDE4"
--on-surface = "#DCEDE4"
--on-surface-medium = "rgba(220,237,228,0.85)"
--on-surface-disabled = "rgba(220,237,228,0.58)"
--error = "#CF6679"
--on-error = "#000000"
--success = "#81C784"
--on-success = "#000000"
--warning = "#FFB74D"
--on-warning = "#000000"
--divider = "rgba(130,205,170,0.14)"
--outline = "rgba(130,205,170,0.16)"
--scrim = "rgba(0,0,0,0.6)"
--surface-hover = "rgba(130,205,170,0.07)"
--surface-alt = "rgba(130,205,170,0.05)"
--surface-active = "rgba(130,205,170,0.10)"
--glass-button = "rgba(130,205,170,0.06)"
--glass-button-hover = "rgba(130,205,170,0.12)"
--card-border = "rgba(130,205,170,0.26)"
--text-shadow = "rgba(0,0,0,0.50)"
--input-overlay-text = "rgba(220,237,228,0.30)"
--slider-text = "rgba(220,237,228,0.85)"
--thumb-fill = "rgba(130,205,170,0.15)"
--thumb-border = "rgba(130,205,170,0.50)"
--disabled-label = "rgba(130,205,170,0.18)"
--chart-grid = "rgba(130,205,170,0.05)"
--chart-crosshair = "rgba(130,205,170,0.15)"
--chart-hover-ring = "rgba(130,205,170,0.30)"
--tooltip-bg = "rgba(9,20,16,0.92)"
--tooltip-border = "rgba(130,205,170,0.12)"
--glass-fill = "rgba(130,205,170,0.08)"
--glass-border = "rgba(47,160,122,0.30)"
--glass-noise-tint = "rgba(130,205,170,0.03)"
--tactile-top = "rgba(130,205,170,0.06)"
--tactile-bottom = "rgba(130,205,170,0.0)"
--hover-overlay = "rgba(130,205,170,0.05)"
--active-overlay = "rgba(130,205,170,0.10)"
--rim-light = "rgba(130,205,170,0.14)"
--status-divider = "rgba(130,205,170,0.08)"
--sidebar-hover = "rgba(130,205,170,0.10)"
--sidebar-icon = "rgba(130,205,170,0.42)"
--sidebar-badge = "rgba(220,237,228,1.0)"
--sidebar-divider = "rgba(130,205,170,0.06)"
--chart-line = "rgba(130,205,170,0.10)"
--window-control = "rgba(220,237,228,0.78)"
--window-control-hover = "rgba(130,205,170,0.12)"
--window-close-hover = "rgba(232,17,35,0.78)"
--spinner-track = "rgba(130,205,170,0.10)"
--spinner-active = "rgba(79,184,154,0.85)"
--shutdown-panel-bg = "rgba(7,18,14,0.90)"
--shutdown-panel-border = "rgba(130,205,170,0.07)"
--ram-bar-app = "#2FA07A"
--ram-bar-system = "rgba(255,255,255,0.18)"
--accent-total = "#7FD1B5"
--accent-shielded = "#4FB89A"
--accent-transparent = "#C9A24E"
--accent-action = "#2FA07A"
--accent-market = "#4FB89A"
--accent-portfolio = "#7FD1B5"
--toast-info-accent = "#2FA07A"
--toast-info-text = "#7FD1B5"
--toast-success-accent = "rgba(50,180,80,1.0)"
--toast-success-text = "rgba(180,255,180,1.0)"
--toast-warning-accent = "rgba(204,166,50,1.0)"
--toast-warning-text = "rgba(255,230,130,1.0)"
--toast-error-accent = "rgba(204,64,64,1.0)"
--toast-error-text = "rgba(255,153,153,1.0)"
--snackbar-bg = "rgba(24,40,34,0.95)"
--snackbar-text = "rgba(220,237,228,0.87)"
--snackbar-action = "rgba(79,184,154,1.0)"
--snackbar-action-hover = "rgba(127,209,181,1.0)"
--switch-track-off = "rgba(130,205,170,0.12)"
--switch-track-on = "rgba(47,160,122,0.50)"
--switch-thumb-off = "#A0C0B4"
--switch-thumb-on = "#DCEDE4"
--control-shadow = "rgba(0,0,0,0.24)"
--checkbox-check = "#000000"
--app-bar-shadow = "rgba(0,0,0,0.25)"
[backdrop]
base-color-top = "rgba(14,32,26,210)"
base-color-bottom = "rgba(6,18,14,210)"
texture-tint-alpha = 120
gradient-top-r = 10
gradient-top-g = 30
gradient-top-b = 22
gradient-top-a = 90
gradient-bottom-r = 5
gradient-bottom-g = 16
gradient-bottom-b = 12
gradient-bottom-a = 70
background-alpha = 0.42
surface-alpha = 0.56
frame-alpha = 0.78
surface-inline-alpha = 0.58
background-inline-alpha = 0.40
# ---------------------------------------------------------------------------
# Theme Visual Effects — Jade (polished stone sheen + gold veining)
# A soft jade specular highlight drifts across panels like light on
# polished nephrite; the active sidebar button traces a jade-to-gold
# border echoing the stone's veins — restrained, like Obsidian.
# ---------------------------------------------------------------------------
[effects]
hue-cycle-enabled = { size = 0.0 }
rainbow-border-enabled = { size = 0.0 }
# No shimmer sweep — replaced by specular glare
shimmer-enabled = { size = 0.0 }
positional-hue-enabled = { size = 0.0 }
glow-pulse-enabled = { size = 0.0 }
edge-trace-enabled = { size = 0.0 }
# Specular glare — soft blurred jade highlights
specular-glare-enabled = { size = 1.0 }
specular-glare-speed = { size = 0.018 }
specular-glare-intensity = { size = 0.008 }
specular-glare-radius = { size = 0.65 }
specular-glare-count = { size = 1.0 }
specular-glare-color = { color = "rgba(150,220,180,1.0)" }
# Jade-to-gold color-shifting border on the active sidebar button
gradient-border-enabled = { size = 1.0 }
gradient-border-speed = { size = 0.12 }
gradient-border-thickness = { size = 1.5 }
gradient-border-alpha = { size = 0.55 }
gradient-border-color-a = { color = "#7FD1B5" }
gradient-border-color-b = { color = "#C9A24E" }
ember-rise-enabled = { size = 0.0 }
# Shader-like viewport overlay — deep green stone atmosphere
viewport-wash-enabled = { size = 1.0 }
viewport-wash-alpha = { size = 0.05 }
viewport-wash-tl = { color = "#12402E" }
viewport-wash-tr = { color = "#0E3828" }
viewport-wash-bl = { color = "#16442E" }
viewport-wash-br = { color = "#1A4A34" }
viewport-wash-rotate = { size = 0.015 }
viewport-wash-pulse = { size = 0.0 }
viewport-wash-pulse-depth = { size = 0.0 }
viewport-vignette-enabled = { size = 1.0 }
viewport-vignette-color = { color = "#04140D" }
viewport-vignette-radius = { size = 0.22 }
viewport-vignette-alpha = { size = 0.15 }

View File

@@ -2005,8 +2005,8 @@ void App::render()
// Send confirm popup // Send confirm popup
ui::RenderSendConfirmPopup(this); ui::RenderSendConfirmPopup(this);
// Console RPC Command Reference popup // Console command-reference popup (full-node RPC reference / lite backend verbs).
console_tab_.renderCommandsPopupModal(); console_tab_.renderCommandsPopupModal(console_exec_.get());
// Key export dialog (triggered from balance tab context menu) // Key export dialog (triggered from balance tab context menu)
ui::KeyExportDialog::render(this); ui::KeyExportDialog::render(this);

View File

@@ -434,8 +434,15 @@ std::optional<NetworkRefreshService::PriceRefreshResult> NetworkRefreshService::
result.market.market_cap = data.value("usd_market_cap", 0.0); result.market.market_cap = data.value("usd_market_cap", 0.0);
char buf[64]; char buf[64];
std::tm* tm = std::localtime(&fetchedAt); // Runs on the RPC worker thread — std::localtime shares a process-wide static tm, so use the
if (tm && std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm) > 0) { // reentrant variant into a local tm (matches the rest of the codebase).
std::tm tmv{};
#ifdef _WIN32
localtime_s(&tmv, &fetchedAt);
#else
localtime_r(&fetchedAt, &tmv);
#endif
if (std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tmv) > 0) {
result.market.last_updated = buf; result.market.last_updated = buf;
} }
return result; return result;

View File

@@ -557,9 +557,20 @@ void RenderChatTab(App* app)
const float metaSz = scaledSize(metaFont); const float metaSz = scaledSize(metaFont);
// ---- Left: new-conversation button + conversation list ---- // ---- Left: new-conversation button + conversation list ----
// Faint sidebar tint so the list reads as distinct from the thread pane (V6). // Frosted-glass pane so the list blurs the backdrop like the rest of the app (the contacts_tab
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImGui::ColorConvertU32ToFloat4(material::WithAlpha(material::OnSurface(), 10))); // pattern): draw the glass behind the child, then give the child a TRANSPARENT bg. A flat ChildBg
ImGui::BeginChild("##ChatList", ImVec2(listW, avail.y), ImGuiChildFlags_Borders, // tint (the old approach) never samples the acrylic blur, so the raw texture showed through.
{
ImDrawList* paneDL = ImGui::GetWindowDrawList();
const ImVec2 pMin = ImGui::GetCursorScreenPos();
material::GlassPanelSpec g; g.rounding = 12.0f * Layout::dpiScale(); g.fillAlpha = 20; g.borderAlpha = 34;
material::DrawGlassPanel(paneDL, pMin, ImVec2(pMin.x + listW, pMin.y + avail.y), g);
}
// Inner padding so the list content (buttons, search, conversation cards) doesn't hug the glass
// pane's edges now that it's a visible frosted card.
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f * Layout::dpiScale(), 10.0f * Layout::dpiScale()));
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0)); // transparent — the frosted glass shows through
ImGui::BeginChild("##ChatList", ImVec2(listW, avail.y), ImGuiChildFlags_AlwaysUseWindowPadding,
ImGuiWindowFlags_NoScrollWithMouse); ImGuiWindowFlags_NoScrollWithMouse);
material::ApplySmoothScroll(); // wheel-driven lerp scroll, matching the rest of the app material::ApplySmoothScroll(); // wheel-driven lerp scroll, matching the rest of the app
if (s_show_emoji_picker) { if (s_show_emoji_picker) {
@@ -683,6 +694,7 @@ void RenderChatTab(App* app)
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleColor(); // list ChildBg tint (V6) ImGui::PopStyleColor(); // list ChildBg tint (V6)
ImGui::PopStyleVar(); // list inner WindowPadding
ImGui::SameLine(0.0f, 10.0f * Layout::dpiScale()); // breathing gutter between the list and thread panes ImGui::SameLine(0.0f, 10.0f * Layout::dpiScale()); // breathing gutter between the list and thread panes
@@ -699,7 +711,21 @@ void RenderChatTab(App* app)
// the box doesn't jump when a reply arrives. // the box doesn't jump when a reply arrives.
const float composerAreaH = sel ? (composerBoxH + metaSz + 12.0f * tdp) : 0.0f; const float composerAreaH = sel ? (composerBoxH + metaSz + 12.0f * tdp) : 0.0f;
ImGui::BeginChild("##ChatThread", ImVec2(0, avail.y - composerAreaH), true); // Frosted-glass thread pane (same reasoning as the list): glass behind + transparent child bg so
// the message area blurs the backdrop instead of showing the sharp texture. Replaces the old
// bordered child (which drew the flat, translucent WindowBg) — the glass supplies the border.
{
ImDrawList* paneDL = ImGui::GetWindowDrawList();
const ImVec2 pMin = ImGui::GetCursorScreenPos();
const float pW = ImGui::GetContentRegionAvail().x;
material::GlassPanelSpec g; g.rounding = 12.0f * tdp; g.fillAlpha = 12; g.borderAlpha = 34;
material::DrawGlassPanel(paneDL, pMin, ImVec2(pMin.x + pW, pMin.y + (avail.y - composerAreaH)), g);
}
// Inner padding so the header + messages don't hug the glass card's edges (the message child
// below trims its own inset to compensate so bubbles aren't double-indented).
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f * tdp, 10.0f * tdp));
ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(0, 0, 0, 0)); // transparent — frosted glass shows through
ImGui::BeginChild("##ChatThread", ImVec2(0, avail.y - composerAreaH), ImGuiChildFlags_AlwaysUseWindowPadding);
{ {
if (sel) { if (sel) {
app->markChatConversationSeen(sel->cid, sel->lastTs); // viewing the thread clears its unread (Q1) app->markChatConversationSeen(sel->cid, sel->lastTs); // viewing the thread clears its unread (Q1)
@@ -942,14 +968,15 @@ void RenderChatTab(App* app)
ImGui::Separator(); ImGui::Separator();
const float dp = Layout::dpiScale(); const float dp = Layout::dpiScale();
// Inset the message list from the pane edges so bubbles don't hug the border/scrollbar (8px). // Small message-list inset (the ##ChatThread WindowPadding above already holds content off
// the glass edge; keep a little here so bubbles don't hug the scrollbar).
// Zero the VERTICAL item spacing: the message loop reserves its own gaps via Dummy() (tight // Zero the VERTICAL item spacing: the message loop reserves its own gaps via Dummy() (tight
// within a run so the grouped/merged corners read right), so the theme's 6px would double them. // within a run so the grouped/merged corners read right), so the theme's 6px would double them.
const float origSpacingX = ImGui::GetStyle().ItemSpacing.x; const float origSpacingX = ImGui::GetStyle().ItemSpacing.x;
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f * dp, 8.0f * dp)); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(4.0f * dp, 8.0f * dp));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(origSpacingX, 0.0f)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(origSpacingX, 0.0f));
ImGui::BeginChild("##ChatMessages", ImVec2(0, ImGui::GetContentRegionAvail().y), ImGui::BeginChild("##ChatMessages", ImVec2(0, ImGui::GetContentRegionAvail().y),
ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollWithMouse); ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_NoScrollWithMouse);
material::ApplySmoothScroll(); // smooth wheel scroll; syncs with the auto-scroll-to-bottom below material::ApplySmoothScroll(); // smooth wheel scroll; syncs with the auto-scroll-to-bottom below
bool atBottom = true; bool atBottom = true;
const ImVec2 msgWinMin = ImGui::GetWindowPos(); const ImVec2 msgWinMin = ImGui::GetWindowPos();
@@ -1148,6 +1175,8 @@ void RenderChatTab(App* app)
} }
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleColor(); // ##ChatThread transparent ChildBg (GetItemRect below still reads the child)
ImGui::PopStyleVar(); // ##ChatThread inner WindowPadding
// ── Composer — OUTSIDE / below the bordered message box, spanning its width. No divider above it. // ── Composer — OUTSIDE / below the bordered message box, spanning its width. No divider above it.
// Layout (all placed by absolute screen pos so nothing wraps to the window's left edge): a thin // Layout (all placed by absolute screen pos so nothing wraps to the window's left edge): a thin
@@ -1204,7 +1233,17 @@ void RenderChatTab(App* app)
s_emoji_search[0] = '\0'; s_emoji_search[0] = '\0';
} }
} }
// Input. // Input — frosted so it blurs the backdrop like the panes above (glass behind + transparent
// FrameBg); a flat FrameBg showed the sharp texture.
{
ImDrawList* cdl = ImGui::GetWindowDrawList();
material::GlassPanelSpec g; g.rounding = 8.0f * tdp; g.fillAlpha = 16; g.borderAlpha = 34;
material::DrawGlassPanel(cdl, ImVec2(inputX, rowY),
ImVec2(inputX + inputW, rowY + composerBoxH), g);
}
ImGui::PushStyleColor(ImGuiCol_FrameBg, IM_COL32(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, IM_COL32(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, IM_COL32(0, 0, 0, 0));
ImGui::SetCursorScreenPos(ImVec2(inputX, rowY)); ImGui::SetCursorScreenPos(ImVec2(inputX, rowY));
// Enter-to-send (setting): on → Enter sends + Ctrl+Enter newline; off → Enter is a newline and // Enter-to-send (setting): on → Enter sends + Ctrl+Enter newline; off → Enter is a newline and
// the Send button is the only way to send. NOTE: without EnterReturnsTrue, InputTextMultiline // the Send button is the only way to send. NOTE: without EnterReturnsTrue, InputTextMultiline
@@ -1216,6 +1255,7 @@ void RenderChatTab(App* app)
: ImGuiInputTextFlags_None; : ImGuiInputTextFlags_None;
const bool inputReturned = ImGui::InputTextMultiline("##compose", s_compose, sizeof(s_compose), const bool inputReturned = ImGui::InputTextMultiline("##compose", s_compose, sizeof(s_compose),
ImVec2(inputW, composerBoxH), composeFlags); ImVec2(inputW, composerBoxH), composeFlags);
ImGui::PopStyleColor(3); // composer FrameBg (transparent — glass shows through)
bool submit = enterSends && inputReturned; // off-mode sends only via the Send button bool submit = enterSends && inputReturned; // off-mode sends only via the Send button
// Send. // Send.
ImGui::SetCursorScreenPos(ImVec2(sendX, rowY)); ImGui::SetCursorScreenPos(ImVec2(sendX, rowY));

View File

@@ -4,6 +4,7 @@
#include "console_command_executor.h" #include "console_command_executor.h"
#include "console_channel.h" #include "console_channel.h"
#include "console_command_reference.h" // consoleCommandCategories / liteConsoleCommandCategories
#include "console_input_model.h" // BuildConsoleRpcCall #include "console_input_model.h" // BuildConsoleRpcCall
#include "../../app.h" #include "../../app.h"
@@ -194,6 +195,11 @@ void FullNodeConsoleExecutor::printHelp(const ConsoleAddLineFn& add)
add(TR("console_tab_completion"), ConsoleChannel::Info); add(TR("console_tab_completion"), ConsoleChannel::Info);
} }
const std::vector<ConsoleCommandCategory>* FullNodeConsoleExecutor::commandReference() const
{
return &consoleCommandCategories();
}
ConsoleStatusLine FullNodeConsoleExecutor::toolbarStatus() const ConsoleStatusLine FullNodeConsoleExecutor::toolbarStatus() const
{ {
ConsoleStatusLine s; ConsoleStatusLine s;
@@ -258,7 +264,7 @@ bool LiteConsoleExecutor::pollResult(std::string& result, bool& isError)
if (!lw) return false; if (!lw) return false;
wallet::LiteConsoleResult res; wallet::LiteConsoleResult res;
if (!lw->takeConsoleResult(res)) return false; if (!lw->takeConsoleResult(res)) return false;
result = res.response.empty() ? std::string("(no output)") : res.response; result = res.response.empty() ? std::string(TR("console_no_output")) : res.response;
isError = !res.ok; isError = !res.ok;
return true; return true;
} }
@@ -286,6 +292,19 @@ void LiteConsoleExecutor::printHelp(const ConsoleAddLineFn& add)
add(TR("console_help_clear"), ConsoleChannel::None); add(TR("console_help_clear"), ConsoleChannel::None);
add(TR("console_help_help"), ConsoleChannel::None); add(TR("console_help_help"), ConsoleChannel::None);
add(TR("lite_console_help_passthrough"), ConsoleChannel::Info); add(TR("lite_console_help_passthrough"), ConsoleChannel::Info);
// The lite backend's own command verbs (literal command tokens, not RPC methods — mirrors the
// backend's get_commands() registry). Listed for discoverability: the C++ tab intercepts `help`
// before it can reach the backend's own HelpCommand, so its "type help" advice would dead-end.
add(TR("lite_console_backend_commands"), ConsoleChannel::Info);
add(" sync syncstatus balance addresses height info list notes encryptionstatus", ConsoleChannel::None);
add(" send shield new seed import timport export", ConsoleChannel::None);
add(" encrypt decrypt lock unlock rescan save sietch saplingtree coinsupply", ConsoleChannel::None);
add(TR("console_click_commands"), ConsoleChannel::Info);
}
const std::vector<ConsoleCommandCategory>* LiteConsoleExecutor::commandReference() const
{
return &liteConsoleCommandCategories();
} }
std::vector<ConsoleStatusLine> LiteConsoleExecutor::statusLines() const std::vector<ConsoleStatusLine> LiteConsoleExecutor::statusLines() const
@@ -294,20 +313,20 @@ std::vector<ConsoleStatusLine> LiteConsoleExecutor::statusLines() const
wallet::LiteWalletController* lw = app_->liteWallet(); wallet::LiteWalletController* lw = app_->liteWallet();
if (lw && lw->walletOpen()) { if (lw && lw->walletOpen()) {
const SyncInfo& sync = app_->state().sync; const SyncInfo& sync = app_->state().sync;
char buf[96]; char buf[128];
if (sync.syncing && !sync.isSynced()) { if (sync.syncing && !sync.isSynced()) {
double vp = sync.verification_progress; double vp = sync.verification_progress;
if (vp < 0.0) vp = 0.0; else if (vp > 1.0) vp = 1.0; if (vp < 0.0) vp = 0.0; else if (vp > 1.0) vp = 1.0;
std::snprintf(buf, sizeof(buf), "Syncing %.1f%% (block %d / %d)", std::snprintf(buf, sizeof(buf), "%s %.1f%% (block %d / %d)",
vp * 100.0, sync.blocks, sync.headers); TR("lite_net_syncing"), vp * 100.0, sync.blocks, sync.headers);
} else { } else {
std::snprintf(buf, sizeof(buf), "Synced (block %d)", sync.blocks); std::snprintf(buf, sizeof(buf), "%s (block %d)", TR("lite_net_synced"), sync.blocks);
} }
out.push_back({std::string(buf), OnSurfaceMedium(), false}); out.push_back({std::string(buf), OnSurfaceMedium(), false});
} }
const std::string& err = app_->liteOpenError(); const std::string& err = app_->liteOpenError();
if (!err.empty() && (!lw || !lw->walletOpen())) if (!err.empty() && (!lw || !lw->walletOpen()))
out.push_back({std::string("Last error: ") + err, Error(), false}); out.push_back({std::string(TR("console_last_error")) + " " + err, Error(), false});
return out; return out;
} }
@@ -315,10 +334,10 @@ ConsoleStatusLine LiteConsoleExecutor::toolbarStatus() const
{ {
ConsoleStatusLine s; ConsoleStatusLine s;
wallet::LiteWalletController* lw = app_->liteWallet(); wallet::LiteWalletController* lw = app_->liteWallet();
if (!lw) { s.text = "No backend"; s.color = Error(); return s; } if (!lw) { s.text = TR("console_backend_unavailable"); s.color = Error(); return s; }
if (lw->walletOpen()) { s.text = "Connected"; s.color = Success(); } if (lw->walletOpen()) { s.text = TR("lite_net_connected"); s.color = Success(); }
else if (lw->openInProgress()) { s.text = "Connecting"; s.color = Warning(); s.pulse = true; } else if (lw->openInProgress()) { s.text = TR("lite_net_connecting"); s.color = Warning(); s.pulse = true; }
else { s.text = "Disconnected"; s.color = Error(); } else { s.text = TR("lite_net_disconnected"); s.color = Error(); }
return s; return s;
} }

View File

@@ -25,6 +25,8 @@ namespace dragonx {
class App; class App;
namespace ui { namespace ui {
struct ConsoleCommandCategory; // console_command_reference.h — command-reference table
using ConsoleAddLineFn = std::function<void(const std::string&, ConsoleChannel)>; using ConsoleAddLineFn = std::function<void(const std::string&, ConsoleChannel)>;
struct ConsoleStatusLine { struct ConsoleStatusLine {
@@ -64,7 +66,13 @@ public:
virtual void pollLogLines(const ConsoleAddLineFn& add) { (void)add; } virtual void pollLogLines(const ConsoleAddLineFn& add) { (void)add; }
// UI-chrome capabilities. // UI-chrome capabilities.
virtual bool hasRpcReference() const { return false; } // show the RPC command-reference popup // True when this backend speaks JSON-RPC to a daemon (the full node). Gates daemon-specific
// console behavior (the 'stop' shutdown confirm, "not connected to daemon" wording) and the
// command-reference modal's JSON string-arg quoting — the lite backend takes bare tokens.
virtual bool hasRpcReference() const { return false; }
// The command-reference table this backend offers (browsed by the console's reference modal),
// or nullptr for none. Full node = the JSON-RPC reference; lite = its own backend verbs.
virtual const std::vector<ConsoleCommandCategory>* commandReference() const { return nullptr; }
// Which log-filter toggles the toolbar should show (default: none). // Which log-filter toggles the toolbar should show (default: none).
virtual ConsoleLogFilterCaps logFilterCaps() const { return {}; } virtual ConsoleLogFilterCaps logFilterCaps() const { return {}; }
@@ -88,6 +96,7 @@ public:
bool pollResult(std::string& result, bool& isError) override; bool pollResult(std::string& result, bool& isError) override;
void pollLogLines(const ConsoleAddLineFn& add) override; void pollLogLines(const ConsoleAddLineFn& add) override;
bool hasRpcReference() const override { return true; } bool hasRpcReference() const override { return true; }
const std::vector<ConsoleCommandCategory>* commandReference() const override;
// Full node: daemon/xmrig log, errors-only, RPC trace, and app messages. // Full node: daemon/xmrig log, errors-only, RPC trace, and app messages.
ConsoleLogFilterCaps logFilterCaps() const override { return {true, true, true, true}; } ConsoleLogFilterCaps logFilterCaps() const override { return {true, true, true, true}; }
void printHelp(const ConsoleAddLineFn& add) override; void printHelp(const ConsoleAddLineFn& add) override;
@@ -119,6 +128,7 @@ public:
// Lite: no daemon log / RPC trace — its diagnostics ring maps to the App + Error // Lite: no daemon log / RPC trace — its diagnostics ring maps to the App + Error
// channels, so offer errors-only + app-messages (plus the always-shown text filter). // channels, so offer errors-only + app-messages (plus the always-shown text filter).
ConsoleLogFilterCaps logFilterCaps() const override { return {false, true, false, true}; } ConsoleLogFilterCaps logFilterCaps() const override { return {false, true, false, true}; }
const std::vector<ConsoleCommandCategory>* commandReference() const override;
void printHelp(const ConsoleAddLineFn& add) override; void printHelp(const ConsoleAddLineFn& add) override;
std::vector<ConsoleStatusLine> statusLines() const override; std::vector<ConsoleStatusLine> statusLines() const override;
ConsoleStatusLine toolbarStatus() const override; ConsoleStatusLine toolbarStatus() const override;

View File

@@ -280,6 +280,105 @@ const ConsoleCommandEntry kUtilityCommands[] = {
"reconsiderblock \"0000000000abc123\"", "undo invalidate accept block again re-enable block restore chain reconsider fix rollback fork", true}, "reconsiderblock \"0000000000abc123\"", "undo invalidate accept block again re-enable block restore chain reconsider fix rollback fork", true},
}; };
// ============================================================================
// Lite backend command set — the verbs the SDXL lite backend accepts (see its
// commands.rs get_commands()). Unlike the full node these are NOT JSON-RPC: the
// backend takes plain, space-separated tokens (no quoting), so the reference
// modal inserts them bare. clear/help/quit are intentionally omitted — the C++
// console tab intercepts those before they reach the backend.
// ============================================================================
const ConsoleCommandEntry kLiteWalletCommands[] = {
{"balance", "Show your DRGX balance", "",
"Shows the DRGX balance held in this wallet across all its shielded and transparent addresses.",
"balance", "money funds amount total holdings"},
{"addresses", "List all addresses in the wallet", "",
"Lists every address this wallet owns \xE2\x80\x94 shielded (zs1...) and transparent (R.../t...) \xE2\x80\x94 so you can pick one to receive to.",
"addresses", "receive address list wallet mine deposit"},
{"new", "Create a new address in this wallet", "type",
"Creates a fresh receive address. Pass zs for a shielded (private) sapling address, or R (a capital R) for a transparent one \xE2\x80\x94 those exact tokens (case-sensitive).",
"new zs", "create address receive generate new shielded transparent zs"},
{"list", "List all transactions in the wallet", "",
"Shows the wallet's transaction history \xE2\x80\x94 sends, receives and shields \xE2\x80\x94 with amounts, addresses and confirmations.",
"list", "history transactions txs payments activity sent received"},
{"notes", "List sapling notes and UTXOs", "[all]",
"Lists the individual shielded notes and transparent UTXOs that make up your balance. Pass all to include spent ones.",
"notes", "utxo notes unspent coins inputs sapling"},
{"info", "Get the lightwalletd server's info", "",
"Reports the lightwalletd server the wallet is connected to \xE2\x80\x94 its version, chain and block height.",
"info", "server node lightwalletd version connection status"},
};
const ConsoleCommandEntry kLiteSyncCommands[] = {
{"sync", "Download compact blocks and sync to the server", "",
"Fetches new compact blocks from the lightwalletd server and scans them for transactions belonging to this wallet.",
"sync", "update refresh scan blocks download catch up"},
{"syncstatus", "Get the sync status of the wallet", "",
"Reports how far the wallet has synced \xE2\x80\x94 whether a sync is in progress and the block it has reached.",
"syncstatus", "progress status syncing scanning percent blocks"},
{"height", "Get the latest block height the wallet is at", "",
"Shows the block height the wallet has scanned up to. Compare with the network height to gauge sync.",
"height", "block height number chain tip synced"},
{"rescan", "Rescan the wallet from scratch", "",
"Discards the scanned state and re-downloads/re-scans every block from the wallet's birthday. Slow, but fixes a stuck or incomplete balance.",
"rescan", "rescan resync repair fix balance rebuild from scratch"},
{"save", "Save the wallet file to disk", "",
"Writes the current wallet state to disk. The wallet also saves automatically after a sync or send.",
"save", "save persist write disk store"},
};
const ConsoleCommandEntry kLiteSendCommands[] = {
{"send", "Send DRGX to an address", "[{\"address\":\"zs1...\",\"amount\":0}]",
"Sends DRGX from the console using a JSON array of recipients (the Send tab is the easy way; this is the power-user form). amount is in puposhis (the base unit); memo is optional and delivered privately to shielded recipients.",
"send [{\"address\":\"zs1exampleaddress\",\"amount\":100000000,\"memo\":\"thanks\"}]",
"pay transfer send spend money transaction json", true},
{"shield", "Shield transparent DRGX into a sapling address", "[address]",
"Moves your transparent (public) DRGX into a shielded sapling address for privacy. With no address it shields to the wallet's own sapling address.",
"shield", "shield private sapling transparent move protect", true},
};
const ConsoleCommandEntry kLiteKeyCommands[] = {
{"seed", "Display the wallet seed phrase", "",
"Reveals the seed phrase that backs up this wallet. Anyone who sees it can spend your funds \xE2\x80\x94 keep it secret and offline.",
"seed", "seed phrase mnemonic backup recovery words secret", true},
{"export", "Export the private key for an address", "[address]",
"Prints the private/spending key for a wallet address \xE2\x80\x94 anyone with it controls the funds. With no address it exports every key.",
"export zs1exampleaddress", "export private key spending backup secret", true},
{"import", "Import a spending or viewing key", "key",
"Imports a shielded spending or viewing key (pass just the key). The wallet rescans from the sapling activation height to find the key's transactions.",
"import somekey", "import restore key spending viewing add watch"},
{"timport", "Import a transparent WIF private key", "wif",
"Imports a transparent private key in WIF format (begins with U, 5, K or L) so the wallet can spend its funds.",
"timport somewifkey", "import transparent wif private key taddr"},
{"encrypt", "Encrypt the wallet with a password", "password",
"Encrypts the wallet with a password and locks it immediately. You will need the password to send or reveal keys afterwards. If you forget it, only the seed phrase can recover the wallet.",
"encrypt strongpassword", "encrypt password protect lock secure passphrase", true},
{"decrypt", "Completely remove wallet encryption", "password",
"Permanently removes the wallet's password encryption, leaving it unprotected on disk. Requires the current password.",
"decrypt strongpassword", "decrypt remove encryption password unprotect", true},
{"unlock", "Unlock the wallet for spending", "password",
"Temporarily unlocks an encrypted wallet so it can send or reveal keys. Use lock to re-lock it.",
"unlock strongpassword", "unlock password spend open temporarily"},
{"lock", "Lock a temporarily-unlocked wallet", "",
"Re-locks a wallet that was unlocked for spending, without removing its encryption.",
"lock", "lock secure re-lock protect close"},
{"encryptionstatus", "Check if the wallet is encrypted and locked", "",
"Reports whether the wallet is encrypted and, if so, whether it is currently locked or unlocked.",
"encryptionstatus", "encryption status locked unlocked encrypted state"},
};
const ConsoleCommandEntry kLiteAdvancedCommands[] = {
{"sietch", "Create a Sietch address", "[type]",
"Creates a Sietch address, used for enhanced-privacy sends. Pass zs for a sapling Sietch address.",
"sietch zs", "sietch privacy address decoy sapling advanced"},
{"saplingtree", "Dump the latest Sapling commitment tree (debug)", "",
"Prints the latest Sapling commitment tree state \xE2\x80\x94 a debugging aid, not needed for everyday use.",
"saplingtree", "sapling tree commitment debug advanced merkle"},
{"coinsupply", "Get the coin supply info", "",
"Reports coin-supply figures for the chain as seen by the wallet's server.",
"coinsupply", "supply coins total emission circulating amount"},
};
} // namespace } // namespace
const std::vector<ConsoleCommandCategory>& consoleCommandCategories() const std::vector<ConsoleCommandCategory>& consoleCommandCategories()
@@ -296,5 +395,17 @@ const std::vector<ConsoleCommandCategory>& consoleCommandCategories()
return categories; return categories;
} }
const std::vector<ConsoleCommandCategory>& liteConsoleCommandCategories()
{
static const std::vector<ConsoleCommandCategory> categories = {
{"Wallet", kLiteWalletCommands, CountOf(kLiteWalletCommands)},
{"Sync", kLiteSyncCommands, CountOf(kLiteSyncCommands)},
{"Send", kLiteSendCommands, CountOf(kLiteSendCommands)},
{"Keys & Security", kLiteKeyCommands, CountOf(kLiteKeyCommands)},
{"Advanced", kLiteAdvancedCommands, CountOf(kLiteAdvancedCommands)},
};
return categories;
}
} // namespace ui } // namespace ui
} // namespace dragonx } // namespace dragonx

View File

@@ -23,7 +23,12 @@ struct ConsoleCommandCategory {
int count; int count;
}; };
// The full-node daemon's JSON-RPC command reference (browsed by the console's command-reference modal).
const std::vector<ConsoleCommandCategory>& consoleCommandCategories(); const std::vector<ConsoleCommandCategory>& consoleCommandCategories();
// The lite backend's own command set (the ~25 verbs the SDXL backend accepts) — the lite-variant
// analog of the RPC reference, shown by the same modal when the executor is the lite one.
const std::vector<ConsoleCommandCategory>& liteConsoleCommandCategories();
} // namespace ui } // namespace ui
} // namespace dragonx } // namespace dragonx

View File

@@ -464,12 +464,18 @@ void ConsoleTab::render(ConsoleCommandExecutor& exec)
ImGui::EndChild(); ImGui::EndChild();
} }
void ConsoleTab::renderCommandsPopupModal() void ConsoleTab::renderCommandsPopupModal(ConsoleCommandExecutor* exec)
{ {
if (!show_commands_popup_) { if (!show_commands_popup_) {
return; return;
} }
renderCommandsPopup(); // Need a backend that offers a reference table. If the console hasn't built its executor yet
// (popup can't have been opened normally) or the backend has none, just dismiss.
if (!exec || !exec->commandReference()) {
show_commands_popup_ = false;
return;
}
renderCommandsPopup(*exec);
} }
void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec) void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec)
@@ -544,14 +550,16 @@ void ConsoleTab::renderToolbar(ConsoleCommandExecutor& exec)
ImGui::SameLine(); ImGui::SameLine();
// Commands reference button (full-node RPC reference only) // Commands reference button — shown whenever the backend offers a reference table (full-node
if (exec.hasRpcReference()) { // JSON-RPC commands, or the lite backend's own verbs).
if (exec.commandReference()) {
if (TactileButton(TR("console_commands"), ImVec2(0, 0), schema::UI().resolveFont("button"))) { if (TactileButton(TR("console_commands"), ImVec2(0, 0), schema::UI().resolveFont("button"))) {
command_search_[0] = '\0'; // fresh search each open (dismiss paths don't all reset it) command_search_[0] = '\0'; // fresh search each open (dismiss paths don't all reset it)
show_commands_popup_ = true; show_commands_popup_ = true;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
material::Tooltip("%s", TR("console_show_rpc_ref")); material::Tooltip("%s", exec.hasRpcReference() ? TR("console_show_rpc_ref")
: TR("console_show_backend_ref"));
} }
ImGui::SameLine(); ImGui::SameLine();
} }
@@ -611,7 +619,7 @@ void ConsoleTab::drawToolbarStatus(ConsoleCommandExecutor& exec)
ImVec2 cp = ImGui::GetCursorScreenPos(); ImVec2 cp = ImGui::GetCursorScreenPos();
float dotR = schema::UI().drawElement("tabs.console", "status-dot-radius-base").size + schema::UI().drawElement("tabs.console", "status-dot-radius-scale").size * Layout::hScale(); float dotR = schema::UI().drawElement("tabs.console", "status-dot-radius-base").size + schema::UI().drawElement("tabs.console", "status-dot-radius-scale").size * Layout::hScale();
float dotY = cp.y + ImGui::GetTextLineHeight() * 0.5f; float dotY = cp.y + ImGui::GetTextLineHeight() * 0.5f;
float dotX = cp.x + dotR + 2; float dotX = cp.x + dotR + 2.0f * Layout::dpiScale();
if (st.pulse) { if (st.pulse) {
float a = schema::UI().drawElement("animations", "pulse-base-glow").size + schema::UI().drawElement("animations", "pulse-amp-glow").size * (float)std::sin((double)ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-fast").size); float a = schema::UI().drawElement("animations", "pulse-base-glow").size + schema::UI().drawElement("animations", "pulse-amp-glow").size * (float)std::sin((double)ImGui::GetTime() * schema::UI().drawElement("animations", "pulse-speed-fast").size);
@@ -621,7 +629,7 @@ void ConsoleTab::drawToolbarStatus(ConsoleCommandExecutor& exec)
dl->AddCircleFilled(ImVec2(dotX, dotY), dotR, st.color); dl->AddCircleFilled(ImVec2(dotX, dotY), dotR, st.color);
} }
ImGui::Dummy(ImVec2(dotR * 2 + 6, 0)); ImGui::Dummy(ImVec2(dotR * 2 + 6.0f * Layout::dpiScale(), 0));
ImGui::SameLine(); ImGui::SameLine();
Type().textColored(TypeStyle::Caption, st.color, st.text.c_str()); Type().textColored(TypeStyle::Caption, st.color, st.text.c_str());
} else { } else {
@@ -1434,18 +1442,22 @@ bool ConsoleTab::submitConsoleCommand(ConsoleCommandExecutor& exec, const std::s
exec.printHelp(add); exec.printHelp(add);
} else if (first == "quit" || first == "exit") { } else if (first == "quit" || first == "exit") {
addLine(TR("console_quit_note"), ConsoleChannel::Info); addLine(TR("console_quit_note"), ConsoleChannel::Info);
} else if (first == "stop") { } else if (first == "stop" && exec.hasRpcReference()) {
// Full-node 'stop' shuts down the daemon (destructive) — gate behind a confirming second
// 'stop'. Lite has no node: `stop` isn't a backend verb, so it falls through below and the
// backend reports it as unknown — no misleading "shut down the node" warning or dead gate.
if (!stop_confirm_pending_) { if (!stop_confirm_pending_) {
stop_confirm_pending_ = true; stop_confirm_pending_ = true;
addLine("'stop' will shut down the node and disconnect the wallet. Type 'stop' again to confirm.", addLine(TR("console_stop_confirm_node"), ConsoleChannel::Warning);
ConsoleChannel::Warning);
} else { } else {
stop_confirm_pending_ = false; stop_confirm_pending_ = false;
if (!exec.isReady()) addLine(TR("console_not_connected"), ConsoleChannel::Error); if (!exec.isReady()) addLine(TR("console_not_connected"), ConsoleChannel::Error);
else exec.submit(cmd); else exec.submit(cmd);
} }
} else if (!exec.isReady()) { } else if (!exec.isReady()) {
addLine(TR("console_not_connected"), ConsoleChannel::Error); // Full node connects to a daemon; the lite backend opens a wallet — word the error per variant.
addLine(exec.hasRpcReference() ? TR("console_not_connected") : TR("console_not_connected_lite"),
ConsoleChannel::Error);
} else { } else {
exec.submit(cmd); exec.submit(cmd);
} }
@@ -1557,6 +1569,11 @@ const char* consoleCategoryLabel(const char* name)
if (!std::strcmp(name, "Wallet")) return TR("console_cat_wallet"); if (!std::strcmp(name, "Wallet")) return TR("console_cat_wallet");
if (!std::strcmp(name, "Raw Transactions")) return TR("console_cat_raw_transactions"); if (!std::strcmp(name, "Raw Transactions")) return TR("console_cat_raw_transactions");
if (!std::strcmp(name, "Utility")) return TR("console_cat_utility"); if (!std::strcmp(name, "Utility")) return TR("console_cat_utility");
// Lite backend reference categories.
if (!std::strcmp(name, "Sync")) return TR("console_cat_sync");
if (!std::strcmp(name, "Send")) return TR("console_cat_send");
if (!std::strcmp(name, "Keys & Security")) return TR("console_cat_keys");
if (!std::strcmp(name, "Advanced")) return TR("console_cat_advanced");
return name; return name;
} }
} // namespace } // namespace
@@ -1576,7 +1593,7 @@ void ConsoleTab::insertCommandToInput(const ConsoleCommandEntry& cmd)
show_commands_popup_ = false; show_commands_popup_ = false;
} }
void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel) void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel, bool jsonArgs)
{ {
using namespace material; using namespace material;
float dp = Layout::dpiScale(); float dp = Layout::dpiScale();
@@ -1627,8 +1644,11 @@ void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char*
ImGui::PopFont(); ImGui::PopFont();
ImGui::SameLine(labelW); ImGui::SameLine(labelW);
ImGui::SetNextItemWidth(-1); ImGui::SetNextItemWidth(-1);
// Lite backend args are bare freeform tokens, so its param types (string/number) don't
// apply — show a neutral "value" hint there instead of a misleading "number".
std::string typeHint = jsonArgs ? s.type : std::string(TR("console_ref_value"));
std::string hint = s.optional std::string hint = s.optional
? (s.type + " \xC2\xB7 " + std::string(TR("console_ref_optional"))) : s.type; ? (typeHint + " \xC2\xB7 " + std::string(TR("console_ref_optional"))) : typeHint;
ImGui::InputTextWithHint("##pv", hint.c_str(), cmd_param_bufs_[k], sizeof(cmd_param_bufs_[k])); ImGui::InputTextWithHint("##pv", hint.c_str(), cmd_param_bufs_[k], sizeof(cmd_param_bufs_[k]));
ImGui::PopID(); ImGui::PopID();
} }
@@ -1658,7 +1678,9 @@ void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char*
built += " " + specs[k].raw; built += " " + specs[k].raw;
complete = false; complete = false;
} else { } else {
if (specs[k].type == "string" && val.front() != '"' && val.front() != '\'' && // JSON-RPC (full node) auto-quotes string args; the lite backend takes bare tokens, so
// leave the value exactly as typed there (quoting would break its address/key parsing).
if (jsonArgs && specs[k].type == "string" && val.front() != '"' && val.front() != '\'' &&
val.front() != '[' && val.front() != '{') val.front() != '[' && val.front() != '{')
val = "\"" + val + "\""; val = "\"" + val + "\"";
built += " " + val; built += " " + val;
@@ -1731,13 +1753,16 @@ void ConsoleTab::renderCommandDetail(const ConsoleCommandEntry& cmd, const char*
} }
} }
void ConsoleTab::renderCommandsPopup() void ConsoleTab::renderCommandsPopup(ConsoleCommandExecutor& exec)
{ {
using namespace material; using namespace material;
float dp = Layout::dpiScale(); float dp = Layout::dpiScale();
// Full node speaks JSON-RPC (quote string args); the lite backend takes bare tokens.
const bool jsonArgs = exec.hasRpcReference();
material::OverlayDialogSpec ov; material::OverlayDialogSpec ov;
ov.title = TR("console_rpc_reference"); ov.title = jsonArgs ? TR("console_rpc_reference") : TR("console_backend_reference");
ov.p_open = &show_commands_popup_; ov.p_open = &show_commands_popup_;
ov.style = material::OverlayStyle::BlurFloat; // floating content on the blur, plain heading ov.style = material::OverlayStyle::BlurFloat; // floating content on the blur, plain heading
ov.cardWidth = 960.0f; // wide enough for two panes ov.cardWidth = 960.0f; // wide enough for two panes
@@ -1766,7 +1791,7 @@ void ConsoleTab::renderCommandsPopup()
std::transform(q.begin(), q.end(), q.begin(), ::tolower); std::transform(q.begin(), q.end(), q.begin(), ::tolower);
const bool searching = !q.empty(); const bool searching = !q.empty();
const auto& categories = consoleCommandCategories(); const auto& categories = *exec.commandReference(); // non-null: guarded by renderCommandsPopupModal
// Flat display order of (cat,idx): ranked when searching, category order when browsing. Drives // Flat display order of (cat,idx): ranked when searching, category order when browsing. Drives
// keyboard nav + auto-selection; the browse view still renders grouped headers below. // keyboard nav + auto-selection; the browse view still renders grouped headers below.
@@ -1908,7 +1933,7 @@ void ConsoleTab::renderCommandsPopup()
if (cmd_sel_cat_ >= 0 && cmd_sel_cat_ < (int)categories.size() && if (cmd_sel_cat_ >= 0 && cmd_sel_cat_ < (int)categories.size() &&
cmd_sel_idx_ >= 0 && cmd_sel_idx_ < categories[cmd_sel_cat_].count) { cmd_sel_idx_ >= 0 && cmd_sel_idx_ < categories[cmd_sel_cat_].count) {
renderCommandDetail(categories[cmd_sel_cat_].commands[cmd_sel_idx_], renderCommandDetail(categories[cmd_sel_cat_].commands[cmd_sel_idx_],
consoleCategoryLabel(categories[cmd_sel_cat_].name)); consoleCategoryLabel(categories[cmd_sel_cat_].name), jsonArgs);
} else { } else {
ImVec2 av = ImGui::GetContentRegionAvail(); ImVec2 av = ImGui::GetContentRegionAvail();
ImGui::SetCursorPosY(av.y * 0.4f); ImGui::SetCursorPosY(av.y * 0.4f);

View File

@@ -44,10 +44,11 @@ public:
void render(ConsoleCommandExecutor& exec); void render(ConsoleCommandExecutor& exec);
/** /**
* @brief Render the RPC Command Reference popup at top-level scope. * @brief Render the command-reference popup at top-level scope.
* Must be called outside any child window so the modal blocks all input. * Must be called outside any child window so the modal blocks all input. `exec` supplies the
* reference table (full-node RPC vs lite backend verbs); may be null (no console yet) -> no-op.
*/ */
void renderCommandsPopupModal(); void renderCommandsPopupModal(ConsoleCommandExecutor* exec);
// Debug/UI-sweep hook: force the RPC command-reference popup open/closed so the full UI sweep // Debug/UI-sweep hook: force the RPC command-reference popup open/closed so the full UI sweep
// can capture it (the popup is otherwise opened only by a toolbar button). // can capture it (the popup is otherwise opened only by a toolbar button).
@@ -115,8 +116,9 @@ private:
// Format a completed command result (JSON role -> channel) into console lines. // Format a completed command result (JSON role -> channel) into console lines.
void addFormattedResult(const std::string& result, bool is_error); void addFormattedResult(const std::string& result, bool is_error);
void renderStatusHeader(ConsoleCommandExecutor& exec); void renderStatusHeader(ConsoleCommandExecutor& exec);
void renderCommandsPopup(); void renderCommandsPopup(ConsoleCommandExecutor& exec);
void renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel); // right pane // right pane; jsonArgs=true (full-node RPC) auto-quotes string args, false (lite) inserts bare
void renderCommandDetail(const ConsoleCommandEntry& cmd, const char* catLabel, bool jsonArgs);
void insertCommandToInput(const ConsoleCommandEntry& cmd); // fill input + close the modal void insertCommandToInput(const ConsoleCommandEntry& cmd); // fill input + close the modal
// renderToolbar() draws the top bar; these are its sub-steps: // renderToolbar() draws the top bar; these are its sub-steps:
@@ -186,10 +188,11 @@ private:
// consumed by the renderer + hit-testing. // consumed by the renderer + hit-testing.
mutable ConsoleLayout layout_; mutable ConsoleLayout layout_;
// Commands popup (RPC command explorer) // Commands popup (command explorer) — indexes into the active executor's commandReference()
// table (full-node RPC commands or the lite backend verbs), not always the full-node one.
bool show_commands_popup_ = false; bool show_commands_popup_ = false;
char command_search_[128] = {0}; // RPC-reference search filter (cleared when the modal opens) char command_search_[128] = {0}; // reference search filter (cleared when the modal opens)
int cmd_sel_cat_ = -1; // detail-pane selection: category index into consoleCommandCategories() int cmd_sel_cat_ = -1; // detail-pane selection: category index into that table
int cmd_sel_idx_ = -1; // detail-pane selection: command index within that category int cmd_sel_idx_ = -1; // detail-pane selection: command index within that category
std::string pending_submit_; // command to run next frame (deferred so the modal needs no executor) std::string pending_submit_; // command to run next frame (deferred so the modal needs no executor)
const ConsoleCommandEntry* run_confirm_cmd_ = nullptr; // destructive "Insert & run" awaiting confirmation const ConsoleCommandEntry* run_confirm_cmd_ = nullptr; // destructive "Insert & run" awaiting confirmation

View File

@@ -1005,9 +1005,23 @@ void RenderContactsTab(App* app)
if (viewMode == 2) { if (viewMode == 2) {
// ── TABLE mode (material-ized): no outer/grid borders, row backgrounds, interactive sort. ── // ── TABLE mode (material-ized): no outer/grid borders, row backgrounds, interactive sort. ──
// Frosted-glass panel behind the table so it blurs the backdrop like the card/list view (the
// table's translucent row backgrounds let the blur show through). A BeginTable can't take
// AlwaysUseWindowPadding, so inset the table itself inside the glass so its headers/cells
// don't hug the container edges.
ImDrawList* tpdl = ImGui::GetWindowDrawList();
const ImVec2 tpMin = ImGui::GetCursorScreenPos();
const float tpW = ImGui::GetContentRegionAvail().x;
const float tIpad = 12.0f * dp, tVpad = 10.0f * dp;
{
material::GlassPanelSpec g; g.rounding = 12.0f * dp; g.fillAlpha = 14; g.borderAlpha = 34;
material::DrawGlassPanel(tpdl, tpMin, ImVec2(tpMin.x + tpW, tpMin.y + listH), g);
}
ImGui::SetCursorScreenPos(ImVec2(tpMin.x + tIpad, tpMin.y + tVpad));
if (ImGui::BeginTable("AddressBookTable", 3, if (ImGui::BeginTable("AddressBookTable", 3,
ImGuiTableFlags_RowBg | ImGuiTableFlags_Sortable | ImGuiTableFlags_RowBg | ImGuiTableFlags_Sortable |
ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY, ImVec2(0, listH))) ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY,
ImVec2(tpW - 2.0f * tIpad, listH - 2.0f * tVpad)))
{ {
ImGui::TableSetupColumn(TR("label"), ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultSort, 1.5f); ImGui::TableSetupColumn(TR("label"), ImGuiTableColumnFlags_WidthStretch | ImGuiTableColumnFlags_DefaultSort, 1.5f);
ImGui::TableSetupColumn(TR("address_label"), ImGuiTableColumnFlags_WidthStretch, 2.6f); ImGui::TableSetupColumn(TR("address_label"), ImGuiTableColumnFlags_WidthStretch, 2.6f);
@@ -1093,8 +1107,20 @@ void RenderContactsTab(App* app)
return toLower(entries[a].label).compare(toLower(entries[b].label)) < 0; return toLower(entries[a].label).compare(toLower(entries[b].label)) < 0;
}); });
} }
// Frosted-glass list container so the contacts area blurs the backdrop (peers_tab pattern:
// glass behind a NoBackground list child) instead of showing the raw texture through the pane.
{
ImDrawList* pdl = ImGui::GetWindowDrawList();
const ImVec2 pMin = ImGui::GetCursorScreenPos();
const float pW = ImGui::GetContentRegionAvail().x;
material::GlassPanelSpec g; g.rounding = 12.0f * dp; g.fillAlpha = 14; g.borderAlpha = 34;
material::DrawGlassPanel(pdl, pMin, ImVec2(pMin.x + pW, pMin.y + listH), g);
}
// AlwaysUseWindowPadding: a borderless child ignores WindowPadding without it, so the rows
// would hug the frosted container's edges.
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f * dp, 10.0f * dp));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0,0,0,0)); ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0,0,0,0));
ImGui::BeginChild("##contactList", ImVec2(0, listH), false, ImGui::BeginChild("##contactList", ImVec2(0, listH), ImGuiChildFlags_AlwaysUseWindowPadding,
ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollWithMouse); ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoScrollWithMouse);
material::ApplySmoothScroll(); // wheel-lerp scroll, matching the rest of the app material::ApplySmoothScroll(); // wheel-lerp scroll, matching the rest of the app
if (visibleRows.empty()) { if (visibleRows.empty()) {
@@ -1243,6 +1269,7 @@ void RenderContactsTab(App* app)
} }
ImGui::EndChild(); ImGui::EndChild();
ImGui::PopStyleColor(); ImGui::PopStyleColor();
ImGui::PopStyleVar(); // ##contactList inner WindowPadding
} }
// Shared right-click context menu (opened by either view's row right-click; acts on the selection). // Shared right-click context menu (opened by either view's row right-click; acts on the selection).

View File

@@ -1395,6 +1395,12 @@ void I18n::loadBuiltinEnglish()
strings_["console_available_commands"] = "Available commands:"; strings_["console_available_commands"] = "Available commands:";
strings_["console_quit_note"] = "'quit'/'exit' aren't needed here — just close the window."; strings_["console_quit_note"] = "'quit'/'exit' aren't needed here — just close the window.";
strings_["lite_console_help_passthrough"] = "Any other input runs as a lite-wallet console command."; strings_["lite_console_help_passthrough"] = "Any other input runs as a lite-wallet console command.";
strings_["lite_console_backend_commands"] = "Backend commands:";
strings_["console_no_output"] = "(no output)";
strings_["console_backend_unavailable"] = "No backend";
strings_["console_last_error"] = "Last error:";
strings_["console_not_connected_lite"] = "Error: no wallet open";
strings_["console_stop_confirm_node"] = "'stop' will shut down the node and disconnect the wallet. Type 'stop' again to confirm.";
strings_["console_capturing_output"] = "Capturing daemon output..."; strings_["console_capturing_output"] = "Capturing daemon output...";
strings_["console_clear"] = "Clear"; strings_["console_clear"] = "Clear";
strings_["console_clear_console"] = "Clear Console"; strings_["console_clear_console"] = "Clear Console";
@@ -1434,6 +1440,7 @@ void I18n::loadBuiltinEnglish()
strings_["console_no_daemon"] = "No daemon"; strings_["console_no_daemon"] = "No daemon";
strings_["console_not_connected"] = "Error: Not connected to daemon"; strings_["console_not_connected"] = "Error: Not connected to daemon";
strings_["console_rpc_reference"] = "RPC Command Reference"; strings_["console_rpc_reference"] = "RPC Command Reference";
strings_["console_backend_reference"] = "Backend Command Reference";
strings_["console_rpc_trace"] = "RPC"; strings_["console_rpc_trace"] = "RPC";
strings_["console_app"] = "App"; strings_["console_app"] = "App";
strings_["console_show_app_output"] = "Show [app] wallet log lines"; strings_["console_show_app_output"] = "Show [app] wallet log lines";
@@ -1442,6 +1449,7 @@ void I18n::loadBuiltinEnglish()
strings_["console_show_daemon_output"] = "Show daemon output"; strings_["console_show_daemon_output"] = "Show daemon output";
strings_["console_show_errors_only"] = "Show errors only"; strings_["console_show_errors_only"] = "Show errors only";
strings_["console_show_rpc_ref"] = "Show RPC command reference"; strings_["console_show_rpc_ref"] = "Show RPC command reference";
strings_["console_show_backend_ref"] = "Show backend command reference";
strings_["console_show_rpc_trace"] = "Show app RPC calls"; strings_["console_show_rpc_trace"] = "Show app RPC calls";
strings_["console_showing_lines"] = "Showing %zu of %zu lines"; strings_["console_showing_lines"] = "Showing %zu of %zu lines";
strings_["console_starting_node"] = "Starting node..."; strings_["console_starting_node"] = "Starting node...";
@@ -1467,10 +1475,15 @@ void I18n::loadBuiltinEnglish()
strings_["console_cat_wallet"] = "Wallet"; strings_["console_cat_wallet"] = "Wallet";
strings_["console_cat_raw_transactions"] = "Raw Transactions"; strings_["console_cat_raw_transactions"] = "Raw Transactions";
strings_["console_cat_utility"] = "Utility"; strings_["console_cat_utility"] = "Utility";
strings_["console_cat_sync"] = "Sync";
strings_["console_cat_send"] = "Send";
strings_["console_cat_keys"] = "Keys & Security";
strings_["console_cat_advanced"] = "Advanced";
strings_["console_ref_search_hint"] = "Search by name or task\xE2\x80\xA6"; strings_["console_ref_search_hint"] = "Search by name or task\xE2\x80\xA6";
strings_["console_ref_parameters"] = "Parameters"; strings_["console_ref_parameters"] = "Parameters";
strings_["console_ref_no_params"] = "Takes no parameters."; strings_["console_ref_no_params"] = "Takes no parameters.";
strings_["console_ref_optional"] = "optional"; strings_["console_ref_optional"] = "optional";
strings_["console_ref_value"] = "value";
strings_["console_ref_example"] = "Example"; strings_["console_ref_example"] = "Example";
strings_["console_ref_builds"] = "Builds"; strings_["console_ref_builds"] = "Builds";
strings_["console_ref_destructive"] = "Consequential"; strings_["console_ref_destructive"] = "Consequential";
@@ -1854,6 +1867,7 @@ void I18n::loadBuiltinEnglish()
strings_["lite_net_show_hidden"] = "Show hidden servers"; strings_["lite_net_show_hidden"] = "Show hidden servers";
strings_["lite_net_hidden_section"] = "Hidden servers"; strings_["lite_net_hidden_section"] = "Hidden servers";
strings_["lite_net_connected"] = "Connected"; strings_["lite_net_connected"] = "Connected";
strings_["lite_net_connecting"] = "Connecting";
strings_["lite_net_disconnected"] = "Not connected"; strings_["lite_net_disconnected"] = "Not connected";
strings_["lite_net_syncing"] = "Syncing"; strings_["lite_net_syncing"] = "Syncing";
strings_["lite_net_synced"] = "Synced"; strings_["lite_net_synced"] = "Synced";

View File

@@ -57,7 +57,15 @@ void Logger::write(const std::string& message)
now.time_since_epoch()) % 1000; now.time_since_epoch()) % 1000;
std::stringstream ss; std::stringstream ss;
ss << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S"); // Reachable from worker/monitor threads — std::localtime shares a process-wide static tm, so use the
// reentrant variant into a local tm (the logger's own mutex can't protect other localtime callers).
std::tm tmv{};
#ifdef _WIN32
localtime_s(&tmv, &time);
#else
localtime_r(&time, &tmv);
#endif
ss << std::put_time(&tmv, "%Y-%m-%d %H:%M:%S");
ss << '.' << std::setfill('0') << std::setw(3) << ms.count(); ss << '.' << std::setfill('0') << std::setw(3) << ms.count();
ss << " | " << message; ss << " | " << message;

View File

@@ -5300,8 +5300,11 @@ void testXmrigAssetSelection()
EXPECT_TRUE(linux >= 0); EXPECT_TRUE(linux >= 0);
EXPECT_TRUE(win >= 0); EXPECT_TRUE(win >= 0);
EXPECT_TRUE(linux != win); EXPECT_TRUE(linux != win);
EXPECT_TRUE(rel.assets[linux].name.find("linux-x64.zip") != std::string::npos); // Guard the index reads: select*Asset returns -1 when nothing matches, and EXPECT_TRUE doesn't
EXPECT_TRUE(rel.assets[win].name.find("win-x64.zip") != std::string::npos); // abort this harness, so an unguarded rel.assets[-1] on a fixture/parser regression would SIGSEGV
// the whole suite instead of reporting the failed EXPECT above.
if (linux >= 0) EXPECT_TRUE(rel.assets[linux].name.find("linux-x64.zip") != std::string::npos);
if (win >= 0) EXPECT_TRUE(rel.assets[win].name.find("win-x64.zip") != std::string::npos);
// No macOS build in this fixture -> graceful "not found". // No macOS build in this fixture -> graceful "not found".
EXPECT_EQ(selectXmrigAsset(rel, "macos-x86_64"), -1); EXPECT_EQ(selectXmrigAsset(rel, "macos-x86_64"), -1);
EXPECT_EQ(selectXmrigAsset(rel, "macos-arm64"), -1); EXPECT_EQ(selectXmrigAsset(rel, "macos-arm64"), -1);
@@ -5607,9 +5610,11 @@ void testDaemonAssetSelection()
const int win = selectDaemonAsset(rel, "win64"); const int win = selectDaemonAsset(rel, "win64");
EXPECT_TRUE(lin >= 0 && mac >= 0 && win >= 0); EXPECT_TRUE(lin >= 0 && mac >= 0 && win >= 0);
EXPECT_TRUE(lin != mac && mac != win && lin != win); EXPECT_TRUE(lin != mac && mac != win && lin != win);
EXPECT_TRUE(rel.assets[lin].name.find("linux-amd64.zip") != std::string::npos); // Guard the index reads (select*Asset returns -1 on no match; EXPECT_TRUE doesn't abort here) so a
EXPECT_TRUE(rel.assets[mac].name.find("macos.zip") != std::string::npos); // fixture/parser regression reports the failed EXPECT above instead of an OOB rel.assets[-1] crash.
EXPECT_TRUE(rel.assets[win].name.find("win64.zip") != std::string::npos); if (lin >= 0) EXPECT_TRUE(rel.assets[lin].name.find("linux-amd64.zip") != std::string::npos);
if (mac >= 0) EXPECT_TRUE(rel.assets[mac].name.find("macos.zip") != std::string::npos);
if (win >= 0) EXPECT_TRUE(rel.assets[win].name.find("win64.zip") != std::string::npos);
// Wrong/foreign tokens (e.g. the miner's naming) must NOT match the daemon archives. // Wrong/foreign tokens (e.g. the miner's naming) must NOT match the daemon archives.
EXPECT_EQ(selectDaemonAsset(rel, "linux-x64"), -1); EXPECT_EQ(selectDaemonAsset(rel, "linux-x64"), -1);
EXPECT_EQ(selectDaemonAsset(rel, "linux-arm64"), -1); EXPECT_EQ(selectDaemonAsset(rel, "linux-arm64"), -1);
@@ -6357,8 +6362,11 @@ void testAddressBookScope()
fs::create_directories(tmp); fs::create_directories(tmp);
setenv("HOME", tmp.string().c_str(), 1); setenv("HOME", tmp.string().c_str(), 1);
// A pre-scoping addressbook.json (no "scope" field) migrates to global on load. // A pre-scoping addressbook.json (no "scope" field) migrates to global on load. Write to the
fs::path cfg = tmp / ".config" / "ObsidianDragon"; // SAME per-variant config dir AddressBook::load() reads from (Lite -> ObsidianDragonLite/),
// resolved under the temp HOME set above. Hardcoding ".config/ObsidianDragon" made the lite-build
// ctest write where load() never looks -> 0 entries -> the entries()[0] below segfaulted.
fs::path cfg = dragonx::util::Platform::getConfigDir();
fs::create_directories(cfg); fs::create_directories(cfg);
std::ofstream(cfg / "addressbook.json") std::ofstream(cfg / "addressbook.json")
<< R"({"entries":[{"label":"Legacy","address":"zs1legacy","notes":""}]})"; << R"({"entries":[{"label":"Legacy","address":"zs1legacy","notes":""}]})";
@@ -6366,7 +6374,9 @@ void testAddressBookScope()
AddressBook book; AddressBook book;
EXPECT_TRUE(book.load()); EXPECT_TRUE(book.load());
EXPECT_EQ(book.size(), (size_t)1); EXPECT_EQ(book.size(), (size_t)1);
EXPECT_TRUE(book.entries()[0].isGlobal()); // migrated -> global // Guard the [0] access — EXPECT_EQ doesn't abort this harness, so an empty book here must not
// SIGSEGV the whole suite (it would take every later test down with it).
EXPECT_TRUE(!book.entries().empty() && book.entries()[0].isGlobal()); // migrated -> global
} }
// Same address may be a contact in two DIFFERENT wallets, but not twice in one; and a global // Same address may be a contact in two DIFFERENT wallets, but not twice in one; and a global