diff --git a/hive-c0re/src/dashboard.rs b/hive-c0re/src/dashboard.rs index 37e3fbd..eb55d9e 100644 --- a/hive-c0re/src/dashboard.rs +++ b/hive-c0re/src/dashboard.rs @@ -249,12 +249,12 @@ async fn render_approvals(approvals: &[Approval]) -> String { hive_sh4re::ApprovalKind::ApplyCommit => { let sha_short = &a.commit_ref[..a.commit_ref.len().min(12)]; let diff = approval_diff(&a.agent, &a.commit_ref).await; + let diff_html = render_diff_lines(&diff); let _ = writeln!( out, - "
  • \n
    #{id} {agent} apply {sha_short}\n
    \n
    \n
    \n
    diff vs applied
    {diff}
    \n
  • ", + "
  • \n
    #{id} {agent} apply {sha_short}\n
    \n
    \n
    \n
    diff vs applied
    {diff_html}
    \n
  • ", id = a.id, agent = a.agent, - diff = html_escape(&diff), ); } hive_sh4re::ApprovalKind::Spawn => { @@ -294,6 +294,33 @@ fn gc_orphans(coord: &Coordinator, approvals: Vec) -> Vec { .collect() } +/// Render a unified diff with per-line CSS classes so the dashboard can +/// colour adds / dels / hunk headers / context. Each line becomes a +/// `` tagged by its leading character; the wrapping `
    ` keeps
    +/// whitespace intact.
    +fn render_diff_lines(diff: &str) -> String {
    +    let mut out = String::new();
    +    for raw in diff.lines() {
    +        let cls = match raw.as_bytes().first() {
    +            // file headers (`--- a/...` / `+++ b/...`) come before any
    +            // line starting with a single `+`/`-`. similar-rs emits them
    +            // with the doubled prefix.
    +            _ if raw.starts_with("--- ") => "diff-file",
    +            _ if raw.starts_with("+++ ") => "diff-file",
    +            Some(b'@') => "diff-hunk",
    +            Some(b'+') => "diff-add",
    +            Some(b'-') => "diff-del",
    +            _ => "diff-ctx",
    +        };
    +        let _ = writeln!(
    +            out,
    +            "{}",
    +            html_escape(raw),
    +        );
    +    }
    +    out
    +}
    +
     /// Returns either an empty string (agent is up-to-date / no rev known) or
     /// a clickable "needs update" badge whose form POSTs to /rebuild/.
     fn update_badge_for(name: &str, current_rev: Option<&str>) -> String {
    @@ -599,9 +626,15 @@ const STYLE: &str = r#"
         overflow-x: auto;
         font-size: 0.85em;
         line-height: 1.4;
    -    color: var(--fg);
    +    color: var(--muted);
         white-space: pre;
       }
    +  .diff span { display: block; }
    +  .diff .diff-add  { color: var(--green); }
    +  .diff .diff-del  { color: var(--red); }
    +  .diff .diff-hunk { color: var(--cyan); }
    +  .diff .diff-file { color: var(--purple); font-weight: bold; }
    +  .diff .diff-ctx  { color: var(--fg); }
       .msgflow {
         background: var(--bg-elev);
         border: 1px solid var(--border);