From 69e1b12d63543040e654b9eab8ff4c4d74fd67b4 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 6 May 2026 04:18:48 +0000 Subject: [PATCH] refactor(web): normalize advanced theme surfaces --- web/src/components/ChatControl.tsx | 521 ++++++++-------- web/src/components/PresetMarketplace.tsx | 590 ++++++------------ web/src/components/layout/Header.dom.test.tsx | 13 +- .../strategy/CodeStrategyEditor.tsx | 97 ++- .../components/strategy/VisualRuleBuilder.tsx | 61 +- web/src/views/ScreenerView.dom.test.tsx | 4 +- web/src/views/SettingsView.dom.test.tsx | 2 +- 7 files changed, 546 insertions(+), 742 deletions(-) diff --git a/web/src/components/ChatControl.tsx b/web/src/components/ChatControl.tsx index f124ef3..de00500 100644 --- a/web/src/components/ChatControl.tsx +++ b/web/src/components/ChatControl.tsx @@ -5,9 +5,11 @@ import { getPlatformAccessToken } from '../lib/authSession'; import { createRequestId } from '../../../shared/request-id.js'; import { Send, X, Bot, User, - Check, Loader2, - Zap, Copy -} from 'lucide-react'; + Check, Loader2, + Zap, Copy +} from 'lucide-react'; +import { Button } from './ui/button'; +import { cn } from '../lib/utils'; interface ChatMessage { id: number; @@ -297,17 +299,31 @@ export const ChatControl = ({ profiles, onApplyProfile }: ChatControlProps) => { navigator.clipboard.writeText(JSON.stringify(data, null, 2)); }; - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !e.shiftKey) { - e.preventDefault(); - sendMessage(); - } - }; - - // Floating robot button - bottom right corner (portaled to body to avoid parent CSS issues) - if (!isOpen) { - return createPortal( - + + + + + ); +}; + +export const PresetMarketplace: React.FC = ({ onSelect, onClose }) => { + const [customPresets, setCustomPresets] = useState([]); + + useEffect(() => { const fetchCustomPresets = async () => { try { const data = await fetchMarketplacePresets(); @@ -255,147 +164,60 @@ export const PresetMarketplace: React.FC = ({ onSelect, typicalTradesPerDay: d.typical_trades_per_day, performanceTag: d.performance_tag, isPopular: d.is_popular, - strategy_config: d.strategy_config + strategy_config: d.strategy_config, })); - setCustomPresets(mappedData as any); + setCustomPresets(mappedData as StrategyPreset[]); } catch (e) { console.error('Error fetching marketplace presets:', e); } - }; - - fetchCustomPresets(); - }, []); - - const allPresets = [...STRATEGY_PRESETS, ...customPresets]; - - return ( -
- {/* Premium Header Alignment Fix */} -
-
- -
- - {/* Removed indenting line for perfect optical left-alignment */} -
- QUANTITATIVE REPOSITORY -
-
- -
-
-

- Strategy
- Marketplace -

-

- Institutional-grade algorithm DNA for automated retail deployment. -

-
- -
-
- Profiles - {allPresets.length} -
- {onClose && ( - - )} -
-
-
- - {/* Grid Layout - Perfectly Symmetrical */} -
- {allPresets.map((preset, idx) => ( - - ))} - - {/* DNA Loader Placeholder - Aligned Center */} -
- - Analyzing DNA - Verification queue currently active for new strategies. -
-
- - -
- ); -}; + }; + + void fetchCustomPresets(); + }, []); + + const allPresets = useMemo(() => [...STRATEGY_PRESETS, ...customPresets], [customPresets]); + + return ( +
+
+ +
+
{allPresets.length} presets
+ {onClose ? ( + + ) : null} +
+
+ +
+ {allPresets.map((preset, idx) => ( + + ))} + + + +
+ +
+
+
+ Analyzing DNA +
+

+ Verification queue is active for new marketplace strategies. +

+
+
+
+
+
+ ); +}; diff --git a/web/src/components/layout/Header.dom.test.tsx b/web/src/components/layout/Header.dom.test.tsx index 1052ee8..c2da8a9 100644 --- a/web/src/components/layout/Header.dom.test.tsx +++ b/web/src/components/layout/Header.dom.test.tsx @@ -3,6 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, fireEvent, render, screen } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { AppContext, type AppContextValue } from '../../context/AppContext'; +import { ThemeProvider } from '../theme/ThemeProvider'; import { Header } from './Header'; const { fetchMarketIndicesMock } = vi.hoisted(() => ({ @@ -45,11 +46,13 @@ function setVisibilityState(value: DocumentVisibilityState) { function renderHeader() { return render( - - -
- - , + + + +
+ + + , ); } diff --git a/web/src/components/strategy/CodeStrategyEditor.tsx b/web/src/components/strategy/CodeStrategyEditor.tsx index 91a1482..ffac343 100644 --- a/web/src/components/strategy/CodeStrategyEditor.tsx +++ b/web/src/components/strategy/CodeStrategyEditor.tsx @@ -9,6 +9,8 @@ import { createTradeProfile } from '../../lib/profileApi'; import '../../lib/monacoLocalWorkers'; import { tradingRuntime } from '../../lib/runtime'; import { createRequestId } from '../../../../shared/request-id.js'; +import { Button } from '../ui/button'; +import { Card, CardContent } from '../ui/card'; const DEFAULT_TEMPLATE = `/** * Custom Trading Strategy @@ -197,42 +199,30 @@ export function CodeStrategyEditor({ return (
{/* Toolbar */} -
- +
+ Code Editor — {symbol} - + Cmd/Ctrl-S save · Cmd/Ctrl-Enter backtest
- - + - + - + +
{/* Monaco editor */} -
+
}> - {error} -
+ + + {error} + + )} {/* Backtest results */} {result && ( -
-
+ + +
Backtest Results
@@ -289,13 +275,13 @@ export function CodeStrategyEditor({ ['Max Drawdown', fmt(result.maxDrawdown, '%')], ].map(([label, val]) => (
-
+
{label}
-
{val}
+
{val}
))}
@@ -308,19 +294,19 @@ export function CodeStrategyEditor({
- + {['Date','Side','Price','Qty','P&L'].map(h => ( - + ))} {result.tradeLog.slice(-10).map((t: any, i: number) => ( - - + + - - + + @@ -330,7 +316,8 @@ export function CodeStrategyEditor({
{h}{h}
{t.date ?? '—'}
{t.date ?? '—'} {t.side}{t.price != null ? `$${t.price.toFixed(2)}` : '—'}{t.qty ?? '—'}{t.price != null ? `$${t.price.toFixed(2)}` : '—'}{t.qty ?? '—'} = 0 ? '#16A34A' : '#DC2626', fontWeight: 600 }}> {t.pnl != null ? `${t.pnl >= 0 ? '+' : ''}$${t.pnl.toFixed(2)}` : '—'}
)} -
+ + )}
); @@ -346,8 +333,8 @@ function CodeEditorFallback() { display: 'flex', alignItems: 'center', justifyContent: 'center', - background: 'linear-gradient(135deg, #F8FAFC, #EEF2FF)', - color: '#4B5563', + background: 'var(--hero-gradient)', + color: 'var(--muted-foreground)', fontSize: 13, fontWeight: 700, }} @@ -356,13 +343,3 @@ function CodeEditorFallback() {
); } - -function toolBtn(bg: string, color: string, border: string): React.CSSProperties { - return { - display: 'flex', alignItems: 'center', gap: 5, - padding: '7px 12px', border: `1px solid ${border}`, - borderRadius: 8, background: bg, color, - fontSize: 12, fontWeight: 600, cursor: 'pointer', - fontFamily: 'inherit', - }; -} diff --git a/web/src/components/strategy/VisualRuleBuilder.tsx b/web/src/components/strategy/VisualRuleBuilder.tsx index 19113b3..4947616 100644 --- a/web/src/components/strategy/VisualRuleBuilder.tsx +++ b/web/src/components/strategy/VisualRuleBuilder.tsx @@ -19,6 +19,8 @@ import { } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import { GripVertical, Plus, Trash2, Save, Play } from 'lucide-react'; +import { Button } from '../ui/button'; +import { Card, CardContent } from '../ui/card'; // ─── Types ──────────────────────────────────────────────────────────────────── export type Indicator = 'RSI' | 'MACD' | 'EMA_50' | 'EMA_200' | 'Price' | 'Volume'; @@ -88,9 +90,9 @@ function RuleCard({ transform: CSS.Transform.toString(transform), transition, opacity: isDragging ? 0.55 : 1, - background: '#fff', - border: '1px solid #E5E7EB', - borderRadius: 10, + background: 'var(--card)', + border: '1px solid var(--border)', + borderRadius: 14, padding: '12px 14px', display: 'flex', alignItems: 'center', @@ -100,8 +102,8 @@ function RuleCard({ }; const sel: React.CSSProperties = { - border: '1px solid #E5E7EB', borderRadius: 6, padding: '5px 8px', - fontSize: 12, background: '#F9FAFB', cursor: 'pointer', color: '#374151', + border: '1px solid var(--border)', borderRadius: 10, padding: '6px 10px', + fontSize: 12, background: 'var(--input)', cursor: 'pointer', color: 'var(--foreground)', fontFamily: 'inherit', }; const numInp: React.CSSProperties = { @@ -292,55 +294,47 @@ export function VisualRuleBuilder({ symbol, onSave, onBacktest }: Props) { {/* Header row */}
-
Strategy name
+
Strategy name
setName(e.target.value)} style={{ - border: '1px solid #E5E7EB', borderRadius: 8, + border: '1px solid var(--border)', borderRadius: 12, padding: '7px 12px', fontSize: 14, fontWeight: 600, - color: '#111827', background: '#fff', fontFamily: 'inherit', + color: 'var(--foreground)', background: 'var(--input)', fontFamily: 'inherit', outline: 'none', width: 260, }} />
{savedMsg && ( - {savedMsg} + {savedMsg} )} {onBacktest && ( - + )} - +
{/* Column headers */}
IF Indicator @@ -372,8 +366,8 @@ export function VisualRuleBuilder({ symbol, onSave, onBacktest }: Props) { onClick={() => setRules(prev => [...prev, makeRule()])} style={{ display: 'flex', alignItems: 'center', gap: 7, - width: '100%', padding: '10px 0', border: '1px dashed #D1D5DB', - borderRadius: 10, background: 'transparent', color: '#6B7280', + width: '100%', padding: '10px 0', border: '1px dashed var(--border-strong)', + borderRadius: 14, background: 'transparent', color: 'var(--muted-foreground)', fontSize: 13, fontWeight: 600, cursor: 'pointer', justifyContent: 'center', marginTop: 4, }} @@ -383,20 +377,19 @@ export function VisualRuleBuilder({ symbol, onSave, onBacktest }: Props) { {/* Rule summary */} {rules.length > 0 && ( -
-
+ + +
Strategy Preview — {symbol}
{rules.map((r, i) => ( -
+
{i + 1}. IF {INDICATOR_LABELS[r.indicator]} {CONDITION_LABELS[r.condition]} {r.value} {' → '}{r.action} {r.quantity} {r.quantityType === 'percent' ? `% of capital` : 'shares'}
))} -
+
+
)}
); diff --git a/web/src/views/ScreenerView.dom.test.tsx b/web/src/views/ScreenerView.dom.test.tsx index cda5755..0f3f03f 100644 --- a/web/src/views/ScreenerView.dom.test.tsx +++ b/web/src/views/ScreenerView.dom.test.tsx @@ -66,8 +66,8 @@ describe('ScreenerView sector filters', () => { expect(moreSectors).toHaveValue('Energy'); expect(moreSectors).toHaveStyle({ - background: '#EFF6FF', - color: '#2563EB', + background: 'var(--accent-soft)', + color: 'var(--primary)', fontWeight: '700', }); await waitFor(() => expect(globalThis.fetch).toHaveBeenCalledTimes(2)); diff --git a/web/src/views/SettingsView.dom.test.tsx b/web/src/views/SettingsView.dom.test.tsx index b449406..18693d0 100644 --- a/web/src/views/SettingsView.dom.test.tsx +++ b/web/src/views/SettingsView.dom.test.tsx @@ -51,7 +51,7 @@ describe('SettingsView legacy surface contrast', () => { const surface = container.querySelector('.settings-legacy-surface') as HTMLDivElement; expect(surface).toBeInTheDocument(); - expect(surface).toHaveStyle({ color: '#F9FAFB' }); + expect(surface).toHaveStyle({ color: 'var(--foreground)' }); expect(screen.getByText('Account settings content')).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'Bot Config' }));