From ac353e8de5679f78a689fc1e0d7c0578e56f8fb4 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 6 May 2026 18:53:38 +0000 Subject: [PATCH] feat(plans): make plans route canonical --- .../components/layout/AppShell.dom.test.tsx | 11 +++++ web/src/components/layout/AppShell.tsx | 10 +++- web/src/components/layout/Sidebar.tsx | 2 +- web/src/views/PortfolioView.tsx | 2 +- web/src/views/SimpleView.tsx | 47 ++++++++++++++++++- 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/web/src/components/layout/AppShell.dom.test.tsx b/web/src/components/layout/AppShell.dom.test.tsx index d987718..e02b77d 100644 --- a/web/src/components/layout/AppShell.dom.test.tsx +++ b/web/src/components/layout/AppShell.dom.test.tsx @@ -53,6 +53,17 @@ vi.mock('../../views/SettingsView', () => ({ })); describe('AppShell routing fallback', () => { + it('keeps /simple as a redirect alias to the plans view', async () => { + render( + + + , + ); + + expect(await screen.findByText('Simple view')).toBeInTheDocument(); + expect(screen.queryByRole('heading', { name: 'Route not found' })).not.toBeInTheDocument(); + }); + it('shows a useful 404 state for unknown routes', () => { render( diff --git a/web/src/components/layout/AppShell.tsx b/web/src/components/layout/AppShell.tsx index 5fa1ccf..3633a72 100644 --- a/web/src/components/layout/AppShell.tsx +++ b/web/src/components/layout/AppShell.tsx @@ -1,5 +1,5 @@ import { Suspense, lazy } from 'react'; -import { Link, Routes, Route, useLocation } from 'react-router-dom'; +import { Link, Routes, Route, Navigate, useLocation } from 'react-router-dom'; import { Sidebar } from './Sidebar'; import { Header } from './Header'; import { RightPanel } from './RightPanel'; @@ -73,6 +73,11 @@ function NotFoundView() { ); } +function SimpleAliasRedirect() { + const location = useLocation(); + return ; +} + export function AppShell() { return (
@@ -93,7 +98,8 @@ export function AppShell() { } /> } /> } /> - } /> + } /> + } /> } /> } /> } /> diff --git a/web/src/components/layout/Sidebar.tsx b/web/src/components/layout/Sidebar.tsx index 227e5ec..ae349c1 100644 --- a/web/src/components/layout/Sidebar.tsx +++ b/web/src/components/layout/Sidebar.tsx @@ -9,7 +9,7 @@ const NAV = [ { to: '/', icon: Home, label: 'Home', end: true }, { to: '/portfolio', icon: Briefcase, label: 'Portfolio', end: false }, { to: '/research', icon: FlaskConical, label: 'Research', end: false }, - { to: '/simple', icon: Target, label: 'Plans', end: false }, + { to: '/plans', icon: Target, label: 'Plans', end: false }, { to: '/markets', icon: TrendingUp, label: 'Markets', end: false }, { to: '/screener', icon: SlidersHorizontal, label: 'Screener', end: false }, { to: '/watchlist', icon: Star, label: 'Watchlist', end: false }, diff --git a/web/src/views/PortfolioView.tsx b/web/src/views/PortfolioView.tsx index 5c03da8..2621602 100644 --- a/web/src/views/PortfolioView.tsx +++ b/web/src/views/PortfolioView.tsx @@ -44,7 +44,7 @@ export function PortfolioView() { symbol: position.symbol, }); if (action !== 'open-plan' && position.tradeId) params.set('tradeId', position.tradeId); - navigate(`/simple?${params.toString()}`); + navigate(`/plans?${params.toString()}`); }} /> )} diff --git a/web/src/views/SimpleView.tsx b/web/src/views/SimpleView.tsx index c77870f..3bf8dfa 100644 --- a/web/src/views/SimpleView.tsx +++ b/web/src/views/SimpleView.tsx @@ -1437,7 +1437,7 @@ export function SimpleView() {
{savedSetups.length === 0 && (
- No Simple setups saved yet. + No Trade Plans saved yet.
)} @@ -1450,6 +1450,18 @@ export function SimpleView() { const updatedAt = formatSetupUpdatedAt(entry); const eventHistory = deriveSimpleEventHistory(entry, runtimeEvents); const holdingMode = normalizeHoldingMode(entry.holding_mode); + const linkedHoldingTradeId = String(entry.linked_trade_id || '').trim(); + const linkedHolding = side === 'sell' + ? availableSellHoldings.find((holding) => { + if (linkedHoldingTradeId) { + return holding.tradeId === linkedHoldingTradeId; + } + return ( + holding.symbol === String(entry.symbol || '').trim().toUpperCase() + && String(holding.profileId || '').trim() === String(entry.profile_id || '').trim() + ); + }) || null + : null; const canConvertToLongTerm = side === 'buy' && holdingMode === 'short_term' && runtimeSnapshot?.stage === 'filled'; const canResumeExitManagement = side === 'buy' && holdingMode === 'long_term' && runtimeSnapshot?.stage === 'filled'; return ( @@ -1585,6 +1597,39 @@ export function SimpleView() { ) : null}
+ {side === 'sell' ? ( +
+
+ Linked holding +
+
+ + {linkedHolding?.symbol || String(entry.symbol || '').trim().toUpperCase()} + + {(linkedHolding?.tradeId || linkedHoldingTradeId) ? ( + + Trade {linkedHolding?.tradeId || linkedHoldingTradeId} + + ) : null} + {(linkedHolding?.profileId || entry.profile_id) ? ( + + Profile {String(linkedHolding?.profileId || entry.profile_id)} + + ) : null} + {linkedHolding?.entryPrice ? ( + + Entry {Number(linkedHolding.entryPrice).toFixed(4)} + + ) : null} + {linkedHolding?.size ? ( + + Qty {linkedHolding.size} + + ) : null} +
+
+ ) : null} +
{SIMPLE_TIMELINE_STEPS.map((step) => { const complete = isTimelineStepComplete(runtimeSnapshot?.stage, step);