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 committed
dfed245e4a / 0d999dc4ed which 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:
vasilito
2026-06-20 20:03:43 +03:00
parent 0d999dc4ed
commit 8935be79eb
5 changed files with 259 additions and 12 deletions
+47
View File
@@ -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 |
+7 -6
View File
@@ -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]
+3 -4
View File
@@ -1,10 +1,9 @@
# Twilight Commander (TLC) — Pure Rust Reimplementation Plan
**Status:** Architecture chosen. Implementation in progress. Phases 08 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.