Conversation
Merge to main
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
… widgets Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com> Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/4b105e77-2a52-47cf-8b61-1c2c78970529
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>
Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com> Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/6e0f3b7e-91d5-43c1-8466-64f2277ac5ab
…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
|
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
WalkthroughThe 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)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
|
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/f5928aa4-aca7-4331-a9e8-e5ff5d61f3f8 Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
There was a problem hiding this comment.
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 | 🟠 MajorPrice impact calculation assumes constant-product AMM, but Balancer uses weighted pools.
The
calcPriceImpactfunction uses thek = r0 * r1constant-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 | 🟠 MajorSSRF risk: Arbitrary URL fetch without validation.
The
sendHttpRequestmethod 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 | 🟠 MajorPOST/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 | 🟠 MajorNormalize 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 | 🟠 MajorDemo mode never populates demo containers.
In the Docker failure path you only switch flags.
containersstays 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 | 🟠 MajorLive container actions only change local UI state.
toggleContainerStatus(),restartContainer(), andremoveContainer()never call Docker whendockerAvailableis 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 | 🟠 MajorThe refresh affordance currently does nothing.
The header button and the 10-second interval both call
refreshRates(), but the method is empty andrateHistoryis 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 | 🟠 MajorTTL 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 | 🟠 MajorBound
ethCall()latency.All higher-level readers rely on
catchto degrade tonull, 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 | 🟠 MajorDon't mark runs successful before a process actually completes.
The terminal path clears
isRunningand recordsexitCode: 0as soon as input is written, and the no-terminal path also appends a successfulRunRecordeven though nothing executed. That makes run history and metrics misleading, andstopRun()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 | 🟠 MajorAdd a timeout around
hlPost().Every API method funnels through this helper, but the fetch never aborts. If Hyperliquid hangs, callers like
fetchRecentOhlcv()andfetchMidPrice()never reach their fallbackcatch, 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 | 🟠 MajorDon't hard-code borrow capacity to 80% of collateral.
availableToBorrowignores the actual weighted collateral limits and replaces them with a flat0.8multiplier. 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 | 🟠 MajorOnly 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 onlypythonandshellwith 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 intraining.This path flips the existing model to
training, thenstartTraining()completes by appending a brand-new model instead of updating that record. The original entry never returns totrained, 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 | 🟠 MajorNormalize
ETH/WETHbefore building the route graph.Hyperliquid/Balancer edges are keyed as
ETH, but GMX edges are keyed asWETH. 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. Returningsuccess: true/exitCode: 0here means failed shell or Python commands still look successful, and$ ${command}also writes any substituted secret values intooutputHistory.🤖 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 | 🟠 MajorPeriodic rescans drop Balancer out of the graph.
refreshOnChainPrices()rebuildsdexPriceMapwithbalancerPoolTokens = null, so every refresh removes the Balancer-derived edges that were present duringinitLivePrices(). 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 | 🟠 MajorStop fabricating the DEX price table from fixed spreads.
dexPricesFromCache()still invents Uniswap/Balancer prices and liquidity from static offsets, while the arb engine usesbuildDexPriceMap()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 | 🟠 MajorGuard the full bootstrap path against upstream failures.
Only
getHlAllMids()is wrapped intry/catch. A rejection fromreadGmxV1Price(),fetchBalancerPools(), orreadBalancerPoolTokens()will make the constructor'svoid 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 | 🟡 MinorUse
overflow-wrapinstead of deprecatedword-break: break-word.The
break-wordvalue forword-breakis deprecated. Useoverflow-wrap: break-wordfor 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 | 🟡 MinorInclude all
useMemoinputs in the dependency list.
entry.widthandentry.heightare used in theinnerRectobject (line 96) but missing from the dependency array, causingnodeModelto become stale when dimensions change for the sameblockId.🔧 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 | 🟡 MinorReplace deprecated
word-break: break-wordvalue.Same issue as above - use
overflow-wrap: break-wordinstead.🔧 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 | 🟡 MinorReplace deprecated
word-break: break-wordvalue.The value
break-wordforword-breakis deprecated. Useoverflow-wrap: break-wordinstead, 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 | 🟡 MinorSame duplicate key issue in PositionsTab.
The
key={pos.symbol}pattern has the same potential issue here as inOverviewTab.🔧 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 | 🟡 MinorPotential duplicate key issue with
pos.symbol.Using
pos.symbolas 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 | 🟡 MinorGuard against division by zero in ROE calculation.
If
pos.entryPriceis zero (e.g., from malformed data), this line will produceInfinityorNaN, 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 | 🟡 MinorGenerate 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 | 🟡 MinorAdd 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 | 🟡 MinorSpread calculation semantics appear incorrect.
The spread is computed as
max/minof[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 | 🟡 MinorMissing request timeout could cause indefinite hangs.
The
cgGethelper performsfetchwithout 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 | 🟡 MinorRace condition:
startRefresh()called beforeinitLiveData()completes.
initLiveData()is async and setsdataSourceto"live"on success. ButstartRefresh()is called immediately after, and its interval callback checksdataSource. If the interval fires beforeinitLiveDatacompletes, it may incorrectly callmockTick().Consider awaiting
initLiveDataor 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 | 🟡 MinorAction 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 | 🟡 MinorSwap button enabled when no valid swap preview exists.
When
swapPreviewis 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
avgApycalculation is not a true APY.The formula
(fees / tvl) * 100calculates 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 | 🟡 MinorHTTP client has no timeout configured.
http.DefaultClienthas no timeout by default. Whilehttp.NewRequestWithContextuses 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 | 🟡 MinorAdd explorer coverage for every declared chain.
CHAIN_IDSexportsARBITRUM_NOVA, butexplorerUrl()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
TreeClassifierhyperparameters bypass validation.
validateHyperparams()covers GBM/LR/NN/RF/NumpyLogistics, but notTreeClassifier. Invalidtree_max_depthortree_min_samples_splitvalues 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
📒 Files selected for processing (51)
frontend/app/block/block.tsxfrontend/preview/mock/mockwaveenv.tsfrontend/preview/previews/defi-widgets.preview.tsxfrontend/widgets/REALTIME_DATA_TODO.mdfrontend/widgets/ammliquidity/ammliquidity-model.tsfrontend/widgets/ammliquidity/ammliquidity.scssfrontend/widgets/ammliquidity/ammliquidity.tsxfrontend/widgets/arbitragebot/arbitragebot-model.tsfrontend/widgets/arbitragebot/arbitragebot.scssfrontend/widgets/arbitragebot/arbitragebot.tsxfrontend/widgets/codeeditor/codeeditor-model.tsfrontend/widgets/codeeditor/codeeditor.scssfrontend/widgets/codeeditor/codeeditor.tsxfrontend/widgets/containers/containers-model.tsfrontend/widgets/containers/containers.scssfrontend/widgets/containers/containers.tsxfrontend/widgets/defilending/defilending-model.tsfrontend/widgets/defilending/defilending.scssfrontend/widgets/defilending/defilending.tsxfrontend/widgets/flashloan/flashloan-model.tsfrontend/widgets/flashloan/flashloan.scssfrontend/widgets/flashloan/flashloan.tsxfrontend/widgets/index.tsfrontend/widgets/mlmodel/mlmodel-model.tsfrontend/widgets/mlmodel/mlmodel.scssfrontend/widgets/mlmodel/mlmodel.tsxfrontend/widgets/services/arbitrage-engine.tsfrontend/widgets/services/balancer.tsfrontend/widgets/services/blockchain.tsfrontend/widgets/services/coingecko.tsfrontend/widgets/services/hyperliquid.tsfrontend/widgets/services/index.tsfrontend/widgets/services/morpho.tsfrontend/widgets/services/rpc.tsfrontend/widgets/shellworkflow/shellworkflow-model.tsfrontend/widgets/shellworkflow/shellworkflow.scssfrontend/widgets/shellworkflow/shellworkflow.tsxfrontend/widgets/tradingalgobot/tradingalgobot-model.tsfrontend/widgets/tradingalgobot/tradingalgobot.scssfrontend/widgets/tradingalgobot/tradingalgobot.tsxfrontend/widgets/widgetbuilder/widgetbuilder-model.tsfrontend/widgets/widgetbuilder/widgetbuilder.scssfrontend/widgets/widgetbuilder/widgetbuilder.tsxpkg/databus/bus.gopkg/databus/coingecko.gopkg/databus/hyperliquid.gopkg/tsgen/tsgenevent.gopkg/wconfig/defaultconfig/widgets.jsonpkg/wps/wpstypes.gopkg/wshrpc/wshrpctypes.gotsconfig.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); |
There was a problem hiding this comment.
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.
| // 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; | ||
| }); |
There was a problem hiding this comment.
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.
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/
There was a problem hiding this comment.
🧹 Nitpick comments (1)
.github/workflows/deploy-docsite.yml (1)
22-25: Consider addingfrontend/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 tomainwithout 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
📒 Files selected for processing (1)
.github/workflows/deploy-docsite.yml
Agent-Logs-Url: https://github.com/somatothing/waveterm/sessions/55c4ed89-5589-4d18-979d-9d1c6be50e06 Co-authored-by: somatothing <187345895+somatothing@users.noreply.github.com>
fix: correct Vite base path and asset hrefs for GitHub Pages deployment
|
No idea what this is. No title, and doesn't follow the contribution guidelines at all. |
Merge, please!