From f309bd5691ab689f55d2ea6cd9a368557943cd37 Mon Sep 17 00:00:00 2001 From: Dennis Thiessen Date: Tue, 14 Jul 2026 10:55:27 +0200 Subject: [PATCH] Polish exit timeline and always show S/R levels. Keep support/resistance visible even near trade lines, tighten the how-this-exits spine so steps connect, unify step styling, and add space above the panel. --- .../components/charts/CandlestickChart.tsx | 15 +++------ .../src/components/ticker/ExitPlanPanel.tsx | 31 +++++++++---------- 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/frontend/src/components/charts/CandlestickChart.tsx b/frontend/src/components/charts/CandlestickChart.tsx index 28f9bb9..9dce01c 100644 --- a/frontend/src/components/charts/CandlestickChart.tsx +++ b/frontend/src/components/charts/CandlestickChart.tsx @@ -354,8 +354,8 @@ export function CandlestickChart({ ctx.restore(); } - // Left-side role labels only; prices live on the right axis. - // Priority when levels crowd: Now > Stop > Gate > Entry > S/R. + // Exterior left-side role labels only; prices live on the right axis. + // Priority when levels crowd: Now > Stop > Gate > Entry. // Best practice: hide Entry when it sits on top of Now — at market, Now // *is* the fill; Entry only matters once price has drifted from the scan. type LineLabel = { @@ -385,27 +385,20 @@ export function CandlestickChart({ && (entryDriftPx >= 12 || entryDriftR >= 0.25), ); - const tradeYs: number[] = [nowY]; - if (tradeSetup) { - tradeYs.push(yScale(tradeSetup.stop_loss), yScale(tradeSetup.target)); - if (showEntry) tradeYs.push(yScale(tradeSetup.entry_price)); - } - // In-plot S/R labels (drawn after candles so they stay readable). Not // exterior margin labels — they ride the line itself, left-aligned. type InPlotLabel = { y: number; text: string; color: string; bg: string }; const inPlotLabels: InPlotLabel[] = []; // Nearest support/resistance only (band if it came from a zone). - // Drop S/R entirely when it sits on a trade line — the trade label wins. + // Always draw them — labels live in-plot on the line, so proximity to + // Now/Stop no longer forces a hide (that was only to free the left gutter). markers.forEach((m) => { const isSupport = m.role === 'support'; // Support stays cyan; resistance is a soft coral (warmer than neutral ink, // cooler/softer than the stop so the two never read as the same thing). const color = isSupport ? '#2f9db2' : '#e89b8c'; const yMid = yScale(m.price); - const collidesTrade = tradeYs.some((ty) => Math.abs(ty - yMid) < LABEL_MIN_GAP_PX); - if (collidesTrade) return; if (m.high > m.low) { const yTop = yScale(m.high); diff --git a/frontend/src/components/ticker/ExitPlanPanel.tsx b/frontend/src/components/ticker/ExitPlanPanel.tsx index 4105547..d60fe29 100644 --- a/frontend/src/components/ticker/ExitPlanPanel.tsx +++ b/frontend/src/components/ticker/ExitPlanPanel.tsx @@ -16,35 +16,34 @@ export function ExitPlanPanel({ plan, direction }: { plan: ExitPlan; direction: const steps = buildSteps(plan, isLong); return ( -
+
how this exits {plan.headline}
-
    +
      {steps.map((step, i) => { const isLast = i === steps.length - 1; return ( -
    1. - {/* Spine */} -
      +
    2. + {/* Spine column: continuous connector from this node into the next */} +
      - {step.icon ?? i + 1} + {i + 1} {!isLast && ( - + )}
      -
      +

      {step.title}

      {step.primary && (

      @@ -73,9 +72,6 @@ interface ExitStep { title: string; primary?: string; detail?: string; - /** Optional glyph instead of a step number (e.g. clock for time stop). */ - icon?: string; - tone?: 'default' | 'muted'; } function buildSteps(plan: ExitPlan, isLong: boolean): ExitStep[] { @@ -114,11 +110,12 @@ function buildSteps(plan: ExitPlan, isLong: boolean): ExitStep[] { }); } + // Same visual weight as the other steps — time stop is a real exit rule, + // not a footnote. steps.push({ title: 'Time stop', primary: `${plan.maxHoldDays} trading days`, detail: 'Flat if still open after the max hold.', - tone: 'muted', }); return steps;