@@ -14,7 +14,9 @@ mod ui;
1414mod watchlist;
1515mod websocket;
1616
17- use ui:: { App , AppState , LandingPanel , WebSocketStatus } ;
17+ use ui:: { App , AppState , LandingPanel , MarketPanel , WebSocketStatus } ;
18+ use std:: collections:: HashMap ;
19+ use crate :: stock:: QuoteSnapshot ;
1820use websocket:: LivePrice ;
1921use std:: fs:: OpenOptions ;
2022use std:: io:: Write ;
@@ -34,6 +36,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3436
3537 let ( tx, mut rx) = mpsc:: unbounded_channel :: < LivePrice > ( ) ;
3638 let ( status_tx, mut status_rx) = mpsc:: unbounded_channel :: < WebSocketStatus > ( ) ;
39+ let ( quotes_tx, mut quotes_rx) = mpsc:: unbounded_channel :: < HashMap < String , QuoteSnapshot > > ( ) ;
40+
41+ // Fetch landing quotes in background so terminal opens immediately
42+ tokio:: spawn ( async move {
43+ let result = tokio:: task:: spawn_blocking ( move || {
44+ match crate :: stock:: YahooSession :: new ( ) {
45+ Ok ( session) => {
46+ let syms: Vec < & str > = vec ! [
47+ "^GSPC" , "^DJI" , "^IXIC" , "SPY" , "QQQ" ,
48+ "AAPL" , "MSFT" , "GOOGL" , "AMZN" , "TSLA" , "NVDA" , "META" ,
49+ ] ;
50+ crate :: stock:: fetch_batch_quotes ( & session, & syms) . ok ( )
51+ }
52+ Err ( _) => None ,
53+ }
54+ } ) . await ;
55+ if let Ok ( Some ( quotes) ) = result {
56+ let _ = quotes_tx. send ( quotes) ;
57+ }
58+ } ) ;
3759
3860 // Setup terminal
3961 enable_raw_mode ( ) ?;
@@ -43,7 +65,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
4365 let mut terminal = Terminal :: new ( backend) ?;
4466
4567 // Run the app
46- let res = run_app ( & mut terminal, & mut app, & mut rx, & mut status_rx, tx, status_tx) . await ;
68+ let res = run_app ( & mut terminal, & mut app, & mut rx, & mut status_rx, & mut quotes_rx , tx, status_tx) . await ;
4769
4870 // Restore terminal
4971 disable_raw_mode ( ) ?;
@@ -62,6 +84,7 @@ async fn run_app(
6284 app : & mut App ,
6385 rx : & mut mpsc:: UnboundedReceiver < LivePrice > ,
6486 status_rx : & mut mpsc:: UnboundedReceiver < WebSocketStatus > ,
87+ quotes_rx : & mut mpsc:: UnboundedReceiver < HashMap < String , QuoteSnapshot > > ,
6588 tx : mpsc:: UnboundedSender < LivePrice > ,
6689 status_tx : mpsc:: UnboundedSender < WebSocketStatus > ,
6790) -> Result < ( ) , io:: Error > {
@@ -72,13 +95,17 @@ async fn run_app(
7295
7396 // Check for WebSocket status updates
7497 while let Ok ( status) = status_rx. try_recv ( ) {
75- // Add errors to error log
7698 if let WebSocketStatus :: Error { ref message, .. } = status {
7799 app. add_error_to_log ( message. clone ( ) ) ;
78100 }
79101 app. ws_status = status;
80102 }
81103
104+ // Check for background quote updates
105+ if let Ok ( quotes) = quotes_rx. try_recv ( ) {
106+ app. landing_quotes = quotes;
107+ }
108+
82109 // Check for live price updates with throttling
83110 // Drain all pending messages to prevent unbounded queueing
84111 let mut latest_price = None ;
@@ -203,11 +230,44 @@ async fn handle_input(
203230 KeyCode :: Char ( 'h' ) => {
204231 app. show_help = !app. show_help ;
205232 }
233+ KeyCode :: Char ( 'm' ) => {
234+ app. state = AppState :: Market ;
235+ app. fetch_market_data ( ) ;
236+ }
237+ KeyCode :: Char ( 'r' ) => {
238+ app. refresh_landing_quotes ( ) ;
239+ }
206240 _ => { }
207241 }
208242 }
209243 false
210244 }
245+ AppState :: Market => {
246+ match key {
247+ KeyCode :: Char ( 'q' ) => return true ,
248+ KeyCode :: Char ( 'b' ) | KeyCode :: Esc => {
249+ app. state = AppState :: Landing ;
250+ }
251+ KeyCode :: Char ( 'r' ) => {
252+ app. fetch_market_data ( ) ;
253+ }
254+ KeyCode :: Tab => {
255+ app. market_panel = match app. market_panel {
256+ MarketPanel :: Gainers => MarketPanel :: Losers ,
257+ MarketPanel :: Losers => MarketPanel :: Active ,
258+ MarketPanel :: Active => MarketPanel :: Gainers ,
259+ } ;
260+ }
261+ KeyCode :: Up => app. previous_market ( ) ,
262+ KeyCode :: Down => app. next_market ( ) ,
263+ KeyCode :: Enter => {
264+ stop_websocket ( ws_task_handle, & app. ws_should_stop ) . await ;
265+ app. select_market ( ) ;
266+ }
267+ _ => { }
268+ }
269+ false
270+ }
211271 AppState :: Chart => {
212272 // Handle popups first
213273 if app. show_error_log {
0 commit comments