diff --git a/web/src/components/strategy/CodeStrategyEditor.dom.test.tsx b/web/src/components/strategy/CodeStrategyEditor.dom.test.tsx
index 0057e17..e171ef3 100644
--- a/web/src/components/strategy/CodeStrategyEditor.dom.test.tsx
+++ b/web/src/components/strategy/CodeStrategyEditor.dom.test.tsx
@@ -49,7 +49,7 @@ describe('CodeStrategyEditor save behavior', () => {
const user = userEvent.setup();
render();
- fireEvent.change(screen.getByLabelText('strategy code'), {
+ fireEvent.change(await screen.findByLabelText('strategy code'), {
target: { value: 'function strategy() { return { signal: "HOLD" }; }' },
});
await user.click(screen.getByRole('button', { name: /^save$/i }));
@@ -99,7 +99,7 @@ describe('CodeStrategyEditor save behavior', () => {
it('saves with Cmd/Ctrl-S while focused in the editor', async () => {
render();
- fireEvent.keyDown(screen.getByLabelText('strategy code'), { key: 's', metaKey: true });
+ fireEvent.keyDown(await screen.findByLabelText('strategy code'), { key: 's', metaKey: true });
await waitFor(() => expect(createTradeProfileMock).toHaveBeenCalledTimes(1));
});
@@ -112,7 +112,7 @@ describe('CodeStrategyEditor save behavior', () => {
vi.stubGlobal('fetch', fetchMock);
render();
- fireEvent.keyDown(screen.getByLabelText('strategy code'), { key: 'Enter', metaKey: true });
+ fireEvent.keyDown(await screen.findByLabelText('strategy code'), { key: 'Enter', metaKey: true });
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
expect(String(fetchMock.mock.calls[0][0])).toBe('https://trading.test/api/backtest/run');
diff --git a/web/src/components/strategy/CodeStrategyEditor.tsx b/web/src/components/strategy/CodeStrategyEditor.tsx
index d898ed6..1909a43 100644
--- a/web/src/components/strategy/CodeStrategyEditor.tsx
+++ b/web/src/components/strategy/CodeStrategyEditor.tsx
@@ -2,8 +2,7 @@
* Monaco-based code strategy editor.
* Users write a JS strategy function; "Run Backtest" posts it to /api/backtest.
*/
-import { useCallback, useEffect, useRef, useState } from 'react';
-import Editor from '@monaco-editor/react';
+import { Suspense, lazy, useCallback, useEffect, useRef, useState } from 'react';
import { Play, Save, Copy, RotateCcw } from 'lucide-react';
import { getPlatformAccessToken } from '../../lib/authSession';
import { createTradeProfile } from '../../lib/profileApi';
@@ -40,6 +39,8 @@ function strategy({ symbol, price, rsi, ema50, ema200, macd, volume }) {
}
`;
+const MonacoEditor = lazy(() => import('@monaco-editor/react'));
+
interface BacktestResult {
trades?: number;
winRate?: number;
@@ -231,29 +232,31 @@ export function CodeStrategyEditor({
{/* Monaco editor */}
- {
- setCode(v ?? '');
- setSaved(false);
- }}
- theme="light"
- options={{
- fontSize: 13,
- minimap: { enabled: false },
- scrollBeyondLastLine: false,
- wordWrap: 'on',
- lineNumbers: 'on',
- renderLineHighlight: 'gutter',
- padding: { top: 12, bottom: 12 },
- fontFamily: '"Fira Code", "Cascadia Code", "Consolas", monospace',
- fontLigatures: true,
- tabSize: 2,
- automaticLayout: true,
- }}
- />
+ }>
+ {
+ setCode(v ?? '');
+ setSaved(false);
+ }}
+ theme="light"
+ options={{
+ fontSize: 13,
+ minimap: { enabled: false },
+ scrollBeyondLastLine: false,
+ wordWrap: 'on',
+ lineNumbers: 'on',
+ renderLineHighlight: 'gutter',
+ padding: { top: 12, bottom: 12 },
+ fontFamily: '"Fira Code", "Cascadia Code", "Consolas", monospace',
+ fontLigatures: true,
+ tabSize: 2,
+ automaticLayout: true,
+ }}
+ />
+
{/* Error */}
@@ -332,6 +335,27 @@ export function CodeStrategyEditor({
);
}
+function CodeEditorFallback() {
+ return (
+
+ Loading code editor…
+
+ );
+}
+
function toolBtn(bg: string, color: string, border: string): React.CSSProperties {
return {
display: 'flex', alignItems: 'center', gap: 5,
diff --git a/web/src/views/ResearchView.tsx b/web/src/views/ResearchView.tsx
index 52ed22e..4bb7299 100644
--- a/web/src/views/ResearchView.tsx
+++ b/web/src/views/ResearchView.tsx
@@ -1,15 +1,18 @@
-import { useState } from 'react';
+import { Suspense, lazy, useState } from 'react';
import { useAppContext } from '../context/AppContext';
import { SignalsTab } from '../tabs/SignalsTab';
import { BacktestTab } from '../tabs/BacktestTab';
import { MyStrategiesTab } from '../tabs/MyStrategiesTab';
import { VisualRuleBuilder, type VisualRule } from '../components/strategy/VisualRuleBuilder';
-import { CodeStrategyEditor } from '../components/strategy/CodeStrategyEditor';
import { createTradeProfile } from '../lib/profileApi';
import { BacktestRunnerPanel } from '../backtest/components/BacktestRunnerPanel';
type ResearchTab = 'Strategies' | 'Visual Builder' | 'Code Editor' | 'Signals' | 'Backtesting';
+const CodeStrategyEditor = lazy(() =>
+ import('../components/strategy/CodeStrategyEditor').then(module => ({ default: module.CodeStrategyEditor })),
+);
+
function buildVisualStrategyConfig(rules: VisualRule[]) {
return {
type: 'visual',
@@ -148,11 +151,13 @@ export function ResearchView() {
Write a custom strategy function in JavaScript. Click "Run Backtest" to test it against historical data.
-
+ }>
+
+
)}
@@ -166,3 +171,26 @@ export function ResearchView() {
);
}
+
+function CodeStrategyEditorFallback() {
+ return (
+
+ Loading code strategy editor…
+
+ );
+}