Web Redesign — Systematic Audit & Fix Plan
Scope: everything introduced by commits f62c3b1 (UI shell + 6 endpoints) and 938ed86
(live data wiring + strategy builder + screener). Reviewed code, design, UX,
backend integration, mobile parity, security, and tests.
Legend: 🔴 critical (broken / data loss / security) · 🟠 high (clearly wrong but
not catastrophic) · 🟡 medium (UX or polish) · 🟢 low (nice-to-have).
Status: ⬜ open · 🟦 in PR · ✅ fixed (commit hash on the right).
A. Critical — Broken integrations (will 404 / 401 / corrupt data)
| # |
Issue |
Severity |
Status |
Fix commit |
| A1 |
CodeStrategyEditor POSTs to /api/backtest. Real endpoint is /api/backtest/run. Result: every "Run Backtest" returns 404. |
🔴 |
✅ |
bucket A |
| A2 |
CodeStrategyEditor payload sends {symbol, strategyCode, mode}. Backend /api/backtest/run requires {symbols[], strategyConfig}. |
🔴 |
✅ |
bucket A |
| A3 |
VisualRuleBuilder save → /api/profiles body uses {strategyType, visualRules, description}. saveTradeProfileForUser expects strategy_config shape. Result: 400 or silently-discarded fields. |
🔴 |
✅ |
bucket A |
| A4 |
RightPanel.NewsFeed calls fetch() with no Authorization header. /api/news is requireAuth. Result: 401 every render. |
🔴 |
✅ |
bucket A |
| A5 |
RightPanel.NewsFeed reads import.meta.env.VITE_TRADING_API_URL directly instead of tradingRuntime.tradingApiUrl. Breaks in prod where the runtime resolver is the source of truth. |
🟠 |
✅ |
bucket A |
| A6 |
Backend /api/chart/bars previously crashed on crypto symbols (BTC/USD) because /v2/stocks rejects them. Verified in 938ed86: encodeURIComponent('BTC/USD') → BTC%2FUSD (correct for query string), and the response lookup cryptoBars[symbol] uses the un-encoded key (matches Alpaca's response). |
🟠 |
✅ |
938ed86 |
B. Functional gaps (feature exists in plan but not implemented)
| # |
Issue |
Severity |
Status |
Fix commit |
| B1 |
Phase 2 plan called for RSI / MACD / Bollinger Band overlay toggles below StockChart. Not built. |
🟠 |
✅ |
c54b9f2 |
| B2 |
TickerHeader "★ Watchlist" and 🔔 buttons have no onClick — purely cosmetic. |
🟠 |
✅ |
ed8175e |
| B3 |
TickerHeader company name is the symbol (placeholder). Should pull from fetchResearchProfile. |
🟠 |
✅ |
ed8175e |
| B4 |
TickerHeader "as of" timestamp uses new Date() (always = now). Should use the latest bar timestamp. |
🟡 |
✅ |
311d79c |
| B5 |
QuickStats reads RSI/EMA only from botState.symbols[symbol].indicators. The bot only computes these for symbols it actively tracks → user-searched tickers always show —. Need an indicator service or compute from bars client-side. |
🟠 |
✅ |
27b3d1a |
| B6 |
CodeStrategyEditor "Save" button writes to localStorage only, then shows "Saved!". Misleading — no persistence. |
🟠 |
✅ |
da79682 |
| B7 |
CodeStrategyEditor defines editorRef but never uses it. |
🟢 |
✅ |
1fd1bf3 |
| B8 |
VisualRuleBuilder exposes onBacktest prop but ResearchView doesn't pass one — backtest button never appears. |
🟡 |
✅ |
1cd23f3 |
| B9 |
ResearchView "Strategies" tab still shows the legacy MyStrategiesTab; visual/code-saved strategies don't appear there. |
🟡 |
✅ |
70db4c9 |
| B10 |
No 404 / catch-all React Router route — invalid paths render blank. |
🟡 |
✅ |
ee7404a |
C. Security & correctness
| # |
Issue |
Severity |
Status |
Fix commit |
| C1 |
Backend posts arbitrary user JS (strategyCode) to /api/backtest if A1+A2 are "fixed" naively. Must sandbox or refuse. |
🔴 |
✅ |
6aa001a |
| C2 |
No FMP response cache. Free tier = 250 req/day. Every Home view load = 3 req. 80 page loads/day → quota burnt by lunch. |
🟠 |
✅ |
0828007 |
| C3 |
/api/screener passes sector query through to FMP without an allow-list. Low-impact injection, but should validate. |
🟡 |
✅ |
c173aeb |
| C4 |
/api/news passes symbols through to Alpaca without validation. |
🟡 |
✅ |
7c4b08c |
| C5 |
Header fetchMarketIndices polls every 60 s even when the tab is hidden. Should pause via document.visibilityState. |
🟡 |
✅ |
e089832 |
| C6 |
backend/.env.example keeps FMP_API_KEY=demo AND apiServer.ts falls back to 'demo'. Two sources of truth. Demo key is shared globally and rate-limited. |
🟡 |
✅ |
1377bf2 |
| C7 |
FMP apikey is sent as a query string → leaks into proxy / CDN logs. FMP doesn't support headers, so the only mitigation is server-side caching (see C2). |
🟡 |
✅ |
e2e189e |
D. UX / UI polish
| # |
Issue |
Severity |
Status |
Fix commit |
| D1 |
News, ResearchCards, Screener show plain "Loading…" text. Should be skeleton placeholders to prevent layout jank. |
🟡 |
✅ |
0fb1d63 |
| D2 |
Light theme broke contrast in legacy tabs (SettingsTab, ConfigTab, etc.) which were styled for dark. |
🟡 |
✅ |
5ca8183 |
| D3 |
TickerHeader exchange is hard-coded "NASDAQ". |
🟢 |
✅ |
1e502b9 |
| D4 |
ScreenerView "More sectors…" <select> doesn't visually highlight the chosen sector pill once selected. |
🟡 |
✅ |
3bf1ca7 |
| D5 |
Layout is fixed-width (72 px sidebar + 308 px right panel). Breaks below ~1024 px viewport. No mobile-web responsive breakpoints. |
🟠 |
✅ |
a9c33b1 |
| D6 |
VisualRuleBuilder and CodeStrategyEditor use setTimeout without cleanup — leak warning if component unmounts mid-timer. |
🟢 |
✅ |
2089b9a |
| D7 |
No keyboard shortcuts (⌘K search, ⌘S save in code editor, ⌘Enter run backtest). |
🟢 |
✅ |
1b2130e |
| D8 |
EmptyState ticker chips are hard-coded equities. For a crypto-config bot the chips don't apply. |
🟢 |
✅ |
b1f872f |
| D9 |
No prefers-reduced-motion handling on spinners / animations. |
🟢 |
✅ |
fce0228 |
E. Build / infrastructure
| # |
Issue |
Severity |
Status |
Fix commit |
| E1 |
Bundle is 1.08 MB (309 kB gzipped) — Monaco is the bulk. Lazy-load Monaco via React.lazy. |
🟠 |
✅ |
8a8c313 |
| E2 |
Root pnpm-lock.yaml doesn't contain react-router-dom, @monaco-editor/react, @dnd-kit/* (they were installed via npm in web/ causing web/package-lock.json and web/pnpm-lock.yaml to appear). Workspace builds in CI will fail. Partial fix: stray subpackage lockfiles deleted + .gitignore updated to prevent recurrence. Still TODO: run pnpm install -r --no-frozen-lockfile from the repo root on a workstation that has GITEA_NPM_TOKEN exported (this session can't reach the private registry → mobile install fails). One-line follow-up commit. |
🔴 |
✅ |
3c9117f |
| E3 |
Monaco's web workers (TS/JSON/CSS/HTML) are pulled at runtime from a CDN by default. Need explicit Vite config to bundle workers locally for offline / CSP-strict deployments. |
🟡 |
✅ |
e8b3c9c, e266a8b |
| E4 |
No README / docs section describing the new layout, env vars (FMP_API_KEY), or routes. |
🟡 |
⬜ |
|
F. Test coverage
| # |
Issue |
Severity |
Status |
Fix commit |
| F1 |
0 tests for web/src/lib/marketApi.ts. |
🟡 |
⬜ |
|
| F2 |
0 tests for VisualRuleBuilder.tsx. |
🟡 |
⬜ |
|
| F3 |
0 tests for CodeStrategyEditor.tsx. |
🟡 |
⬜ |
|
| F4 |
0 tests for HomeView.tsx (StockChart fetch path, ResearchCards). |
🟡 |
⬜ |
|
| F5 |
0 tests for ScreenerView.tsx (filter/sort/sector logic). |
🟡 |
⬜ |
|
| F6 |
0 tests for the 7 new backend endpoints (/api/chart/bars, /api/news, /api/market/indices, /api/research/*, /api/screener). |
🟠 |
✅ |
e2806b2 |
| F7 |
Pre-existing: 3 useTabFeatureFlags.dom.test.tsx failures from module-level cache leaking between tests. |
🟡 |
✅ |
ece7fa9 |
| F8 |
Pre-existing: 1 EntryForm.dom.test.tsx failure ("alerts error when trade execution fails"). |
🟡 |
✅ |
ece7fa9 |
G. Mobile parity gap
| # |
Issue |
Severity |
Status |
Fix commit |
| G1 |
Mobile app (mobile/app/(tabs)/) has 5 tabs: index, positions, history, strategies, settings. None of the new web features (news, research, screener) exist on mobile. |
🟠 |
⬜ |
|
| G2 |
Mobile TradingDataProvider doesn't fetch news / indices / company profile. It only consumes the WebSocket bot state. |
🟠 |
⬜ |
|
| G3 |
No mobile counterpart to VisualRuleBuilder or CodeStrategyEditor. (Code editor likely shouldn't ship to mobile, but visual builder should.) |
🟡 |
⬜ |
|
| G4 |
Mobile lib/runtime.ts doesn't expose the new endpoints — no shared client. |
🟡 |
⬜ |
|
Ordering of fixes
The plan: walk top-to-bottom, one bucket per commit, smallest blast radius first.
- Section A (critical broken integrations) — unblocks Strategy Builder + News + Backtest in one go.
- Section E2 (lockfile mess) — unblocks CI; do this before anyone else pulls.
- Section B (functional gaps) — split into sub-commits per gap to keep diffs reviewable.
- Section C (security & correctness).
- Section D (UX polish).
- Section F (tests) — added alongside each fix where possible, sweep at the end.
- Section G (mobile parity) — separate commit set; biggest scope, least urgent.
Each fix lands in its own commit with the issue ID in the subject (e.g. fix(A1): point CodeStrategyEditor at /api/backtest/run). Ticking the table as we go.