tix-tracker 0.12.0

A terminal issue tracker: markdown + YAML frontmatter issues with a kanban TUI and CLI
Documentation
#[allow(clippy::wildcard_imports)]
use super::*;

pub(in crate::app) fn render_repos(app: &App, f: &mut ratatui::Frame, area: Rect) {
    let block = Block::default()
        .borders(Borders::ALL)
        .title("Repositories (F6)");
    let inner = block.inner(area);
    f.render_widget(block, area);
    if inner.height == 0 || inner.width == 0 {
        return;
    }
    let entries = app.repo_entries();
    if entries.is_empty() {
        let lines = vec![
            Line::from(Span::styled(
                "No repositories configured.",
                Style::default()
                    .fg(Color::Yellow)
                    .add_modifier(Modifier::BOLD),
            )),
            Line::from(""),
            Line::from(
                "Add the child repositories this project coordinates under `[repos]` in tix.toml:",
            ),
            Line::from(Span::styled(
                "    [repos]",
                Style::default().fg(Color::DarkGray),
            )),
            Line::from(Span::styled(
                "    tix-tracker = \"../tix\"",
                Style::default().fg(Color::DarkGray),
            )),
            Line::from(""),
            Line::from("Each value is that repository's project root; Enter runs tix inside it."),
            Line::from("When this repository is itself a managed child, set `parent_repo` so this"),
            Line::from("mode can jump back to the parent."),
        ];
        f.render_widget(Paragraph::new(lines).wrap(Wrap { trim: false }), inner);
        return;
    }
    let mut lines: Vec<Line<'static>> = Vec::new();
    for (index, (label, path, is_parent)) in entries.iter().enumerate() {
        let selected = index == app.repos_cursor;
        let style = if selected {
            Style::default()
                .fg(Color::Black)
                .bg(Color::Cyan)
                .add_modifier(Modifier::BOLD)
        } else {
            Style::default()
        };
        let marker = if selected { ">" } else { " " };
        let kind = if *is_parent { "  (parent)" } else { "" };
        lines.push(Line::from(Span::styled(
            format!("{marker} {label}{kind}"),
            style,
        )));
        lines.push(Line::from(Span::styled(
            format!("    {}", path.display()),
            Style::default().fg(Color::DarkGray),
        )));
    }
    f.render_widget(Paragraph::new(lines), inner);
    // One click region per repository row (two drawn lines each), so a repository
    // can be run with the pointer as well as with Enter.
    let mut y = inner.y;
    for (_, path, _) in &entries {
        if y >= inner.y + inner.height {
            break;
        }
        app.record_region(
            Rect {
                x: inner.x,
                y,
                width: inner.width,
                height: 2.min(inner.y + inner.height - y),
            },
            Action::OpenRepo(path.clone()),
        );
        y += 2;
    }
}

/// Run `tix` inside `repo`, with `TIX_PARENT_REPO` set to `parent` so the child
/// can jump straight back (tix #1529).
///
/// Spawns the **current executable** rather than a `tix` resolved on `PATH`: the
/// binary running this session is, by definition, the one the user built or
/// installed, so a repository opened from a development build runs that build.
/// The caller hands over the terminal; this only spawns and waits.
///
/// Refuses a path that is not a repository, and says which of the two it is —
/// a `tix.toml`-less directory is the common mistake, and naming it is more
/// useful than a generic launch failure.
pub(in crate::app) fn run_child_tix(repo: &Path, parent: &Path) -> Result<()> {
    if !repo.is_dir() {
        return Err(crate::error::TixError::InvalidInput(format!(
            "{} is not a directory",
            repo.display()
        )));
    }
    if !repo.join("tix.toml").is_file() {
        return Err(crate::error::TixError::InvalidInput(format!(
            "{} has no tix.toml — is it a tix repository?",
            repo.display()
        )));
    }
    let exe = std::env::current_exe().map_err(io_err)?;
    std::process::Command::new(exe)
        .current_dir(repo)
        .env("TIX_PARENT_REPO", parent)
        .status()
        .map_err(|e| {
            crate::error::TixError::InvalidInput(format!(
                "failed to launch tix in {}: {e}",
                repo.display()
            ))
        })?;
    Ok(())
}