From d5359691b1ff76d3c52c116346b7128bc9c5493a Mon Sep 17 00:00:00 2001
From: Viktor Barzin
Date: Wed, 27 May 2026 17:22:53 +0000
Subject: [PATCH] feat(dashboard): show actions + convictions + outlook on
Videos cards
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
API already returns top_tickers as [{symbol, action, conviction}] but
the dashboard's VideoSummary type was string[] — so the rich data
was dropped at the type boundary and the cards only showed bare
ticker symbols.
Changes:
- VideoSummary.top_tickers: string[] → VideoTopTicker[]
- Videos.tsx card now renders:
* outlook pill (bullish/bearish/neutral/mixed, color-coded)
* action-colored chip per ticker (buy=green, sell=red, watch=yellow,
avoid=rose, hold=slate) with conviction% inline
* one-line summary excerpt
- Home.tsx (only other top_tickers consumer) updated to use .symbol
instead of treating each entry as a string
Effect: skim /meet-kevin/videos to see at a glance "this video says
buy NVDA 85% + watch SPCX 65%, outlook bullish" without clicking in.
---
dashboard/src/pages/meetKevin/Home.tsx | 9 +++--
dashboard/src/pages/meetKevin/Videos.tsx | 47 +++++++++++++++++++++---
dashboard/src/types/meetKevin.ts | 8 +++-
3 files changed, 53 insertions(+), 11 deletions(-)
diff --git a/dashboard/src/pages/meetKevin/Home.tsx b/dashboard/src/pages/meetKevin/Home.tsx
index be1daa9..44e8b4c 100644
--- a/dashboard/src/pages/meetKevin/Home.tsx
+++ b/dashboard/src/pages/meetKevin/Home.tsx
@@ -118,12 +118,13 @@ export default function MeetKevinHome() {
{video.outlook}
)}
- {video.top_tickers.slice(0, 5).map((ticker: string) => (
+ {video.top_tickers.slice(0, 5).map((t) => (
- ${ticker}
+ {t.symbol}
))}
diff --git a/dashboard/src/pages/meetKevin/Videos.tsx b/dashboard/src/pages/meetKevin/Videos.tsx
index e11a508..45c0341 100644
--- a/dashboard/src/pages/meetKevin/Videos.tsx
+++ b/dashboard/src/pages/meetKevin/Videos.tsx
@@ -2,7 +2,7 @@ import { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import meetKevinApi from '../../api/meetKevin';
-import type { VideoStatus } from '../../types/meetKevin';
+import type { MarketOutlook, TickerAction, VideoStatus } from '../../types/meetKevin';
const STATUS_OPTIONS: { value: VideoStatus | ''; label: string }[] = [
{ value: '', label: 'All' },
@@ -19,6 +19,21 @@ function statusColor(status: VideoStatus): string {
return 'text-yellow-300';
}
+const ACTION_CHIP: Record = {
+ buy: 'bg-green-600/30 text-green-300 border-green-500/40',
+ sell: 'bg-red-600/30 text-red-300 border-red-500/40',
+ hold: 'bg-slate-600/30 text-slate-300 border-slate-500/40',
+ watch: 'bg-yellow-600/30 text-yellow-200 border-yellow-500/40',
+ avoid: 'bg-rose-600/40 text-rose-200 border-rose-500/40',
+};
+
+const OUTLOOK_PILL: Record = {
+ bullish: 'bg-green-600/20 text-green-300 border-green-500/40',
+ bearish: 'bg-red-600/20 text-red-300 border-red-500/40',
+ neutral: 'bg-slate-600/20 text-slate-300 border-slate-500/40',
+ mixed: 'bg-purple-600/20 text-purple-300 border-purple-500/40',
+};
+
export default function MeetKevinVideos() {
const [status, setStatus] = useState('');
const [q, setQ] = useState('');
@@ -146,18 +161,38 @@ export default function MeetKevinVideos() {
)}
+ {video.outlook && (
+
+
+ {video.outlook}
+
+
+ )}
+
{video.top_tickers.length > 0 && (
-
- {video.top_tickers.map((ticker) => (
+
+ {video.top_tickers.map((t) => (
- {ticker}
+ {t.symbol}
+
+ {Math.round(t.conviction * 100)}
+
))}
)}
+
+ {video.one_line_summary && (
+
+ {video.one_line_summary}
+
+ )}
))}
diff --git a/dashboard/src/types/meetKevin.ts b/dashboard/src/types/meetKevin.ts
index 8a67325..3c04ef4 100644
--- a/dashboard/src/types/meetKevin.ts
+++ b/dashboard/src/types/meetKevin.ts
@@ -14,6 +14,12 @@ export type VideoStatus =
| 'failed'
| 'skipped';
+export interface VideoTopTicker {
+ symbol: string;
+ action: TickerAction;
+ conviction: number;
+}
+
export interface VideoSummary {
id: number;
youtube_video_id: string;
@@ -23,7 +29,7 @@ export interface VideoSummary {
thumbnail_url: string | null;
status: VideoStatus;
failure_reason: string | null;
- top_tickers: string[];
+ top_tickers: VideoTopTicker[];
outlook: MarketOutlook | null;
one_line_summary: string | null;
}