diff --git a/hanatui/publish/hanatui b/hanatui/publish/hanatui
index c406661..d02e393 100755
Binary files a/hanatui/publish/hanatui and b/hanatui/publish/hanatui differ
diff --git a/hanatui/src/Tui/Components/StatsPanel.cs b/hanatui/src/Tui/Components/StatsPanel.cs
index 335f695..3c2a94b 100644
--- a/hanatui/src/Tui/Components/StatsPanel.cs
+++ b/hanatui/src/Tui/Components/StatsPanel.cs
@@ -10,133 +10,138 @@ namespace HanaTui.Tui.Components;
///
public static class StatsPanel
{
- public static IRenderable Build(SystemSnapshot snap, TimeSpan elapsed, int panelWidth)
+ public static IRenderable Build(SystemSnapshot snap, TimeSpan elapsed, int panelWidth)
+ {
+ var barWidth = Math.Max(10, panelWidth - 16);
+
+ var grid = new Grid();
+ grid.AddColumn(new GridColumn().NoWrap());
+
+ // --- CPU ---
+ grid.AddRow(new Markup("[bold yellow]CPU[/]"));
+
+ if (snap.CpuPercents.Length == 0)
{
- var barWidth = Math.Max(10, panelWidth - 16);
-
- var grid = new Grid();
- grid.AddColumn(new GridColumn().NoWrap());
-
- // --- CPU ---
- grid.AddRow(new Markup("[bold yellow]CPU[/]"));
-
- if (snap.CpuPercents.Length == 0)
- {
- grid.AddRow(new Markup("[grey]No data[/]"));
- }
- else
- {
- for (int i = 0; i < snap.CpuPercents.Length; i++)
- {
- var label = snap.CpuLabels.Length > i ? snap.CpuLabels[i] : $"cpu{i}";
- var displayLabel = label == "cpu" ? "Total" : label.Replace("cpu", "Core");
- var pct = snap.CpuPercents[i];
- // Build as Columns: label | bar | pct — no interpolation of dynamic values into markup
- grid.AddRow(BuildBarRow(displayLabel, pct, barWidth));
- }
- }
-
- grid.AddRow(new Text(""));
-
- // --- Memory ---
- grid.AddRow(new Markup("[bold cyan]MEMORY[/]"));
- if (snap.MemTotalKb > 0)
- {
- var memPct = snap.MemUsedPercent;
- grid.AddRow(BuildBarRow("Used", memPct, barWidth));
- grid.AddRow(new Text($" {SystemSnapshot.FormatGb(snap.MemUsedKb)} / {SystemSnapshot.FormatGb(snap.MemTotalKb)}",
- new Style(Color.Grey)));
- }
- else
- {
- grid.AddRow(new Markup("[grey]No data[/]"));
- }
-
- grid.AddRow(new Text(""));
-
- // --- Swap ---
- grid.AddRow(new Markup("[bold cyan]SWAP[/]"));
- if (snap.SwapTotalKb > 0)
- {
- var swapPct = snap.SwapUsedPercent;
- grid.AddRow(BuildBarRow("Used", swapPct, barWidth));
- grid.AddRow(new Text($" {SystemSnapshot.FormatGb(snap.SwapUsedKb)} / {SystemSnapshot.FormatGb(snap.SwapTotalKb)}",
- new Style(Color.Grey)));
- }
- else
- {
- grid.AddRow(new Markup("[grey]No swap[/]"));
- }
-
- grid.AddRow(new Text(""));
-
- // --- Elapsed ---
- // Two separate renderables composed — no interpolation of elapsed into markup
- var elapsedGrid = new Grid();
- elapsedGrid.AddColumn(new GridColumn().NoWrap());
- elapsedGrid.AddColumn(new GridColumn().NoWrap());
- elapsedGrid.AddRow(
- new Markup("[bold]Elapsed:[/]"),
- new Text(" " + FormatElapsed(elapsed), new Style(Color.Yellow)));
- grid.AddRow(elapsedGrid);
-
- return new Panel(grid)
- {
- Header = new PanelHeader("[bold] SYSTEM STATS [/]"),
- Border = BoxBorder.Rounded,
- Padding = new Padding(1, 0),
- };
+ grid.AddRow(new Markup("[grey]No data[/]"));
+ }
+ else
+ {
+ for (int i = 0; i < snap.CpuPercents.Length; i++)
+ {
+ var label = snap.CpuLabels.Length > i ? snap.CpuLabels[i] : $"cpu{i}";
+ var displayLabel = label == "cpu" ? "Total" : label.Replace("cpu", "Core");
+ var pct = snap.CpuPercents[i];
+ // Build as Columns: label | bar | pct — no interpolation of dynamic values into markup
+ grid.AddRow(BuildBarRow(displayLabel, pct, barWidth));
+ }
}
- // -------------------------------------------------------------------------
- // Helpers
- // -------------------------------------------------------------------------
+ grid.AddRow(new Text(""));
- ///
- /// Builds a single bar row as a Grid with three columns:
- /// label (dim) | bar (block chars, plain Text with color) | pct (bold)
- /// Nothing here goes through markup parsing with dynamic content.
- ///
- private static IRenderable BuildBarRow(string label, double pct, int barWidth)
+ // --- Memory ---
+ grid.AddRow(new Markup("[bold cyan]MEMORY[/]"));
+ if (snap.MemTotalKb > 0)
{
- var filled = (int)Math.Round(pct / 100.0 * barWidth);
- filled = Math.Clamp(filled, 0, barWidth);
- var empty = barWidth - filled;
-
- var filledColor = CpuColor(pct);
- var filledStr = new string('\u2588', filled);
- var emptyStr = new string('\u2591', empty);
-
- var row = new Grid();
- row.AddColumn(new GridColumn().NoWrap());
- var colorMarkup = filledColor.ToMarkup();
-
- // Construct a seamless markup string
- var barMarkup = $"[grey][[[/][{colorMarkup}]{filledStr}[/][grey dim]{emptyStr}[/][grey]]][/]";
-
- row.AddRow(
- new Text($" {label,-5}", new Style(Color.Grey, decoration: Decoration.Dim)),
- new Markup(barMarkup), // Replace Columns with a single Markup object
- new Text($" {pct,5:F1}%", new Style(Color.White, decoration: Decoration.Bold))
- );
-
- return row;
+ var memPct = snap.MemUsedPercent;
+ grid.AddRow(BuildBarRow("Used", memPct, barWidth));
+ grid.AddRow(new Text($" {SystemSnapshot.FormatGb(snap.MemUsedKb)} / {SystemSnapshot.FormatGb(snap.MemTotalKb)}",
+ new Style(Color.Grey)));
+ }
+ else
+ {
+ grid.AddRow(new Markup("[grey]No data[/]"));
}
- private static Color CpuColor(double pct) => pct switch
+ grid.AddRow(new Text(""));
+
+ // --- Swap ---
+ grid.AddRow(new Markup("[bold cyan]SWAP[/]"));
+ if (snap.SwapTotalKb > 0)
{
- > 90 => Color.Red,
- > 70 => Color.Yellow,
- > 40 => Color.Green,
- _ => Color.Blue,
+ var swapPct = snap.SwapUsedPercent;
+ grid.AddRow(BuildBarRow("Used", swapPct, barWidth));
+ grid.AddRow(new Text($" {SystemSnapshot.FormatGb(snap.SwapUsedKb)} / {SystemSnapshot.FormatGb(snap.SwapTotalKb)}",
+ new Style(Color.Grey)));
+ }
+ else
+ {
+ grid.AddRow(new Markup("[grey]No swap[/]"));
+ }
+
+ grid.AddRow(new Text(""));
+
+ // --- Elapsed ---
+ // Two separate renderables composed — no interpolation of elapsed into markup
+ var elapsedGrid = new Grid();
+ elapsedGrid.AddColumn(new GridColumn().NoWrap());
+ elapsedGrid.AddColumn(new GridColumn().NoWrap());
+ elapsedGrid.AddRow(
+ new Markup("[bold]Elapsed:[/]"),
+ new Text(" " + FormatElapsed(elapsed), new Style(Color.Yellow)));
+ grid.AddRow(elapsedGrid);
+
+ return new Panel(grid)
+ {
+ Header = new PanelHeader("[bold] SYSTEM STATS [/]"),
+ Border = BoxBorder.Rounded,
+ Padding = new Padding(1, 0),
};
+ }
- private static string FormatElapsed(TimeSpan t)
- {
- if (t.TotalHours >= 1)
- return $"{(int)t.TotalHours}h {t.Minutes:D2}m {t.Seconds:D2}s";
- if (t.TotalMinutes >= 1)
- return $"{t.Minutes}m {t.Seconds:D2}s";
- return $"{t.Seconds}s";
- }
+ // -------------------------------------------------------------------------
+ // Helpers
+ // -------------------------------------------------------------------------
+
+ ///
+ /// Builds a single bar row as a Grid with three columns:
+ /// label (dim) | bar (block chars, plain Text with color) | pct (bold)
+ /// Nothing here goes through markup parsing with dynamic content.
+ ///
+ private static IRenderable BuildBarRow(string label, double pct, int barWidth)
+ {
+ var filled = (int)Math.Round(pct / 100.0 * barWidth);
+ filled = Math.Clamp(filled, 0, barWidth);
+ var empty = barWidth - filled;
+
+ var filledColor = CpuColor(pct);
+ var filledStr = new string('\u2588', filled);
+ var emptyStr = new string('\u2591', empty);
+
+ var row = new Grid();
+
+ // FIX: Add all three columns back!
+ row.AddColumn(new GridColumn().NoWrap().Width(9)); // Column 1: Label
+ row.AddColumn(new GridColumn().NoWrap()); // Column 2: Bar
+ row.AddColumn(new GridColumn().NoWrap().Width(7)); // Column 3: Pct
+
+ var colorMarkup = filledColor.ToMarkup();
+
+ // Construct a seamless markup string
+ var barMarkup = $"[grey][[[/][{colorMarkup}]{filledStr}[/][grey dim]{emptyStr}[/][grey]]][/]";
+
+ row.AddRow(
+ new Text($" {label,-5}", new Style(Color.Grey, decoration: Decoration.Dim)),
+ new Markup(barMarkup),
+ new Text($" {pct,5:F1}%", new Style(Color.White, decoration: Decoration.Bold))
+ );
+
+ return row;
+ }
+
+ private static Color CpuColor(double pct) => pct switch
+ {
+ > 90 => Color.Red,
+ > 70 => Color.Yellow,
+ > 40 => Color.Green,
+ _ => Color.Blue,
+ };
+
+ private static string FormatElapsed(TimeSpan t)
+ {
+ if (t.TotalHours >= 1)
+ return $"{(int)t.TotalHours}h {t.Minutes:D2}m {t.Seconds:D2}s";
+ if (t.TotalMinutes >= 1)
+ return $"{t.Minutes}m {t.Seconds:D2}s";
+ return $"{t.Seconds}s";
+ }
}