diff --git a/src/Hashrate.tsx b/src/Hashrate.tsx index c9f060e..7198f2f 100644 --- a/src/Hashrate.tsx +++ b/src/Hashrate.tsx @@ -1,6 +1,6 @@ import { useSignals } from "@preact/signals-react/runtime"; import { useRef, useEffect, useState } from "react"; -import { hashrate } from "./signals"; +import { hashrate, miningStatus } from "./signals"; const fixed = (n: number) => n.toFixed(2); @@ -10,6 +10,7 @@ const SMOOTHING_FACTOR = 0.15; // Lower = smoother but slower to react export default function Hashrate() { useSignals(); const rawValue = hashrate.value; + const status = miningStatus.value; const smoothedRef = useRef(rawValue); const [displayValue, setDisplayValue] = useState(rawValue); @@ -25,6 +26,14 @@ export default function Hashrate() { const value = displayValue; + // While mining is active but the first GPU batch hasn't yet completed (so + // we have no measured rate), show an explicit "warming up…" instead of a + // bare "0.00 H/s" — the latter looks like the miner is broken even though + // it's actually dispatching work. The first batch typically takes 3–8 s. + if ((status === "mining" || status === "change") && value === 0) { + return "warming up…"; + } + if (value > 1000000000) { return `${fixed(value / 1000000000)} Gh/s`; } else if (value > 1000000) { diff --git a/src/Messages.tsx b/src/Messages.tsx index 50849fe..048125d 100644 --- a/src/Messages.tsx +++ b/src/Messages.tsx @@ -146,7 +146,9 @@ export default function Messages() { )} {m.type === "start" && <>Mining started} - {m.type === "stop" && <>Mining stopped} + {m.type === "stop" && ( + <>Mining stopped{m.reason ? <> — {m.reason} : null} + )} ))} diff --git a/src/initWallet.ts b/src/initWallet.ts index fbd54ab..d145c5a 100644 --- a/src/initWallet.ts +++ b/src/initWallet.ts @@ -45,24 +45,49 @@ contractsUrl.value = useIndexerApi.value = localStorage.getItem("useIndexerApi") !== ""; // Load RXinDexer REST API URL restApiUrl.value = localStorage.getItem("restApiUrl") || "https://glyph-miner.com/api"; -// Default to enabled unless explicitly disabled -autoReseed.value = localStorage.getItem("autoReseed") !== ""; -// If servers isn't saved then set to default servers, randomly sorted -// Also ensure any new default servers are added to stored list +// autoReseed default: ON. Honor an explicit "0" to disable; treat anything +// else (including missing / legacy empty string) as enabled. The previous +// check (`!== ""`) inverted the convention so writing "0" silently failed +// to disable — kept as a fallback for legacy stored values. +{ + const stored = localStorage.getItem("autoReseed"); + if (stored === null) { + autoReseed.value = true; + } else if (stored === "0") { + autoReseed.value = false; + } else if (stored === "") { + // Legacy form written by the old Settings UI; treat as disabled. + autoReseed.value = false; + } else { + autoReseed.value = true; + } +} + +// If servers isn't saved then set to default servers in canonical order. +// +// On first run we seed the user's stored list with the defaults. On +// subsequent runs we respect the stored list as-is — we do NOT silently +// merge in any new defaults that the user previously removed, because that +// makes "remove server" stick for one session and reappear on the next. +// Users who want refreshed defaults can clear the entry from Settings or +// localStorage. const storedServers = localStorage.getItem("servers"); if (storedServers) { - const parsed: string[] = JSON.parse(storedServers); - const missing = defaultServers.filter((s) => !parsed.includes(s)); - if (missing.length > 0) { - parsed.push(...missing); - localStorage.setItem("servers", JSON.stringify(parsed)); + try { + const parsed: string[] = JSON.parse(storedServers); + servers.value = Array.isArray(parsed) && parsed.length > 0 + ? parsed + : defaultServers.slice(); + } catch { + // Corrupt JSON in storage — fall back to defaults rather than crash. + servers.value = defaultServers.slice(); } - servers.value = parsed; } else { // Deterministic order — first entries are Radiant Core nodes that // accept V2 BLAKE3/K12 dMint contracts. See defaultServers comment. servers.value = defaultServers.slice(); + localStorage.setItem("servers", JSON.stringify(servers.value)); } connect(); diff --git a/src/miner.ts b/src/miner.ts index 3c6613d..6ebad4a 100644 Binary files a/src/miner.ts and b/src/miner.ts differ diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 16a0708..9ec6559 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -84,7 +84,7 @@ export default function Settings() { mineToAddress: mineToAddress.value, mintMessage: mintMessage.value, hideMessages: hideMessages.value ? "1" : "", - autoReseed: autoReseed.value ? "1" : "", + autoReseed: autoReseed.value ? "1" : "0", servers: servers.value.join("\n"), contractsUrl: contractsUrl.value, restApiUrl: restApiUrl.value, diff --git a/src/types.ts b/src/types.ts index 677bb3b..f81cd04 100644 --- a/src/types.ts +++ b/src/types.ts @@ -202,6 +202,14 @@ export type Message = { seconds: number; } | { - type: "start" | "stop"; + type: "start"; + } + | { + type: "stop"; + // Optional human-readable reason. When present, the Messages UI + // renders it as "Mining stopped — {reason}" so the user knows why + // the miner halted (nonce exhaustion, GPU error, algo mismatch, + // etc.) instead of seeing the Start button silently toggle back on. + reason?: string; } );