Skip to content

Merge@@@@#3129

Closed
somatothing wants to merge 28 commits intowavetermdev:mainfrom
somatothing:main
Closed

Merge@@@@#3129
somatothing wants to merge 28 commits intowavetermdev:mainfrom
somatothing:main

Conversation

@somatothing
Copy link
Copy Markdown

Merge, please!

somatothing and others added 22 commits March 24, 2026 17:27
All widgets are self-contained in frontend/widgets/ (separate folder as required).
Only minimal change to existing files: block.tsx (register widgets) + tsconfig.json (path alias).

Widgets:
- tradingalgobot: Trading Algobot with ONNX/joblib ML + Hyperliquid API + metrics
- arbitragebot: Triangular Arbitrage Bot on Arbitrum + ML model
- defilending: DeFi Lending Supply/Borrow/CollateralizedSwapRepay + ML
- flashloan: Flash Loan Arbitrage Rebalance Portfolio
- ammliquidity: AMM Liquidity Pools (Uniswap V3, Camelot, Curve, Balancer)"

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/f1db82f9-f2e4-4716-97ae-c05c893010a1
… tab, fix metric sign display

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/f1db82f9-f2e4-4716-97ae-c05c893010a1
…ock IDs, preview server

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/da3ae04d-815b-4c1a-b784-75e9fe9f3b99
Creates frontend/widgets/codeeditor/ with 3 files following the
tradingalgobot widget pattern:

- codeeditor-model.ts: CodeEditorViewModel implementing ViewModel with
  jotai atoms, runCode()/stopRun() with simulated async execution,
  viewText derived atom showing '<filename> | <language>' with Run/Stop
  header icon buttons, and proper dispose() cleanup.

- codeeditor.tsx: CodeEditor React component with 4 tabs — Editor
  (syntax-highlighted code area with line numbers, output pane),
  AI Assist (prompt, context summary, 5 suggestions with inline diff
  preview), Files (collapsible file tree, search), Metrics (stat cards,
  language breakdown bar chart, execution history table).

- codeeditor.scss: BEM-style classes inside .code-editor-widget,
  dark theme, cyan (#06b6d4) accent, monospace code areas, syntax
  highlight colors (blue keywords, green strings, yellow numbers,
  gray comments), SCSS variable for line-numbers width.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a new Docker/K8s Container Manager widget at
frontend/widgets/containers/ following the tradingalgobot pattern.

- containers-model.ts: ContainerManagerViewModel implementing ViewModel,
  6 mock containers, Jotai atoms for all state, setInterval CPU drift,
  live log tail, viewText header showing running/total count.
- containers.tsx: four-tab React component (Containers, Logs, Metrics,
  Shell) with status badges, CPU sparkline, memory bar, shell history.
- containers.scss: dark theme, Docker whale accent #0db7ed, BEM inside
  .containers-widget, pill-style status badges.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Creates frontend/widgets/shellworkflow/ with 3 files following
the tradingalgobot pattern exactly:

- shellworkflow-model.ts: ShellWorkflowViewModel with jotai atoms,
  4 mock workflows (Deploy API, ML Training Pipeline, DB Backup,
  Health Check), setTimeout-based step execution simulation, output
  history, and workflow variables with sensitive-value detection.

- shellworkflow.tsx: 4-tab React component — Workflows (list/new/delete),
  Steps (per-workflow step editor with run/expand per step), Output
  (execution timeline with collapsible stdout), Variables (masked
  key-value table with add/delete).

- shellworkflow.scss: Dark theme, BEM under .shellworkflow-widget,
  green accent (#22c55e), left-border step status coloring, code
  blocks as mini terminal with monospace font and subtle glow border.

viewText header shows '<workflowName> | <N> steps' with Run All button.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Creates a new widget at frontend/widgets/mlmodel/ following the
tradingalgobot widget pattern:

- mlmodel-model.ts: ViewModel with atoms (activeTab, models,
  selectedModelId, selectedModelType, selectedDataSource, hyperparams,
  isTraining, trainingProgress, trainLog, exportHistory).
  Implements startTraining() with setInterval simulation, exportModel(),
  retrain(), deleteModel(), and dispose().

- mlmodel.tsx: 5-tab React component:
  Models (table + ONNX/Joblib/Retrain/Delete actions),
  Train (type/datasource pills, hyperparams per model type, progress bar),
  Evaluate (confusion matrix, metrics, feature importance, loss curve),
  Data (source tabs, preview, schema inspector, preprocessing),
  Export (ONNX opset-17, Joblib compression, CLI copy, export history).

- mlmodel.scss: BEM inside .mlmodel-widget, purple accent (#a855f7),
  dark theme, CSS pulse animation on training progress bar, gradient
  feature importance bars (purple→indigo).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…del, widgetbuilder

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/9555d8df-e431-46e1-81d1-258438def070
Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/029d2a89-a09c-4790-b108-179881c42337
…ations

- widgetbuilder: real AI streaming via StreamWaveAiCommand with isPreviewWindow()
  fallback, real fetch() for HTTP tab, SQL-aware query mock results
- codeeditor: fetchAiSuggestions() calling StreamWaveAiCommand, refreshAiSuggestions()
  method, dataSource atom, real-timing runCode()
- shellworkflow: real fetch() for HTTP steps, variable substitution ($VAR_NAME),
  localStorage persistence via persistWorkflows()/saveWorkflows(), load on construct
- containers: DockerApiContainer type, initDockerData() tries Docker TCP API,
  fetchContainerLogs() for live logs, live/demo badge in viewText header,
  dockerAvailable/dataSource atoms, updated startRefresh() with live polling
- mlmodel: validateHyperparams(), loadDataFile() file picker, parseDataSource()
  for CSV/JSON, dataSource/dataPreview/selectedDataPath/exportDir atoms,
  per-model-type training speeds, exportDir-aware export paths

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- widgetbuilder: rename (v, k) to (value, key) in header forEach, add
  comment explaining charCodeAt fallback seed value
- codeeditor: fix code-fence regex to use /```\s*$/m to handle trailing whitespace
- shellworkflow: add JSDoc for substituteVariables documenting  format
- containers: add security notice about Docker TCP API, expand Docker
  multiplexed stream framing comment
- mlmodel: document last-column-as-target heuristic, document basic CSV
  parser limitations and edge cases

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…o services

- services/rpc.ts: pure-TS keccak256, eth_call, GMX V1 price reads,
  V2 pair reserves, Balancer pool token reads, Hop AMM price
- services/arbitrage-engine.ts: Bellman-Ford triangular arb screener
  with ML scoring (weighted feature vector, clamped 0-1)
- services/balancer.ts: Balancer V2 subgraph fetch + implied price formula
- services/morpho.ts: Morpho Blue GraphQL market data
- services/blockchain.ts: add GMX_V1, DFYN, HOP_AMM, Polygon token addrs
- services/index.ts: re-export all new services
- arbitragebot-model.ts: add onChainPrices/arbRoutes/balancerPools atoms,
  wire on-chain reads and arb engine into initLivePrices/forceScan,
  call refreshOnChainPrices every 4 ticks in startScanning
- defilending-model.ts: add morphoMarkets/dataSource atoms,
  fetch Morpho markets in initLivePrices

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…/INITIAL_ constants

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/6e0f3b7e-91d5-43c1-8466-64f2277ac5ab
… remaining mock data

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/6b48c0fd-9dd5-4501-ae3b-24c482ae6745
…nerators, add terminal runtime integration

- arbitragebot-model: remove OPPORTUNITY_DATA/generateOpportunities/generateStats/generateDexPrices; populate opportunities from live ArbRoute[] via arbRouteToOpportunity(); build DexPrice[] from live Hyperliquid cache only
- defilending-model: remove generateAssets/generateUserPosition/generateRateHistory; add morphoToAsset() to map Morpho Blue markets; assets start empty and are populated exclusively from fetchMorphoMarkets()
- flashloan-model: remove generatePortfolio/generateStrategies/generateRebalanceTrades/simulateExecution; portfolio from CoinGecko; STRATEGY_TEMPLATES replace hardcoded profit values; runSimulation() fills profit/apy from real calculation
- shellworkflow-model: add executeShellStep() using RpcApi.ControllerInputCommand + stringToBase64; shell/python steps sent to connectedTermBlockId terminal block; python uses heredoc (WAVE_EOF) to avoid escaping issues
- codeeditor-model: add connectedTermBlockId atom; runCode() sends code to terminal block via ControllerInputCommand; falls back to "connect terminal" stub; remove generateRunHistory() mock; executionMetrics starts at zero

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/de2a9950-1600-4068-b98e-3a4987f3606a
…predictions live dep, priceDelta guard, swapPreview null guard, widgetbuilder UUID+no-mock-query, flashloan no-setTimeout, hlWsBase removed, code editor empty default

Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/f418c2c7-c08c-4c62-bd24-f63a19e31ae5

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
…Graph GraphQL with REST API GET

Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/af5acd99-b3a1-4f74-bad5-da8105b3afed

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
…g-application

Add 10 widgets — DeFi/financial, code editor, containers, shell workflows, ML model, widget builder — live data only
Copilot AI review requested due to automatic review settings March 27, 2026 08:03
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 188853ba-8eaa-4acd-9c29-c1812f246cd4

📥 Commits

Reviewing files that changed from the base of the PR and between 5f29b1d and 23da598.

📒 Files selected for processing (2)
  • .github/workflows/deploy-docsite.yml
  • frontend/preview/index.html
✅ Files skipped from review due to trivial changes (1)
  • frontend/preview/index.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/deploy-docsite.yml

Walkthrough

The diff adds ten new frontend widgets (view models, React components, and SCSS) plus a widgets index that exports WIDGET_REGISTRY_ENTRIES and dynamic runtime registration. It extends the preview mocks and adds a DeFi widgets preview page. A services layer (coingecko, hyperliquid, balancer, morpho, rpc, blockchain, arbitrage-engine) and TypeScript path alias were added. On the backend, a Go databus and CoinGecko/Hyperliquid fetchers were introduced, new WPS event types and RPC types were added, and CI docsite preview build steps were updated.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.76% 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 pull request title 'Merge@@@@' is vague and does not clearly describe the main changes in the changeset, which include adding 10 new financial widgets, backend data services, and infrastructure updates. Revise the title to clearly summarize the primary change, such as 'Add financial widgets (Trading Algo Bot, Arbitrage Bot, DeFi Lending, Flash Loan, AMM Liquidity) and data services'.
Description check ❓ Inconclusive The pull request description 'Merge, please!' is too generic and does not explain any part of the changeset or provide meaningful context about what is being added or modified. Provide a detailed description explaining the purpose of the changes, key widget additions, backend infrastructure updates, and any testing performed.

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

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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot reviewed 50 out of 51 changed files in this pull request and generated no comments.

Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/f5928aa4-aca7-4331-a9e8-e5ff5d61f3f8

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@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: 2

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (19)
frontend/widgets/ammliquidity/ammliquidity-model.ts-108-148 (1)

108-148: ⚠️ Potential issue | 🟠 Major

Price impact calculation assumes constant-product AMM, but Balancer uses weighted pools.

The calcPriceImpact function uses the k = r0 * r1 constant-product formula, which is correct for Uniswap V2-style pools but incorrect for Balancer weighted pools. Balancer pools use the formula Π(Bi^Wi) = k, and price impact differs significantly.

This could display misleading price impact estimates to users.

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

In `@frontend/widgets/ammliquidity/ammliquidity-model.ts` around lines 108 - 148,
The calcPriceImpact function currently assumes a constant-product AMM (k = r0 *
r1); detect weighted/Balancer pools (e.g., presence of pool.weight0/pool.weight1
or pool.type === 'weighted') and replace the k-based branch with the Balancer
formulas: compute amountWithFee as before, then for an exact-in swap use the
Balancer output formula outputAmount = r_out * (1 - (r_in / (r_in +
amountWithFee))^(w_in / w_out)) (where r_in/r_out and w_in/w_out depend on
isToken0), compute spotPrice using the marginal/weight-adjusted ratio (spotPrice
≈ (r_out / r_in) * (w_in / w_out)), set effectivePrice = outputAmount /
inputAmount and priceImpact = abs((effectivePrice - spotPrice) / spotPrice) *
100; keep the existing constant-product branch as fallback for non-weighted
pools and return the same object fields (inputToken, outputToken, inputAmount,
outputAmount, priceImpact, fee, effectivePrice).
frontend/widgets/widgetbuilder/widgetbuilder-model.ts-221-250 (1)

221-250: ⚠️ Potential issue | 🟠 Major

SSRF risk: Arbitrary URL fetch without validation.

The sendHttpRequest method fetches any URL the user provides. This could be used to probe internal services or localhost endpoints. Consider restricting to known-safe domains or warning users.

🛡️ Proposed fix to add basic URL validation
     async sendHttpRequest() {
         globalStore.set(this.isSendingRequest, true);
         globalStore.set(this.httpResponse, null);

         const method = globalStore.get(this.httpMethod);
         const url = globalStore.get(this.httpUrl);

+        // Basic SSRF protection: block localhost and private IPs
+        try {
+            const parsed = new URL(url);
+            const host = parsed.hostname.toLowerCase();
+            if (host === "localhost" || host === "127.0.0.1" || host.startsWith("192.168.") || host.startsWith("10.") || host === "0.0.0.0") {
+                globalStore.set(this.httpResponse, { status: 0, body: "Blocked: Cannot fetch localhost or private IPs", ms: 0, headers: {} });
+                globalStore.set(this.isSendingRequest, false);
+                return;
+            }
+        } catch {
+            globalStore.set(this.httpResponse, { status: 0, body: "Invalid URL", ms: 0, headers: {} });
+            globalStore.set(this.isSendingRequest, false);
+            return;
+        }
+
         let resp: HttpResponse;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/widgetbuilder/widgetbuilder-model.ts` around lines 221 -
250, The sendHttpRequest method accepts arbitrary user-provided URLs (httpUrl)
which creates an SSRF risk; before calling fetch in sendHttpRequest, parse the
URL with the URL constructor, ensure the protocol is http or https, and perform
validation: reject localhost/127.0.0.0/8, ::1, and private RFC1918 ranges or
only allow a configured whitelist of safe hosts/domains; if validation fails set
globalStore.httpResponse to an error response (status 0 and explanatory body)
and return early. Keep existing timing/error handling and use the same resp
shape when reporting the validation failure so callers see a consistent
response.
frontend/widgets/widgetbuilder/widgetbuilder.tsx-428-430 (1)

428-430: ⚠️ Potential issue | 🟠 Major

POST/PUT/PATCH bodies are never sent.

The Body tab renders an uncontrolled textarea with no state binding, and sendHttpRequest() is called without any body value. Users can type JSON here, but the request is still sent without it.

Also applies to: 455-467

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

In `@frontend/widgets/widgetbuilder/widgetbuilder.tsx` around lines 428 - 430, The
Body tab's textarea is uncontrolled and its content is never passed to
model.sendHttpRequest(), so POST/PUT/PATCH bodies are omitted; make the textarea
a controlled component (bind it to a state property on the component or model)
and update that state onChange, then change the button onClick to call
model.sendHttpRequest(body) (or set the model's body property before calling) so
sendHttpRequest receives the current body; also validate/trim the body (e.g.,
only include for methods that allow a body and optionally try JSON.parse for
feedback) and update references to the textarea, sendHttpRequest, and any
model.body/stateName accordingly.
frontend/widgets/services/blockchain.ts-29-32 (1)

29-32: ⚠️ Potential issue | 🟠 Major

Normalize token symbols the same way on write and read.

getTokenAddress() uppercases the lookup key, but this registry stores "USDC.e" with a lowercase suffix. That makes the entry unreachable via the public helper.

Patch sketch
-        "USDC.e": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
+        "USDC.E": "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",

Also applies to: 179-180

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

In `@frontend/widgets/services/blockchain.ts` around lines 29 - 32, The registry
stores token symbols like "USDC.e" with mixed case but getTokenAddress()
uppercases lookup keys, so the entry is never found; fix by normalizing keys on
write to match reads—update the CHAIN_IDS.ARBITRUM mapping (and the other
entries noted around lines 179-180) to use uppercased token symbols (e.g.,
"USDC.E") or otherwise ensure all registry keys are consistently uppercased;
keep getTokenAddress() unchanged and make the registry canonicalization change
so lookups succeed.
frontend/widgets/containers/containers-model.ts-168-173 (1)

168-173: ⚠️ Potential issue | 🟠 Major

Demo mode never populates demo containers.

In the Docker failure path you only switch flags. containers stays at its initial empty array, and the later demo refresh logic just remaps that same empty list, so the widget shows an empty “demo” state forever.

Also applies to: 193-201, 329-339

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

In `@frontend/widgets/containers/containers-model.ts` around lines 168 - 173, The
catch block toggles dockerAvailable and dataSource but never populates the
containers list, so the demo UI stays empty; update the Docker-failure path in
containers-model.ts to also populate the containers state (this.containers) with
the demo fixtures or call the existing demo-loading routine (e.g., a method like
loadDemoContainers or whatever remaps demo data) before calling
this.startRefresh(); ensure the same change is applied to the other failure
paths referenced (around the other blocks at the 193-201 and 329-339 areas) so
the demo mode actually shows demo containers.
frontend/widgets/containers/containers-model.ts-268-296 (1)

268-296: ⚠️ Potential issue | 🟠 Major

Live container actions only change local UI state.

toggleContainerStatus(), restartContainer(), and removeContainer() never call Docker when dockerAvailable is true. Users can believe an action succeeded, then watch it snap back on the next refresh because the daemon was never changed.

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

In `@frontend/widgets/containers/containers-model.ts` around lines 268 - 296, The
three methods toggleContainerStatus, restartContainer, and removeContainer only
mutate the local globalStore (this.containers) and never invoke the Docker layer
when dockerAvailable is true; update each method to call the appropriate Docker
action when dockerAvailable is true (e.g., start/stop for toggleContainerStatus,
restart for restartContainer, remove for removeContainer) perform an optimistic
update to globalStore, await the Docker result, and on error revert the
optimistic change and surface the error; use the existing dockerAvailable flag
and reference this.containers/globalStore to apply and roll back changes so UI
stays consistent with the daemon.
frontend/widgets/defilending/defilending-model.ts-134-138 (1)

134-138: ⚠️ Potential issue | 🟠 Major

The refresh affordance currently does nothing.

The header button and the 10-second interval both call refreshRates(), but the method is empty and rateHistory is never updated. This ships a live-refresh UI that cannot actually refresh rates or history.

Also applies to: 171-175, 230-242

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

In `@frontend/widgets/defilending/defilending-model.ts` around lines 134 - 138,
The refresh affordance is a no-op because refreshRates() is empty; implement
refreshRates to fetch current market data and append or replace rateHistory so
the header button and 10s interval actually update the UI. Locate refreshRates()
and have it read the current dataSource atom and morphoMarkets/methods (e.g.,
morphoMarkets atom or any existing fetch/getRates helper), perform the network
or calculation to produce a RateHistory[] snapshot, then write that result into
the rateHistory atom (using the jotai setter or atom write pattern); also update
dataSource to "live" on successful fetch and handle errors (leave
dataSource/demo unchanged on failure) so the refresh button and interval reflect
live updates. Ensure any existing callers at lines noted (the header button and
the interval) continue calling refreshRates() without further changes.
frontend/widgets/widgetbuilder/widgetbuilder.tsx-157-181 (1)

157-181: ⚠️ Potential issue | 🟠 Major

TTL is currently a no-op.

The selected TTL only lives in local state. handleSave() never persists it or applies expiry, so entries shown as “1 hour” or “7 days” are actually permanent.

Also applies to: 258-263

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

In `@frontend/widgets/widgetbuilder/widgetbuilder.tsx` around lines 157 - 181, The
TTL selection is never persisted or enforced: update the StorageEntry shape to
include an optional expiresAt timestamp, update handleSave to compute expiresAt
from the ttl state (e.g., "none" => undefined, "1h" => Date.now()+3600*1000,
"7d" => Date.now()+7*24*3600*1000) and store it when creating or updating
entries, and ensure any code that reads entries (e.g., the entries
reducer/renderer and the get/getResult flow) filters out or treats expired
entries as missing; refer to ttl/setTtl, handleSave, StorageEntry, entries and
ensure the same change is applied to the other save/update path referenced
around the get logic so TTL is consistently persisted and enforced.
frontend/widgets/services/rpc.ts-153-164 (1)

153-164: ⚠️ Potential issue | 🟠 Major

Bound ethCall() latency.

All higher-level readers rely on catch to degrade to null, but this fetch has no timeout. A hung RPC request never rejects, so price readers can block indefinitely instead of failing closed.

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

In `@frontend/widgets/services/rpc.ts` around lines 153 - 164, The ethCall
function currently issues fetch(rpcUrl, ...) with no timeout, which can hang
indefinitely; update ethCall to use an AbortController and pass its signal to
fetch, start a timer (e.g., configurable default like 3–5s) that calls
controller.abort() on timeout, and clear the timer after fetch resolves/rejects
so you don't leak timers; keep the rejection behavior (so callers that catch and
return null still work) and ensure any abort/timeout produces a rejection with a
clear message so higher-level readers can degrade to null.
frontend/widgets/codeeditor/codeeditor-model.ts-161-177 (1)

161-177: ⚠️ Potential issue | 🟠 Major

Don't mark runs successful before a process actually completes.

The terminal path clears isRunning and records exitCode: 0 as soon as input is written, and the no-terminal path also appends a successful RunRecord even though nothing executed. That makes run history and metrics misleading, and stopRun() cannot control live executions.

Also applies to: 185-214, 225-233

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

In `@frontend/widgets/codeeditor/codeeditor-model.ts` around lines 161 - 177, The
code marks runs as finished and creates a successful RunRecord immediately after
sending input (RpcApi.ControllerInputCommand) or for the no-terminal branch;
instead, keep this.isRunning true and do not append a final RunRecord or set
exitCode until you receive an actual completion event/result (e.g., the terminal
process-exit/ControllerProcessExit RPC or the real RPC response for no-terminal
execution). Update the logic around RpcApi.ControllerInputCommand, termBlockId
handling, and the no-terminal execution path to either (a) create a pending run
entry and update it on completion with durationMs and exitCode, or (b) wait for
the completion callback/promise before creating the RunRecord; ensure stopRun()
communicates with the live terminal/process termination mechanism so it can
cancel and produce the correct exitCode, and remove the premature
globalStore.set(this.isRunning, false) and runHistory append from the send-input
path.
frontend/widgets/services/hyperliquid.ts-9-16 (1)

9-16: ⚠️ Potential issue | 🟠 Major

Add a timeout around hlPost().

Every API method funnels through this helper, but the fetch never aborts. If Hyperliquid hangs, callers like fetchRecentOhlcv() and fetchMidPrice() never reach their fallback catch, so the widget can wait forever instead of degrading cleanly.

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

In `@frontend/widgets/services/hyperliquid.ts` around lines 9 - 16, hlPost
currently performs an unbounded fetch; wrap it with an AbortController and a
timeout so requests fail fast: inside hlPost create an AbortController, pass
controller.signal to fetch, start a timer (e.g., HL_TIMEOUT ms) that calls
controller.abort(), and clear the timer when fetch completes or errors; ensure
the thrown error from an aborted fetch still bubbles to callers (so
fetchRecentOhlcv() and fetchMidPrice() hit their catch paths). Use the existing
function name hlPost and the HL_BASE constant when locating where to add the
controller, timer, and clearTimeout.
frontend/widgets/defilending/defilending-model.ts-99-109 (1)

99-109: ⚠️ Potential issue | 🟠 Major

Don't hard-code borrow capacity to 80% of collateral.

availableToBorrow ignores the actual weighted collateral limits and replaces them with a flat 0.8 multiplier. On conservative markets this overstates headroom and can show an unsafe borrow as available.

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

In `@frontend/widgets/defilending/defilending-model.ts` around lines 99 - 109, The
availableToBorrow calculation incorrectly uses a hard-coded 0.8 multiplier;
replace it to use the computed weighted collateral limit (avgThreshold) so
borrowing headroom respects actual collateral limits. Specifically, in the
return object replace the expression that computes availableToBorrow (currently
totalCollateral * 0.8 - totalBorrow) with a calculation that uses avgThreshold
(e.g., totalCollateral * avgThreshold - totalBorrow) and still clamp the result
with Math.max(0, ...); ensure you handle the totalCollateral === 0 case
consistently with how avgThreshold is derived.
frontend/widgets/codeeditor/codeeditor-model.ts-156-160 (1)

156-160: ⚠️ Potential issue | 🟠 Major

Only advertise "Run" for languages you can actually execute.

The editor claims to support 8 languages (python, typescript, javascript, go, rust, sql, shell, json), but wraps only python and shell with proper execution commands. The other six languages fall through to the generic case, which pastes raw source into the terminal with a comment header instead of executing it. Users selecting TypeScript, JavaScript, Go, Rust, SQL, or JSON will see their code pasted as text, not executed.

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

In `@frontend/widgets/codeeditor/codeeditor-model.ts` around lines 156 - 160, The
current ternary builds a runnable command for python and shell but falls back to
pasting source with a comment for other languages (variables: cmd, lang, code in
codeeditor-model.ts); change this so cmd is only produced for truly executable
languages (python and shell) and returns null/undefined for the others, and
ensure the UI logic that shows the "Run" button checks cmd truthiness before
advertising run; alternatively, implement real execution wrappers for additional
languages if you intend to support them (e.g., node for javascript/typescript
via ts-node or deno, go run, rustc/cargo, sql client), but do not advertise
"Run" until a proper exec path is implemented.
frontend/widgets/mlmodel/mlmodel-model.ts-404-417 (1)

404-417: ⚠️ Potential issue | 🟠 Major

retrain() leaves the original model stuck in training.

This path flips the existing model to training, then startTraining() completes by appending a brand-new model instead of updating that record. The original entry never returns to trained, and retraining accumulates duplicates.

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

In `@frontend/widgets/mlmodel/mlmodel-model.ts` around lines 404 - 417, retrain()
marks the existing model as "training" but startTraining() appends a new model
so the original stays stuck in "training"; change the flow so startTraining
updates the existing model record instead of adding a duplicate: pass the
modelId (and optionally existing model type) into startTraining from retrain(),
then modify startTraining to locate the model by id in
globalStore.get(this.models) and replace that entry with the trained/updated
model (updating status back to "trained" and preserving id/type) rather than
pushing a new item; ensure selectedModelId/selectedModelType handling remains
consistent.
frontend/widgets/arbitragebot/arbitragebot-model.ts-264-281 (1)

264-281: ⚠️ Potential issue | 🟠 Major

Normalize ETH/WETH before building the route graph.

Hyperliquid/Balancer edges are keyed as ETH, but GMX edges are keyed as WETH. Without a canonical alias or bridge edge, GMX ETH liquidity is disconnected from the rest of the graph, so cross-DEX ETH opportunities will be missed.

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

In `@frontend/widgets/arbitragebot/arbitragebot-model.ts` around lines 264 - 281,
The graph disconnects GMX's WETH edges from Hyperliquid's ETH edges; normalize
WETH<->ETH when building the route graph by adding a canonical mapping or
explicit bridge edges: use the existing onChain["WETH"] or livePriceCache["ETH"]
to create two edges between "WETH" and "ETH" (e.g., addEdge("ETH","WETH", 1 /
price, "bridge", cap) and addEdge("WETH","ETH", price, "bridge", cap)) before or
alongside the GMX V1 block so all subsequent addEdge calls reference the same
ETH token name and connect liquidity across DEXes. Ensure you reference
livePriceCache, onChain, and addEdge when making this change.
frontend/widgets/shellworkflow/shellworkflow-model.ts-142-176 (1)

142-176: ⚠️ Potential issue | 🟠 Major

executeShellStep() reports success too early and echoes substituted secrets.

ControllerInputCommand() only confirms the input was sent to the terminal. Returning success: true/exitCode: 0 here means failed shell or Python commands still look successful, and $ ${command} also writes any substituted secret values into outputHistory.

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

In `@frontend/widgets/shellworkflow/shellworkflow-model.ts` around lines 142 -
176, executeShellStep currently returns success: true as soon as
ControllerInputCommand confirms input was sent and it echoes the fully
substituted command (including secrets) into the output; change it so it does
NOT claim success based on send-only confirmation and does NOT echo substituted
secrets. Specifically, in executeShellStep use
RpcApi.ControllerInputCommand(TabRpcClient, ...) only to send input to the
terminal block (keep termBlockId and the python here-doc logic), but change the
returned object to indicate "sent" rather than success (e.g. success: false or a
new status like sent: true) until you receive an actual execution/exit result
via the terminal execution/exit RPC or event subscription (implement or call the
existing terminal result callback instead of assuming success). Also redact or
mask the substituted command in the output (replace `$ ${command}` with a
neutral message like `[command sent to terminal]` or a maskedCommand produced by
a maskSecrets utility) so substituted secrets are never written into
outputHistory; reference executeShellStep, ControllerInputCommand, TabRpcClient
and connectedTermBlockId to locate the change.
frontend/widgets/arbitragebot/arbitragebot-model.ts-307-323 (1)

307-323: ⚠️ Potential issue | 🟠 Major

Periodic rescans drop Balancer out of the graph.

refreshOnChainPrices() rebuilds dexPriceMap with balancerPoolTokens = null, so every refresh removes the Balancer-derived edges that were present during initLivePrices(). After the first rescan, any route that depends on Balancer disappears.

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

In `@frontend/widgets/arbitragebot/arbitragebot-model.ts` around lines 307 - 323,
refreshOnChainPrices() rebuilds the dex price map with balancerPoolTokens = null
which drops Balancer edges created in initLivePrices(); change
refreshOnChainPrices to pass the existing balancer pool tokens into
buildDexPriceMap instead of null (e.g., read the stored balancer pool tokens
used by initLivePrices from the model/globalStore or a property on this and pass
that value into buildDexPriceMap), so buildDexPriceMap receives the same
balancerPoolTokens used when the graph was first built and Balancer-derived
edges persist across rescans.
frontend/widgets/arbitragebot/arbitragebot-model.ts-87-109 (1)

87-109: ⚠️ Potential issue | 🟠 Major

Stop fabricating the DEX price table from fixed spreads.

dexPricesFromCache() still invents Uniswap/Balancer prices and liquidity from static offsets, while the arb engine uses buildDexPriceMap() from live/on-chain reads. The prices tab can therefore disagree with the routes/opportunities being presented as live.

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

In `@frontend/widgets/arbitragebot/arbitragebot-model.ts` around lines 87 - 109,
dexPricesFromCache currently fabricates DEX prices from static spreads and
liquidity; replace that behavior by wiring up the actual on-chain/live price
builder used by the arb engine: remove the hard-coded DEX_CONFIG and
tokenMap-driven fabrication inside dexPricesFromCache and instead delegate to or
call buildDexPriceMap (the same function the arb engine uses) or accept its
output as an injected parameter so the UI displays the real live/on-chain
DexPrice[] used by buildDexPriceMap; ensure you still filter out missing tokens
(base === 0) and preserve the DexPrice type shape when returning.
frontend/widgets/arbitragebot/arbitragebot-model.ts-195-249 (1)

195-249: ⚠️ Potential issue | 🟠 Major

Guard the full bootstrap path against upstream failures.

Only getHlAllMids() is wrapped in try/catch. A rejection from readGmxV1Price(), fetchBalancerPools(), or readBalancerPoolTokens() will make the constructor's void this.initLivePrices() fail as an unhandled rejection during widget startup.

🟡 Minor comments (18)
frontend/widgets/widgetbuilder/widgetbuilder.scss-783-783 (1)

783-783: ⚠️ Potential issue | 🟡 Minor

Use overflow-wrap instead of deprecated word-break: break-word.

The break-word value for word-break is deprecated. Use overflow-wrap: break-word for the same behavior with better browser support.

Proposed fix
     &__response-body {
         margin: 0;
         padding: 8px 10px;
         font-size: 10px;
         font-family: monospace;
         color: `#c7d2fe`;
         white-space: pre-wrap;
-        word-break: break-word;
+        overflow-wrap: break-word;
         max-height: 180px;
         overflow-y: auto;
         background: rgba(0, 0, 0, 0.25);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/widgetbuilder/widgetbuilder.scss` at line 783, Replace the
deprecated declaration "word-break: break-word" with the modern equivalent
"overflow-wrap: break-word" in widgetbuilder.scss — locate the CSS rule that
currently contains the "word-break: break-word;" line and swap it to use
"overflow-wrap: break-word" (keeping any other existing properties on that same
selector intact).
frontend/preview/previews/defi-widgets.preview.tsx-91-99 (1)

91-99: ⚠️ Potential issue | 🟡 Minor

Include all useMemo inputs in the dependency list.

entry.width and entry.height are used in the innerRect object (line 96) but missing from the dependency array, causing nodeModel to become stale when dimensions change for the same blockId.

🔧 Proposed fix
-        [entry.blockId]
+        [entry.blockId, entry.width, entry.height]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/preview/previews/defi-widgets.preview.tsx` around lines 91 - 99, The
useMemo for nodeModel calls makeMockNodeModel and uses entry.width and
entry.height in innerRect but the dependency array only lists entry.blockId, so
nodeModel can become stale; update the dependency array for the React.useMemo
that creates nodeModel to include entry.width and entry.height (in addition to
entry.blockId) so the memo recomputes when dimensions change.
frontend/widgets/containers/containers.scss-584-591 (1)

584-591: ⚠️ Potential issue | 🟡 Minor

Replace deprecated word-break: break-word value.

Same issue as above - use overflow-wrap: break-word instead.

🔧 Proposed fix
         pre {
             margin: 0;
             font-family: monospace;
             font-size: 11px;
             color: rgba(226, 232, 240, 0.85);
             white-space: pre-wrap;
-            word-break: break-word;
+            overflow-wrap: break-word;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/containers/containers.scss` around lines 584 - 591, In the
pre selector block (the rules defined for pre), replace the deprecated property
"word-break: break-word" with "overflow-wrap: break-word" (remove the old
word-break line and add overflow-wrap) so the pre{} rules use overflow-wrap for
line breaking while keeping the existing white-space and font settings intact.
frontend/widgets/containers/containers.scss-397-400 (1)

397-400: ⚠️ Potential issue | 🟡 Minor

Replace deprecated word-break: break-word value.

The value break-word for word-break is deprecated. Use overflow-wrap: break-word instead, which has better browser support and is the modern standard.

🔧 Proposed fix
     &__log-msg {
         color: rgba(226, 232, 240, 0.85);
-        word-break: break-word;
+        overflow-wrap: break-word;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/containers/containers.scss` around lines 397 - 400, The
.&__log-msg rule uses the deprecated word-break: break-word; replace it with
overflow-wrap: break-word (and optionally keep a safe fallback like word-break:
normal if you want explicit behavior) so update the &__log-msg selector to
remove the deprecated value and add overflow-wrap: break-word to ensure modern,
cross-browser line-wrapping behavior.
frontend/widgets/tradingalgobot/tradingalgobot.tsx-304-322 (1)

304-322: ⚠️ Potential issue | 🟡 Minor

Same duplicate key issue in PositionsTab.

The key={pos.symbol} pattern has the same potential issue here as in OverviewTab.

🔧 Suggested fix
-                            <div key={pos.symbol} className="table-row">
+                            <div key={pos.id ?? `${pos.symbol}-${idx}`} className="table-row">

Also update the map callback to include the index parameter.

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

In `@frontend/widgets/tradingalgobot/tradingalgobot.tsx` around lines 304 - 322,
The PositionsTab map is using non-unique keys (key={pos.symbol}) which can cause
duplicate key issues; update the positions.map callback to accept the index
parameter (e.g., positions.map((pos, index) => ...)) and use a stable unique key
such as combining symbol and index (for example `${pos.symbol}-${index}`) when
rendering the <div key=...> in the component to ensure keys are unique across
items.
frontend/widgets/tradingalgobot/tradingalgobot.tsx-147-159 (1)

147-159: ⚠️ Potential issue | 🟡 Minor

Potential duplicate key issue with pos.symbol.

Using pos.symbol as the React key assumes each position has a unique symbol. If the data can contain multiple positions with the same symbol (e.g., positions opened at different times), this will cause React reconciliation issues.

Consider using a unique identifier like a position ID instead:

🔧 Suggested fix
-                    {positions.map((pos) => (
-                        <div key={pos.symbol} className="table-row">
+                    {positions.map((pos, idx) => (
+                        <div key={pos.id ?? `${pos.symbol}-${idx}`} className="table-row">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/tradingalgobot/tradingalgobot.tsx` around lines 147 - 159,
The list rendering uses pos.symbol as the React key which can collide if
multiple positions share the same symbol; update the positions.map rendering
(the map callback that currently sets key={pos.symbol}) to use a truly unique
identifier (e.g., pos.id or a composite key like
`${pos.id}-${pos.openTimestamp}`) so each <div> has a stable, unique key; locate
the positions.map block in tradingalgobot.tsx and replace key={pos.symbol} with
the chosen unique field(s) (ensure the data model provides pos.id or add one
where positions are created).
frontend/widgets/tradingalgobot/tradingalgobot.tsx-304-319 (1)

304-319: ⚠️ Potential issue | 🟡 Minor

Guard against division by zero in ROE calculation.

If pos.entryPrice is zero (e.g., from malformed data), this line will produce Infinity or NaN, which will render poorly in the UI.

🛡️ Proposed fix
                     {positions.map((pos) => {
-                        const roe = ((pos.unrealizedPnl / (pos.size * pos.entryPrice)) * pos.leverage) * 100;
+                        const costBasis = pos.size * pos.entryPrice;
+                        const roe = costBasis > 0 ? ((pos.unrealizedPnl / costBasis) * pos.leverage) * 100 : 0;
                         return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/tradingalgobot/tradingalgobot.tsx` around lines 304 - 319,
In the positions.map block where you compute const roe = ((pos.unrealizedPnl /
(pos.size * pos.entryPrice)) * pos.leverage) * 100, guard against division by
zero or invalid input by checking that pos.entryPrice and pos.size are finite
and non-zero before performing the division (use Number.isFinite and > 0
checks); if the inputs are invalid, set roe to null (or NaN) and update the JSX
rendering of the ROE span to display a safe placeholder like "--" and avoid
prepending "+" when roe is not a finite number, while keeping the rest of the UI
output logic (pos.unrealizedPnl sign, classes) unchanged so the component
doesn't render Infinity/NaN.
frontend/widgets/codeeditor/codeeditor.tsx-186-196 (1)

186-196: ⚠️ Potential issue | 🟡 Minor

Generate button clears prompt without triggering AI generation.

The "Generate" button only calls setPrompt(""), discarding the user's input without invoking any AI generation logic from the model. This appears to be incomplete functionality.

🐛 Suggested fix to wire up generation
                     <button className="code-editor-widget__btn code-editor-widget__btn--primary" onClick={() => setPrompt("")}>
+                    <button
+                        className="code-editor-widget__btn code-editor-widget__btn--primary"
+                        onClick={() => {
+                            // TODO: Call model.generateCode(prompt) or similar
+                            setPrompt("");
+                        }}
+                    >
                         Generate
                     </button>

Would you like me to open an issue to track implementing the AI code generation functionality?

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

In `@frontend/widgets/codeeditor/codeeditor.tsx` around lines 186 - 196, The
Generate button currently only calls setPrompt("") which discards the user's
input; implement and wire a generation handler (e.g., handleGenerate) that
accepts the current prompt state, calls the AI/code-generation API or existing
generation function, handles loading/error states, and only clears the prompt
after a successful submit (or as desired). Replace the button onClick and the
input onKeyDown (Enter) to call this new handler instead of setPrompt(""), and
ensure the handler uses the prompt variable and updates any relevant UI state
(loading, result storage, error handling) so generation actually occurs.
frontend/widgets/ammliquidity/ammliquidity.tsx-362-362 (1)

362-362: ⚠️ Potential issue | 🟡 Minor

Add Liquidity button lacks onClick handler.

Similar to the position action buttons, this button has no functionality attached.

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

In `@frontend/widgets/ammliquidity/ammliquidity.tsx` at line 362, The Add
Liquidity button with className "add-liquidity-btn" is missing an onClick
handler; wire it to the component's add-liquidity action (e.g., call an existing
handleAddLiquidity or openAddLiquidityModal function) by adding onClick={() =>
handleAddLiquidity(...)} (or creating handleAddLiquidity in the same component
if absent) and ensure any required props/state (modal open setter, selectedPool,
etc.) are passed or imported; update the button element to include that onClick
and implement or reuse the handler logic used by the position action buttons for
consistency.
frontend/widgets/services/arbitrage-engine.ts-101-103 (1)

101-103: ⚠️ Potential issue | 🟡 Minor

Spread calculation semantics appear incorrect.

The spread is computed as max/min of [rAB, 1/rCA, rBC] — these are rates for different token pairs (A→B, C→A inverted, B→C). This doesn't represent a meaningful price spread across DEXes for the same pair.

If the intent is to measure cross-DEX price discrepancy for arbitrage viability, this should compare the same pair's prices across different DEXes.

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

In `@frontend/widgets/services/arbitrage-engine.ts` around lines 101 - 103, The
current spread calculation uses heterogeneous rates rAB, 1/rCA, and rBC together
(in function or block that defines allPrices and spread), which mixes different
pair directions and is semantically wrong; change the logic to compute spreads
per identical trading pair (i.e., gather prices for the same A→B direction from
all DEX sources or normalize rates to a common direction before comparing) and
then compute spread as (max/min - 1) for that set; update the variables used
around allPrices and spread (and any callers of spread) to use per-pair price
arrays or normalized rates so you're comparing like-for-like prices rather than
rAB, 1/rCA, rBC together.
frontend/widgets/services/coingecko.ts-9-15 (1)

9-15: ⚠️ Potential issue | 🟡 Minor

Missing request timeout could cause indefinite hangs.

The cgGet helper performs fetch without a timeout. If CoinGecko becomes unresponsive, this could cause requests to hang indefinitely.

🛡️ Proposed fix to add timeout
 async function cgGet<T>(path: string): Promise<T> {
     const res = await fetch(`${CG_BASE}${path}`, {
         headers: { Accept: "application/json" },
+        signal: AbortSignal.timeout(10000),
     });
     if (!res.ok) throw new Error(`CoinGecko API ${res.status}: ${res.statusText}`);
     return res.json() as Promise<T>;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/services/coingecko.ts` around lines 9 - 15, The cgGet helper
(function cgGet) currently calls fetch without a timeout and can hang; update
cgGet to use an AbortController to enforce a request timeout (e.g., configurable
constant or parameter, e.g., 5s), pass controller.signal into fetch, set a timer
to call controller.abort() after the timeout and clear the timer on success, and
ensure the thrown error from an aborted request is handled/rethrown with a clear
message (e.g., "CoinGecko request timed out") so callers can distinguish
timeouts from other failures.
frontend/widgets/tradingalgobot/tradingalgobot-model.ts-103-105 (1)

103-105: ⚠️ Potential issue | 🟡 Minor

Race condition: startRefresh() called before initLiveData() completes.

initLiveData() is async and sets dataSource to "live" on success. But startRefresh() is called immediately after, and its interval callback checks dataSource. If the interval fires before initLiveData completes, it may incorrectly call mockTick().

Consider awaiting initLiveData or starting the refresh only after initialization.

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

In `@frontend/widgets/tradingalgobot/tradingalgobot-model.ts` around lines 103 -
105, The code calls startRefresh() immediately after void this.initLiveData(),
causing a race where initLiveData() (which sets dataSource to "live") may not
complete before the interval fires and calls mockTick(); change the flow so
initLiveData() is awaited (or otherwise completes) before starting the periodic
refresh: ensure initLiveData() finishes and sets dataSource to "live" and only
then invoke startRefresh(), or move the interval-start logic
inside/init-completion callback so startRefresh() runs after successful
initLiveData() and the interval callback can safely rely on dataSource.
frontend/widgets/ammliquidity/ammliquidity.tsx-165-168 (1)

165-168: ⚠️ Potential issue | 🟡 Minor

Action buttons have no onClick handlers.

"Collect Fees" and "Remove Liquidity" buttons are rendered but have no click handlers, making them non-functional. Users may click expecting an action.

🛡️ Proposed fix to disable or add handlers
                         <div className="pos-actions">
-                            <button className="pos-action-btn collect">Collect Fees</button>
-                            <button className="pos-action-btn remove">Remove Liquidity</button>
+                            <button className="pos-action-btn collect" disabled title="Coming soon">Collect Fees</button>
+                            <button className="pos-action-btn remove" disabled title="Coming soon">Remove Liquidity</button>
                         </div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/ammliquidity/ammliquidity.tsx` around lines 165 - 168, The
two buttons in the AMMLiquidity component lack click handlers so they do
nothing; add handlers (e.g., handleCollectFees and handleRemoveLiquidity) and
wire them to the "Collect Fees" and "Remove Liquidity" buttons via onClick, or
accept and call props like onCollectFees and onRemoveLiquidity from the
component's props; also ensure proper disabled state/UI feedback when the
actions are not available (e.g., use a isCollectDisabled/isRemoveDisabled flag)
and keep the className "pos-action-btn collect" and "pos-action-btn remove"
intact when attaching the handlers.
frontend/widgets/ammliquidity/ammliquidity.tsx-263-263 (1)

263-263: ⚠️ Potential issue | 🟡 Minor

Swap button enabled when no valid swap preview exists.

When swapPreview is null (invalid input or no pool), the swap button remains enabled. Clicking would likely fail or do nothing.

🛡️ Proposed fix to disable button conditionally
-                            <button className="swap-execute-btn">Swap {inputToken} → {inputToken === pool.token0 ? pool.token1 : pool.token0}</button>
+                            <button className="swap-execute-btn" disabled={!swapPreview}>Swap {inputToken} → {inputToken === pool.token0 ? pool.token1 : pool.token0}</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/ammliquidity/ammliquidity.tsx` at line 263, The Swap button
is enabled even when no valid swapPreview exists; update the render of the
button with class "swap-execute-btn" (the Swap {inputToken} → ... button) to be
disabled when swapPreview is null/undefined (and optionally when preview
indicates invalid state), e.g., set the disabled prop based on "!swapPreview" or
a more specific check on swapPreview.valid and adjust any click handlers to
no-op when disabled so clicking cannot proceed without a valid preview; locate
the button rendering near the references to swapPreview, inputToken, and pool to
implement this conditional disable.
frontend/widgets/ammliquidity/ammliquidity-model.ts-100-106 (1)

100-106: ⚠️ Potential issue | 🟡 Minor

avgApy calculation is not a true APY.

The formula (fees / tvl) * 100 calculates a simple fee ratio, not annualized yield. True APY would require annualizing based on the time period of fee accrual.

📝 Suggested fix to rename or fix calculation
-    const avgApy = positions.length > 0 ? positions.reduce((s, p) => s + p.feesEarned, 0) / (totalTvl || 1) * 100 : 0;
+    // Simple fee ratio (not annualized) - consider renaming to avgFeeRatio
+    const avgApy = positions.length > 0 ? positions.reduce((s, p) => s + p.feesEarned, 0) / (totalTvl || 1) * 100 : 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/widgets/ammliquidity/ammliquidity-model.ts` around lines 100 - 106,
The value named avgApy in calcPoolStats is actually a simple fees-to-TVL
percent, not an annualized APY; change the name to reflect that (e.g.,
avgFeePercent or avgFeeYield) or implement proper annualization if you have
time/duration data; update calcPoolStats to compute avgFeePercent =
positions.length > 0 ? positions.reduce((s,p) => s + p.feesEarned,0) / (totalTvl
|| 1) * 100 : 0 and rename the returned property and all callers that reference
calcPoolStats.avgApy (or, if you choose to implement true APY, compute
per-position rates using a timestamp/duration field from UserLpPosition and
annualize before averaging).
pkg/databus/coingecko.go-67-75 (1)

67-75: ⚠️ Potential issue | 🟡 Minor

HTTP client has no timeout configured.

http.DefaultClient has no timeout by default. While http.NewRequestWithContext uses the context, if the parent context has no deadline (common for long-running fetchers), requests could hang indefinitely.

🛡️ Proposed fix to use a client with timeout
+var cgClient = &http.Client{
+    Timeout: 15 * time.Second,
+}
+
 func GetSimplePrices(ctx context.Context, symbols []string) (map[string]float64, error) {
     // ...
-    resp, err := http.DefaultClient.Do(req)
+    resp, err := cgClient.Do(req)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/databus/coingecko.go` around lines 67 - 75, The HTTP call in the
coingecko fetcher uses http.DefaultClient which has no timeout; replace usage of
http.DefaultClient.Do with a local http.Client configured with a sensible
Timeout (or derive a timeout via context before creating the request) so
requests cannot hang indefinitely—create a client variable (e.g., client :=
&http.Client{Timeout: 10 * time.Second}) and call client.Do(req) instead of
http.DefaultClient.Do(req), and ensure related imports (time) are added and any
tests updated accordingly.
frontend/widgets/services/blockchain.ts-12-21 (1)

12-21: ⚠️ Potential issue | 🟡 Minor

Add explorer coverage for every declared chain.

CHAIN_IDS exports ARBITRUM_NOVA, but explorerUrl() has no Nova mapping and silently falls back to Arbiscan, so Nova links are wrong.

Also applies to: 185-195

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

In `@frontend/widgets/services/blockchain.ts` around lines 12 - 21, The
explorerUrl function is missing a mapping for ARBITRUM_NOVA (and possibly other
chains declared in CHAIN_IDS), causing Nova addresses to fall back to Arbiscan;
update explorerUrl to include a specific entry for CHAIN_IDS.ARBITRUM_NOVA (and
verify mappings for BASE, AVALANCHE, BSC, etc. declared in CHAIN_IDS) so each
chain ID returns the correct block explorer base URL, and ensure any
switch/lookup logic uses the CHAIN_IDS constants rather than numeric literals to
avoid future mismatches.
frontend/widgets/mlmodel/mlmodel-model.ts-216-243 (1)

216-243: ⚠️ Potential issue | 🟡 Minor

TreeClassifier hyperparameters bypass validation.

validateHyperparams() covers GBM/LR/NN/RF/NumpyLogistics, but not TreeClassifier. Invalid tree_max_depth or tree_min_samples_split values will start training even though the other model types are guarded.

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

In `@frontend/widgets/mlmodel/mlmodel-model.ts` around lines 216 - 243,
validateHyperparams currently validates GBM, LR, NN-Adam, RF and NumpyLogistics
but omits TreeClassifier, allowing invalid tree hyperparameters to pass; update
validateHyperparams() to add a branch for modelType === "TreeClassifier" that
checks hp.tree_max_depth (e.g., ≥1 and ≤ a sensible upper bound) and
hp.tree_min_samples_split (e.g., ≥2) and push clear error messages (referencing
validateHyperparams, TreeClassifier, tree_max_depth, tree_min_samples_split) so
training is blocked when those values are invalid.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 461006b5-4d19-4eb1-afa2-23e7728c5ebe

📥 Commits

Reviewing files that changed from the base of the PR and between e6d83d7 and e7c22ed.

📒 Files selected for processing (51)
  • frontend/app/block/block.tsx
  • frontend/preview/mock/mockwaveenv.ts
  • frontend/preview/previews/defi-widgets.preview.tsx
  • frontend/widgets/REALTIME_DATA_TODO.md
  • frontend/widgets/ammliquidity/ammliquidity-model.ts
  • frontend/widgets/ammliquidity/ammliquidity.scss
  • frontend/widgets/ammliquidity/ammliquidity.tsx
  • frontend/widgets/arbitragebot/arbitragebot-model.ts
  • frontend/widgets/arbitragebot/arbitragebot.scss
  • frontend/widgets/arbitragebot/arbitragebot.tsx
  • frontend/widgets/codeeditor/codeeditor-model.ts
  • frontend/widgets/codeeditor/codeeditor.scss
  • frontend/widgets/codeeditor/codeeditor.tsx
  • frontend/widgets/containers/containers-model.ts
  • frontend/widgets/containers/containers.scss
  • frontend/widgets/containers/containers.tsx
  • frontend/widgets/defilending/defilending-model.ts
  • frontend/widgets/defilending/defilending.scss
  • frontend/widgets/defilending/defilending.tsx
  • frontend/widgets/flashloan/flashloan-model.ts
  • frontend/widgets/flashloan/flashloan.scss
  • frontend/widgets/flashloan/flashloan.tsx
  • frontend/widgets/index.ts
  • frontend/widgets/mlmodel/mlmodel-model.ts
  • frontend/widgets/mlmodel/mlmodel.scss
  • frontend/widgets/mlmodel/mlmodel.tsx
  • frontend/widgets/services/arbitrage-engine.ts
  • frontend/widgets/services/balancer.ts
  • frontend/widgets/services/blockchain.ts
  • frontend/widgets/services/coingecko.ts
  • frontend/widgets/services/hyperliquid.ts
  • frontend/widgets/services/index.ts
  • frontend/widgets/services/morpho.ts
  • frontend/widgets/services/rpc.ts
  • frontend/widgets/shellworkflow/shellworkflow-model.ts
  • frontend/widgets/shellworkflow/shellworkflow.scss
  • frontend/widgets/shellworkflow/shellworkflow.tsx
  • frontend/widgets/tradingalgobot/tradingalgobot-model.ts
  • frontend/widgets/tradingalgobot/tradingalgobot.scss
  • frontend/widgets/tradingalgobot/tradingalgobot.tsx
  • frontend/widgets/widgetbuilder/widgetbuilder-model.ts
  • frontend/widgets/widgetbuilder/widgetbuilder.scss
  • frontend/widgets/widgetbuilder/widgetbuilder.tsx
  • pkg/databus/bus.go
  • pkg/databus/coingecko.go
  • pkg/databus/hyperliquid.go
  • pkg/tsgen/tsgenevent.go
  • pkg/wconfig/defaultconfig/widgets.json
  • pkg/wps/wpstypes.go
  • pkg/wshrpc/wshrpctypes.go
  • tsconfig.json

selectedStrategyId = jotai.atom<string>("strat-aave-uni");
simulationResult = jotai.atom<SimulationResult | null>(null) as jotai.PrimitiveAtom<SimulationResult | null>;
isSimulating = jotai.atom<boolean>(false);
totalPortfolioValue = jotai.atom<number>(0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Portfolio math never leaves zero.

refreshPortfolio() seeds every asset with currentPct: 0 and value: 0, and totalPortfolioValue is never written anywhere. The header stays at $0, all rebalance trades compute as $0, and runSimulation() models only gas loss.

Also applies to: 159-162, 205-218, 224-239

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

In `@frontend/widgets/flashloan/flashloan-model.ts` at line 149, The portfolio
total never gets updated because totalPortfolioValue (jotai atom in
flashloan-model.ts) is never written to and refreshPortfolio() seeds assets with
value: 0 and currentPct: 0; update refreshPortfolio(), any balance/pricing
loaders it calls, and runSimulation() to compute each asset's value = balance *
price, sum those into a computed total and write that sum to totalPortfolioValue
(use the atom's write/update API), and then set each asset.currentPct =
asset.value / total; also ensure any places that seed assets (lines around
refreshPortfolio, the asset seeding code and the simulation initialization) use
those computed values instead of zeros so header, rebalance trade math, and
runSimulation() get real numbers.

Comment on lines +218 to +291
// Fixed durations per step type — realistic but deterministic
const STEP_DURATIONS: Record<StepType, number> = {
shell: 1400,
python: 2200,
http: 450,
condition: 200,
};

let delay = 0;
wf.steps.forEach((step, idx) => {
const stepDuration = STEP_DURATIONS[step.type];

const runningTimer = setTimeout(() => {
this.updateStep(workflowId, step.id, { status: "running" });
}, delay);
this.runTimers.push(runningTimer);

const capturedDelay = delay + stepDuration;
if (step.type === "http" || step.type === "shell" || step.type === "python") {
const startMs = delay;
const execTimer = setTimeout(async () => {
const { success, output, durationMs } =
step.type === "http"
? await this.executeHttpStep(step)
: await this.executeShellStep(step);
const status: StepStatus = success ? "success" : "error";
this.updateStep(workflowId, step.id, { status });
const entry: OutputEntry = {
id: `out-${Date.now()}-${idx}`,
stepName: step.name,
workflowName: wf.name,
timestamp: Date.now(),
duration: durationMs,
exitCode: success ? 0 : 1,
stdout: output,
status,
expanded: false,
};
const prev = globalStore.get(this.outputHistory);
globalStore.set(this.outputHistory, [entry, ...prev]);
if (idx === wf.steps.length - 1) {
this.finalizeWorkflow(workflowId);
}
}, startMs);
this.runTimers.push(execTimer);
} else {
// condition step — evaluate synchronously
const doneTimer = setTimeout(() => {
const resolvedCommand = this.substituteVariables(step.command);
const output = `[condition] ${resolvedCommand} → true`;
const status: StepStatus = "success";
this.updateStep(workflowId, step.id, { status });
const entry: OutputEntry = {
id: `out-${Date.now()}-${idx}`,
stepName: step.name,
workflowName: wf.name,
timestamp: Date.now(),
duration: stepDuration,
exitCode: 0,
stdout: output,
status,
expanded: false,
};
const prev = globalStore.get(this.outputHistory);
globalStore.set(this.outputHistory, [entry, ...prev]);
if (idx === wf.steps.length - 1) {
this.finalizeWorkflow(workflowId);
}
}, capturedDelay);
this.runTimers.push(doneTimer);
}

delay += stepDuration;
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Run workflow steps sequentially, not on precomputed timers.

All steps are scheduled up front using fixed durations. A slow fetch() or terminal command can still be running while later steps start, and finalizeWorkflow() fires when the last indexed timer completes rather than when all prior async work has actually finished.

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

In `@frontend/widgets/shellworkflow/shellworkflow-model.ts` around lines 218 -
291, The current implementation pre-schedules every step with setTimeouts (using
STEP_DURATIONS and runTimers) which can start later steps before earlier async
work actually finishes; change the logic to run steps sequentially instead:
iterate wf.steps in an async function, for each step call updateStep(workflowId,
step.id, {status: "running"}), then await the actual work (await
this.executeHttpStep/this.executeShellStep or evaluate condition synchronously),
create and push the OutputEntry after that await, and only after the loop
completes call this.finalizeWorkflow(workflowId); keep STEP_DURATIONS for
simulated waits by awaiting a delay promise (e.g., await new
Promise(r=>setTimeout(r, STEP_DURATIONS[step.type]))) if you still want pacing,
and remove the precomputed captured timers pushed into this.runTimers (or
replace with cancelable promise handles) so finalizeWorkflow is fired only after
real async tasks finish.

Copilot AI and others added 2 commits March 27, 2026 08:38
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/f71aa08e-662f-4917-a07f-394e6aa94862

Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
ci: deploy preview app to GitHub Pages at /preview/
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (1)
.github/workflows/deploy-docsite.yml (1)

22-25: Consider adding frontend/preview/** to the PR trigger paths.

The workflow now builds frontend/preview, but changes to that directory won't trigger the workflow on PRs. If someone modifies the preview app, the build could break on merge to main without prior CI validation.

Proposed fix
         paths:
             - "docs/**"
             - ".github/workflows/deploy-docsite.yml"
             - "Taskfile.yml"
+            - "frontend/preview/**"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/deploy-docsite.yml around lines 22 - 25, The PR trigger
paths for the deploy-docsite workflow omit the frontend preview app; update the
paths section in .github/workflows/deploy-docsite.yml to include
"frontend/preview/**" so changes under the preview app will trigger the workflow
on PRs — locate the paths list shown (currently containing "docs/**",
".github/workflows/deploy-docsite.yml", "Taskfile.yml") and add
"frontend/preview/**" alongside the existing entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/deploy-docsite.yml:
- Around line 22-25: The PR trigger paths for the deploy-docsite workflow omit
the frontend preview app; update the paths section in
.github/workflows/deploy-docsite.yml to include "frontend/preview/**" so changes
under the preview app will trigger the workflow on PRs — locate the paths list
shown (currently containing "docs/**", ".github/workflows/deploy-docsite.yml",
"Taskfile.yml") and add "frontend/preview/**" alongside the existing entries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f057c5ad-c219-4751-ab29-effa5005f267

📥 Commits

Reviewing files that changed from the base of the PR and between e7c22ed and 5f29b1d.

📒 Files selected for processing (1)
  • .github/workflows/deploy-docsite.yml

Copilot AI and others added 2 commits March 27, 2026 08:51
fix: correct Vite base path and asset hrefs for GitHub Pages deployment
@sawka
Copy link
Copy Markdown
Member

sawka commented Mar 27, 2026

No idea what this is. No title, and doesn't follow the contribution guidelines at all.

@sawka sawka closed this Mar 27, 2026
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.

5 participants