use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph};
use crate::tui::app::{App, BlueprintKind, PanelMode};
use crate::tui::theme::Palette;
pub fn render(frame: &mut Frame, area: Rect, app: &App, palette: &Palette) {
match app.mode {
PanelMode::SatDials => super::dials::render(frame, area, app, palette),
PanelMode::Shifts => render_shifts(frame, area, app, palette),
PanelMode::Blueprints => render_blueprints(frame, area, app, palette),
PanelMode::Backlog => render_backlog(frame, area, app, palette),
PanelMode::Judges => render_judges(frame, area, app, palette),
}
}
fn render_shifts(frame: &mut Frame, area: Rect, app: &App, palette: &Palette) {
let total = app.runs.len() + app.approvals.len();
let active = app
.runs
.iter()
.filter(|r| r.status == "running" || r.status == "awaiting_approval")
.count()
+ app.approvals.len();
let block = Block::new()
.borders(Borders::ALL)
.border_style(Style::new().fg(palette.rule))
.title(Span::styled(
format!(" ACTIVE SHIFTS · {active} live · {total} total "),
Style::new().fg(palette.fg_2).add_modifier(Modifier::BOLD),
));
let inner = block.inner(area);
frame.render_widget(block, area);
let mut lines: Vec<Line> = Vec::new();
for r in app.runs.iter().chain(app.approvals.iter()) {
let glyph_color = match r.status.as_str() {
"running" => palette.ember,
"awaiting_approval" => palette.amber,
"completed" | "merged" => palette.state_pass,
"failed" | "cooled" => palette.red,
_ => palette.fg_3,
};
let glyph = match r.status.as_str() {
"running" => "●",
"awaiting_approval" => "▲",
"completed" | "merged" => "✓",
"failed" | "cooled" => "✖",
_ => "○",
};
let id_short = &r.id[..8.min(r.id.len())];
lines.push(Line::from(vec![
Span::styled(format!(" {glyph} "), Style::new().fg(glyph_color)),
Span::styled(format!("{id_short:8}"), Style::new().fg(palette.cyan)),
Span::raw(" "),
Span::styled(r.issue.clone(), Style::new().fg(palette.fg_1)),
Span::styled(" ", Style::new()),
Span::styled(r.duration.clone(), Style::new().fg(palette.fg_3)),
]));
}
if lines.is_empty() {
lines.push(Line::styled(
" no shifts yet — start with `darq run issue <N>`",
Style::new().fg(palette.fg_3),
));
}
let para = Paragraph::new(lines).style(Style::new().bg(palette.bg_0));
frame.render_widget(para, inner);
}
fn render_blueprints(frame: &mut Frame, area: Rect, app: &App, palette: &Palette) {
let block = Block::new()
.borders(Borders::ALL)
.border_style(Style::new().fg(palette.rule))
.title(Span::styled(
format!(" BLUEPRINTS · {} matches ", app.blueprints.len()),
Style::new().fg(palette.fg_2).add_modifier(Modifier::BOLD),
));
let inner = block.inner(area);
frame.render_widget(block, area);
let mut lines: Vec<Line> = Vec::new();
for b in &app.blueprints {
let kind_style = match b.kind {
BlueprintKind::Reuse => Style::new()
.fg(palette.bg_0)
.bg(palette.copper)
.add_modifier(Modifier::BOLD),
BlueprintKind::Avoid => Style::new()
.fg(palette.bg_0)
.bg(palette.amber)
.add_modifier(Modifier::BOLD),
};
let kind_label = match b.kind {
BlueprintKind::Reuse => " REUSE ",
BlueprintKind::Avoid => " AVOID ",
};
let short = &b.run_id[..8.min(b.run_id.len())];
lines.push(Line::from(vec![
Span::styled(kind_label, kind_style),
Span::raw(" "),
Span::styled(format!("#{short}"), Style::new().fg(palette.cyan)),
Span::raw(" · "),
Span::styled(b.utility_name.clone(), Style::new().fg(palette.fg_1)),
Span::raw(" "),
Span::styled(
format!("{:.2}", b.similarity),
Style::new().fg(palette.fg_2),
),
]));
}
if lines.is_empty() {
lines.push(Line::styled(
" no blueprints injected for the focused shift yet",
Style::new().fg(palette.fg_3),
));
}
let para = Paragraph::new(lines).style(Style::new().bg(palette.bg_0));
frame.render_widget(para, inner);
}
fn render_backlog(frame: &mut Frame, area: Rect, app: &App, palette: &Palette) {
let block = Block::new()
.borders(Borders::ALL)
.border_style(Style::new().fg(palette.rule))
.title(Span::styled(
format!(
" BACKLOG · MILESTONE {} ",
app.milestone_name.as_deref().unwrap_or("—")
),
Style::new().fg(palette.fg_2).add_modifier(Modifier::BOLD),
));
let inner = block.inner(area);
frame.render_widget(block, area);
let mut lines: Vec<Line> = Vec::new();
if app.runs.is_empty() && app.approvals.is_empty() {
lines.push(Line::styled(
" no backlog items — connect to daemon and trigger `darq sweep <milestone>`",
Style::new().fg(palette.fg_3),
));
} else {
for r in app.runs.iter().chain(app.approvals.iter()) {
let (chip_label, chip_color) = match r.status.as_str() {
"running" => ("[ACTIVE]", palette.ember),
"awaiting_approval" => ("[QUEUED]", palette.amber),
"completed" | "merged" => ("[MERGED]", palette.state_pass),
"failed" | "cooled" => ("[STALLED]", palette.red),
_ => ("[—]", palette.fg_3),
};
lines.push(Line::from(vec![
Span::styled(" ", Style::new()),
Span::styled(r.issue.clone(), Style::new().fg(palette.fg_1)),
Span::styled(" ", Style::new()),
Span::styled(
chip_label,
Style::new().fg(chip_color).add_modifier(Modifier::BOLD),
),
]));
}
}
let para = Paragraph::new(lines).style(Style::new().bg(palette.bg_0));
frame.render_widget(para, inner);
}
fn render_judges(frame: &mut Frame, area: Rect, app: &App, palette: &Palette) {
let block = Block::new()
.borders(Borders::ALL)
.border_style(Style::new().fg(palette.rule))
.title(Span::styled(
" JUDGES · ensemble (stub) ",
Style::new().fg(palette.fg_2).add_modifier(Modifier::BOLD),
));
let inner = block.inner(area);
frame.render_widget(block, area);
let mut lines: Vec<Line> = Vec::new();
let judges = [
(
"JUDGE 1",
"ensemble · gpt-tier",
app.sat_scores.get("junior"),
),
(
"JUDGE 2",
"ensemble · claude-tier",
app.sat_scores.get("senior"),
),
(
"JUDGE 3",
"running · arbitration",
app.sat_scores.get("maintainer"),
),
];
for (name, attribution, state) in judges {
let score = state
.map(|s| format!("{:.1}", s.target))
.unwrap_or_else(|| "—".into());
lines.push(Line::from(vec![
Span::styled(format!(" {name} "), Style::new().fg(palette.fg_2)),
Span::styled("· ", Style::new().fg(palette.fg_4)),
Span::styled(attribution, Style::new().fg(palette.violet)),
Span::styled(" ", Style::new()),
Span::styled(score, Style::new().fg(palette.fg_0)),
]));
}
lines.push(Line::raw(""));
lines.push(Line::styled(
" stub: real per-judge attribution requires SAT engine extension",
Style::new().fg(palette.fg_3),
));
lines.push(Line::styled(
" (TODO: wire from sat.rs judge enumeration)",
Style::new().fg(palette.fg_4),
));
let para = Paragraph::new(lines).style(Style::new().bg(palette.bg_0));
frame.render_widget(para, inner);
}