Skip to content

fix: increase responsiveness#54

Open
hulxv wants to merge 5 commits into
mainfrom
fix/increase-responsive
Open

fix: increase responsiveness#54
hulxv wants to merge 5 commits into
mainfrom
fix/increase-responsive

Conversation

@hulxv
Copy link
Copy Markdown
Collaborator

@hulxv hulxv commented Mar 24, 2026

Close #37

Summary by CodeRabbit

  • New Features

    • Consistent loading component with spinner, message, optional detail and inline/full-page modes
    • More informative, stage-wise loading states across Home, Maker Details, Wallet UTXOs, and Setup pages
    • Real-time setup progress messages surfaced from logs
    • “Refreshing…” indicator during background data refreshes
  • Bug Fixes

    • Global 30-second request timeout to prevent hung requests and improve reliability

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 24, 2026

📝 Walkthrough

Walkthrough

Adds a 30s request timeout to the shared API helper and introduces staged, user-facing loading indicators (via a new LoadingState component and log-derived progressDetail) to replace blank loading screens and distinguish initial vs. background refresh loading states.

Changes

Cohort / File(s) Summary
Request Timeout Handling
frontend/app/api.ts
Adds REQUEST_TIMEOUT_MS = 30000, uses AbortController + setTimeout in the shared request helper, passes controller.signal to fetch, clears timer in finally, and throws an ApiError with status 0 on timeout.
Loading State Component
frontend/app/components/LoadingState.tsx
New default-exported LoadingState React component showing Loader2, a primary message, optional pulsing detail, and inline vs full-height rendering.
Home & Maker Details Loading
frontend/app/routes/home.tsx, frontend/app/routes/makerDetails/index.tsx, frontend/app/routes/makerDetails/wallet.tsx
Introduces loadingDetail state and isInitialLoad ref to differentiate initial full-page loading vs background refresh; initial loads render LoadingState with staged messages; background refresh sets refreshing and shows a small “Refreshing…” indicator; wallet UTXO loading shows spinner + status text.
Maker Setup Progress
frontend/app/routes/makersetup.tsx
Adds progressPatterns and latestProgressDetail(logs) helper plus progressDetail state; UI subtitles and inline status text now prefer dynamic progressDetail with fallback to previous static copy.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • keraliss

Poem

🐰 I hop where spinners used to hide,
Messages bloom and timeouts guard the ride,
Thirty seconds keeps the clock in tune,
Progress whispers under a brighter moon,
Hooray — no more blank-screen fright!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'fix: increase responsiveness' is vague and does not specifically describe the main changes (timeout enforcement, loading state improvements, and progress messaging). Consider a more specific title such as 'fix: add request timeout and improve loading UX with progress messages' to clearly reflect the implementation approach.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed The PR addresses all key requirements from issue #37: adds request timeout to prevent UI freezing, provides contextual progress messages during long operations, and improves loading UX across multiple pages (home, makerDetails, wallet, makersetup).
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #37 objectives: timeout enforcement, LoadingState component, progress messaging, and loading state improvements are all in scope for increasing responsiveness.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/increase-responsive

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/app/routes/home.tsx (1)

79-83: 🧹 Nitpick | 🔵 Trivial

Consider wrapping loadMakers in useCallback for safer effect dependencies.

The loadMakers function is referenced in useEffect but not included in the dependency array. While this works because the function doesn't depend on props/state that change independently, the pattern can become fragile if the function is modified later.

♻️ Safer pattern with useCallback
+import { useEffect, useRef, useState, useCallback } from "react";
-import { useEffect, useRef, useState } from "react";
...
-  async function loadMakers() {
+  const loadMakers = useCallback(async () => {
     const initial = isInitialLoad.current;
     ...
-  }
+  }, []);

   useEffect(() => {
     loadMakers();
     const interval = setInterval(loadMakers, 15_000);
     return () => clearInterval(interval);
-  }, []);
+  }, [loadMakers]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/routes/home.tsx` around lines 79 - 83, Wrap the loadMakers
function in useCallback to stabilize its identity and then include that callback
in the useEffect dependency array; specifically change the current loadMakers
implementation to a memoized callback (useCallback(() => { ... }, [/* any real
deps */])) and update the useEffect to depend on loadMakers so the setInterval
uses the stable function and the cleanup/reauthorization remains correct (adjust
any internal dependencies of loadMakers as needed).
frontend/app/routes/makerDetails/index.tsx (1)

65-73: ⚠️ Potential issue | 🟡 Minor

Loading detail message is misleading.

The message "Resolving Tor address…" is set but the subsequent torAddress and dataDir fetches are fire-and-forget (not awaited). The loading state will immediately transition to complete while these calls are still in flight, making the detail message inaccurate.

Consider either:

  1. Awaiting these calls before clearing loading state, or
  2. Removing this detail message since it doesn't reflect actual loading behavior
🔧 Option 1: Await the calls
       if (initial) setLoadingDetail("Resolving Tor address…");
-      monitoring
-        .torAddress(id)
-        .then(setTorAddress)
-        .catch(() => {});
-      monitoring
-        .dataDir(id)
-        .then(setDataDir)
-        .catch(() => {});
+      await Promise.allSettled([
+        monitoring.torAddress(id).then(setTorAddress),
+        monitoring.dataDir(id).then(setDataDir),
+      ]);
     } catch (e) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/routes/makerDetails/index.tsx` around lines 65 - 73, The loading
message "Resolving Tor address…" is misleading because you call
monitoring.torAddress(id) and monitoring.dataDir(id) as fire-and-forget
promises; update the logic in the maker details loader (where setLoadingDetail,
monitoring.torAddress, monitoring.dataDir, and initial are used) to either await
both calls (e.g., Promise.all on monitoring.torAddress(id) and
monitoring.dataDir(id)) before clearing the loading state so the message
reflects real work, or remove the setLoadingDetail("Resolving Tor address…")
call entirely if you want to keep the fetches non-blocking; ensure setTorAddress
and setDataDir still get invoked with resolved values when choosing the await
approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/api.ts`:
- Around line 152-160: Remove the redundant clearTimeout(timer) call from the
catch block in frontend/app/api.ts: keep the existing AbortError detection and
rethrow logic (throw new ApiError(...) and throw err) but rely on the finally
block to clear the timer; ensure only the finally block contains
clearTimeout(timer) so the timer is always cleared once.

In `@frontend/app/routes/makersetup.tsx`:
- Around line 163-165: The current update uses latestProgressDetail(next) and
only calls setProgressDetail(detail) when detail is truthy, which leaves
progressDetail stale if no pattern matches; change the logic around
latestProgressDetail()/setProgressDetail so you always call setProgressDetail
with the matcher result (explicitly set to null when no match) — update the code
that uses latestProgressDetail(next) and setProgressDetail to set null when
there is no detail (so PROGRESS_PATTERNS/ stageConfig fallback shows) and ensure
symbols referenced are latestProgressDetail, setProgressDetail, progressDetail,
PROGRESS_PATTERNS and stageConfig.
- Around line 61-90: Hoist the static patterns array out of latestProgressDetail
to module scope (e.g., const LATEST_PROGRESS_PATTERNS: [string,string][] =
[...]) so it is allocated once; then update latestProgressDetail to iterate over
LATEST_PROGRESS_PATTERNS instead of recreating patterns on every call,
preserving the same entries, order and typing and keeping the constant
immutable.

---

Outside diff comments:
In `@frontend/app/routes/home.tsx`:
- Around line 79-83: Wrap the loadMakers function in useCallback to stabilize
its identity and then include that callback in the useEffect dependency array;
specifically change the current loadMakers implementation to a memoized callback
(useCallback(() => { ... }, [/* any real deps */])) and update the useEffect to
depend on loadMakers so the setInterval uses the stable function and the
cleanup/reauthorization remains correct (adjust any internal dependencies of
loadMakers as needed).

In `@frontend/app/routes/makerDetails/index.tsx`:
- Around line 65-73: The loading message "Resolving Tor address…" is misleading
because you call monitoring.torAddress(id) and monitoring.dataDir(id) as
fire-and-forget promises; update the logic in the maker details loader (where
setLoadingDetail, monitoring.torAddress, monitoring.dataDir, and initial are
used) to either await both calls (e.g., Promise.all on monitoring.torAddress(id)
and monitoring.dataDir(id)) before clearing the loading state so the message
reflects real work, or remove the setLoadingDetail("Resolving Tor address…")
call entirely if you want to keep the fetches non-blocking; ensure setTorAddress
and setDataDir still get invoked with resolved values when choosing the await
approach.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 01245abf-4e3e-45da-b383-d47dca83d415

📥 Commits

Reviewing files that changed from the base of the PR and between a9a0314 and 874baaf.

📒 Files selected for processing (6)
  • frontend/app/api.ts
  • frontend/app/components/LoadingState.tsx
  • frontend/app/routes/home.tsx
  • frontend/app/routes/makerDetails/index.tsx
  • frontend/app/routes/makerDetails/wallet.tsx
  • frontend/app/routes/makersetup.tsx

Comment thread frontend/app/api.ts
Comment thread frontend/app/routes/makersetup.tsx
Comment thread frontend/app/routes/makersetup.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
frontend/app/routes/makersetup.tsx (2)

64-90: 🧹 Nitpick | 🔵 Trivial

Consider hoisting the patterns array to module scope.

The patterns array is recreated on every call to latestProgressDetail. Since it's static, moving it outside the function avoids unnecessary allocations during frequent log streaming.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/routes/makersetup.tsx` around lines 64 - 90, Hoist the static
patterns array out of the latestProgressDetail function to module scope to avoid
reallocating it on every call: create a top-level const (e.g., PATTERNS or
patterns) with the same type annotation ([string,string][]) and move the current
array literal there, then update latestProgressDetail to reference that
top-level symbol instead of recreating the array; ensure you keep the same
entries and type so callers and TypeScript types remain unchanged.

170-172: ⚠️ Potential issue | 🟡 Minor

progressDetail is never reset to null once set.

The if (detail) guard prevents the state from being cleared when no patterns match subsequent log batches. This can result in stale progress messages persisting across stage transitions.

🛠️ Suggested fix
             // Update progress detail from latest log
             const detail = latestProgressDetail(next);
-            if (detail) setProgressDetail(detail);
+            setProgressDetail(detail);

This allows progressDetail to reset to null when no patterns match, enabling the fallback text in stageConfig to display.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/routes/makersetup.tsx` around lines 170 - 172, progressDetail
isn't reset because the code only calls setProgressDetail when
latestProgressDetail(next) returns a truthy value; change the logic so
setProgressDetail is always called with the function result (or explicitly null)
to clear stale messages. Specifically, replace the guarded update around
latestProgressDetail(next) so that setProgressDetail(detail) (or
setProgressDetail(detail ?? null)) is invoked even when detail is falsy,
referencing latestProgressDetail and setProgressDetail in makersetup.tsx so the
fallback text in stageConfig can display when no pattern matches.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@frontend/app/routes/makersetup.tsx`:
- Around line 64-90: Hoist the static patterns array out of the
latestProgressDetail function to module scope to avoid reallocating it on every
call: create a top-level const (e.g., PATTERNS or patterns) with the same type
annotation ([string,string][]) and move the current array literal there, then
update latestProgressDetail to reference that top-level symbol instead of
recreating the array; ensure you keep the same entries and type so callers and
TypeScript types remain unchanged.
- Around line 170-172: progressDetail isn't reset because the code only calls
setProgressDetail when latestProgressDetail(next) returns a truthy value; change
the logic so setProgressDetail is always called with the function result (or
explicitly null) to clear stale messages. Specifically, replace the guarded
update around latestProgressDetail(next) so that setProgressDetail(detail) (or
setProgressDetail(detail ?? null)) is invoked even when detail is falsy,
referencing latestProgressDetail and setProgressDetail in makersetup.tsx so the
fallback text in stageConfig can display when no pattern matches.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2132149f-6d6f-4983-af75-6fc1e691c53b

📥 Commits

Reviewing files that changed from the base of the PR and between 874baaf and 588b7cf.

📒 Files selected for processing (5)
  • frontend/app/api.ts
  • frontend/app/components/LoadingState.tsx
  • frontend/app/routes/home.tsx
  • frontend/app/routes/makerDetails/index.tsx
  • frontend/app/routes/makersetup.tsx

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 24, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

@hulxv hulxv force-pushed the fix/increase-responsive branch from 6312f16 to cd01239 Compare March 24, 2026 20:08
@hulxv hulxv requested a review from keraliss March 27, 2026 09:30
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/api.ts`:
- Around line 194-217: The current global REQUEST_TIMEOUT_MS used by request()
causes ambiguous failures for non-idempotent endpoints (e.g., wallet.send(),
makers.start(), bitcoind.start()); change request(path, options) to accept a
per-call timeout (e.g., options.timeoutMs) that overrides REQUEST_TIMEOUT_MS,
use that value to set the AbortController timer, and preserve existing behavior
when not provided; for known long-running or write endpoints update their
callers to pass longer timeouts or implement idempotency tokens / client-side
deduplication or status-polling (e.g., add idempotency header and/or a polling
flow) so retries don’t create duplicate operations.

In `@frontend/app/components/LoadingState.tsx`:
- Around line 17-31: The LoadingState component currently renders the visual
loader and the optional detail string but does not expose these updates to
assistive tech; update the rendered DOM to include an accessible live region by
adding role="status" and aria-live="polite" to the container that wraps the
textual content (e.g., the div that contains the <p> elements or the parent
"content" wrapper used when inline is true), ensuring the detail text updates
are announced by screen readers while preserving the existing structure and
inline behavior.

In `@frontend/app/routes/home.tsx`:
- Around line 56-60: The useRef call must be moved out of the async helper to
component scope: remove the `const isInitialLoad = useRef(true)` from inside the
`loadMakers` function and instead declare `const isInitialLoad = useRef(true)`
once at the component level (immediately after line 54), then inside
`loadMakers` read `const initial = isInitialLoad.current` and later set
`isInitialLoad.current = false` where the code currently updates it (around the
existing line ~140); this preserves the persistent ref across invocations so
`setRefreshing(true)` executes correctly.

In `@frontend/app/routes/makersetup.tsx`:
- Around line 106-114: latestProgressDetail is never used so progressDetail
remains null; call setProgressDetail(latestProgressDetail(...)) when logs are
initialized and whenever logs change. Specifically, after computing or loading
existingLogs (or the initial logs state) invoke
setProgressDetail(latestProgressDetail(existingLogs)), and inside the logs
update/append handler (where you receive next logs) compute const detail =
latestProgressDetail(next) and call setProgressDetail(detail) so progressDetail
is populated whenever logs change; refer to the functions latestProgressDetail
and setProgressDetail and the log variables existingLogs/next in your updates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 82fa306b-7685-4507-b9fd-59c89f2e4fb0

📥 Commits

Reviewing files that changed from the base of the PR and between 588b7cf and 7378340.

📒 Files selected for processing (6)
  • frontend/app/api.ts
  • frontend/app/components/LoadingState.tsx
  • frontend/app/routes/home.tsx
  • frontend/app/routes/makerDetails/index.tsx
  • frontend/app/routes/makerDetails/wallet.tsx
  • frontend/app/routes/makersetup.tsx

Comment thread frontend/app/api.ts
Comment on lines +194 to +217
const REQUEST_TIMEOUT_MS = 30_000;

async function request<T>(path: string, options: RequestInit = {}): Promise<T> {
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) {
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);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 'Shared timeout implementation:\n'
sed -n '194,217p' frontend/app/api.ts
printf '\nWrappers and affected mutating endpoints:\n'
sed -n '250,381p' frontend/app/api.ts | rg -n 'function (post|put|del)|=>\s*(post|put|del)\('

Repository: citadel-tech/maker-dasboard

Length of output: 1450


🏁 Script executed:

wc -l frontend/app/api.ts

Repository: citadel-tech/maker-dasboard

Length of output: 94


🏁 Script executed:

cat -n frontend/app/api.ts | head -250

Repository: citadel-tech/maker-dasboard

Length of output: 7443


🏁 Script executed:

cat -n frontend/app/api.ts | tail -144

Repository: citadel-tech/maker-dasboard

Length of output: 6359


🏁 Script executed:

cd frontend && npx prettier --write . && npm run build

Repository: citadel-tech/maker-dasboard

Length of output: 1477


🏁 Script executed:

# Check if there are any retry mechanisms, idempotency keys, or status polling patterns elsewhere in the codebase
rg -i "retry|idempotent|idempotency|polling|debounce" frontend/app --type ts --type tsx -A 2 -B 2

Repository: citadel-tech/maker-dasboard

Length of output: 98


🏁 Script executed:

rg -i "retry|idempotent|idempotency|polling|debounce" frontend/app --type ts -A 2 -B 2

Repository: citadel-tech/maker-dasboard

Length of output: 1041


🏁 Script executed:

# Check how mutating endpoints are called in components - look for error handling patterns
rg "wallet\.send|wallet\.sync|makers\.start|makers\.stop|makers\.delete|bitcoind\.start|bitcoind\.stop" frontend/app --type ts -B 3 -A 5

Repository: citadel-tech/maker-dasboard

Length of output: 6601


A blanket 30s abort makes write requests ambiguous.

request() backs all post/put/del operations, so timeout affects wallet.send(), makers.start(), bitcoind.start(), and other non-idempotent writes. After 30s the browser stops waiting, but the backend may still complete the operation. The error handler shows a timeout message, but there is no status polling or idempotency mechanism to confirm the operation failed. Users can manually retry, creating duplicate sends or state inconsistency.

Make timeouts configurable per endpoint. For long-running or non-idempotent operations, either implement idempotency tokens and client-side deduplication, or use status polling to confirm operation completion before retrying.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/api.ts` around lines 194 - 217, The current global
REQUEST_TIMEOUT_MS used by request() causes ambiguous failures for
non-idempotent endpoints (e.g., wallet.send(), makers.start(),
bitcoind.start()); change request(path, options) to accept a per-call timeout
(e.g., options.timeoutMs) that overrides REQUEST_TIMEOUT_MS, use that value to
set the AbortController timer, and preserve existing behavior when not provided;
for known long-running or write endpoints update their callers to pass longer
timeouts or implement idempotency tokens / client-side deduplication or
status-polling (e.g., add idempotency header and/or a polling flow) so retries
don’t create duplicate operations.

Comment on lines +17 to +31
const content = (
<div className="flex flex-col items-center gap-3">
<Loader2 className="w-6 h-6 text-orange-500 animate-spin" />
<div className="text-center">
<p className="text-gray-300 text-sm font-medium">{message}</p>
{detail && (
<p className="text-gray-500 text-xs mt-1 animate-pulse">{detail}</p>
)}
</div>
</div>
);

if (inline) return content;

return <div className="flex items-center justify-center h-64">{content}</div>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Expose the loader as a live status region.

The staged detail text is a key part of this PR, but it is only visual right now. Without role="status" and aria-live="polite", screen readers will miss these loading-state updates.

♿ Suggested fix
   const content = (
-    <div className="flex flex-col items-center gap-3">
-      <Loader2 className="w-6 h-6 text-orange-500 animate-spin" />
+    <div
+      className="flex flex-col items-center gap-3"
+      role="status"
+      aria-live="polite"
+      aria-atomic="true"
+    >
+      <Loader2
+        className="w-6 h-6 text-orange-500 animate-spin"
+        aria-hidden="true"
+      />
       <div className="text-center">
         <p className="text-gray-300 text-sm font-medium">{message}</p>
         {detail && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/components/LoadingState.tsx` around lines 17 - 31, The
LoadingState component currently renders the visual loader and the optional
detail string but does not expose these updates to assistive tech; update the
rendered DOM to include an accessible live region by adding role="status" and
aria-live="polite" to the container that wraps the textual content (e.g., the
div that contains the <p> elements or the parent "content" wrapper used when
inline is true), ensuring the detail text updates are announced by screen
readers while preserving the existing structure and inline behavior.

Comment on lines 56 to +60
async function loadMakers(forceSwapRefresh = false) {
const isInitialLoad = useRef(true);

const initial = isInitialLoad.current;
if (!initial) setRefreshing(true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '56,60p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 249


🏁 Script executed:

#!/bin/bash
# Get the full structure of the component to understand context
head -80 frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 2639


🏁 Script executed:

#!/bin/bash
# Search for where loadMakers is called
rg "loadMakers" frontend/app/routes/home.tsx -n -A 2 -B 2

Repository: citadel-tech/maker-dasboard

Length of output: 659


🏁 Script executed:

#!/bin/bash
# Verify the suggested fix location by checking the component state declarations
sed -n '42,56p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 777


🏁 Script executed:

#!/bin/bash
# Check if there are any other hook-related issues in the file
# Look for useEffect, useState, useRef, useCallback, useMemo, useContext, useReducer
rg "use[A-Z]\w+\(" frontend/app/routes/home.tsx -n | head -30

Repository: citadel-tech/maker-dasboard

Length of output: 288


🏁 Script executed:

#!/bin/bash
# Verify the exact behavior by checking what happens with isInitialLoad
# Look at how isInitialLoad is used in loadMakers
sed -n '56,75p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 828


🏁 Script executed:

#!/bin/bash
# Check if isInitialLoad is referenced anywhere else in the file
rg "isInitialLoad" frontend/app/routes/home.tsx -n

Repository: citadel-tech/maker-dasboard

Length of output: 200


🏁 Script executed:

#!/bin/bash
# Check context around where isInitialLoad is set to false (line 140)
sed -n '135,145p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 327


🏁 Script executed:

#!/bin/bash
# Let's verify the complete context of the useEffect and understand the lifecycle
sed -n '144,160p' frontend/app/routes/home.tsx

Repository: citadel-tech/maker-dasboard

Length of output: 607


🏁 Script executed:

#!/bin/bash
# Final verification: ensure the entire loadMakers function is properly scoped
# and confirm the hook violation by checking ESLint or similar would flag this
wc -l frontend/app/routes/home.tsx && echo "---" && sed -n '52,142p' frontend/app/routes/home.tsx | head -50

Repository: citadel-tech/maker-dasboard

Length of output: 2104


Move useRef(true) to component scope.

Line 57 violates React's Rules of Hooks: useRef is called inside the async loadMakers helper function instead of at component level. This breaks the persistent state—each call recreates the ref with true, so isInitialLoad.current = false at line 140 is lost on the next invocation, and the setRefreshing(true) path never executes correctly.

Declare the ref once at component scope (after line 54) and read/update it inside loadMakers.

🔧 Suggested fix
 export default function Home() {
   const [makerRows, setMakerRows] = useState<MakerRow[]>([]);
   const [loading, setLoading] = useState(true);
   const [refreshing, setRefreshing] = useState(false);
   const [loadingDetail, setLoadingDetail] = useState<string>("");
   const [error, setError] = useState<string | null>(null);
   const [pending, setPending] = useState<Set<string>>(new Set());
   const swapHistoryCache = useRef<Record<string, UtxoInfo[]>>({});
   const swapReportCache = useRef<Record<string, SwapReportDto[]>>({});
   const lastSwapRefreshAt = useRef(0);
+  const isInitialLoad = useRef(true);

   async function loadMakers(forceSwapRefresh = false) {
-    const isInitialLoad = useRef(true);
-
     const initial = isInitialLoad.current;
🧰 Tools
🪛 GitHub Actions: Lint

[error] 57-57: ESLint (react-hooks/rules-of-hooks): React Hook "useRef" is called in function "loadMakers" that is neither a React function component nor a custom React Hook. Function/component names must start uppercase; Hook names must start with "use".

🪛 GitHub Check: frontend

[failure] 57-57:
React Hook "useRef" is called in function "loadMakers" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/routes/home.tsx` around lines 56 - 60, The useRef call must be
moved out of the async helper to component scope: remove the `const
isInitialLoad = useRef(true)` from inside the `loadMakers` function and instead
declare `const isInitialLoad = useRef(true)` once at the component level
(immediately after line 54), then inside `loadMakers` read `const initial =
isInitialLoad.current` and later set `isInitialLoad.current = false` where the
code currently updates it (around the existing line ~140); this preserves the
persistent ref across invocations so `setRefreshing(true)` executes correctly.

Comment on lines +106 to +114
/** Extract the latest meaningful progress detail from logs for user display */
function latestProgressDetail(logs: string[]): string | null {
for (const line of [...logs].reverse()) {
for (const [needle, detail] of progressPatterns) {
if (line.includes(needle)) return detail;
}
}
return null;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

progressDetail is never populated.

latestProgressDetail() and setProgressDetail() are both introduced here, but nothing calls them in this file. That means progressDetail stays null, so all of the new progressDetail ?? ... branches keep rendering the fallback copy and the log-derived status text never appears.

💡 Minimal wiring
setProgressDetail(latestProgressDetail(existingLogs));

...

const detail = latestProgressDetail(next);
setProgressDetail(detail);

Also applies to: 142-142

🧰 Tools
🪛 GitHub Check: frontend

[failure] 107-107:
'latestProgressDetail' is defined but never used

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/routes/makersetup.tsx` around lines 106 - 114,
latestProgressDetail is never used so progressDetail remains null; call
setProgressDetail(latestProgressDetail(...)) when logs are initialized and
whenever logs change. Specifically, after computing or loading existingLogs (or
the initial logs state) invoke
setProgressDetail(latestProgressDetail(existingLogs)), and inside the logs
update/append handler (where you receive next logs) compute const detail =
latestProgressDetail(next) and call setProgressDetail(detail) so progressDetail
is populated whenever logs change; refer to the functions latestProgressDetail
and setProgressDetail and the log variables existingLogs/next in your updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Increase responsiveness

2 participants