From 499e10498d132ae55ad7503cd3e4216bab113aac Mon Sep 17 00:00:00 2001 From: hulxv Date: Tue, 24 Mar 2026 09:25:59 +0200 Subject: [PATCH 1/4] fix: increase responsiveness --- frontend/app/api.ts | 25 +++++++++-- frontend/app/components/LoadingState.tsx | 34 +++++++++++++++ frontend/app/routes/home.tsx | 39 +++++++++++++---- frontend/app/routes/makerDetails/index.tsx | 26 ++++++++++-- frontend/app/routes/makerDetails/wallet.tsx | 15 +++++-- frontend/app/routes/makersetup.tsx | 47 ++++++++++++++++++--- 6 files changed, 160 insertions(+), 26 deletions(-) create mode 100644 frontend/app/components/LoadingState.tsx diff --git a/frontend/app/api.ts b/frontend/app/api.ts index ec46f5c..7fe2ad7 100644 --- a/frontend/app/api.ts +++ b/frontend/app/api.ts @@ -143,11 +143,28 @@ export class ApiError extends Error { // ─── Fetch helpers ──────────────────────────────────────────────────────────── +const REQUEST_TIMEOUT_MS = 30_000; + async function request(path: string, options: RequestInit = {}): Promise { - const res = await fetch(`/api${path}`, { - headers: { "Content-Type": "application/json", ...options.headers }, - ...options, - }); + const controller = new AbortController(); + const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); + + let res: Response; + try { + res = await fetch(`/api${path}`, { + headers: { "Content-Type": "application/json", ...options.headers }, + signal: controller.signal, + ...options, + }); + } catch (err) { + clearTimeout(timer); + if (err instanceof DOMException && err.name === "AbortError") { + throw new ApiError(0, `Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`); + } + throw err; + } finally { + clearTimeout(timer); + } let body: ApiResponse; try { diff --git a/frontend/app/components/LoadingState.tsx b/frontend/app/components/LoadingState.tsx new file mode 100644 index 0000000..3d13a4a --- /dev/null +++ b/frontend/app/components/LoadingState.tsx @@ -0,0 +1,34 @@ +import { Loader2 } from "lucide-react"; + +interface LoadingStateProps { + /** Primary message, e.g. "Loading maker data" */ + message: string; + /** Optional secondary detail, e.g. "Fetching wallet balance…" */ + detail?: string; + /** Show as full-page centered (default) or inline */ + inline?: boolean; +} + +export default function LoadingState({ + message, + detail, + inline, +}: LoadingStateProps) { + const content = ( +
+ +
+

{message}

+ {detail && ( +

{detail}

+ )} +
+
+ ); + + if (inline) return content; + + return ( +
{content}
+ ); +} diff --git a/frontend/app/routes/home.tsx b/frontend/app/routes/home.tsx index fb191ee..918c7a1 100644 --- a/frontend/app/routes/home.tsx +++ b/frontend/app/routes/home.tsx @@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react"; import { Link } from "react-router-dom"; import { X } from "lucide-react"; import Nav from "../components/Nav"; +import LoadingState from "../components/LoadingState"; import OnboardingWizard from "./onboarding"; import { makers, @@ -41,19 +42,28 @@ function swapKey( export default function Home() { const [makerRows, setMakerRows] = useState([]); const [loading, setLoading] = useState(true); + const [refreshing, setRefreshing] = useState(false); + const [loadingDetail, setLoadingDetail] = useState(""); const [error, setError] = useState(null); const [pending, setPending] = useState>(new Set()); const swapHistoryCache = useRef>({}); const lastSwapRefreshAt = useRef(0); async function loadMakers(forceSwapRefresh = false) { + const isInitialLoad = useRef(true); + + async function loadMakers() { + const initial = isInitialLoad.current; + if (!initial) setRefreshing(true); try { setError(null); + if (initial) setLoadingDetail("Fetching maker list…"); const list = await makers.list(); const includeSwaps = forceSwapRefresh || lastSwapRefreshAt.current === 0 || Date.now() - lastSwapRefreshAt.current >= SWAP_HISTORY_REFRESH_MS; + if (initial) setLoadingDetail(`Loading details for ${list.length} maker${list.length !== 1 ? "s" : ""}…`); const rows = await Promise.all( list.map(async ({ id }): Promise => { const requests = [ @@ -100,6 +110,8 @@ export default function Home() { setError(err instanceof Error ? err.message : "Failed to load makers"); } finally { setLoading(false); + setRefreshing(false); + isInitialLoad.current = false; } } @@ -132,9 +144,10 @@ export default function Home() { return (
); } @@ -199,12 +212,20 @@ export default function Home() {

Your Makers

- - + Add New Maker - +
+ {refreshing && ( + + + Refreshing... + + )} + + + Add New Maker + +
diff --git a/frontend/app/routes/makerDetails/index.tsx b/frontend/app/routes/makerDetails/index.tsx index e6c98f7..a9ebec6 100644 --- a/frontend/app/routes/makerDetails/index.tsx +++ b/frontend/app/routes/makerDetails/index.tsx @@ -1,7 +1,8 @@ -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, useRef } from "react"; import { useParams } from "react-router-dom"; import { Zap, Moon } from "lucide-react"; import Nav from "../../components/Nav"; +import LoadingState from "../../components/LoadingState"; import { makers, wallet, @@ -39,16 +40,20 @@ export default function MakerDetails() { const [torAddress, setTorAddress] = useState(null); const [dataDir, setDataDir] = useState(null); const [loading, setLoading] = useState(true); + const [loadingDetail, setLoadingDetail] = useState(""); const [error, setError] = useState(null); const [actionLoading, setActionLoading] = useState(false); const [syncLoading, setSyncLoading] = useState(false); const [syncMsg, setSyncMsg] = useState(null); + const isInitialLoad = useRef(true); const loadCore = useCallback(async () => { if (!id) return; - setLoading(true); + const initial = isInitialLoad.current; + if (initial) setLoading(true); setError(null); try { + if (initial) setLoadingDetail("Fetching maker config and status…"); const [infoData, statusData, balanceData] = await Promise.allSettled([ makers.get(id), monitoring.status(id), @@ -58,6 +63,7 @@ export default function MakerDetails() { if (statusData.status === "fulfilled") setStatus(statusData.value); if (balanceData.status === "fulfilled") setBalances(balanceData.value); + if (initial) setLoadingDetail("Resolving Tor address…"); monitoring .torAddress(id) .then(setTorAddress) @@ -70,6 +76,7 @@ export default function MakerDetails() { setError(e instanceof Error ? e.message : "Failed to load maker data"); } finally { setLoading(false); + isInitialLoad.current = false; } }, [id]); @@ -120,6 +127,19 @@ export default function MakerDetails() { }; const swapLiquidity = balances ? `${satsToBtc(balances.swap)} BTC` : "—"; + + if (loading) { + return ( +
+
+ ); + } + return (
diff --git a/frontend/app/routes/makerDetails/wallet.tsx b/frontend/app/routes/makerDetails/wallet.tsx index dff99c0..8c15015 100644 --- a/frontend/app/routes/makerDetails/wallet.tsx +++ b/frontend/app/routes/makerDetails/wallet.tsx @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback } from "react"; +import { Loader2 } from "lucide-react"; import { wallet, satsToBtc, btcToSats, type UtxoInfo } from "../../api"; interface Props { @@ -181,10 +182,16 @@ export default function Wallet({ id, onBalanceRefresh }: Props) {
{utxosLoading ? ( -
- {[...Array(3)].map((_, i) => ( -
- ))} +
+
+ + Fetching UTXOs from wallet… +
+
+ {[...Array(3)].map((_, i) => ( +
+ ))} +
) : utxos && utxos.length > 0 ? (
diff --git a/frontend/app/routes/makersetup.tsx b/frontend/app/routes/makersetup.tsx index 7a9236e..b727324 100644 --- a/frontend/app/routes/makersetup.tsx +++ b/frontend/app/routes/makersetup.tsx @@ -71,6 +71,37 @@ function isInfoLog(line: string): boolean { return levelMatch[1].toUpperCase() === "INFO"; } +/** Extract the latest meaningful progress detail from logs for user display */ +function latestProgressDetail(logs: string[]): string | null { + const patterns: [string, string][] = [ + ["maker server listening on port", "Maker server started"], + ["Successfully created fidelity bond", "Fidelity bond confirmed"], + ["No spendable UTXOs available", "No funds yet — waiting for deposit"], + ["Insufficient fund to create fidelity bond", "Insufficient funds for bond — waiting for deposit"], + ["Next sync in", "Waiting for next wallet sync cycle…"], + ["Synced & Saved", "Wallet synced and saved"], + ["Scanning completed", "Blockchain scan completed"], + ["Re-scanning Blockchain", "Re-scanning blockchain…"], + ["Sync Started for", "Syncing wallet with Bitcoin Core…"], + ["Sync at:----setup_fidelity_bond----", "Setting up fidelity bond…"], + ["Fidelity timelock", "Configuring fidelity bond timelock…"], + ["Fidelity value chosen", "Fidelity bond amount selected"], + ["No active Fidelity Bonds found", "No fidelity bonds found — creating one"], + ["Generated new Tor Hidden Service", "Generated new Tor hidden service"], + ["Generated existing Tor Hidden Service", "Tor hidden service restored"], + ["Starting Maker Server", "Starting maker server…"], + ["Selected 1 regular UTXOs", "Funds found — selecting UTXOs for bond"], + ["Coinselection", "Selecting coins for bond transaction…"], + ["Transaction seen in mempool", "Incoming transaction detected"], + ]; + for (const line of [...logs].reverse()) { + for (const [needle, detail] of patterns) { + if (line.includes(needle)) return detail; + } + } + return null; +} + // ─── Component ──────────────────────────────────────────────────────────────── export default function MakerSetup() { @@ -83,6 +114,7 @@ export default function MakerSetup() { const [minAmount, setMinAmount] = useState(null); const [copied, setCopied] = useState(false); const [errorMsg, setErrorMsg] = useState(null); + const [progressDetail, setProgressDetail] = useState(null); const logsEndRef = useRef(null); const pollRef = useRef | null>(null); @@ -142,6 +174,10 @@ export default function MakerSetup() { if (addr) setDepositAddress(addr); if (amt) setMinAmount(amt); + // Update progress detail from latest log + const detail = latestProgressDetail(next); + if (detail) setProgressDetail(detail); + // Advance stage based on log content if (fundsDetected(next)) setStage("creating_bond"); if (bondCreated(next) || makerLive(next)) setStage("live"); @@ -241,7 +277,7 @@ export default function MakerSetup() {
), title: "Starting Maker", - subtitle: "Initializing wallet and connecting to Bitcoin Core…", + subtitle: progressDetail ?? "Initializing wallet and connecting to Bitcoin Core…", color: "orange", }, awaiting_funds: { @@ -251,7 +287,7 @@ export default function MakerSetup() {
), title: "Deposit Required", - subtitle: "Send Bitcoin to this address to create your fidelity bond", + subtitle: progressDetail ?? "Send Bitcoin to this address to create your fidelity bond", color: "orange", }, creating_bond: { @@ -279,7 +315,7 @@ export default function MakerSetup() {
), title: "Creating Fidelity Bond", - subtitle: "Funds detected — waiting for confirmation and bond creation…", + subtitle: progressDetail ?? "Funds detected — waiting for confirmation and bond creation…", color: "blue", }, live: { @@ -533,8 +569,7 @@ export default function MakerSetup() { d="M4 12a8 8 0 018-8v8z" /> - Watching for incoming funds — this page will update - automatically + {progressDetail ?? "Watching for incoming funds — this page will update automatically"}
)} @@ -580,7 +615,7 @@ export default function MakerSetup() { d="M4 12a8 8 0 018-8v8z" /> - Waiting for block confirmation… + {progressDetail ?? "Waiting for block confirmation…"} )} From 3f1fa2d03da307f7a32f38689e8a569d60f99816 Mon Sep 17 00:00:00 2001 From: hulxv Date: Tue, 24 Mar 2026 09:31:38 +0200 Subject: [PATCH 2/4] fix: format --- frontend/app/api.ts | 5 ++++- frontend/app/components/LoadingState.tsx | 4 +--- frontend/app/routes/home.tsx | 4 ++++ frontend/app/routes/makersetup.tsx | 24 ++++++++++++++++++------ 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/frontend/app/api.ts b/frontend/app/api.ts index 7fe2ad7..2800e6f 100644 --- a/frontend/app/api.ts +++ b/frontend/app/api.ts @@ -159,7 +159,10 @@ async function request(path: string, options: RequestInit = {}): Promise { } catch (err) { clearTimeout(timer); if (err instanceof DOMException && err.name === "AbortError") { - throw new ApiError(0, `Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`); + throw new ApiError( + 0, + `Request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`, + ); } throw err; } finally { diff --git a/frontend/app/components/LoadingState.tsx b/frontend/app/components/LoadingState.tsx index 3d13a4a..cef680d 100644 --- a/frontend/app/components/LoadingState.tsx +++ b/frontend/app/components/LoadingState.tsx @@ -28,7 +28,5 @@ export default function LoadingState({ if (inline) return content; - return ( -
{content}
- ); + return
{content}
; } diff --git a/frontend/app/routes/home.tsx b/frontend/app/routes/home.tsx index 918c7a1..9508bb6 100644 --- a/frontend/app/routes/home.tsx +++ b/frontend/app/routes/home.tsx @@ -59,6 +59,10 @@ export default function Home() { setError(null); if (initial) setLoadingDetail("Fetching maker list…"); const list = await makers.list(); + if (initial) + setLoadingDetail( + `Loading details for ${list.length} maker${list.length !== 1 ? "s" : ""}…`, + ); const includeSwaps = forceSwapRefresh || lastSwapRefreshAt.current === 0 || diff --git a/frontend/app/routes/makersetup.tsx b/frontend/app/routes/makersetup.tsx index b727324..6d4f602 100644 --- a/frontend/app/routes/makersetup.tsx +++ b/frontend/app/routes/makersetup.tsx @@ -77,7 +77,10 @@ function latestProgressDetail(logs: string[]): string | null { ["maker server listening on port", "Maker server started"], ["Successfully created fidelity bond", "Fidelity bond confirmed"], ["No spendable UTXOs available", "No funds yet — waiting for deposit"], - ["Insufficient fund to create fidelity bond", "Insufficient funds for bond — waiting for deposit"], + [ + "Insufficient fund to create fidelity bond", + "Insufficient funds for bond — waiting for deposit", + ], ["Next sync in", "Waiting for next wallet sync cycle…"], ["Synced & Saved", "Wallet synced and saved"], ["Scanning completed", "Blockchain scan completed"], @@ -86,7 +89,10 @@ function latestProgressDetail(logs: string[]): string | null { ["Sync at:----setup_fidelity_bond----", "Setting up fidelity bond…"], ["Fidelity timelock", "Configuring fidelity bond timelock…"], ["Fidelity value chosen", "Fidelity bond amount selected"], - ["No active Fidelity Bonds found", "No fidelity bonds found — creating one"], + [ + "No active Fidelity Bonds found", + "No fidelity bonds found — creating one", + ], ["Generated new Tor Hidden Service", "Generated new Tor hidden service"], ["Generated existing Tor Hidden Service", "Tor hidden service restored"], ["Starting Maker Server", "Starting maker server…"], @@ -277,7 +283,8 @@ export default function MakerSetup() { ), title: "Starting Maker", - subtitle: progressDetail ?? "Initializing wallet and connecting to Bitcoin Core…", + subtitle: + progressDetail ?? "Initializing wallet and connecting to Bitcoin Core…", color: "orange", }, awaiting_funds: { @@ -287,7 +294,9 @@ export default function MakerSetup() { ), title: "Deposit Required", - subtitle: progressDetail ?? "Send Bitcoin to this address to create your fidelity bond", + subtitle: + progressDetail ?? + "Send Bitcoin to this address to create your fidelity bond", color: "orange", }, creating_bond: { @@ -315,7 +324,9 @@ export default function MakerSetup() { ), title: "Creating Fidelity Bond", - subtitle: progressDetail ?? "Funds detected — waiting for confirmation and bond creation…", + subtitle: + progressDetail ?? + "Funds detected — waiting for confirmation and bond creation…", color: "blue", }, live: { @@ -569,7 +580,8 @@ export default function MakerSetup() { d="M4 12a8 8 0 018-8v8z" /> - {progressDetail ?? "Watching for incoming funds — this page will update automatically"} + {progressDetail ?? + "Watching for incoming funds — this page will update automatically"} )} From f5fe86b518aef21c7c17d76d4d82a86e7133969a Mon Sep 17 00:00:00 2001 From: hulxv Date: Tue, 24 Mar 2026 09:40:21 +0200 Subject: [PATCH 3/4] comments --- frontend/app/api.ts | 1 - frontend/app/routes/makersetup.tsx | 58 +++++++++++++++--------------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/frontend/app/api.ts b/frontend/app/api.ts index 2800e6f..64d635d 100644 --- a/frontend/app/api.ts +++ b/frontend/app/api.ts @@ -157,7 +157,6 @@ async function request(path: string, options: RequestInit = {}): Promise { ...options, }); } catch (err) { - clearTimeout(timer); if (err instanceof DOMException && err.name === "AbortError") { throw new ApiError( 0, diff --git a/frontend/app/routes/makersetup.tsx b/frontend/app/routes/makersetup.tsx index 6d4f602..3773626 100644 --- a/frontend/app/routes/makersetup.tsx +++ b/frontend/app/routes/makersetup.tsx @@ -58,6 +58,28 @@ function fundsDetected(logs: string[]): boolean { l.includes("Coinselection"), ); } +/** Pattern → human-readable progress mappings */ +const progressPatterns: [string, string][] = [ + ["maker server listening on port", "Maker server started"], + ["Successfully created fidelity bond", "Fidelity bond confirmed"], + ["No spendable UTXOs available", "No funds yet — waiting for deposit"], + ["Insufficient fund to create fidelity bond", "Insufficient funds for bond — waiting for deposit"], + ["Next sync in", "Waiting for next wallet sync cycle…"], + ["Synced & Saved", "Wallet synced and saved"], + ["Scanning completed", "Blockchain scan completed"], + ["Re-scanning Blockchain", "Re-scanning blockchain…"], + ["Sync Started for", "Syncing wallet with Bitcoin Core…"], + ["Sync at:----setup_fidelity_bond----", "Setting up fidelity bond…"], + ["Fidelity timelock", "Configuring fidelity bond timelock…"], + ["Fidelity value chosen", "Fidelity bond amount selected"], + ["No active Fidelity Bonds found", "No fidelity bonds found — creating one"], + ["Generated new Tor Hidden Service", "Generated new Tor hidden service"], + ["Generated existing Tor Hidden Service", "Tor hidden service restored"], + ["Starting Maker Server", "Starting maker server…"], + ["Selected 1 regular UTXOs", "Funds found — selecting UTXOs for bond"], + ["Coinselection", "Selecting coins for bond transaction…"], + ["Transaction seen in mempool", "Incoming transaction detected"], +]; const LOG_LEVEL_RE = /(?:^|\s|\[)(INFO|WARN|ERROR|DEBUG|TRACE)(?=$|\s|:|\])/i; @@ -73,33 +95,13 @@ function isInfoLog(line: string): boolean { /** Extract the latest meaningful progress detail from logs for user display */ function latestProgressDetail(logs: string[]): string | null { - const patterns: [string, string][] = [ - ["maker server listening on port", "Maker server started"], - ["Successfully created fidelity bond", "Fidelity bond confirmed"], - ["No spendable UTXOs available", "No funds yet — waiting for deposit"], - [ - "Insufficient fund to create fidelity bond", - "Insufficient funds for bond — waiting for deposit", - ], - ["Next sync in", "Waiting for next wallet sync cycle…"], - ["Synced & Saved", "Wallet synced and saved"], - ["Scanning completed", "Blockchain scan completed"], - ["Re-scanning Blockchain", "Re-scanning blockchain…"], - ["Sync Started for", "Syncing wallet with Bitcoin Core…"], - ["Sync at:----setup_fidelity_bond----", "Setting up fidelity bond…"], - ["Fidelity timelock", "Configuring fidelity bond timelock…"], - ["Fidelity value chosen", "Fidelity bond amount selected"], - [ - "No active Fidelity Bonds found", - "No fidelity bonds found — creating one", - ], - ["Generated new Tor Hidden Service", "Generated new Tor hidden service"], - ["Generated existing Tor Hidden Service", "Tor hidden service restored"], - ["Starting Maker Server", "Starting maker server…"], - ["Selected 1 regular UTXOs", "Funds found — selecting UTXOs for bond"], - ["Coinselection", "Selecting coins for bond transaction…"], - ["Transaction seen in mempool", "Incoming transaction detected"], - ]; + for (const line of [...logs].reverse()) { + for (const [needle, detail] of progressPatterns) { + if (line.includes(needle)) return detail; + } + } + return null; +} for (const line of [...logs].reverse()) { for (const [needle, detail] of patterns) { if (line.includes(needle)) return detail; @@ -182,7 +184,7 @@ export default function MakerSetup() { // Update progress detail from latest log const detail = latestProgressDetail(next); - if (detail) setProgressDetail(detail); + setProgressDetail(detail); // Advance stage based on log content if (fundsDetected(next)) setStage("creating_bond"); From cd01239d7e8a6912eb4825b26335bcb8679f8979 Mon Sep 17 00:00:00 2001 From: hulxv Date: Tue, 24 Mar 2026 22:07:17 +0200 Subject: [PATCH 4/4] fix --- frontend/app/routes/home.tsx | 8 +++++--- frontend/app/routes/makersetup.tsx | 12 ++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/frontend/app/routes/home.tsx b/frontend/app/routes/home.tsx index 9508bb6..6b3dcd6 100644 --- a/frontend/app/routes/home.tsx +++ b/frontend/app/routes/home.tsx @@ -50,9 +50,8 @@ export default function Home() { const lastSwapRefreshAt = useRef(0); async function loadMakers(forceSwapRefresh = false) { - const isInitialLoad = useRef(true); + const isInitialLoad = useRef(true); - async function loadMakers() { const initial = isInitialLoad.current; if (!initial) setRefreshing(true); try { @@ -67,7 +66,10 @@ export default function Home() { forceSwapRefresh || lastSwapRefreshAt.current === 0 || Date.now() - lastSwapRefreshAt.current >= SWAP_HISTORY_REFRESH_MS; - if (initial) setLoadingDetail(`Loading details for ${list.length} maker${list.length !== 1 ? "s" : ""}…`); + if (initial) + setLoadingDetail( + `Loading details for ${list.length} maker${list.length !== 1 ? "s" : ""}…`, + ); const rows = await Promise.all( list.map(async ({ id }): Promise => { const requests = [ diff --git a/frontend/app/routes/makersetup.tsx b/frontend/app/routes/makersetup.tsx index 3773626..3027636 100644 --- a/frontend/app/routes/makersetup.tsx +++ b/frontend/app/routes/makersetup.tsx @@ -63,7 +63,10 @@ const progressPatterns: [string, string][] = [ ["maker server listening on port", "Maker server started"], ["Successfully created fidelity bond", "Fidelity bond confirmed"], ["No spendable UTXOs available", "No funds yet — waiting for deposit"], - ["Insufficient fund to create fidelity bond", "Insufficient funds for bond — waiting for deposit"], + [ + "Insufficient fund to create fidelity bond", + "Insufficient funds for bond — waiting for deposit", + ], ["Next sync in", "Waiting for next wallet sync cycle…"], ["Synced & Saved", "Wallet synced and saved"], ["Scanning completed", "Blockchain scan completed"], @@ -102,13 +105,6 @@ function latestProgressDetail(logs: string[]): string | null { } return null; } - for (const line of [...logs].reverse()) { - for (const [needle, detail] of patterns) { - if (line.includes(needle)) return detail; - } - } - return null; -} // ─── Component ────────────────────────────────────────────────────────────────