From fc0945367a8d5561386473bef69f5fd7a37601ce Mon Sep 17 00:00:00 2001 From: Dennis Thiessen Date: Fri, 10 Jul 2026 08:17:05 +0200 Subject: [PATCH] Ticker page: one unified drill-down panel, no card salad The previous pass kept a stack of independent glass cards (header, recommendation, chart section, tab pills, per-tab sections), which read as clutter. Now the page is a single casting, like the mockup: One glass panel containing: - header (identity, price, chips | actions + score fingerprint) - inline hairline tab row (Analysis / Indicators / S/R Levels / Sentiment / Fundamentals) with the chart-overlay pills riding on the right of the row during Analysis - tab body: Analysis = the candlestick chart; other tabs render their panel inside via .hz-frameless, which strips the nested card chrome (no card-in-card) - panel foot: data freshness row (frameless now) with per-source refresh Below the panel, only on Analysis: the recommendation module and a two-column Standing | Dimensions grid. Nothing removed; the standalone Tabs bar, the separate chart card, and the duplicated section stack are gone. Co-Authored-By: Claude Fable 5 --- frontend/src/pages/TickerDetailPage.tsx | 256 ++++++++++++------------ frontend/src/styles/globals.css | 11 + 2 files changed, 144 insertions(+), 123 deletions(-) diff --git a/frontend/src/pages/TickerDetailPage.tsx b/frontend/src/pages/TickerDetailPage.tsx index 3b8dd22..38e14bc 100644 --- a/frontend/src/pages/TickerDetailPage.tsx +++ b/frontend/src/pages/TickerDetailPage.tsx @@ -20,7 +20,6 @@ import { RecommendationPanel } from '../components/ticker/RecommendationPanel'; import { Button } from '../components/ui/Button'; import { Callout } from '../components/ui/Callout'; import { Section } from '../components/ui/Section'; -import { Tabs } from '../components/ui/Tabs'; import { formatPrice } from '../lib/format'; import type { TradeSetup } from '../lib/types'; import type { FieldPoint } from '../components/ticker/StandingMatrix'; @@ -93,7 +92,7 @@ function DataFreshnessBar({ busy: boolean; }) { return ( -
+
{items.map((item) => (
{/* Drill-down header — identity + chips left, score fingerprint right */} -
+
+

Ticker

@@ -360,18 +360,54 @@ export default function TickerDetailPage() { )}
-
+
- + {/* Tab row — inline, hairline, overlay pills ride along on Analysis */} +
+ {detailTabs.map((t) => ( + + ))} + {activeTab === 'Analysis' && (longSetup || shortSetup) && ( + + overlay + {([ + { key: 'auto', label: 'Auto', show: true }, + { key: 'long', label: 'Long', show: !!longSetup }, + { key: 'short', label: 'Short', show: !!shortSetup }, + { key: 'none', label: 'None', show: true }, + ] as const).filter((o) => o.show).map((o) => ( + + ))} + + )} +
- {/* Chart — always visible */} -
+ {/* Tab body */} +
+ {activeTab === 'Analysis' && ( +
{ohlcv.isLoading && } {ohlcv.isError && ( )} {ohlcv.data && ( -
- {(longSetup || shortSetup) && ( -
- Overlay setup: - {([ - { key: 'auto', label: 'Auto', show: true }, - { key: 'long', label: 'Long', show: !!longSetup }, - { key: 'short', label: 'Short', show: !!shortSetup }, - { key: 'none', label: 'None', show: true }, - ] as const).filter((o) => o.show).map((o) => ( - - ))} - {overlaySetup && ( - - showing {overlaySetup.direction.toUpperCase()} · entry {formatPrice(overlaySetup.entry_price)} → target {formatPrice(overlaySetup.target)} - - )} -
- )} + <> -

+

Only the nearest support & resistance are drawn. Full list in the S/R Levels tab. {srLevels.isError && ' S/R levels unavailable.'}

- {/* Data freshness — per-source age + refresh, at the panel foot like the mockup */} -
- -
-
+ )} -
+
+ )} - {/* Detail tabs */} - + {activeTab === 'Indicators' && ( +
+ +
+ )} + + {activeTab === 'S/R Levels' && ( +
+ {sortedLevels.length === 0 ? ( + No S/R levels detected for this ticker yet. + ) : ( +
+ + + + + + + + + + + {sortedLevels.map((level) => ( + + + + + + + ))} + +
TypePrice LevelStrengthMethod
+ {level.type} + {formatPrice(level.price_level)}{level.strength}{level.detection_method}
+
+ )} +
+ )} + + {activeTab === 'Sentiment' && ( +
+ {sentiment.isLoading && } + {sentiment.isError && ( + sentiment.refetch()} /> + )} + {sentiment.data && } +
+ )} + + {activeTab === 'Fundamentals' && ( +
+ {fundamentals.isLoading && } + {fundamentals.isError && ( + fundamentals.refetch()} /> + )} + {fundamentals.data && } +
+ )} +
+ + {/* Panel foot — per-source data age + refresh */} +
+ +
+ {activeTab === 'Analysis' && (
+ +
{scores.isLoading && } {scores.isError && ( @@ -487,73 +563,7 @@ export default function TickerDetailPage() { )}
-
- )} - - {activeTab === 'Sentiment' && ( -
-
- {sentiment.isLoading && } - {sentiment.isError && ( - sentiment.refetch()} /> - )} - {sentiment.data && } -
-
- )} - - {activeTab === 'Fundamentals' && ( -
-
- {fundamentals.isLoading && } - {fundamentals.isError && ( - fundamentals.refetch()} /> - )} - {fundamentals.data && } -
-
- )} - - {activeTab === 'Indicators' && ( -
-
- -
-
- )} - - {activeTab === 'S/R Levels' && ( -
-
- {sortedLevels.length === 0 ? ( - No S/R levels detected for this ticker yet. - ) : ( -
- - - - - - - - - - - {sortedLevels.map((level) => ( - - - - - - - ))} - -
TypePrice LevelStrengthMethod
- {level.type} - {formatPrice(level.price_level)}{level.strength}{level.detection_method}
-
- )} -
+
)} diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css index c81971f..185a5c7 100644 --- a/frontend/src/styles/globals.css +++ b/frontend/src/styles/globals.css @@ -267,6 +267,17 @@ .hz-lvl-up { fill: var(--up-text); } .hz-lvl-down { fill: var(--down-text); } + /* Strip a nested panel's own card chrome when it renders inside the + unified drill-down panel (ticker page) — one casting, no card-in-card. */ + .hz-frameless > .glass { + background: transparent; + border: 0; + border-radius: 0; + backdrop-filter: none; + -webkit-backdrop-filter: none; + padding: 0; + } + /* Radar fingerprint */ .hz-radar-wrap { position: relative; display: inline-block; } .hz-radar-axis {