4runr-os 2.3.8 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,16 @@ use ratatui::prelude::*;
5
5
  use ratatui::widgets::{Block, Borders, Paragraph, Wrap, List, ListItem};
6
6
  use crate::app::AppState;
7
7
 
8
+ // === 4RUNR BRAND COLORS (matching layout.rs) ===
9
+ const BRAND_PURPLE: Color = Color::Rgb(138, 43, 226);
10
+ const CYBER_CYAN: Color = Color::Rgb(0, 255, 255);
11
+ const NEON_GREEN: Color = Color::Rgb(57, 255, 20);
12
+ const AMBER_WARN: Color = Color::Rgb(255, 191, 0);
13
+ const TEXT_PRIMARY: Color = Color::Rgb(230, 230, 240);
14
+ const TEXT_DIM: Color = Color::Rgb(100, 100, 120);
15
+ const TEXT_MUTED: Color = Color::Rgb(60, 60, 80);
16
+ const BG_PANEL: Color = Color::Rgb(18, 18, 25);
17
+
8
18
  /// Run Manager state
9
19
  #[derive(Debug, Clone)]
10
20
  pub struct RunManagerState {
@@ -25,6 +35,9 @@ pub struct RunManagerState {
25
35
 
26
36
  /// Last refresh time
27
37
  pub last_refresh: Option<std::time::Instant>,
38
+
39
+ /// Detailed view state (None = list view, Some(index) = detail view)
40
+ pub detail_view: Option<usize>,
28
41
  }
29
42
 
30
43
  #[derive(Debug, Clone)]
@@ -36,6 +49,14 @@ pub struct RunInfo {
36
49
  pub started_at: String,
37
50
  pub duration: Option<String>,
38
51
  pub progress: Option<f32>, // 0.0-1.0
52
+
53
+ // Extended details
54
+ pub input_tokens: Option<u32>,
55
+ pub output_tokens: Option<u32>,
56
+ pub total_cost: Option<f64>,
57
+ pub model: Option<String>,
58
+ pub error_message: Option<String>,
59
+ pub logs: Vec<String>,
39
60
  }
40
61
 
41
62
  #[derive(Debug, Clone, PartialEq)]
@@ -60,11 +81,11 @@ impl RunStatus {
60
81
 
61
82
  pub fn color(&self) -> Color {
62
83
  match self {
63
- RunStatus::Pending => Color::Yellow,
64
- RunStatus::Running => Color::Cyan,
65
- RunStatus::Completed => Color::Green,
66
- RunStatus::Failed => Color::Red,
67
- RunStatus::Cancelled => Color::DarkGray,
84
+ RunStatus::Pending => AMBER_WARN,
85
+ RunStatus::Running => CYBER_CYAN,
86
+ RunStatus::Completed => NEON_GREEN,
87
+ RunStatus::Failed => Color::Rgb(255, 69, 69), // Bright red
88
+ RunStatus::Cancelled => TEXT_DIM,
68
89
  }
69
90
  }
70
91
  }
@@ -129,6 +150,18 @@ impl Default for RunManagerState {
129
150
  started_at: "2m ago".to_string(),
130
151
  duration: Some("2m 15s".to_string()),
131
152
  progress: Some(0.65),
153
+ input_tokens: Some(1250),
154
+ output_tokens: Some(850),
155
+ total_cost: Some(0.0315),
156
+ model: Some("gpt-4-turbo".to_string()),
157
+ error_message: None,
158
+ logs: vec![
159
+ "[00:00] Run started".to_string(),
160
+ "[00:15] Loading data from source...".to_string(),
161
+ "[00:45] Processing 1,234 records...".to_string(),
162
+ "[01:30] Analyzing patterns...".to_string(),
163
+ "[02:15] Generating insights...".to_string(),
164
+ ],
132
165
  },
133
166
  RunInfo {
134
167
  id: "run-002".to_string(),
@@ -138,6 +171,18 @@ impl Default for RunManagerState {
138
171
  started_at: "15m ago".to_string(),
139
172
  duration: Some("3m 42s".to_string()),
140
173
  progress: Some(1.0),
174
+ input_tokens: Some(3200),
175
+ output_tokens: Some(1800),
176
+ total_cost: Some(0.075),
177
+ model: Some("gpt-4".to_string()),
178
+ error_message: None,
179
+ logs: vec![
180
+ "[00:00] Run started".to_string(),
181
+ "[00:30] Analyzing code structure...".to_string(),
182
+ "[01:45] Checking for issues...".to_string(),
183
+ "[02:30] Generating recommendations...".to_string(),
184
+ "[03:42] Review complete".to_string(),
185
+ ],
141
186
  },
142
187
  RunInfo {
143
188
  id: "run-003".to_string(),
@@ -147,6 +192,16 @@ impl Default for RunManagerState {
147
192
  started_at: "1h ago".to_string(),
148
193
  duration: Some("1m 05s".to_string()),
149
194
  progress: Some(0.25),
195
+ input_tokens: Some(450),
196
+ output_tokens: Some(120),
197
+ total_cost: Some(0.0086),
198
+ model: Some("gpt-3.5-turbo".to_string()),
199
+ error_message: Some("API rate limit exceeded".to_string()),
200
+ logs: vec![
201
+ "[00:00] Run started".to_string(),
202
+ "[00:30] Loading template...".to_string(),
203
+ "[01:05] ERROR: API rate limit exceeded".to_string(),
204
+ ],
150
205
  },
151
206
  ],
152
207
  selected_index: 0,
@@ -154,6 +209,7 @@ impl Default for RunManagerState {
154
209
  sort: RunSort::DateDesc,
155
210
  loading: false,
156
211
  last_refresh: None,
212
+ detail_view: None,
157
213
  }
158
214
  }
159
215
  }
@@ -190,6 +246,27 @@ impl RunManagerState {
190
246
  filtered.get(self.selected_index).copied()
191
247
  }
192
248
 
249
+ /// Toggle detail view for selected run
250
+ pub fn toggle_detail_view(&mut self) {
251
+ if self.detail_view.is_some() {
252
+ // Close detail view
253
+ self.detail_view = None;
254
+ } else {
255
+ // Open detail view for selected run
256
+ self.detail_view = Some(self.selected_index);
257
+ }
258
+ }
259
+
260
+ /// Close detail view
261
+ pub fn close_detail_view(&mut self) {
262
+ self.detail_view = None;
263
+ }
264
+
265
+ /// Check if in detail view
266
+ pub fn is_detail_view(&self) -> bool {
267
+ self.detail_view.is_some()
268
+ }
269
+
193
270
  /// Move selection up
194
271
  pub fn select_previous(&mut self) {
195
272
  if self.selected_index > 0 {
@@ -234,23 +311,28 @@ pub fn render(f: &mut Frame, state: &AppState) {
234
311
  // Get run manager state from AppState
235
312
  let run_state = &state.run_manager;
236
313
 
237
- // Split screen into sections
238
- use ratatui::layout::{Constraint, Direction, Layout};
239
-
240
- let chunks = Layout::default()
241
- .direction(Direction::Vertical)
242
- .constraints([
243
- Constraint::Length(3), // Header
244
- Constraint::Min(10), // Run list
245
- Constraint::Length(8), // Details panel
246
- Constraint::Length(3), // Actions
247
- ])
248
- .split(area);
249
-
250
- render_header(f, chunks[0], run_state);
251
- render_run_list(f, chunks[1], run_state);
252
- render_details_panel(f, chunks[2], run_state);
253
- render_actions(f, chunks[3]);
314
+ // Check if in detail view
315
+ if run_state.is_detail_view() {
316
+ render_detail_view(f, area, run_state);
317
+ } else {
318
+ // Split screen into sections
319
+ use ratatui::layout::{Constraint, Direction, Layout};
320
+
321
+ let chunks = Layout::default()
322
+ .direction(Direction::Vertical)
323
+ .constraints([
324
+ Constraint::Length(3), // Header
325
+ Constraint::Min(10), // Run list
326
+ Constraint::Length(8), // Details panel
327
+ Constraint::Length(3), // Actions
328
+ ])
329
+ .split(area);
330
+
331
+ render_header(f, chunks[0], run_state);
332
+ render_run_list(f, chunks[1], run_state);
333
+ render_details_panel(f, chunks[2], run_state);
334
+ render_actions(f, chunks[3]);
335
+ }
254
336
  }
255
337
 
256
338
  fn render_header(f: &mut Frame, area: Rect, state: &RunManagerState) {
@@ -259,22 +341,24 @@ fn render_header(f: &mut Frame, area: Rect, state: &RunManagerState) {
259
341
 
260
342
  let block = Block::default()
261
343
  .title(format!(
262
- " Run Manager - {} ({}/{} runs) ",
344
+ " Run Manager - {} ({}/{} runs) ",
263
345
  state.filter.as_str(),
264
346
  filtered_count,
265
347
  total_count
266
348
  ))
267
349
  .borders(Borders::ALL)
268
- .border_style(Style::default().fg(Color::Green));
350
+ .border_style(Style::default().fg(BRAND_PURPLE))
351
+ .style(Style::default().bg(BG_PANEL));
269
352
 
270
353
  f.render_widget(block, area);
271
354
  }
272
355
 
273
356
  fn render_run_list(f: &mut Frame, area: Rect, state: &RunManagerState) {
274
357
  let block = Block::default()
275
- .title(format!(" Runs (Sort: {}) ", state.sort.as_str()))
358
+ .title(format!(" 📋 Runs (Sort: {}) ", state.sort.as_str()))
276
359
  .borders(Borders::ALL)
277
- .border_style(Style::default().fg(Color::Green));
360
+ .border_style(Style::default().fg(CYBER_CYAN))
361
+ .style(Style::default().bg(BG_PANEL));
278
362
 
279
363
  let inner = block.inner(area);
280
364
  f.render_widget(block, area);
@@ -302,23 +386,39 @@ fn render_run_list(f: &mut Frame, area: Rect, state: &RunManagerState) {
302
386
 
303
387
  let duration_str = run.duration.as_deref().unwrap_or("--");
304
388
 
305
- let line = format!(
306
- "{} {} | {} | {} | {} | {}",
307
- if is_selected { "▶" } else { " " },
308
- run.status.as_str(),
309
- run.name,
310
- run.agent_name,
311
- duration_str,
312
- progress_bar
313
- );
314
-
315
- let style = if is_selected {
316
- Style::default().fg(Color::Yellow).bold()
317
- } else {
318
- Style::default().fg(run.status.color())
319
- };
389
+ // Build line with colored spans
390
+ let line = Line::from(vec![
391
+ Span::styled(
392
+ if is_selected { "▶ " } else { " " },
393
+ Style::default().fg(BRAND_PURPLE).bold()
394
+ ),
395
+ Span::styled(
396
+ format!("{:<10}", run.status.as_str()),
397
+ Style::default().fg(run.status.color()).bold()
398
+ ),
399
+ Span::styled(" ", Style::default().fg(TEXT_DIM)),
400
+ Span::styled(
401
+ format!("{:<20}", run.name),
402
+ Style::default().fg(if is_selected { CYBER_CYAN } else { TEXT_PRIMARY }).bold()
403
+ ),
404
+ Span::styled(" │ ", Style::default().fg(TEXT_DIM)),
405
+ Span::styled(
406
+ format!("{:<15}", run.agent_name),
407
+ Style::default().fg(TEXT_DIM)
408
+ ),
409
+ Span::styled(" │ ", Style::default().fg(TEXT_DIM)),
410
+ Span::styled(
411
+ format!("{:>8}", duration_str),
412
+ Style::default().fg(TEXT_MUTED)
413
+ ),
414
+ Span::raw(" "),
415
+ Span::styled(
416
+ progress_bar,
417
+ Style::default().fg(if run.progress.unwrap_or(0.0) > 0.5 { NEON_GREEN } else { AMBER_WARN })
418
+ ),
419
+ ]);
320
420
 
321
- ListItem::new(line).style(style)
421
+ ListItem::new(line)
322
422
  }).collect();
323
423
 
324
424
  let list = List::new(items);
@@ -327,9 +427,10 @@ fn render_run_list(f: &mut Frame, area: Rect, state: &RunManagerState) {
327
427
 
328
428
  fn render_details_panel(f: &mut Frame, area: Rect, state: &RunManagerState) {
329
429
  let block = Block::default()
330
- .title(" Run Details ")
430
+ .title(" 🔍 Run Details ")
331
431
  .borders(Borders::ALL)
332
- .border_style(Style::default().fg(Color::Green));
432
+ .border_style(Style::default().fg(NEON_GREEN))
433
+ .style(Style::default().bg(BG_PANEL));
333
434
 
334
435
  let inner = block.inner(area);
335
436
  f.render_widget(block, area);
@@ -337,24 +438,24 @@ fn render_details_panel(f: &mut Frame, area: Rect, state: &RunManagerState) {
337
438
  if let Some(run) = state.selected_run() {
338
439
  let text = vec![
339
440
  Line::from(vec![
340
- Span::raw("ID: "),
341
- Span::styled(&run.id, Style::default().fg(Color::Yellow)),
441
+ Span::styled("ID: ", Style::default().fg(TEXT_DIM)),
442
+ Span::styled(&run.id, Style::default().fg(AMBER_WARN)),
342
443
  ]),
343
444
  Line::from(vec![
344
- Span::raw("Name: "),
345
- Span::styled(&run.name, Style::default().fg(Color::Cyan)),
445
+ Span::styled("Name: ", Style::default().fg(TEXT_DIM)),
446
+ Span::styled(&run.name, Style::default().fg(TEXT_PRIMARY).bold()),
346
447
  ]),
347
448
  Line::from(vec![
348
- Span::raw("Agent: "),
349
- Span::styled(&run.agent_name, Style::default().fg(Color::Cyan)),
449
+ Span::styled("Agent: ", Style::default().fg(TEXT_DIM)),
450
+ Span::styled(&run.agent_name, Style::default().fg(CYBER_CYAN)),
350
451
  ]),
351
452
  Line::from(vec![
352
- Span::raw("Status: "),
353
- Span::styled(run.status.as_str(), Style::default().fg(run.status.color())),
453
+ Span::styled("Status: ", Style::default().fg(TEXT_DIM)),
454
+ Span::styled(run.status.as_str(), Style::default().fg(run.status.color()).bold()),
354
455
  ]),
355
456
  Line::from(vec![
356
- Span::raw("Started: "),
357
- Span::styled(&run.started_at, Style::default().fg(Color::DarkGray)),
457
+ Span::styled("Started: ", Style::default().fg(TEXT_DIM)),
458
+ Span::styled(&run.started_at, Style::default().fg(TEXT_MUTED)),
358
459
  ]),
359
460
  ];
360
461
 
@@ -362,39 +463,213 @@ fn render_details_panel(f: &mut Frame, area: Rect, state: &RunManagerState) {
362
463
  f.render_widget(paragraph, inner);
363
464
  } else {
364
465
  let text = Paragraph::new("No run selected")
365
- .style(Style::default().fg(Color::DarkGray))
466
+ .style(Style::default().fg(TEXT_DIM))
366
467
  .alignment(Alignment::Center);
367
468
  f.render_widget(text, inner);
368
469
  }
369
470
  }
370
471
 
472
+ fn render_detail_view(f: &mut Frame, area: Rect, state: &RunManagerState) {
473
+ use ratatui::layout::{Constraint, Direction, Layout};
474
+
475
+ // Split into header, content, and footer
476
+ let chunks = Layout::default()
477
+ .direction(Direction::Vertical)
478
+ .constraints([
479
+ Constraint::Length(3), // Header
480
+ Constraint::Min(10), // Content
481
+ Constraint::Length(3), // Footer
482
+ ])
483
+ .split(area);
484
+
485
+ // Get the selected run
486
+ let run = match state.selected_run() {
487
+ Some(r) => r,
488
+ None => {
489
+ let block = Block::default()
490
+ .title(" Run Details ")
491
+ .borders(Borders::ALL)
492
+ .border_style(Style::default().fg(TEXT_DIM));
493
+ f.render_widget(block, area);
494
+ return;
495
+ }
496
+ };
497
+
498
+ // Header
499
+ let header_block = Block::default()
500
+ .title(format!(" 📊 Run Details - {} ", run.name))
501
+ .borders(Borders::ALL)
502
+ .border_style(Style::default().fg(BRAND_PURPLE))
503
+ .style(Style::default().bg(BG_PANEL));
504
+ f.render_widget(header_block, chunks[0]);
505
+
506
+ // Content - split into left (info) and right (logs)
507
+ let content_chunks = Layout::default()
508
+ .direction(Direction::Horizontal)
509
+ .constraints([
510
+ Constraint::Percentage(40), // Info
511
+ Constraint::Percentage(60), // Logs
512
+ ])
513
+ .split(chunks[1]);
514
+
515
+ // Left: Run Info
516
+ let info_block = Block::default()
517
+ .title(" ℹ️ Information ")
518
+ .borders(Borders::ALL)
519
+ .border_style(Style::default().fg(CYBER_CYAN))
520
+ .style(Style::default().bg(BG_PANEL));
521
+
522
+ let info_inner = info_block.inner(content_chunks[0]);
523
+ f.render_widget(info_block, content_chunks[0]);
524
+
525
+ let mut info_lines = vec![
526
+ Line::from(""),
527
+ Line::from(vec![
528
+ Span::styled("ID: ", Style::default().fg(TEXT_DIM)),
529
+ Span::styled(&run.id, Style::default().fg(TEXT_PRIMARY)),
530
+ ]),
531
+ Line::from(vec![
532
+ Span::styled("Agent: ", Style::default().fg(TEXT_DIM)),
533
+ Span::styled(&run.agent_name, Style::default().fg(CYBER_CYAN).bold()),
534
+ ]),
535
+ Line::from(vec![
536
+ Span::styled("Status: ", Style::default().fg(TEXT_DIM)),
537
+ Span::styled(run.status.as_str(), Style::default().fg(run.status.color()).bold()),
538
+ ]),
539
+ Line::from(""),
540
+ Line::from(vec![
541
+ Span::styled("Started: ", Style::default().fg(TEXT_DIM)),
542
+ Span::styled(&run.started_at, Style::default().fg(TEXT_PRIMARY)),
543
+ ]),
544
+ Line::from(vec![
545
+ Span::styled("Duration: ", Style::default().fg(TEXT_DIM)),
546
+ Span::styled(run.duration.as_deref().unwrap_or("--"), Style::default().fg(TEXT_PRIMARY)),
547
+ ]),
548
+ Line::from(""),
549
+ ];
550
+
551
+ // Add model info if available
552
+ if let Some(model) = &run.model {
553
+ info_lines.push(Line::from(vec![
554
+ Span::styled("Model: ", Style::default().fg(TEXT_DIM)),
555
+ Span::styled(model, Style::default().fg(TEXT_PRIMARY)),
556
+ ]));
557
+ }
558
+
559
+ // Add token usage if available
560
+ if let (Some(input), Some(output)) = (run.input_tokens, run.output_tokens) {
561
+ info_lines.push(Line::from(""));
562
+ info_lines.push(Line::from("Token Usage:").style(Style::default().fg(NEON_GREEN).bold()));
563
+ info_lines.push(Line::from(vec![
564
+ Span::styled(" Input: ", Style::default().fg(TEXT_DIM)),
565
+ Span::styled(format!("{}", input), Style::default().fg(TEXT_PRIMARY)),
566
+ ]));
567
+ info_lines.push(Line::from(vec![
568
+ Span::styled(" Output: ", Style::default().fg(TEXT_DIM)),
569
+ Span::styled(format!("{}", output), Style::default().fg(TEXT_PRIMARY)),
570
+ ]));
571
+ info_lines.push(Line::from(vec![
572
+ Span::styled(" Total: ", Style::default().fg(TEXT_DIM)),
573
+ Span::styled(format!("{}", input + output), Style::default().fg(NEON_GREEN).bold()),
574
+ ]));
575
+ }
576
+
577
+ // Add cost if available
578
+ if let Some(cost) = run.total_cost {
579
+ info_lines.push(Line::from(""));
580
+ info_lines.push(Line::from(vec![
581
+ Span::styled("Cost: ", Style::default().fg(TEXT_DIM)),
582
+ Span::styled(format!("${:.4}", cost), Style::default().fg(AMBER_WARN).bold()),
583
+ ]));
584
+ }
585
+
586
+ // Add error if present
587
+ if let Some(error) = &run.error_message {
588
+ info_lines.push(Line::from(""));
589
+ info_lines.push(Line::from("Error:").style(Style::default().fg(Color::Rgb(255, 69, 69)).bold()));
590
+ info_lines.push(Line::from(format!(" {}", error)).style(Style::default().fg(Color::Rgb(255, 69, 69))));
591
+ }
592
+
593
+ let info_paragraph = Paragraph::new(info_lines)
594
+ .wrap(Wrap { trim: false });
595
+ f.render_widget(info_paragraph, info_inner);
596
+
597
+ // Right: Logs
598
+ let logs_block = Block::default()
599
+ .title(" 📜 Execution Logs ")
600
+ .borders(Borders::ALL)
601
+ .border_style(Style::default().fg(NEON_GREEN))
602
+ .style(Style::default().bg(BG_PANEL));
603
+
604
+ let logs_inner = logs_block.inner(content_chunks[1]);
605
+ f.render_widget(logs_block, content_chunks[1]);
606
+
607
+ let log_lines: Vec<Line> = if run.logs.is_empty() {
608
+ vec![
609
+ Line::from(""),
610
+ Line::from("No logs available").style(Style::default().fg(TEXT_DIM)),
611
+ ]
612
+ } else {
613
+ run.logs.iter().map(|log| {
614
+ Line::from(log.as_str()).style(Style::default().fg(TEXT_PRIMARY))
615
+ }).collect()
616
+ };
617
+
618
+ let logs_paragraph = Paragraph::new(log_lines)
619
+ .wrap(Wrap { trim: false });
620
+ f.render_widget(logs_paragraph, logs_inner);
621
+
622
+ // Footer
623
+ let footer_block = Block::default()
624
+ .title(" ⌨️ Actions ")
625
+ .borders(Borders::ALL)
626
+ .border_style(Style::default().fg(TEXT_DIM))
627
+ .style(Style::default().bg(BG_PANEL));
628
+
629
+ let footer_inner = footer_block.inner(chunks[2]);
630
+ f.render_widget(footer_block, chunks[2]);
631
+
632
+ let footer_text = Line::from(vec![
633
+ Span::styled("ESC/Enter", Style::default().fg(BRAND_PURPLE).bold()),
634
+ Span::styled(" Back to List │ ", Style::default().fg(TEXT_DIM)),
635
+ Span::styled("R", Style::default().fg(NEON_GREEN).bold()),
636
+ Span::styled(" Refresh", Style::default().fg(TEXT_DIM)),
637
+ ]);
638
+
639
+ let footer_paragraph = Paragraph::new(footer_text)
640
+ .alignment(Alignment::Center);
641
+ f.render_widget(footer_paragraph, footer_inner);
642
+ }
643
+
371
644
  fn render_actions(f: &mut Frame, area: Rect) {
372
645
  let block = Block::default()
373
- .title(" Actions ")
646
+ .title(" ⌨️ Actions ")
374
647
  .borders(Borders::ALL)
375
- .border_style(Style::default().fg(Color::DarkGray));
648
+ .border_style(Style::default().fg(TEXT_DIM))
649
+ .style(Style::default().bg(BG_PANEL));
376
650
 
377
651
  let inner = block.inner(area);
378
652
  f.render_widget(block, area);
379
653
 
380
654
  let text = Line::from(vec![
381
- Span::raw("↑/↓ Select | "),
382
- Span::styled("F", Style::default().fg(Color::Yellow)),
383
- Span::raw(" Filter | "),
384
- Span::styled("S", Style::default().fg(Color::Yellow)),
385
- Span::raw(" Sort | "),
386
- Span::styled("R", Style::default().fg(Color::Yellow)),
387
- Span::raw(" Refresh | "),
388
- Span::styled("C", Style::default().fg(Color::Yellow)),
389
- Span::raw(" Cancel | "),
390
- Span::styled("D", Style::default().fg(Color::Yellow)),
391
- Span::raw(" Delete | "),
392
- Span::styled("ESC", Style::default().fg(Color::Red)),
393
- Span::raw(" Close"),
655
+ Span::styled("↑/↓", Style::default().fg(CYBER_CYAN).bold()),
656
+ Span::styled(" Select │ ", Style::default().fg(TEXT_DIM)),
657
+ Span::styled("F", Style::default().fg(AMBER_WARN).bold()),
658
+ Span::styled(" Filter │ ", Style::default().fg(TEXT_DIM)),
659
+ Span::styled("S", Style::default().fg(AMBER_WARN).bold()),
660
+ Span::styled(" Sort │ ", Style::default().fg(TEXT_DIM)),
661
+ Span::styled("R", Style::default().fg(NEON_GREEN).bold()),
662
+ Span::styled(" Refresh │ ", Style::default().fg(TEXT_DIM)),
663
+ Span::styled("C", Style::default().fg(Color::Rgb(255, 69, 69)).bold()),
664
+ Span::styled(" Cancel │ ", Style::default().fg(TEXT_DIM)),
665
+ Span::styled("D", Style::default().fg(Color::Rgb(255, 69, 69)).bold()),
666
+ Span::styled(" Delete │ ", Style::default().fg(TEXT_DIM)),
667
+ Span::styled("ESC", Style::default().fg(BRAND_PURPLE).bold()),
668
+ Span::styled(" Close", Style::default().fg(TEXT_DIM)),
394
669
  ]);
395
670
 
396
671
  let paragraph = Paragraph::new(text)
397
- .style(Style::default().fg(Color::DarkGray))
672
+ .style(Style::default().fg(TEXT_PRIMARY))
398
673
  .alignment(Alignment::Center);
399
674
 
400
675
  f.render_widget(paragraph, inner);