Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions demos/tui_capture_2026-09-11-memory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Memory-label TUI capture — 2026-09-11

New capture pair showing the `Memory: ...` label in the final rendered frame, after the
fix in `42f4602` moved the label above the memory graph's clear/braille passes. It does not
replace the older 2026-09-11 captures; those remain as historical material.

## Build identity

| Field | Value |
| --- | --- |
| Commit | `42f460271a45907d4e571741de4e79642f9f9df8` |
| Working tree at build time | clean (`git status --porcelain` = 0 paths) |
| Build command | `cargo build --locked` |
| Binary | `target/debug/grainx` |
| Binary SHA-256 | `8572c7e189a5736733869c337482a6a348838c9148d44831ff1048f991f7bbcc` |
| Toolchain | `rustc 1.94.1 (e408947bf 2026-03-25)` / `cargo 1.94.1 (29ea6fb6a 2026-03-24)` |
| Renderer | `pyte 0.8.2` (throwaway venv; capture itself uses only the Python standard library) |
| Host | macOS Apple Silicon (darwin arm64), UTC session date 2026-09-11 |

## Captured artifacts

| File | SHA-256 |
| --- | --- |
| `tui_capture_2026-09-11-memory.raw` | `153bc2be869554ac1850c65af18b91658a1fd025e04a3c271fff9b3d0f5a9a1e` |
| `tui_capture_2026-09-11-memory.txt` | `4b83973388f19e59fbebf77e1660d128740d3fb61894f74632116bf5235a8af9` |

## Render command

~~~bash
python3 -m venv /tmp/grainx-c-render-venv
/tmp/grainx-c-render-venv/bin/pip install pyte==0.8.2
/tmp/grainx-c-render-venv/bin/python demos/capture_tui.py \
target/debug/grainx \
demos/tui_capture_2026-09-11-memory.raw \
demos/tui_capture_2026-09-11-memory.txt \
5 110 50
~~~

Final-frame check (fails if the label was cleared):

~~~bash
python3 -c 'import sys; lines=open("demos/tui_capture_2026-09-11-memory.txt").read().splitlines(); sys.exit(0 if any("Memory:" in l for l in lines) else 1)'
~~~

Observed final frame (pyte, 110x50), row 18 is the label and rows 19-25 are the memory graph:

~~~text
Memory: 52.4% (12.6GB/24.0GB)
Network I/O: RX 14.0 KB TX 1.0 KB (last interval)
~~~

The network label keeps its per-interval byte-delta semantics; `KB/s` appears nowhere in the
raw stream.

## Regression test

`tests/tui_memory_label.rs` checks, for several terminal sizes, that `DashboardLayout::memory_label_y()`
is outside the memory graph rectangle (whose clear and braille passes rewrite every cell) and below
the CPU graph clear pass. It fails on the pre-fix row:

~~~bash
cargo test --locked --test tui_memory_label
~~~

## Privacy

Host snapshot fields present: OS name, kernel version, uptime, CPU core percentages, memory totals,
disk label and sizes, top process names, PIDs, per-process CPU and memory sizes. A pattern scan of
the new raw/txt captures found no home-directory paths, credentials, tokens, keys, emails, or IP
addresses. Regenerate on a quiet host if a neutral process list is required.

## Limitations

- Values are one host and one instant, not benchmarks.
- The `CPU Usage:` label still sits inside the CPU graph rectangle and is cleared by the same
class of pass; this pre-existing issue is out of scope for the memory-label fix.
1 change: 1 addition & 0 deletions demos/tui_capture_2026-09-11-memory.raw

Large diffs are not rendered by default.

50 changes: 50 additions & 0 deletions demos/tui_capture_2026-09-11-memory.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
=== grainx System Monitor === Iteration: 2 ===

















Memory: 52.4% (12.6GB/24.0GB)
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
Network I/O: RX 14.0 KB TX 1.0 KB (last interval)
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀

System: Darwin | Kernel: 25.6.0 | Uptime: 1h
Network totals: RX:0.0MB TX:0.0MB
CPU Cores: C0:43.7% C1:34.8% C2:25.6% C3:19.5% C4: 3.0% C5: 2.3% C6: 2.0% C7: 1.7%
Disks: Mac:84.0%(460.4GB) Mac:84.0%(460.4GB)
Top Processes (UP/DOWN to select, 'k' to kill, 'q' to quit):
> 717 ScreenTimeAgent 0.0% 25056KB
852 commerce 0.0% 13664KB
703 CMFSyncAgent 0.0% 8576KB
3194 node 0.0% 103520KB
915 spindump_agent 0.0% 4416KB
677 corespotlightd 0.0% 158992KB
653 CoreLocationAgent 0.0% 44832KB
634 audioaccessoryd 0.0% 29856KB
576 trustd 0.0% 0KB
561 CategoriesService 0.0% 0KB


Performance: 0.0FPS | 0.0ms | Adaptive: ON
Config: grainx_advanced | Controls: q=quit, h=help, p=pause, a=adaptive, s=export
6 changes: 6 additions & 0 deletions src/rendering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ impl DashboardLayout {
footer_y,
}
}

/// Row for the memory label, kept just above `mem_rect` so the memory
/// graph's clear and braille passes can never overwrite it.
pub fn memory_label_y(&self) -> u16 {
self.mem_rect.y.saturating_sub(1)
}
}

pub struct AdvancedCanvas {
Expand Down
2 changes: 1 addition & 1 deletion src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ pub async fn draw_dashboard(
canvas.set_color(cpu_color)?;
canvas.draw_braille_line(&state.cpu_points, &ctx.layout.cpu_rect)?;

canvas.set_cursor(0, ctx.layout.mem_rect.y)?;
canvas.set_cursor(0, ctx.layout.memory_label_y())?;
canvas.set_color(ctx.palette.label)?;
canvas.draw_str(&format!(
"Memory: {:.1}% ({:.1}GB/{:.1}GB)",
Expand Down
24 changes: 24 additions & 0 deletions tests/tui_memory_label.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use grainx::rendering::DashboardLayout;

/// The memory label must be drawn outside the memory graph rectangle, which the
/// clear pass and braille pass rewrite cell by cell, and below the CPU graph
/// clear pass. Otherwise the label is erased before the frame reaches the screen.
#[test]
fn memory_label_row_is_not_cleared_by_graph_passes() {
for (width, height) in [(60, 20), (80, 24), (100, 32), (110, 50), (200, 80)] {
let layout = DashboardLayout::from_terminal_size(width, height);
let label_y = layout.memory_label_y();
let mem_top = layout.mem_rect.y;
let mem_bottom = mem_top + layout.mem_rect.height;
let cpu_bottom = layout.cpu_rect.y + layout.cpu_rect.height;

assert!(
label_y < mem_top,
"memory label row {label_y} overlaps memory graph rows {mem_top}..{mem_bottom} at {width}x{height}"
);
assert!(
label_y >= cpu_bottom,
"memory label row {label_y} is inside the CPU graph clear pass ending at {cpu_bottom} at {width}x{height}"
);
}
}
Loading