Skip to content

feature:Refactor public/index.html into modular frontend files#61

Open
joshkirojohn212-jpg wants to merge 1 commit into
Flamki:masterfrom
joshkirojohn212-jpg:stellarmind7
Open

feature:Refactor public/index.html into modular frontend files#61
joshkirojohn212-jpg wants to merge 1 commit into
Flamki:masterfrom
joshkirojohn212-jpg:stellarmind7

Conversation

@joshkirojohn212-jpg
Copy link
Copy Markdown

@joshkirojohn212-jpg joshkirojohn212-jpg commented May 28, 2026

closes #7

Summary

Describe what changed and why.

Validation

  • Ran relevant local command(s)
  • Verified no secrets were added
  • Updated docs if behavior changed

Checklist

  • No .env or private keys committed
  • No sensitive data in screenshots/logs
  • README.md and examples still accurate

Summary by CodeRabbit

  • Documentation

    • Added comprehensive frontend architecture and refactoring documentation guides.
  • Refactor

    • Reorganized frontend codebase into modular structure with separated concerns. No user-visible changes or feature impacts.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

@bitstarkbridge is attempting to deploy a commit to the flamki's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

📝 Walkthrough

Walkthrough

A monolithic ~1,300-line public/index.html is refactored into modular CSS stylesheets and JavaScript modules organized by concern: CSS variables, layout, components, sidebar, and pages; JavaScript for navigation, budget, agents, wallet, SSE events, rendering, orchestration, pages, and initialization. The HTML is reduced to a semantic shell with external resource links, accompanied by comprehensive architecture documentation.

Changes

Modular Frontend Architecture

Layer / File(s) Summary
CSS Foundation
public/assets/css/variables.css
Establishes root color, gradient, shadow, radius, and spacing variables; applies global box-sizing reset and body base styling for layout and typography.
Layout System
public/assets/css/layout.css
Defines main scrollable container, sticky topbar with pills/title, content grid areas, page visibility toggle pattern, and responsive breakpoints at 1024px/768px adjusting sidebar and grid columns.
Component Styling
public/assets/css/components.css, sidebar.css, pages.css
Styles cards, buttons, inputs, wallet cards, chips, and empty states; full sidebar with logo, navigation, agent list, and footer; page layouts for task input, feed, result panel, agent registry, API status, and transaction table.
Navigation and Budget
public/assets/js/navigation.js, budget.js
Navigation maps page ids to titles and shows/hides pages with sidebar active state; budget slider wires input event to format and display USDC values.
Agent and Wallet Data
public/assets/js/agents.js, wallet.js
Agent module fetches /api/agents and renders sidebar/page cards; wallet module fetches /api/wallet/balances and displays truncated addresses with USDC/XLM priority fallback.
Live Events and Rendering
public/assets/js/sse.js, rendering.js
SSE connects to /api/events, parses orchestration events, and renders per-type feed items with icons/colors/labels; rendering manages result panel state, progress tracking, log history, live chips, and final answer display with per-agent outputs.
Orchestration and Pages
public/assets/js/orchestration.js, pages.js
Orchestration manages run lifecycle (start, track events, POST to /api/orchestrate, render results); pages module provides status/transaction page loaders and API key configuration with validation.
App Initialization
public/assets/js/init.js
Wires up DOM-ready bootstrap: initializes budget slider, loads agents/wallets, connects SSE, schedules 15s wallet refresh, and attaches Enter-key handler to trigger orchestration.
HTML Refactoring
public/index.html
Removes inline <style> and <script> blocks; links external CSS (variables, sidebar, layout, components, pages) and JavaScript files (navigation, budget, agents, wallet, sse, rendering, orchestration, pages, init).
Architecture Documentation
FRONTEND_STRUCTURE.md, README.md, REFACTORING_SUMMARY.md
Comprehensive docs describing modular file organization, CSS layering, JS module dependencies, API/SSE endpoints, responsive design, and migration notes with before/after comparison and deployment readiness.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A monolith once stood so tall and wide,
Now split to parts with CSS pride!
Each module neat, no tangled maze,
The frontend gleams in modular ways. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: refactoring a monolithic HTML file into modular frontend files, which is the core objective of the PR.
Linked Issues check ✅ Passed The PR fully addresses all requirements from issue #7: CSS/JS split into modular files by concern, HTML reduced to semantic shell with external links, no UI regressions across core pages (Dashboard, Agents, Transactions), and README updated with frontend structure documentation.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the refactor: CSS modules, JS modules organized by concern, HTML shell, documentation updates, and supporting architecture documentation. No unrelated changes detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


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.

@drips-wave
Copy link
Copy Markdown

drips-wave Bot commented May 28, 2026

@joshkirojohn212-jpg Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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: 10

🧹 Nitpick comments (8)
public/assets/js/wallet.js (1)

6-33: ⚡ Quick win

Balance/address rendering uses textContent — safe. One note: silent error handling.

Logic is sound and DOM writes use textContent. As with the agents module, the empty catch {} (Line 32) silently discards fetch/parse failures, so a wallet API outage leaves balances blank with no diagnostic. Consider logging the error.

♻️ Example
-  } catch {}
+  } catch (err) {
+    console.error('Failed to load wallet balances', err);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/wallet.js` around lines 6 - 33, The empty catch in
loadWallets() swallows fetch/JSON errors leaving the UI blank and providing no
diagnostics; update the catch block in loadWallets to log the caught error
(e.g., console.error or a provided logger) with a clear message like
"loadWallets failed" and the error object so failures fetching
/api/wallet/balances or JSON parsing are visible when balEl/addrEl/mEl remain
unset.
public/assets/js/agents.js (1)

23-23: ⚡ Quick win

Silent catch {} hides load/render failures.

Empty catch blocks swallow network and rendering errors with no console output or user-facing fallback, making field debugging hard and leaving stale/empty UI without explanation. At minimum log the error; ideally surface a fallback state.

♻️ Example
-  } catch {}
+  } catch (err) {
+    console.error('Failed to load agents', err);
+  }

Also applies to: 47-47

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/agents.js` at line 23, Replace the silent catch blocks (the
bare "catch {}" occurrences) with error handlers that log the error and surface
a fallback UI; e.g., change to "catch (err) { console.error('agents.js
load/render error:', err); /* update DOM: show a user-facing message or call a
fallback renderer such as renderAgentsFallback() or set container.innerText =
'Failed to load agents.' */ }" — ensure both catch sites in this file log the
caught error and update the relevant DOM/container to a clear fallback state
instead of doing nothing.
public/assets/css/components.css (2)

157-175: ⚡ Quick win

Range input styling only targets webkit browsers.

The range input slider thumb styling at lines 167-175 uses -webkit-slider-thumb, which only works in Chrome, Edge, and Safari. Firefox requires -moz-range-thumb for equivalent styling. Without it, Firefox users will see the default (unstyled) slider thumb.

🦊 Add Firefox range slider support
 input[type='range']::-webkit-slider-thumb {
   -webkit-appearance: none;
   width: 16px;
   height: 16px;
   border-radius: 50%;
   background: var(--gradient);
   cursor: pointer;
   box-shadow: 0 0 8px var(--accent-glow);
 }
+
+input[type='range']::-moz-range-thumb {
+  width: 16px;
+  height: 16px;
+  border-radius: 50%;
+  background: var(--gradient);
+  cursor: pointer;
+  box-shadow: 0 0 8px var(--accent-glow);
+  border: none;
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/css/components.css` around lines 157 - 175, The CSS only styles
the range thumb for WebKit via input[type='range']::-webkit-slider-thumb, so
Firefox falls back to the default thumb; add equivalent Gecko rules to style
Firefox by including input[type='range']::-moz-range-thumb (and optionally
::-moz-range-track for track differences) with the same properties (size,
border-radius, background: var(--gradient), box-shadow: var(--accent-glow),
cursor) and ensure any vendor-prefixed appearance resets (e.g., -moz-appearance:
none on input[type='range']) match the WebKit settings to achieve consistent
slider appearance across browsers.

66-108: ⚡ Quick win

Add keyboard focus states for accessibility.

The .btn class has hover styles but no :focus or :focus-visible states. This makes it difficult for keyboard users to see which button has focus. Per accessibility best practices, interactive elements should have visible focus indicators.

♿ Recommended focus state styling
 .btn:hover {
   transform: translateY(-1px);
   box-shadow: 0 5px 20px rgba(99, 102, 241, 0.3);
 }
+
+.btn:focus-visible {
+  outline: 2px solid var(--accent-light);
+  outline-offset: 2px;
+}

 .btn:disabled {
   opacity: 0.35;
   cursor: not-allowed;
   transform: none;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/css/components.css` around lines 66 - 108, The .btn styles lack
keyboard focus indicators; add visible focus states to improve accessibility by
defining .btn:focus and .btn:focus-visible rules that mirror the hover visual
feedback (e.g., a clear outline or box-shadow and ensure transform/opacity
remains consistent) and ensure :focus-visible only shows when appropriate;
update the .btn:disabled selector to not show focus, and verify rules apply to
the existing .btn::after shimmer so keyboard users see a distinct, high-contrast
focus ring while keeping existing hover behavior intact.
public/assets/css/layout.css (1)

10-17: ⚡ Quick win

Scrollbar styling only works in webkit browsers.

The ::-webkit-scrollbar pseudo-elements on lines 10-17 (for .main) and lines 47-54 (for .feed in pages.css) only apply to Chrome, Edge, and Safari. Firefox and other browsers will show default scrollbars. Consider adding fallback styling using the standard scrollbar-width and scrollbar-color properties for broader browser support.

🌐 Cross-browser scrollbar example
 .main::-webkit-scrollbar {
   width: 5px;
 }

 .main::-webkit-scrollbar-thumb {
   background: rgba(255, 255, 255, 0.06);
   border-radius: 3px;
 }
+
+.main {
+  scrollbar-width: thin;
+  scrollbar-color: rgba(255, 255, 255, 0.06) transparent;
+}

Apply the same pattern to other scrollable containers (.feed, .sb-nav, .result-live, .result-out).

Also applies to: 47-54

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/css/layout.css` around lines 10 - 17, The scrollbar styling
currently uses only WebKit pseudo-elements (.main::-webkit-scrollbar and
.main::-webkit-scrollbar-thumb), which leaves Firefox and other browsers with
default scrollbars; add cross-browser fallbacks by setting the standard
properties (e.g., scrollbar-width and scrollbar-color) on the scrollable
containers (.main, .feed, .sb-nav, .result-live, .result-out) to match the
intended thin, semi-transparent thumb and transparent track, and keep the
existing ::-webkit- rules for Chrome/Safari compatibility so both WebKit and
non-WebKit browsers get consistent styling.
public/assets/css/sidebar.css (2)

36-44: ⚡ Quick win

Gradient text effect only works in webkit browsers.

The logo text styling at lines 39-42 uses -webkit-background-clip: text and -webkit-text-fill-color: transparent to create a gradient text effect. In Firefox and other non-webkit browsers, the text may appear invisible or fall back to solid color unpredictably. Since this is the main logo/branding, poor visibility is a user experience issue.

🎨 Add fallback for non-webkit browsers
 .sb-logo-text h1 {
   font-size: 17px;
   font-weight: 800;
+  color: var(--accent-light);
   background: var(--gradient);
   -webkit-background-clip: text;
   -webkit-text-fill-color: transparent;
   background-clip: text;
   letter-spacing: -0.3px;
 }

The color property serves as a fallback for browsers that don't support the gradient clipping.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/css/sidebar.css` around lines 36 - 44, The gradient text
currently relies on WebKit-only properties in .sb-logo-text h1; add a solid
fallback by specifying a readable color (e.g. var(--logo-color) or a hex) before
the gradient lines, ensure the gradient is applied via background-image:
linear-gradient(...) (using var(--gradient) if defined) and keep the existing
-webkit- and unprefixed background-clip/background-clip: text lines; this
ensures non‑WebKit browsers render the fallback color while WebKit browsers get
the gradient effect.

81-105: ⚡ Quick win

Add focus states for keyboard navigation.

The .sb-item navigation links have hover states but no :focus or :focus-visible styling. This makes keyboard navigation difficult for users who rely on visible focus indicators. As per accessibility best practices, interactive navigation elements should have clear focus states.

♿ Recommended focus styling
 .sb-item.active {
   background: var(--accent-glow);
   color: var(--accent-light);
   border-color: rgba(99, 102, 241, 0.12);
 }
+
+.sb-item:focus-visible {
+  outline: 2px solid var(--accent-light);
+  outline-offset: -2px;
+}

 .sb-item .i {
   font-size: 15px;
   width: 18px;
   text-align: center;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/css/sidebar.css` around lines 81 - 105, Add keyboard focus
styles for .sb-item by defining .sb-item:focus and .sb-item:focus-visible to
provide a clear visible indicator (e.g., same background/color as :hover or
.sb-item.active plus a distinct outline or box-shadow and border-color) so
keyboard users can see which item is focused; ensure you target :focus-visible
to avoid showing focus for mouse interactions and mirror accessible styling
choices used in .sb-item:hover and .sb-item.active. Also verify elements using
.sb-item are keyboard-focusable (use <a>, <button> or add tabindex="0" where
appropriate) so the new focus styles can be reached.
public/assets/css/pages.css (1)

28-36: ⚡ Quick win

Budget gradient text won't display correctly in Firefox.

The .budget-val styling uses webkit-specific gradient text properties that won't work in Firefox or other non-webkit browsers. The budget value text may appear invisible or with unpredictable fallback rendering.

🎨 Add color fallback for non-webkit browsers
 .budget-val {
   font-family: 'JetBrains Mono', monospace;
   font-size: 14px;
   font-weight: 700;
+  color: var(--accent-light);
   background: var(--gradient);
   -webkit-background-clip: text;
   -webkit-text-fill-color: transparent;
   background-clip: text;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/css/pages.css` around lines 28 - 36, The gradient text relies
on webkit-only properties in .budget-val so Firefox shows nothing; add a
readable color fallback (e.g. --gradient-fallback) and only apply transparent
text + webkit clipping inside a feature query so non-webkit browsers get the
fallback color. Concretely: add a CSS variable like --gradient-fallback and set
color: var(--gradient-fallback) on .budget-val, keep background: var(--gradient)
and background-clip: text, then wrap the webkit-specific rules
(-webkit-background-clip and -webkit-text-fill-color: transparent and color:
transparent) inside `@supports` (-webkit-text-fill-color: transparent) {
.budget-val { ... } } so only webkit engines use the clipped gradient while
Firefox uses the fallback color.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@FRONTEND_STRUCTURE.md`:
- Around line 115-130: The module graph shows an apparent circular dependency
between orchestration.js and rendering.js; update the tree so the dependency
direction is explicit by either (A) making orchestration.js -> rendering.js and
removing the reverse link (remove the nested rendering.js -> orchestration.js
entry under sse.js), or (B) explicitly mark the coupling as bidirectional (use a
double-headed arrow or add “<->” between orchestration.js and rendering.js) and
add a short parenthetical note like “(intentional bidirectional coupling)” next
to orchestration.js and rendering.js to make the relationship clear for readers;
ensure the sse.js subtree reflects the same corrected direction/annotation.
- Around line 5-6: Replace the absolute claim "100% feature parity" (the
sentence starting with "The frontend has been refactored..." and the other
occurrences of that phrase) with a softened, verifiable statement such as
"validated against the acceptance checklist" or include a link/reference to the
verification artifacts; update the three occurrences (the original sentence and
the two other spots noted) so the doc either cites the acceptance checklist or
shows the exact test/QA artifacts used to validate parity.

In `@public/assets/js/budget.js`:
- Around line 7-12: The init currently assumes elements exist and calls
bSlider.addEventListener which will throw if
document.getElementById('budget-slider') is null; update the logic around the
variables bSlider and bDisp to guard their usage (e.g., check that bSlider and
bDisp are not null before calling addEventListener or updating textContent) so
initialization continues if the slider or display is missing; reference the
consts bSlider, bDisp and the 'budget-slider'/'budget-display' element IDs when
applying the guard.

In `@public/assets/js/navigation.js`:
- Around line 16-20: The DOM lookups in showPage are unguarded and can throw
when elements are missing; update the calls to
document.getElementById(`page-${id}`) and
document.querySelector(`.sb-item[data-page="${id}"]`) to guard against null (use
optional chaining or null checks) before accessing .classList, and ensure the
document.querySelectorAll('.sb-item') loop still runs safely (e.g., only call
.forEach on the NodeList and skip .classList removal if item is falsy); make
these changes inside the showPage function so navigation state updates are
resilient when elements are absent.

In `@public/assets/js/orchestration.js`:
- Around line 133-143: The fetch call to '/api/orchestrate' currently parses the
body and calls showResult(r) unconditionally; change it to first capture the
Response object (e.g., const resp = await fetch(...)), check resp.ok, and only
parse JSON and call showResult(r) on success; on non-ok status parse the error
body (JSON or text) and surface it (e.g., call showResult with an error payload
or throw to be caught by the surrounding try/catch) and avoid calling
loadWallets() for failed responses. Ensure you reference the existing fetch
usage, the variable r/resp, showResult, and loadWallets when making the change.

In `@public/assets/js/pages.js`:
- Line 70: The empty catch block after the try in pages.js leaves the UI stuck;
replace the bare catch {} with a catch(err) that updates the UI elements
(`#status-grid`, `#key-status-tag`, `#endpoint-list`) to an error state: remove any
loading placeholders/classes and set clear error text (e.g. "Error checking
status" or include err.message), and optionally log the error to console or a
logger. Locate the try/catch surrounding the status fetch/render logic in
pages.js and modify the catch to accept the error, update those DOM nodes to a
deterministic error display, and ensure any state flags used by the render flow
are also cleared so the page is not left hanging.
- Around line 85-113: The current rendering maps txs into a string assigned to
document.getElementById('tx-body').innerHTML using unescaped values (memoValue,
op.asset_code, hash, opType, amt, time), which permits XSS; fix by replacing the
string-concatenation innerHTML approach with safe DOM construction or by
HTML-escaping every untrusted field before interpolation: build each <tr> using
document.createElement and set text nodes/textContent for cells (or apply a
sanitizer/escape function to memoValue, op.asset_code, hash, opType, amt, time)
so no raw user-controlled text is inserted as HTML; apply the same escaping
approach to loadStatusPage for any backend-provided strings.

In `@public/assets/js/rendering.js`:
- Around line 47-58: renderLiveChips can throw when r.spent or r.budget are
undefined; update renderLiveChips to defensively coerce both values before
calling toFixed (e.g., const spent = Number(r.spent) || 0; const budget =
Number(r.budget) || 0) and use spent.toFixed(2)/budget.toFixed(2) in the
template, keeping the rest of the logic (getRunProgressInfo, activeRun, status,
progress) unchanged so the chips render even for partially populated runs.
- Around line 107-119: The code builds txL and injects r fields into
ch.innerHTML unsafely: validate p.explorerUrl (from r.payments) to allow only
http(s) origins and reject or omit any other scheme, replace building HTML
strings with DOM construction (createElement('a'), setAttribute('href') only
after validation, and set link text via textContent) to avoid HTML injection,
and likewise create the chips by creating elements and using textContent for
r.agentsUsed, r.totalSpent, r.txCount, r.paymentProtocol, r.elapsed and the
budgetExhausted node instead of interpolating into ch.innerHTML; also ensure
p.txHash fallback displays an empty string or a meaningful label when undefined
(use p.txHash ? p.txHash.slice(0,8) : '—') so links aren’t labeled "undefined".

In `@public/assets/js/sse.js`:
- Around line 8-17: connectSSE currently creates a new EventSource on every
onerror, leaking connections; change it to use a single module-scoped
EventSource (es) and a reconnect timer guard (e.g., reconnectTimer or
reconnectScheduled) so you only schedule one reconnect; in onerror, call
es.close() to close the existing source and then, if no reconnect is scheduled,
set reconnectTimer = setTimeout(() => { reconnectTimer = null; connectSSE(); },
3000); also clear the timer when a successful connection occurs (onopen) and
ensure you reference EventSource.readyState (0/1/2) to avoid scheduling while
already reconnecting.

---

Nitpick comments:
In `@public/assets/css/components.css`:
- Around line 157-175: The CSS only styles the range thumb for WebKit via
input[type='range']::-webkit-slider-thumb, so Firefox falls back to the default
thumb; add equivalent Gecko rules to style Firefox by including
input[type='range']::-moz-range-thumb (and optionally ::-moz-range-track for
track differences) with the same properties (size, border-radius, background:
var(--gradient), box-shadow: var(--accent-glow), cursor) and ensure any
vendor-prefixed appearance resets (e.g., -moz-appearance: none on
input[type='range']) match the WebKit settings to achieve consistent slider
appearance across browsers.
- Around line 66-108: The .btn styles lack keyboard focus indicators; add
visible focus states to improve accessibility by defining .btn:focus and
.btn:focus-visible rules that mirror the hover visual feedback (e.g., a clear
outline or box-shadow and ensure transform/opacity remains consistent) and
ensure :focus-visible only shows when appropriate; update the .btn:disabled
selector to not show focus, and verify rules apply to the existing .btn::after
shimmer so keyboard users see a distinct, high-contrast focus ring while keeping
existing hover behavior intact.

In `@public/assets/css/layout.css`:
- Around line 10-17: The scrollbar styling currently uses only WebKit
pseudo-elements (.main::-webkit-scrollbar and .main::-webkit-scrollbar-thumb),
which leaves Firefox and other browsers with default scrollbars; add
cross-browser fallbacks by setting the standard properties (e.g.,
scrollbar-width and scrollbar-color) on the scrollable containers (.main, .feed,
.sb-nav, .result-live, .result-out) to match the intended thin, semi-transparent
thumb and transparent track, and keep the existing ::-webkit- rules for
Chrome/Safari compatibility so both WebKit and non-WebKit browsers get
consistent styling.

In `@public/assets/css/pages.css`:
- Around line 28-36: The gradient text relies on webkit-only properties in
.budget-val so Firefox shows nothing; add a readable color fallback (e.g.
--gradient-fallback) and only apply transparent text + webkit clipping inside a
feature query so non-webkit browsers get the fallback color. Concretely: add a
CSS variable like --gradient-fallback and set color: var(--gradient-fallback) on
.budget-val, keep background: var(--gradient) and background-clip: text, then
wrap the webkit-specific rules (-webkit-background-clip and
-webkit-text-fill-color: transparent and color: transparent) inside `@supports`
(-webkit-text-fill-color: transparent) { .budget-val { ... } } so only webkit
engines use the clipped gradient while Firefox uses the fallback color.

In `@public/assets/css/sidebar.css`:
- Around line 36-44: The gradient text currently relies on WebKit-only
properties in .sb-logo-text h1; add a solid fallback by specifying a readable
color (e.g. var(--logo-color) or a hex) before the gradient lines, ensure the
gradient is applied via background-image: linear-gradient(...) (using
var(--gradient) if defined) and keep the existing -webkit- and unprefixed
background-clip/background-clip: text lines; this ensures non‑WebKit browsers
render the fallback color while WebKit browsers get the gradient effect.
- Around line 81-105: Add keyboard focus styles for .sb-item by defining
.sb-item:focus and .sb-item:focus-visible to provide a clear visible indicator
(e.g., same background/color as :hover or .sb-item.active plus a distinct
outline or box-shadow and border-color) so keyboard users can see which item is
focused; ensure you target :focus-visible to avoid showing focus for mouse
interactions and mirror accessible styling choices used in .sb-item:hover and
.sb-item.active. Also verify elements using .sb-item are keyboard-focusable (use
<a>, <button> or add tabindex="0" where appropriate) so the new focus styles can
be reached.

In `@public/assets/js/agents.js`:
- Line 23: Replace the silent catch blocks (the bare "catch {}" occurrences)
with error handlers that log the error and surface a fallback UI; e.g., change
to "catch (err) { console.error('agents.js load/render error:', err); /* update
DOM: show a user-facing message or call a fallback renderer such as
renderAgentsFallback() or set container.innerText = 'Failed to load agents.' */
}" — ensure both catch sites in this file log the caught error and update the
relevant DOM/container to a clear fallback state instead of doing nothing.

In `@public/assets/js/wallet.js`:
- Around line 6-33: The empty catch in loadWallets() swallows fetch/JSON errors
leaving the UI blank and providing no diagnostics; update the catch block in
loadWallets to log the caught error (e.g., console.error or a provided logger)
with a clear message like "loadWallets failed" and the error object so failures
fetching /api/wallet/balances or JSON parsing are visible when balEl/addrEl/mEl
remain unset.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: edc0ef15-4e7b-4961-a67c-1923c427fa06

📥 Commits

Reviewing files that changed from the base of the PR and between 3eb6a40 and fd0fdc2.

📒 Files selected for processing (18)
  • FRONTEND_STRUCTURE.md
  • README.md
  • REFACTORING_SUMMARY.md
  • public/assets/css/components.css
  • public/assets/css/layout.css
  • public/assets/css/pages.css
  • public/assets/css/sidebar.css
  • public/assets/css/variables.css
  • public/assets/js/agents.js
  • public/assets/js/budget.js
  • public/assets/js/init.js
  • public/assets/js/navigation.js
  • public/assets/js/orchestration.js
  • public/assets/js/pages.js
  • public/assets/js/rendering.js
  • public/assets/js/sse.js
  • public/assets/js/wallet.js
  • public/index.html

Comment thread FRONTEND_STRUCTURE.md
Comment on lines +5 to +6
The frontend has been refactored from a monolithic 1,300-line HTML file into a modular, maintainable structure with separated concerns. The new architecture organizes CSS and JavaScript into logical modules while maintaining 100% feature parity with the original implementation.

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 | ⚡ Quick win

Avoid absolute “100% feature parity / ready” claims without traceable evidence.

These statements read as guarantees. Consider softening to “validated against acceptance checklist” (or link the exact verification artifacts) to keep docs trustworthy over time.

Also applies to: 192-193, 241-242

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FRONTEND_STRUCTURE.md` around lines 5 - 6, Replace the absolute claim "100%
feature parity" (the sentence starting with "The frontend has been
refactored..." and the other occurrences of that phrase) with a softened,
verifiable statement such as "validated against the acceptance checklist" or
include a link/reference to the verification artifacts; update the three
occurrences (the original sentence and the two other spots noted) so the doc
either cites the acceptance checklist or shows the exact test/QA artifacts used
to validate parity.

Comment thread FRONTEND_STRUCTURE.md
Comment on lines +115 to +130
```
init.js
├── navigation.js
├── budget.js
├── agents.js
├── wallet.js
├── sse.js
│ └── rendering.js
│ └── orchestration.js
├── orchestration.js
│ ├── rendering.js
│ └── wallet.js
├── pages.js
│ └── agents.js
└── (periodic wallet refresh)
```
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 | ⚡ Quick win

Resolve the apparent circular dependency in the module graph.

This tree currently implies both orchestration.js -> rendering.js and rendering.js -> orchestration.js, which is confusing for onboarding and maintenance docs. Please correct the direction (or explicitly mark intentional bidirectional coupling).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@FRONTEND_STRUCTURE.md` around lines 115 - 130, The module graph shows an
apparent circular dependency between orchestration.js and rendering.js; update
the tree so the dependency direction is explicit by either (A) making
orchestration.js -> rendering.js and removing the reverse link (remove the
nested rendering.js -> orchestration.js entry under sse.js), or (B) explicitly
mark the coupling as bidirectional (use a double-headed arrow or add “<->”
between orchestration.js and rendering.js) and add a short parenthetical note
like “(intentional bidirectional coupling)” next to orchestration.js and
rendering.js to make the relationship clear for readers; ensure the sse.js
subtree reflects the same corrected direction/annotation.

Comment on lines +7 to +12
const bSlider = document.getElementById('budget-slider');
const bDisp = document.getElementById('budget-display');

bSlider.addEventListener('input', () => {
bDisp.textContent = `${parseFloat(bSlider.value).toFixed(2)} USDC`;
});
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 | ⚡ Quick win

Guard against missing slider elements.

If budget-slider is absent, bSlider.addEventListener throws and aborts the surrounding init sequence. A short-circuit keeps initialization robust.

🛡️ Proposed guard
   const bSlider = document.getElementById('budget-slider');
   const bDisp = document.getElementById('budget-display');
+  if (!bSlider || !bDisp) return;

   bSlider.addEventListener('input', () => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const bSlider = document.getElementById('budget-slider');
const bDisp = document.getElementById('budget-display');
bSlider.addEventListener('input', () => {
bDisp.textContent = `${parseFloat(bSlider.value).toFixed(2)} USDC`;
});
const bSlider = document.getElementById('budget-slider');
const bDisp = document.getElementById('budget-display');
if (!bSlider || !bDisp) return;
bSlider.addEventListener('input', () => {
bDisp.textContent = `${parseFloat(bSlider.value).toFixed(2)} USDC`;
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/budget.js` around lines 7 - 12, The init currently assumes
elements exist and calls bSlider.addEventListener which will throw if
document.getElementById('budget-slider') is null; update the logic around the
variables bSlider and bDisp to guard their usage (e.g., check that bSlider and
bDisp are not null before calling addEventListener or updating textContent) so
initialization continues if the slider or display is missing; reference the
consts bSlider, bDisp and the 'budget-slider'/'budget-display' element IDs when
applying the guard.

Comment on lines +16 to +20
document.getElementById(`page-${id}`).classList.add('active');

// Update sidebar active state
document.querySelectorAll('.sb-item').forEach(i => i.classList.remove('active'));
document.querySelector(`.sb-item[data-page="${id}"]`).classList.add('active');
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 | ⚡ Quick win

Unguarded DOM lookups can throw on unknown id.

If page-${id} or a matching .sb-item doesn't exist (e.g., a typo in a data-page attribute or a future page added to the sidebar but not the markup), .classList dereferences null and showPage throws, leaving navigation in a half-updated state. Optional chaining keeps the rest of the function resilient.

🛡️ Proposed guard
-  document.getElementById(`page-${id}`).classList.add('active');
+  document.getElementById(`page-${id}`)?.classList.add('active');

   // Update sidebar active state
   document.querySelectorAll('.sb-item').forEach(i => i.classList.remove('active'));
-  document.querySelector(`.sb-item[data-page="${id}"]`).classList.add('active');
+  document.querySelector(`.sb-item[data-page="${id}"]`)?.classList.add('active');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
document.getElementById(`page-${id}`).classList.add('active');
// Update sidebar active state
document.querySelectorAll('.sb-item').forEach(i => i.classList.remove('active'));
document.querySelector(`.sb-item[data-page="${id}"]`).classList.add('active');
document.getElementById(`page-${id}`)?.classList.add('active');
// Update sidebar active state
document.querySelectorAll('.sb-item').forEach(i => i.classList.remove('active'));
document.querySelector(`.sb-item[data-page="${id}"]`)?.classList.add('active');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/navigation.js` around lines 16 - 20, The DOM lookups in
showPage are unguarded and can throw when elements are missing; update the calls
to document.getElementById(`page-${id}`) and
document.querySelector(`.sb-item[data-page="${id}"]`) to guard against null (use
optional chaining or null checks) before accessing .classList, and ensure the
document.querySelectorAll('.sb-item') loop still runs safely (e.g., only call
.forEach on the NodeList and skip .classList removal if item is falsy); make
these changes inside the showPage function so navigation state updates are
resilient when elements are absent.

Comment on lines +133 to +143
try {
const r = await (
await fetch('/api/orchestrate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task, budget }),
})
).json();

showResult(r);
loadWallets();
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 | ⚡ Quick win

Check response.ok before treating the result as success.

fetch only rejects on network failures, not on HTTP error statuses. A 4xx/5xx from /api/orchestrate will resolve normally, and the error body is then passed to showResult(r) as if it were a successful run, leaving the panel in a "Running"/success state.

🛡️ Proposed fix
   try {
-    const r = await (
-      await fetch('/api/orchestrate', {
-        method: 'POST',
-        headers: { 'Content-Type': 'application/json' },
-        body: JSON.stringify({ task, budget }),
-      })
-    ).json();
-
-    showResult(r);
+    const res = await fetch('/api/orchestrate', {
+      method: 'POST',
+      headers: { 'Content-Type': 'application/json' },
+      body: JSON.stringify({ task, budget }),
+    });
+    if (!res.ok) {
+      throw new Error(`Orchestrate request failed (${res.status})`);
+    }
+    const r = await res.json();
+    showResult(r);
     loadWallets();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
const r = await (
await fetch('/api/orchestrate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task, budget }),
})
).json();
showResult(r);
loadWallets();
try {
const res = await fetch('/api/orchestrate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ task, budget }),
});
if (!res.ok) {
throw new Error(`Orchestrate request failed (${res.status})`);
}
const r = await res.json();
showResult(r);
loadWallets();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/orchestration.js` around lines 133 - 143, The fetch call to
'/api/orchestrate' currently parses the body and calls showResult(r)
unconditionally; change it to first capture the Response object (e.g., const
resp = await fetch(...)), check resp.ok, and only parse JSON and call
showResult(r) on success; on non-ok status parse the error body (JSON or text)
and surface it (e.g., call showResult with an error payload or throw to be
caught by the surrounding try/catch) and avoid calling loadWallets() for failed
responses. Ensure you reference the existing fetch usage, the variable r/resp,
showResult, and loadWallets when making the change.

Comment thread public/assets/js/pages.js
</div>
</div>
`;
} catch {}
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 | ⚡ Quick win

Empty catch {} leaves the status UI stuck.

On any failure, #status-grid, #key-status-tag, and #endpoint-list retain their loading/"Checking..." placeholders with no feedback. At minimum render an error state so the page doesn't appear hung.

♻️ Suggested fix
-  } catch {}
+  } catch {
+    const tag = document.getElementById('key-status-tag');
+    if (tag) { tag.textContent = 'Error'; tag.className = 'tag red'; }
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/pages.js` at line 70, The empty catch block after the try in
pages.js leaves the UI stuck; replace the bare catch {} with a catch(err) that
updates the UI elements (`#status-grid`, `#key-status-tag`, `#endpoint-list`) to an
error state: remove any loading placeholders/classes and set clear error text
(e.g. "Error checking status" or include err.message), and optionally log the
error to console or a logger. Locate the try/catch surrounding the status
fetch/render logic in pages.js and modify the catch to accept the error, update
those DOM nodes to a deterministic error display, and ensure any state flags
used by the render flow are also cleared so the page is not left hanging.

Comment thread public/assets/js/pages.js
Comment on lines +85 to +113
document.getElementById('tx-body').innerHTML = txs
.map(tx => {
const ops = tx.operations || [];
const op = ops[0] || {};
let amt = '—';

if (op.amount) {
const num = Number.parseFloat(op.amount);
amt = Number.isFinite(num) ? `${num.toFixed(2)} ${op.asset_code || 'XLM'}` : `${op.amount} ${op.asset_code || 'XLM'}`;
} else if (op.type === 'invoke_host_function') {
amt = 'contract call';
}

const hash = tx.hash || tx.id || '';
const url = `https://stellar.expert/explorer/testnet/tx/${hash}`;
const timeRaw = tx.created_at || tx.createdAt || null;
const time = timeRaw ? new Date(timeRaw).toLocaleString() : '—';
const memoValue = tx.memo_type === 'none' ? 'none' : tx.memo || '—';
const opType = op.type ? op.type.replaceAll('_', ' ') : '—';

return `<tr>
<td><a href="${url}" target="_blank" class="tx-hash">${hash.slice(0, 10)}...${hash.slice(-6)}</a></td>
<td>${opType}</td>
<td><span class="tx-amount">${amt}</span></td>
<td><span class="tx-memo">${memoValue}</span></td>
<td><span class="tx-time">${time}</span></td>
</tr>`;
})
.join('');
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 | ⚡ Quick win

Potential XSS: transaction fields are interpolated into innerHTML without escaping.

memoValue, op.asset_code, hash, and opType originate from on-chain transaction data, which is publicly writable — anyone can send a transaction to the wallet with a crafted text memo. Injecting these directly via innerHTML allows script execution (e.g., a memo like <img src=x onerror=...>). Escape untrusted values before rendering, or build the row with DOM APIs / textContent.

🛡️ Suggested approach (escape untrusted values)
+        const esc = (v) =>
+          String(v).replace(/[&<>"']/g, (c) => ({
+            '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&`#39`;',
+          }[c]));
         return `<tr>
-          <td><a href="${url}" target="_blank" class="tx-hash">${hash.slice(0, 10)}...${hash.slice(-6)}</a></td>
-          <td>${opType}</td>
-          <td><span class="tx-amount">${amt}</span></td>
-          <td><span class="tx-memo">${memoValue}</span></td>
-          <td><span class="tx-time">${time}</span></td>
+          <td><a href="${esc(url)}" target="_blank" rel="noopener" class="tx-hash">${esc(hash.slice(0, 10))}...${esc(hash.slice(-6))}</a></td>
+          <td>${esc(opType)}</td>
+          <td><span class="tx-amount">${esc(amt)}</span></td>
+          <td><span class="tx-memo">${esc(memoValue)}</span></td>
+          <td><span class="tx-time">${esc(time)}</span></td>
         </tr>`;
Consider applying the same escaping in `loadStatusPage` for any non-trusted backend fields.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/pages.js` around lines 85 - 113, The current rendering maps
txs into a string assigned to document.getElementById('tx-body').innerHTML using
unescaped values (memoValue, op.asset_code, hash, opType, amt, time), which
permits XSS; fix by replacing the string-concatenation innerHTML approach with
safe DOM construction or by HTML-escaping every untrusted field before
interpolation: build each <tr> using document.createElement and set text
nodes/textContent for cells (or apply a sanitizer/escape function to memoValue,
op.asset_code, hash, opType, amt, time) so no raw user-controlled text is
inserted as HTML; apply the same escaping approach to loadStatusPage for any
backend-provided strings.

Comment on lines +47 to +58
function renderLiveChips(r = activeRun) {
if (!r) return;
const elapsedMs = Date.now() - r.startedAt;
const elapsedSec = (elapsedMs / 1000).toFixed(1);
const { planned, completed, percent } = getRunProgressInfo(r);
const progress = planned > 0 ? `${completed}/${planned}` : `${completed}`;
const status = r.running ? 'Running' : 'Complete';

document.getElementById('result-chips').innerHTML = `
<div class="chip">Status: <span class="v">${status}</span></div>
<div class="chip">Progress: <span class="v">${progress}</span></div>
<div class="chip">Spent: <span class="v">${r.spent.toFixed(2)} / ${r.budget.toFixed(2)} USDC</span></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 | ⚡ Quick win

renderLiveChips can throw if spent/budget are unset.

The guard only checks if (!r), but r.spent.toFixed(2) and r.budget.toFixed(2) assume both are defined numbers. If activeRun is created/partially populated without these fields, this throws a TypeError and the chips/progress bar fail to render. Coerce defensively.

🛡️ Proposed guard
-    <div class="chip">Spent: <span class="v">${r.spent.toFixed(2)} / ${r.budget.toFixed(2)} USDC</span></div>
+    <div class="chip">Spent: <span class="v">${Number(r.spent || 0).toFixed(2)} / ${Number(r.budget || 0).toFixed(2)} USDC</span></div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/rendering.js` around lines 47 - 58, renderLiveChips can
throw when r.spent or r.budget are undefined; update renderLiveChips to
defensively coerce both values before calling toFixed (e.g., const spent =
Number(r.spent) || 0; const budget = Number(r.budget) || 0) and use
spent.toFixed(2)/budget.toFixed(2) in the template, keeping the rest of the
logic (getRunProgressInfo, activeRun, status, progress) unchanged so the chips
render even for partially populated runs.

Comment on lines +107 to +119
const txL = (r.payments || [])
.map(p => `<a href="${p.explorerUrl}" target="_blank" class="fi-link">${p.txHash?.slice(0, 8)} &nearr;</a>`)
.join(' ');

ch.innerHTML = `
<div class="chip">Agents: <span class="v">${r.agentsUsed || 0}</span></div>
<div class="chip">Spent: <span class="v">${r.totalSpent || '0'} USDC</span></div>
<div class="chip">TXs: <span class="v">${r.txCount || 0}</span></div>
<div class="chip">Protocol: <span class="v">${r.paymentProtocol || 'stellar'}</span></div>
<div class="chip">Time: <span class="v">${r.elapsed || '-'}</span></div>
${r.budgetExhausted ? '<div class="chip" style="color:var(--amber)">Budget Used</div>' : ''}
${txL ? `<div class="chip" style="flex-wrap:wrap;gap:4px;">Verify: ${txL}</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 | 🟠 Major | 🏗️ Heavy lift

Potential XSS via innerHTML from r.payments URLs and result fields.

p.explorerUrl is injected directly into an href and the chip values come from r fields, all via innerHTML. This is the same trust concern raised for the SSE feed: unvalidated explorerUrl enables a javascript:/attribute-breakout vector, and unescaped field values allow markup injection. Validate that explorerUrl is http(s) and escape interpolated values (or build nodes with textContent).

Separately, when p.txHash is missing, p.txHash?.slice(0, 8) yields undefined, rendering a link labeled "undefined ↗".

🔧 Suggested direction for the link text fallback
-    .map(p => `<a href="${p.explorerUrl}" target="_blank" class="fi-link">${p.txHash?.slice(0, 8)} &nearr;</a>`)
+    .map(p => `<a href="${p.explorerUrl}" target="_blank" class="fi-link">${(p.txHash || 'tx').slice(0, 8)} &nearr;</a>`)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const txL = (r.payments || [])
.map(p => `<a href="${p.explorerUrl}" target="_blank" class="fi-link">${p.txHash?.slice(0, 8)} &nearr;</a>`)
.join(' ');
ch.innerHTML = `
<div class="chip">Agents: <span class="v">${r.agentsUsed || 0}</span></div>
<div class="chip">Spent: <span class="v">${r.totalSpent || '0'} USDC</span></div>
<div class="chip">TXs: <span class="v">${r.txCount || 0}</span></div>
<div class="chip">Protocol: <span class="v">${r.paymentProtocol || 'stellar'}</span></div>
<div class="chip">Time: <span class="v">${r.elapsed || '-'}</span></div>
${r.budgetExhausted ? '<div class="chip" style="color:var(--amber)">Budget Used</div>' : ''}
${txL ? `<div class="chip" style="flex-wrap:wrap;gap:4px;">Verify: ${txL}</div>` : ''}
`;
const txL = (r.payments || [])
.map(p => `<a href="${p.explorerUrl}" target="_blank" class="fi-link">${(p.txHash || 'tx').slice(0, 8)} &nearr;</a>`)
.join(' ');
ch.innerHTML = `
<div class="chip">Agents: <span class="v">${r.agentsUsed || 0}</span></div>
<div class="chip">Spent: <span class="v">${r.totalSpent || '0'} USDC</span></div>
<div class="chip">TXs: <span class="v">${r.txCount || 0}</span></div>
<div class="chip">Protocol: <span class="v">${r.paymentProtocol || 'stellar'}</span></div>
<div class="chip">Time: <span class="v">${r.elapsed || '-'}</span></div>
${r.budgetExhausted ? '<div class="chip" style="color:var(--amber)">Budget Used</div>' : ''}
${txL ? `<div class="chip" style="flex-wrap:wrap;gap:4px;">Verify: ${txL}</div>` : ''}
`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/rendering.js` around lines 107 - 119, The code builds txL
and injects r fields into ch.innerHTML unsafely: validate p.explorerUrl (from
r.payments) to allow only http(s) origins and reject or omit any other scheme,
replace building HTML strings with DOM construction (createElement('a'),
setAttribute('href') only after validation, and set link text via textContent)
to avoid HTML injection, and likewise create the chips by creating elements and
using textContent for r.agentsUsed, r.totalSpent, r.txCount, r.paymentProtocol,
r.elapsed and the budgetExhausted node instead of interpolating into
ch.innerHTML; also ensure p.txHash fallback displays an empty string or a
meaningful label when undefined (use p.txHash ? p.txHash.slice(0,8) : '—') so
links aren’t labeled "undefined".

Comment thread public/assets/js/sse.js
Comment on lines +8 to +17
function connectSSE() {
const es = new EventSource('/api/events');
es.onmessage = e => {
try {
const ev = JSON.parse(e.data);
if (ev.type !== 'connected') addFeed(ev);
} catch {}
};
es.onerror = () => setTimeout(connectSSE, 3000);
}
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 | ⚡ Quick win

Reconnection leaks/stacks EventSource connections.

EventSource already retries automatically on transient failures, and onerror fires repeatedly while it is in the reconnecting state. Calling setTimeout(connectSSE, 3000) on every onerror opens a brand-new EventSource without closing es, so each error event spawns an additional connection that keeps firing onerror — connections multiply and the feed receives duplicate events. Close the existing source and guard against repeated scheduling before reconnecting.

🔧 Proposed fix
 function connectSSE() {
   const es = new EventSource('/api/events');
   es.onmessage = e => {
     try {
       const ev = JSON.parse(e.data);
       if (ev.type !== 'connected') addFeed(ev);
     } catch {}
   };
-  es.onerror = () => setTimeout(connectSSE, 3000);
+  es.onerror = () => {
+    es.close();
+    setTimeout(connectSSE, 3000);
+  };
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@public/assets/js/sse.js` around lines 8 - 17, connectSSE currently creates a
new EventSource on every onerror, leaking connections; change it to use a single
module-scoped EventSource (es) and a reconnect timer guard (e.g., reconnectTimer
or reconnectScheduled) so you only schedule one reconnect; in onerror, call
es.close() to close the existing source and then, if no reconnect is scheduled,
set reconnectTimer = setTimeout(() => { reconnectTimer = null; connectSSE(); },
3000); also clear the timer when a successful connection occurs (onopen) and
ensure you reference EventSource.readyState (0/1/2) to avoid scheduling while
already reconnecting.

@Flamki
Copy link
Copy Markdown
Owner

Flamki commented May 29, 2026

@joshkirojohn212-jpg kindly resolve this issue and resolve conflict

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor public/index.html into modular frontend files

3 participants