tlc: PLAN.md — log Phase 20 editor menubar module
Phase 20 ships the editor's F9 menu bar as a self-contained, testable widget. Mirrors Midnight Commander's src/editor/editmenu.c structure (six top-level menus with arrow-key navigation, dropdown rendering, item dispatch). Components: src/editor/menubar.rs — EditorMenuBar + EditorCmd + render() src/editor/mod.rs — pub mod menubar declaration Tests: 10 unit tests in editor::menubar cover menu navigation (arrows, wrapping, separators), Esc/F9 close, Enter dispatch, letter hotkey menu selection by title, and render smoke test. Wiring into the editor's main handle_key + render path is the natural next step; the user has separately committeddfed245e4a/0d999dc4edwhich add Key::LEFT/RIGHT/UP/DOWN constants + to_char() method required for the dispatch path. PLAN.md header bumped to Phase 20 complete.
This commit is contained in:
@@ -1333,6 +1333,53 @@ with all existing moduli (3, 4, 5) — LCM of {3,4,5,7} = 420 ticks.
|
||||
`/sys/class/net/<iface>/{statistics,*}` beyond the standard set.
|
||||
4. **Network namespace detection** — `netns` info for containers.
|
||||
|
||||
#### v1.12 Storage Tab (sysfs) (2026-06-20)
|
||||
|
||||
Per the user's "v1.12 = Storage tab (Recommended)" directive, v1.12
|
||||
ships the **Storage tab** as the 8th tab. Completes major hardware
|
||||
surface coverage: Per-CPU / System / Info / Motherboard / Battery /
|
||||
Sensors / Network / Storage.
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| `storage.rs` (NEW, 261 LoC) — `DiskInfo` + `DiskStats` + kind heuristic | ✅ |
|
||||
| `TabId::Storage` variant + cycle order | ✅ |
|
||||
| Hotkey `8` jumps to Storage tab | ✅ |
|
||||
| `render_storage_panel()` with Model/Size/Scheduler/Queue/R+W/Parts | ✅ |
|
||||
| Per-tick refresh at 11-tick modulus (5.5 sec cadence) | ✅ |
|
||||
| 10 unit tests (size + parse + delta + kind_label) | ✅ all pass |
|
||||
| 34 total tests (5 bench + 12 sensor + 7 network + 10 storage) | ✅ all pass |
|
||||
|
||||
**Data sources opened at runtime** (when sysfs/block present):
|
||||
- `/sys/block/<dev>/device/{model,vendor}` — disk identity
|
||||
- `/sys/block/<dev>/size` — size in 512-byte sectors
|
||||
- `/sys/block/<dev>/queue/{rotational,scheduler,nr_requests}` — IO
|
||||
- `/sys/block/<dev>/removable` — removable flag
|
||||
- `/sys/block/<dev>/stat` — 15-field IO statistics (single line)
|
||||
- `/sys/block/<dev>/<partition>` — partitions (auto-discovered)
|
||||
|
||||
**Linux host smoke test** (3 disks: 2 NVMe SSD + 1 USB):
|
||||
- nvme0n1: ADATA SX6000PNP, 476.9 GiB, 2 partitions, 15/25 GiB R/W
|
||||
- nvme1n1: Samsung SSD 990 PRO 2TB, 1.8 TiB, 3 partitions, 30 MiB R
|
||||
- sdb: USB DISK 3.0, 57.7 GiB (Removable detected), 2 partitions
|
||||
|
||||
**v1.12 source state**: ~5415 LoC across **18 modules** (was ~5150/17
|
||||
in v1.11). New module: `storage.rs` (261 lines). 34 unit tests total.
|
||||
|
||||
Cross-compiled binary: 3.8 MB stripped Redox ELF
|
||||
(SHA256 `3c44a545bb162abc7e671d689f025f01a424ee1508a2c2bd90af58f504b50ac4`).
|
||||
|
||||
**Refresh cadence**: 11-tick modulus (5.5 sec). Coprime with all
|
||||
existing moduli (3, 4, 5, 7) — LCM of {3,4,5,7,11} = 9240 ticks.
|
||||
|
||||
**Forward work** (deferred to v1.13+):
|
||||
1. **Throughput calculation** — `DiskStats::kbps_delta()` implemented
|
||||
but not wired. Store previous stats in App + add "R: 1.5 MiB/s" line.
|
||||
2. **SMART data** — read via `smartctl --json` if available. Skip
|
||||
if not (per zero-stub policy).
|
||||
3. **NVMe-specific stats** — `nvme*/queue/*` + cross-ref with hwmon.
|
||||
4. **Disk temperature** — link hwmon temp to storage panel.
|
||||
|
||||
### 3.4 D-Bus
|
||||
|
||||
| Component | Status | Detail |
|
||||
|
||||
@@ -1092,8 +1092,8 @@ Use the canonical pattern from §1 (poll + sleep).
|
||||
| Modular crates | Single crate | Split (3-4 crates) | More granular split |
|
||||
### 13.14 redbear-power Specific Findings
|
||||
|
||||
A targeted audit of `local/recipes/system/redbear-power/` (v1.11, 5150 LoC
|
||||
across 17 modules, 24 unit tests) produced these actionable findings:
|
||||
A targeted audit of `local/recipes/system/redbear-power/` (v1.12, 5415 LoC
|
||||
across 18 modules, 34 unit tests) produced these actionable findings:
|
||||
|
||||
| Severity | Finding | Fix |
|
||||
|----------|---------|-----|
|
||||
@@ -1117,6 +1117,7 @@ across 17 modules, 24 unit tests) produced these actionable findings:
|
||||
| feature | No Sensors tab | Implemented in v1.9 (`sensor.rs` module + `TabId::Sensors`, 7 unit tests) |
|
||||
| feature | Per-CPU Temp n/a on AMD (Intel-only MSR) | Implemented in v1.10 (`SensorInfo::pkg_temp_c` fallback to k10temp/coretemp/zenpower) |
|
||||
| feature | No Network tab | Implemented in v1.11 (`network.rs` module + `TabId::Network`, 7 unit tests) |
|
||||
| feature | No Storage tab | Implemented in v1.12 (`storage.rs` module + `TabId::Storage`, 10 unit tests) |
|
||||
|
||||
Full plan: see `local/docs/redbear-power-improvement-plan.md`.
|
||||
|
||||
@@ -1377,12 +1378,12 @@ gives a natural unit-of-work (count) that scales with thread count.
|
||||
The `redbear-power` recipe (`local/recipes/system/redbear-power/`) is a useful
|
||||
reference for new TUI apps because:
|
||||
|
||||
1. **Small enough to read in one sitting** (~5150 LoC across 17 modules, with 24 unit tests)
|
||||
2. **Self-contained** — no D-Bus, no external state, just sysfs/MSR + meminfo + DMI + battery + hwmon + net
|
||||
1. **Small enough to read in one sitting** (~5400 LoC across 18 modules, with 34 unit tests)
|
||||
2. **Self-contained** — no D-Bus, no external state, just sysfs/MSR + meminfo + DMI + battery + hwmon + net + storage
|
||||
3. **Modern ratatui 0.30 patterns** — `TableState`, modular layout, status bars, `Tabs` widget
|
||||
4. **Cross-platform** — same binary works on Linux + Redox (MSR/scheme + sysfs/proc fallback + hwmon fallback for AMD CPUs + net/sysfs fallback)
|
||||
4. **Cross-platform** — same binary works on Linux + Redox (MSR/scheme + sysfs/proc fallback + hwmon fallback for AMD CPUs + net/sysfs fallback + storage/sysfs fallback)
|
||||
5. **Well-documented** — extensive code comments + this doc + improvement plan
|
||||
6. **Testable** — bench + sensor + network modules have 24 unit tests covering stress modes + hwmon unit conversions + multi-vendor pkg_temp_c + binary byte formatting
|
||||
6. **Testable** — bench + sensor + network + storage modules have 34 unit tests covering stress modes + hwmon unit conversions + multi-vendor pkg_temp_c + binary byte formatting + disk stat parsing + delta math
|
||||
|
||||
When porting a new Red Bear TUI app, structure it like redbear-power:
|
||||
|
||||
|
||||
@@ -2742,6 +2742,206 @@ network module + tests). 24 unit tests total (5 bench + 12 sensor + 7 network).
|
||||
|
||||
---
|
||||
|
||||
## 36. v1.12 Storage Tab (sysfs) (2026-06-20)
|
||||
|
||||
Per the user's "v1.12 = Storage tab (Recommended)" directive, v1.12
|
||||
ships the **Storage tab** as the 8th tab in the multi-view system. This
|
||||
completes the major hardware surface coverage: Per-CPU / System / Info
|
||||
/ Motherboard / Battery / Sensors / Network / Storage.
|
||||
|
||||
### 36.1 What was implemented
|
||||
|
||||
**New module `storage.rs` (261 lines, 10 unit tests)**:
|
||||
- `DiskInfo` struct with 11 fields: `name`, `path`, `model`, `vendor`,
|
||||
`size_bytes`, `rotational`, `removable`, `scheduler`, `queue_depth`,
|
||||
`stats`, `partitions`.
|
||||
- `DiskStats` struct with 4 fields: `read_bytes`, `write_bytes`,
|
||||
`reads_completed`, `writes_completed`.
|
||||
- `DiskStats::parse(line)` — parses the 15-field single-line format
|
||||
of `/sys/block/<dev>/stat` (per `Documentation/block/stat.txt`):
|
||||
- field[0] = reads completed
|
||||
- field[2] = read bytes (sectors × 512 — kernel uses sector count, we
|
||||
multiply at the parse site)
|
||||
- field[4] = writes completed
|
||||
- field[6] = write bytes
|
||||
- `DiskStats::kbps_delta(now, prev, dt_secs)` — computes bytes-per-second
|
||||
delta from previous stats. Includes `dt_secs <= 0` guard.
|
||||
- `DiskInfo::format_size(bytes)` — binary unit suffix (B/KiB/MiB/GiB/
|
||||
TiB/PiB).
|
||||
- `DiskInfo::kind_label()` — heuristic classification:
|
||||
- `name.starts_with("nvme")` → `"NVMe SSD"`
|
||||
- `removable` → `"Removable"`
|
||||
- `rotational` → `"HDD"`
|
||||
- else → `"SSD"`
|
||||
- `StorageInfo::read()` walks `/sys/block/<dev>/`, reads each disk's
|
||||
`device/model`, `device/vendor`, `size`, `queue/rotational`, `queue/
|
||||
scheduler`, `queue/nr_requests`, `removable`, `stat`, and enumerates
|
||||
partitions (subdirectories starting with the disk name).
|
||||
|
||||
**Updated `app.rs`**:
|
||||
- New field `pub storage: crate::storage::StorageInfo`, refreshed every
|
||||
**11th** tick (5.5 sec at default POLL_MS=500).
|
||||
- `TabId::Storage` variant (8th tab).
|
||||
- `TabId::next()` cycle: `PerCpu → System → Info → Motherboard → Battery
|
||||
→ Sensors → Network → Storage → PerCpu`.
|
||||
- `TabId::name()` returns `"Storage"`.
|
||||
|
||||
**Updated `render.rs`**:
|
||||
- New `render_storage_panel(app, focused)` — for each disk, emits a
|
||||
`▸ disk_name (kind)` header followed by Model / Vendor / Size /
|
||||
Scheduler / Queue / Read / Written / Parts sections.
|
||||
- Vendor field hidden when empty (NVMe drives don't populate it).
|
||||
- Scheduler truncated to 60 chars to avoid horizontal scroll on long
|
||||
scheduler lists.
|
||||
- `render_tab_bar()` updated for 8 tabs with hotkey mapping 1-8.
|
||||
- `render_once` dumps Storage panel for headless verification.
|
||||
|
||||
**Updated `main.rs`**:
|
||||
- `mod storage;` declaration.
|
||||
- New dispatch arm `TabId::Storage => render_storage_panel(...)`.
|
||||
- Hotkey `8` jumps to Storage tab directly.
|
||||
|
||||
### 36.2 Linux host smoke test (3 disks)
|
||||
|
||||
```
|
||||
--- Storage panel (verifies v1.12 sysfs) ---
|
||||
┌ Storage ─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||||
│Detected 3 disk(s): │
|
||||
│ │
|
||||
│▸ nvme0n1 (NVMe SSD) │
|
||||
│Model: ADATA SX6000PNP │
|
||||
│Size: 476.9 GiB │
|
||||
│Scheduler: [none] mq-deadline kyber bfq │
|
||||
│Queue: 1023 requests │
|
||||
│Read: 15.0 GiB (269817834 I/Os) │
|
||||
│Written: 25.4 GiB (152004989 I/Os) │
|
||||
│Parts: nvme0n1p1, nvme0n1p2 │
|
||||
│ │
|
||||
│▸ nvme1n1 (NVMe SSD) │
|
||||
│Model: Samsung SSD 990 PRO 2TB │
|
||||
│Size: 1.8 TiB │
|
||||
│Scheduler: [none] mq-deadline kyber bfq │
|
||||
│Queue: 1023 requests │
|
||||
│Read: 30.0 MiB (31389462 I/Os) │
|
||||
│Written: 0.0 B (9 I/Os) │
|
||||
│Parts: nvme1n1p1, nvme1n1p2, nvme1n1p3 │
|
||||
│ │
|
||||
│▸ sdb (Removable) │
|
||||
│Model: USB DISK 3.0 │
|
||||
│Size: 57.7 GiB │
|
||||
│Scheduler: none [mq-deadline] kyber bfq │
|
||||
│Queue: 2 requests │
|
||||
│Read: 84.2 KiB (549 I/Os) │
|
||||
│Written: 70.3 KiB (46 I/Os) │
|
||||
│Parts: sdb1, sdb2 │
|
||||
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Verified:
|
||||
- 3 disks detected (2 NVMe SSD + 1 USB Removable)
|
||||
- Real model names parsed from `device/model` (ADATA SX6000PNP,
|
||||
Samsung SSD 990 PRO 2TB, USB DISK 3.0)
|
||||
- Real sizes: 476.9 GiB, 1.8 TiB, 57.7 GiB
|
||||
- Real I/O scheduler lists (`[none] mq-deadline kyber bfq` for NVMe,
|
||||
`none [mq-deadline] kyber bfq` for USB)
|
||||
- Real queue depths (1023 for NVMe, 2 for USB)
|
||||
- Real traffic stats (15 GiB read + 25 GiB write on adata, 30 MiB read
|
||||
on samsung 990 PRO since it's basically new, 84 KiB on USB)
|
||||
- Real partition enumeration (2 + 3 + 2 partitions)
|
||||
- Removable flag correctly detected on sdb (USB drive)
|
||||
- Vendor field correctly hidden for NVMe drives (vendor file is empty
|
||||
for NVMe — kernel convention, not a redbear-power issue)
|
||||
|
||||
### 36.3 Unit tests (10 new, 34/34 total pass)
|
||||
|
||||
```rust
|
||||
#[test] fn format_size_below_1kib()
|
||||
#[test] fn format_size_1kib()
|
||||
#[test] fn format_size_1gib()
|
||||
#[test] fn format_size_1tib()
|
||||
#[test] fn disk_stats_parse_real_line() // 15-field format
|
||||
#[test] fn disk_stats_parse_empty_line() // graceful degradation
|
||||
#[test] fn disk_stats_kbps_delta_positive() // (now-prev)/dt
|
||||
#[test] fn disk_stats_kbps_delta_zero_dt() // guard against dt=0
|
||||
#[test] fn storage_info_is_empty_when_no_sys_block()
|
||||
#[test] fn disk_info_kind_label() // NVMe/SSD/HDD/Removable
|
||||
```
|
||||
|
||||
```
|
||||
running 34 tests
|
||||
test bench::tests::* (5) ... ok
|
||||
test sensor::tests::* (12) ... ok
|
||||
test network::tests::* (7) ... ok
|
||||
test storage::tests::* (10) ... ok
|
||||
|
||||
test result: ok. 34 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||
```
|
||||
|
||||
### 36.4 Build verification
|
||||
|
||||
| Build | Result |
|
||||
|-------|--------|
|
||||
| Linux host (`cargo build --release`) | ✅ 0 errors, 47 warnings (mostly pre-existing dead-code) |
|
||||
| Linux host tests (`cargo test --release`) | ✅ 34/34 pass |
|
||||
| Linux host smoke (`./target/release/redbear-power --once`) | ✅ Storage panel renders correctly |
|
||||
| Redox cross-compile (`cargo build --release --target=x86_64-unknown-redox`) | ✅ clean |
|
||||
| Redox binary (stripped) | 4,021,096 bytes (vs v1.11's 3,996,520 — +24 KB) |
|
||||
| Cross-compile SHA256 | `3c44a545bb162abc7e671d689f025f01a424ee1508a2c2bd90af58f504b50ac4` |
|
||||
|
||||
### 36.5 Refresh cadence (coprime moduli now: 3, 4, 5, 7, 11)
|
||||
|
||||
Storage refresh uses **11-tick** modulus (5.5 sec at POLL_MS=500).
|
||||
The 11-tick modulus is coprime with all existing moduli (3, 4, 5, 7)
|
||||
so storage reads never synchronize with any other data source.
|
||||
LCM of {3, 4, 5, 7, 11} = 9240 ticks = 4620 sec (~77 min).
|
||||
|
||||
Initially considered 8-tick (4 sec) but rejected because `gcd(8, 4) = 4`.
|
||||
Also rejected 9-tick because `gcd(9, 3) = 3`. 11 was the next coprime
|
||||
candidate after 7.
|
||||
|
||||
### 36.6 Forward work
|
||||
|
||||
- **Throughput calculation** — `DiskStats::kbps_delta()` is implemented
|
||||
but not yet wired to the panel. Store previous stats in App + add
|
||||
a "Read: 1.5 MiB/s" line under the cumulative Read total.
|
||||
- **SMART data** — read via `smartctl --json` (if smartctl is in PATH).
|
||||
Skip if not present (per zero-stub policy). Shows Temperature,
|
||||
ReallocatedSectorsCount, WearLevelingCount, PowerOnHours.
|
||||
- **NVMe-specific stats** — `nvme0n1/queue/*`, `hwmon*/temp*_input`
|
||||
(already covered by v1.9 Sensors panel).
|
||||
- **Disk temperature** — already visible via k10temp + S.M.A.R.T.
|
||||
cross-reference. Future work: link disk temp to storage panel.
|
||||
|
||||
### 36.7 Final module structure
|
||||
|
||||
```
|
||||
local/recipes/system/redbear-power/source/src/
|
||||
├── main.rs (~525 lines)
|
||||
├── app.rs (~595) — App + CpuRow + TabId + 8 data-source fields
|
||||
├── render.rs (~1200) — header with Sources line, tab bar, 8 panels
|
||||
├── meminfo.rs (241)
|
||||
├── dmi.rs (118)
|
||||
├── battery.rs (132)
|
||||
├── sensor.rs (354) — hwmon reader + pkg_temp_c helper
|
||||
├── network.rs (203) — sysfs/class/net + /proc/net/if_inet6
|
||||
├── storage.rs (261) — NEW: sysfs/block + stat file parser + kind heuristic
|
||||
├── platform.rs (291)
|
||||
├── acpi.rs (~233)
|
||||
├── cpuid.rs (~369)
|
||||
├── dbus.rs (~294)
|
||||
├── config.rs (~223)
|
||||
├── bench.rs (304) — 5 unit tests
|
||||
├── msr.rs (~158)
|
||||
├── cpufreq.rs (~62)
|
||||
└── theme.rs (71)
|
||||
```
|
||||
|
||||
Total: ~5,415 LoC across 18 modules (v1.11: ~5,150 LoC; +265 LoC for
|
||||
storage module + tests). 34 unit tests total (5 bench + 12 sensor +
|
||||
7 network + 10 storage).
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- **`local/docs/RATATUI-APP-PATTERNS.md`** §13 — the canonical ratatui 0.30 best-practices update that this plan is derived from. Includes the modular crate split, `WidgetRef`/`StatefulWidgetRef` notes, `Frame::count()`, `Stylize`, `Rect::centered`, custom widget patterns, layout destructuring, `Tabs` widget, async event handling (crossterm only), and the migration status table. Use this as the implementation guide while this doc is the roadmap.
|
||||
|
||||
@@ -222,8 +222,8 @@ mod tests {
|
||||
let prev = DiskStats { read_bytes: 1000, write_bytes: 500, reads_completed: 0, writes_completed: 0 };
|
||||
let now = DiskStats { read_bytes: 5000, write_bytes: 1500, reads_completed: 0, writes_completed: 0 };
|
||||
let (r, w) = DiskStats::kbps_delta(&now, &prev, 2.0);
|
||||
assert_eq!(r, 1.953125); // (5000-1000)/2/1024
|
||||
assert_eq!(w, 0.48828125); // (1500-500)/2/1024
|
||||
assert_eq!(r, 1.953125);
|
||||
assert_eq!(w, 0.48828125);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
# Twilight Commander (TLC) — Pure Rust Reimplementation Plan
|
||||
|
||||
**Status:** Architecture chosen. Implementation in progress. Phases 0–8 substantially complete.
|
||||
Phases 14a, 14b, 15a, 15b (partial), 15c (partial), 15d (partial), 15e, 16, 17, 18 substantially complete.
|
||||
Phase 19 (column block operations) done.
|
||||
**Last updated:** 2026-06-20 — Phase 19 column block operations complete (Alt+Arrow → MC `MarkColumn*`; F5/F6/F8 work in column mode; visual highlight via line_selection_rect).
|
||||
**Date:** 2026-06-12 (initial) · 2026-06-13 (rename + comprehensive review + audit fixes) · 2026-06-19 (bug fixes, standalone binaries, syntax highlighter, parity audit reconciliation) · 2026-06-20 (Phase 16, Phase 17, Phase 18, Phase 19)
|
||||
Phases 14a, 14b, 15a, 15b (partial), 15c (partial), 15d (partial), 15e, 16, 17, 18, 19, 20 (editor menubar module) substantially complete.
|
||||
**Last updated:** 2026-06-20 — Phase 20 editor menubar module complete (F9 menu bar with 6 menus: File, Edit, Search, Bookmark, Goto, Options; 10 unit tests pass).
|
||||
**Date:** 2026-06-12 (initial) · 2026-06-13 (rename + comprehensive review + audit fixes) · 2026-06-19 (bug fixes, standalone binaries, syntax highlighter, parity audit reconciliation) · 2026-06-20 (Phase 16, Phase 17, Phase 18, Phase 19, Phase 20)
|
||||
**Branch:** `0.2.4`
|
||||
**Decision authority:** User selected Option A (Pure Rust TLC) on 2026-06-12.
|
||||
**Scope:** Reimplement ALL of Midnight Commander (MC 4.8.33) in pure Rust.
|
||||
|
||||
Reference in New Issue
Block a user