darq 0.1.0

darq CLI + TUI — autonomous issue → PR pipeline with SAT and a learning loop.
Documentation
//! Panel layout — the 4-widget alien-living default view.
//!
//! Layout (per docs/design-tui.md §Layout):
//!   - Header (1 row)
//!   - Chain topology (5 rows)
//!   - Two-column main: EventWaterfall (left) | SatPersonaDials (right)
//!   - Footer (1 row)
//!
//! Old 6-panel grid (Milestone/Runs/Approvals/PrState/SatStatus/Learnings) replaced.
//! The legacy `Panel` enum + `runs_state` / `approvals_state` selectors remain in
//! app.rs for now (Phase 6 mode toggles will repurpose them).

use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout};
use ratatui::style::Style;
use ratatui::widgets::{Block, BorderType, Clear, Paragraph};

use super::app::App;
use super::theme::Palette;
use super::widgets::{chain, footer, header, secondary, waterfall};

pub fn render_all(frame: &mut Frame, app: &mut App) {
    let palette = Palette::detect();
    let area = frame.area();
    let width = area.width;

    // Phase 7.3/7.4: responsive layout.
    //   ≥ 120 cols → full layout (chain 7-tile + waterfall+secondary side-by-side)
    //   80–119    → chain still rendered (compresses naturally), main stacks vertically
    //   < 80      → chain hidden, waterfall full-width, secondary as overlay-tab below
    // Chain panel content: blank + 3 station rows + connector + scope + action = 7
    // plus 2 border rows = 9.
    let chain_height = if width >= 80 { 9 } else { 0 };

    let outer = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(1),            // header
            Constraint::Length(chain_height), // chain (or 0 when narrow)
            Constraint::Min(8),               // main content
            Constraint::Length(1),            // footer
        ])
        .split(area);

    header::render(frame, outer[0], app, &palette);
    if chain_height > 0 {
        chain::render(frame, outer[1], app, &palette);
    }

    if width >= 120 {
        // Wide: side-by-side waterfall + secondary
        let main = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([Constraint::Percentage(65), Constraint::Percentage(35)])
            .split(outer[2]);
        waterfall::render(frame, main[0], app, &palette);
        secondary::render(frame, main[1], app, &palette);
    } else {
        // Narrow (80–119 or <80): stack waterfall on top, secondary below
        let main = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Percentage(60), Constraint::Percentage(40)])
            .split(outer[2]);
        waterfall::render(frame, main[0], app, &palette);
        secondary::render(frame, main[1], app, &palette);
    }

    footer::render(frame, outer[3], app, &palette);

    // Confirmation overlay (kept from legacy).
    if let Some(ref confirmation) = app.confirmation {
        render_confirmation_overlay(frame, &confirmation.prompt, &palette);
    }

    // Help modal (toggled by `?`).
    if app.show_help {
        render_help_overlay(frame, &palette);
    }
}

fn render_help_overlay(frame: &mut Frame, palette: &Palette) {
    use ratatui::layout::Rect;
    use ratatui::style::Modifier;
    use ratatui::text::{Line, Span};
    use ratatui::widgets::{Block, BorderType, Clear, Paragraph};

    let area = frame.area();
    let w = 56u16.min(area.width.saturating_sub(4));
    let h = 22u16.min(area.height.saturating_sub(4));
    let popup = Rect::new(
        (area.width.saturating_sub(w)) / 2,
        (area.height.saturating_sub(h)) / 2,
        w,
        h,
    );

    frame.render_widget(Clear, popup);

    let key = ratatui::style::Style::new()
        .fg(palette.cyan)
        .add_modifier(Modifier::BOLD);
    let dim = ratatui::style::Style::new().fg(palette.fg_3);
    let label = ratatui::style::Style::new().fg(palette.fg_1);
    let section = ratatui::style::Style::new()
        .fg(palette.fg_2)
        .add_modifier(Modifier::BOLD);

    let mut lines = vec![
        Line::raw(""),
        Line::from(Span::styled("  GATES", section)),
        Line::from(vec![
            Span::styled("  [enter]", key),
            Span::styled(" approve focused shift", label),
        ]),
        Line::from(vec![
            Span::styled("  [c]    ", key),
            Span::styled(" cancel focused shift", label),
        ]),
        Line::raw(""),
        Line::from(Span::styled(
            "  PANELS  · cycle on / off; auto-returns to dials on judging",
            section,
        )),
        Line::from(vec![
            Span::styled("  [s]    ", key),
            Span::styled(" shifts list", label),
        ]),
        Line::from(vec![
            Span::styled("  [b]    ", key),
            Span::styled(" blueprints", label),
        ]),
        Line::from(vec![
            Span::styled("  [k]    ", key),
            Span::styled(" backlog", label),
        ]),
        Line::from(vec![
            Span::styled("  [j]    ", key),
            Span::styled(" judges", label),
        ]),
        Line::from(vec![
            Span::styled("  [d]    ", key),
            Span::styled(" force back to sat dials", label),
        ]),
        Line::raw(""),
        Line::from(Span::styled("  WATERFALL", section)),
        Line::from(vec![
            Span::styled("  [f]    ", key),
            Span::styled(" cycle filter (all/errors/tools/routing)", label),
        ]),
        Line::raw(""),
        Line::from(Span::styled("  GENERAL", section)),
        Line::from(vec![
            Span::styled("  [tab]  ", key),
            Span::styled(" cycle pane focus", label),
        ]),
        Line::from(vec![
            Span::styled("  [?]    ", key),
            Span::styled(" toggle this help · [esc] dismiss", label),
        ]),
        Line::from(vec![
            Span::styled("  [q]    ", key),
            Span::styled(" detach (daemon keeps running)", label),
        ]),
        Line::raw(""),
        Line::from(Span::styled(
            "  override hotkey omitted by design — see open Q3.",
            dim,
        )),
    ];
    // Trim if vertical space is short.
    while lines.len() as u16 > h.saturating_sub(2) {
        lines.pop();
    }

    let block = Block::bordered()
        .title(Span::styled(" KEYBINDINGS ", key))
        .border_type(BorderType::Double)
        .border_style(ratatui::style::Style::new().fg(palette.cyan));
    let para = Paragraph::new(lines).block(block).style(
        ratatui::style::Style::new()
            .fg(palette.fg_0)
            .bg(palette.bg_0),
    );
    frame.render_widget(para, popup);
}

fn render_confirmation_overlay(frame: &mut Frame, prompt: &str, palette: &Palette) {
    let area = frame.area();
    let popup_width = 40.min(area.width.saturating_sub(2));
    let popup_height = 3;
    let popup_area = ratatui::layout::Rect::new(
        (area.width.saturating_sub(popup_width)) / 2,
        (area.height.saturating_sub(popup_height)) / 2,
        popup_width,
        popup_height,
    );

    frame.render_widget(Clear, popup_area);
    let block = Block::bordered()
        .title("Confirm")
        .border_type(BorderType::Double)
        .border_style(Style::new().fg(palette.ember));
    let paragraph = Paragraph::new(prompt)
        .block(block)
        .alignment(ratatui::layout::Alignment::Center)
        .style(Style::new().fg(palette.fg_0).bg(palette.bg_0));
    frame.render_widget(paragraph, popup_area);
}