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.
Adds the 6th tab in the multi-view system: Sensors, reading
hardware monitoring data from /sys/class/hwmon/hwmonN/ on Linux
hosts. Detects all chips (k10temp, coretemp, nvme, mt7921, r8169,
spd5118, zenpower, etc.) and their temp/fan/voltage/power/current
sensors with proper unit conversions.
New module sensor.rs (231 lines):
- SensorKind enum: Temp (m°C) / Fan (RPM) / Voltage (mV) /
Power (µW) / Current (mA), with #[default] on Temp
- SensorReading: kind, label, raw_value, display_value
- HwmonChip: name, path, readings
- SensorInfo::read() walks /sys/class/hwmon/hwmonN/, reads
name + all *_input files (with corresponding *_label for
human-readable names like 'Tctl', 'Composite')
- 7 unit tests covering unit conversions + empty state
Updated app.rs:
- New field sensors: SensorInfo, refreshed every 3rd tick
(1.5 sec at POLL_MS=500). 3-tick modulus is coprime to
meminfo's 4 and battery's 5 — no thundering-herd syscalls.
- TabId::Sensors variant (6th tab)
- TabId::next() cycles PerCpu → System → Info → Motherboard →
Battery → Sensors → PerCpu
Updated render.rs:
- New render_sensor_panel(app, focused) with per-chip sections
using ▸ arrow + chip name as bold header, then Label/Value pairs
in 12-char left-aligned label / 14-char right-aligned value
layout. Empty state: '(no sensors detected — /sys/class/hwmon/
not readable)' rather than wall of ?.
- render_tab_bar() updated for 6 tabs with hotkey 1/2/3/4/5/6
- render_once now dumps Sensors panel for headless verification
Updated main.rs:
- mod sensor; declaration
- New dispatch arm TabId::Sensors => render_sensor_panel
- Hotkey 6 jumps to Sensors tab directly
- render_sensor_panel added to imports
Linux host smoke test (Manjaro, Ryzen 9 7900X, 7 chips, 11 sensors):
▸ mt7921_phy0 temp 58.0 °C
▸ r8169_0_e00:00 temp 51.0 °C
▸ k10temp Tccd1 82.6 °C
Tccd2 57.1 °C
Tctl 85.6 °C
▸ nvme Sensor 2 53.9 °C
Composite 50.9 °C
Sensor 1 50.9 °C
▸ spd5118 temp 50.0 °C
▸ spd5118 temp 51.5 °C
▸ nvme Composite 48.9 °C
Unit conversions verified: m°C → °C (50850 → 50.9°C), mV → V,
µW → W, mA → A. Unit tests: 12/12 pass (5 bench + 7 sensor).
Source state: 4885 LoC across 16 modules.
Redox stripped: 3.8 MB (SHA256 7a7c31bc...).
Docs: improvement plan §33, CONSOLE-TO-KDE §3.3.2 v1.9,
RATATUI-APP-PATTERNS §13.14 + §14 (16 modules, 12 tests).
Cleans up §14 (Cross-Reference: redbear-power as a Reference
Implementation) which had three duplicate headers from successive
edits. Collapses to a single canonical version and adds the
v1.8-specific bullet:
'Testable — bench module has 5 unit tests covering all stress
modes + toggles'
Net change: -25 lines, +2 lines.
Source code for v1.8 (bench.rs + main.rs + render.rs) and full
docs for §32 + v1.8 in improvement plan + CONSOLE-TO-KDE plan
landed in commit d6ac3d1377 (qtbase bundled). This commit
completes the doc-only cleanup that didn't fit in that bundle.
Phase 18 dialog popup shell migration is now complete. All 16
dialogs from the Phase 17 follow-up list have been migrated to
terminal::popup::render_popup, giving them all the MC-matching
rounded borders + drop shadow chrome.
Commits in the Phase 18 series:
e4987256f7 — Phase 18a: filter, encoding, pattern, confirm,
overwrite, layout, panel_options, chattr
2b2b5803ba — Phase 18c: permission, owner, connection, config,
compare, sort, progress
c032c9a787 — menubar dropdown (via user commit)
Tree (full-screen directory tree) intentionally skipped — uses
the entire frame area, not a centered popup, so it does not
benefit from the popup shell or drop shadow.
Remaining Clear uses in the codebase are all legitimate:
- menubar top F9 bar (not a popup)
- tree.rs (full-screen dialog)
- popup.rs (render_popup itself)
- dialog.rs (Dialog widget body clear)
Tests: 1112 passed; release binaries build clean.
Closes the v1.6 forward-work item from §30.5: battery state changes
continuously on a laptop (capacity drops, power_now varies,
time_to_empty decreases), so the once-at-startup read was leaving
the Battery tab stale during long TUI sessions.
Updated app.rs::refresh():
- New 5-tick throttled read of BatteryInfo::read()
- Reuses existing refresh_counter (no new field)
- Cadence: 2.5 sec at default POLL_MS=500 (0.04% CPU cost)
- Independent of meminfo's 4-tick modulus (coprime moduli prevent
thundering-herd syscalls — only 5% of ticks see simultaneous
meminfo + battery reads)
- find_battery_dir() re-checks on each refresh, so a laptop
plugged in mid-session populates the Battery tab on the next
5th refresh tick without any external trigger
Verification:
- Mock battery at /tmp/fake-battery/BAT0/ with capacity=67:
redbear-power --once shows Capacity: 67%
- Changed capacity to 50, re-ran --once: shows Capacity: 50%
- Strace confirms 14 sysfs opens per read() call
Cadence rationale (modulus 5 chosen):
- Every tick (500ms): 0.2% CPU — too aggressive
- Every 5th tick (2.5s): 0.04% CPU — chosen
- Once at startup: 0% CPU — too stale
- Every 4th tick would also work but 5 chosen for clean
coprime separation from meminfo's 4-tick modulus
Build: same 3.8 MB stripped Redox binary (single if branch added).
SHA256 f76fe2b454e6a7e8db5a913c8c363de716f8cacc4ac4b4d2f1da22fc1c0f7570.
Docs: improvement plan §31, CONSOLE-TO-KDE §3.3.2 v1.7,
RATATUI-APP-PATTERNS §13.19 (coprime moduli pattern) + §14.
Continue Phase 18 mechanical migration of centered dialogs to the
shared terminal::popup::render_popup() shell (MC-matching rounded
borders + drop shadow):
permission, owner, connection_dialog, config_dialog,
compare, sort_dialog, progress
All 7 share the same pattern: inline Clear + Block + title replaced
with render_popup() + centered_cols_rect()/centered_percent_rect().
Title color now derives from [dialog] dtitle via the unified shell.
compare.rs also dropped its local centered_rect helper (used only
for the popup shell; the body Block stays inline since it's a
sub-frame, not the popup shell).
Tree (full-screen directory tree dialog) intentionally skipped — it
uses the entire frame area, not a centered popup, so it does not
benefit from the popup shell or shadow.
Tests: 1112 passed (no regressions).
Same mechanical pattern as Phase 17 skin_dialog. Each dialog now
inherits the MC-matching rounded borders + drop shadow from the
shared terminal::popup::render_popup() shell:
filter_dialog, encoding_dialog, pattern_dialog, confirm_dialog,
overwrite_dialog, layout_dialog, panel_options, chattr_dialog
Changes per file:
- Remove import of Block, Borders, Clear
- Add import of centered_cols_rect, render_popup
- Replace inline geometry+Clear+Block with centered_cols_rect()
+ render_popup() call
- Title no longer needs manual styling (render_popup applies
[dialog] dtitle colors)
confirm_dialog also dropped its BorderType::Double variant (now
matches the rest of TLC's rounded look); removed its now-unused
local centered_rect helper.
Tests: 1112 passed (no regressions; same count as pre-Phase 18).
Document Phase 17 (skin_dialog popup shell migration) and enumerate
the 16 remaining dialogs that still bypass render_popup:
sort, compare, overwrite, filter, tree, encoding, connection,
layout, owner, panel_options, pattern, confirm, config,
permission, chattr, progress
Each is ~50-100 LOC of refactor work (replace inline Clear+Block
with render_popup call). Same premium chrome (rounded borders,
MC-matching drop shadow) as Phase 17.
Migrate src/filemanager/skin_dialog.rs::render() from the bespoke
Clear+Block shell (no shadow, square borders) to
terminal::popup::render_popup (rounded borders + MC-matching drop
shadow + centered layout).
This makes skin_dialog visually consistent with all 13 other TLC
dialogs that already use render_popup. The /tmp/5.png screenshot
(showing the skin selector without shadow) is now fixed — every
TLC dialog has identical premium chrome.
Deleted:
- Duplicate 'centered_rect' helper (replaced by popup::centered_percent_rect)
- Inline Block construction
- Manual Clear render
Added:
- render_drops_shadow_outside_popup test asserts the bottom-right
cell carries theme.shadow background (assertion of MC's
tty_draw_box_shadow algorithm at the dialog level).
Tests: 1112 passed (was 1111, +1).
After cross-referencing MC's button.c (lib/widget/button.c) and the
ratatui button pattern survey, two gaps remained in Phase 16:
1. MC defines NARROW_BUTTON ([X], no inner padding) — adds for
future compact toolbar/dialog use. Width formula: label + 2.
2. While width was already chars().count() (Unicode-safe CJK), no
test asserted this. Added explicit assertion: '日本語' Normal = 7,
Default = 9 cells.
Tests: 1111 passed (was 1109, +2).
Binaries rebuild clean.
Ref: MC lib/widget/button.c button_flags_t {NORMAL,DEFPUSH,NARROW,HIDDEN}.
Implements the three gaps from §26:
- Phase A: new platform.rs (291 lines) with runtime probes for
MSR, ACPI PSS, /proc/stat, cpufreq sysfs, hwmon — emits one
eprintln! diagnostic per source at startup
- Phase B: msr.rs reads /dev/cpu/{cpu}/msr on Linux via lseek+pread;
acpi.rs read_load falls back to /proc/stat; acpi.rs read_acpi_pss
reads /sys/devices/system/cpu/cpu*/cpufreq/scaling_available_frequencies;
cpufreq.rs reads/writes /sys/.../scaling_governor. Removed the
hardcoded P0..P5 fallback table (violates zero-stub policy).
- Phase C: replaced misleading "MSR: not available (QEMU?)" with a
"Sources: MSR=ok PSS=no load=ok gov=ok hwmon=ok" header line
that shows per-source availability at a glance.
Verified on AMD Ryzen 9 7900X (24 threads, AMD-pstate driver):
- /dev/cpu/0/msr detected (probes ok; reads blocked by CAP_SYS_RAWIO
for non-root users — kernel-level permission, not a code issue)
- /proc/stat readable (load populated after second refresh tick)
- /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor readable
(governor shows "powersave")
- hwmon2 (k10temp) detected (not yet wired into per-CPU temp column —
requires per-driver hwmon→tempX_input mapping, deferred to v1.4)
- PSS=no (amd-pstate driver does not expose scaling_available_frequencies)
v1.3 source: 3501 LoC across 12 modules. Cross-compiled Redox binary
3.3 MB stripped, SHA256 cbc0a6d04e9d9252314dd71a1c411d4c488417e25f8d860970f718990864431a.
What is NOT yet wired (deferred to v1.4):
- Hwmon→per-CPU temp mapping (k10temp Tdie is package-level only)
- MSR reads without root (CAP_SYS_RAWIO required)
- zenpower / zen 5+ per-driver logic
- Add x11proto to redbear-full.toml package list
- libxau recipe updated with x11proto dependency and custom build script
- Fixes libxau build failure: 'Package xproto was not found'
Adds comprehensive analysis answering the user's "no per-core info"
question. Documents:
§26.1 Why every per-CPU column is empty on Linux (root cause)
- 6-column trace (Freq, PkgW, Temp, P-state, State, Flags) + Load%
- Maps Redox scheme paths to Linux equivalents (where they exist)
- Identifies that two paths already have Linux fallbacks (detect_cpus,
read_cpu_id) and four do not
§26.2 cpu-x patterns reviewed
- 6-row comparison table: missing-MSR / daemon / per-source UI /
refresh logic / CLI flags / temperature fallback
- Concludes: redbear-power's "n/a" placeholder is already better UX
than cpu-x's empty cells; daemon broker pattern is NOT applicable;
retry-cache and per-source logging ARE worth adopting
§26.3 Recommended Phase A/B/C (v1.3)
- Phase A: new platform.rs with /dev/cpu/*/msr + /proc/stat +
/sys/devices/system/cpu/cpu*/cpufreq/ fallbacks, ~80-120 LoC
- Phase B: replace hardcoded P0..P5 fallback (acpi.rs:101-108) with
real sysfs reads; generalize read_load to /proc/stat
- Phase C: header per-source availability badge
§26.4 AMD-specific concerns (separate from Linux fallback)
- msr.rs reader is Intel-only by design (file comment)
- AMD Zen uses 0xC0010063/0xC0010064 for P-states and k10temp for
temp, not Intel MSR 0x19c
- Recommended: vendor detection branch + Linux hwmon fallback
§26.5 Conclusion
- Screenshot is NOT a bug — TUI is working as designed
- Three substantive gaps: no Linux fallbacks, no per-source
startup logging, no header availability summary
- All three addressable without violating zero-stub policy
Source: cpu-x v4.7 at /tmp/cpu-x-src/ (cloned in earlier session).
All and references updated to deref the LazyLock (). 1103 tests pass.
Editor render test now reads [editor] editlinestate from the MC .ini (with cursor_fg fallback) so the cursor-line color assertion matches the actual rendering.
Viewer match-highlight test reads [viewer] viewselected from the MC .ini (with warning fallback) — the test now matches the actual highlight bg color from julia256 (yellow on cyan).
FileManager.skin_name now stores the canonical MC skin name (e.g. "julia256") rather than the user-config alias (e.g. "default-dark"). When by_name() resolves a legacy alias to a real .ini skin, the resulting theme.name is what gets persisted.
apply_skin_selection follows the same rule.
Skin dialog tests use real MC skin names (dark, nicedark) and verify the catalog contains no TLC hardcoded presets (no more "default-dark", "mc-classic", etc. in the list).
Removes every hand-coded Theme constant. julia256 is the new default; sand256 is the light fallback. All 39 bundled MC skins are now reachable by name, and legacy aliases (default-dark, mc-classic, nord, etc.) resolve to real .ini skins.
Architecture:
- DEFAULT_THEME / LIGHT_THEME are LazyLock<Theme> initialised on first use.
- parse_theme now reads ALL MC sections: core, statusbar, buttonbar, dialog, error, filehighlight, viewer.
- shadow field comes from [core] shadow (MC's gray;black pattern).
- render_drop_shadow matches MC's tty_draw_box_shadow algorithm exactly: two background-recolor rectangles (2-col right strip + 1-row bottom strip offset by 2 cols), no glyph change.
Removes redbear_tui_theme import — TLC no longer has its own palette.
Completes the remaining plan §24 deferred items:
- Config file (TOML): new config.rs module loaded from
/etc/redbear-power.toml and ~/.config/redbear-power.toml (with
--config <path> override). Sections: display, theme, keybindings,
benchmark. --help documents the full schema.
- AMD Zen CCD topology: cpuid.rs detect_hybrid now parses leaf
0x8000001E NC field (cores per CCX) and Zen 4+ leaf 0x80000026
(CCD count + cores per CCD). Linux host with 24 AMD cores now
shows CCD0..CCD5 grouping instead of all-Unknown.
- Multi-view tab system: Per-CPU / System / Info tabs via ratatui
Tabs widget. Hotkeys 1/2/3 jump directly; T cycles. System tab
shows aggregate CPU stats (avg freq, max temp, total pkg power,
aggregate flags, bench status). Info tab shows detailed CPU
identification (family/model/stepping hex, full flags list,
per-level cache hierarchy with KB+way+line size). New
render_tab_bar / render_system_panel / render_info_panel render
functions in render.rs. TabId enum added to app.rs.
- D-Bus methods: added CycleGovernor, SetGovernor(name),
ToggleThrottle, ForceMinPstate, ForceMaxPstate, SetPstate(target)
methods to org.redbear.Power. PowerCommand enum + command
channel back to main thread for MSR-bound actions. New App
methods set_governor(Governor) and set_selected_pstate(i32)
enable D-Bus clients to set governor/P-state without sending
keystrokes.
- Mouse sub-panel navigation: refined hit-test so left-click on
header/controls cycles governor, right-click toggles throttle,
middle-click on table expands P-state. Header now has two
distinct actions (governor + throttle) reachable via different
mouse buttons without per-label x hit-test.
New dependencies in Cargo.toml: toml = "0.8", dirs = "5",
serde = { version = "1", features = ["derive"] }.
Verification:
- cargo build --release (host): 0 errors.
- ./redbear-power --once: shows tab bar + Per-CPU view.
- ./redbear-power --config /tmp/rp-test/config.toml: respects
refresh_ms override (50ms minimum enforced).
- AMD CCD labels visible: '▶ CCD0' / 'CCD1' / etc.
- cook redbear-power (Redox target): 3.2 MB stripped binary at
local/recipes/system/redbear-power/target/x86_64-unknown-redox/stage/usr/bin/redbear-power.
SHA256: 58b7812a5f673e227753c01e93a05678bd9e8f28101d8a447d70d4943170c40a.
ISO rebuild status: still blocked by pre-existing upstream nix-0.30.1
vs Redox relibc SaFlags incompatibility in uutils. v1.2 binary
is staged and will be packaged into the next successful ISO build
once that issue is resolved.
Source size: 2758 LoC across 11 modules (was 2376/10 in v1.1).
Filepos DB reveals canonical paths of opened files. Use 0600 instead of default umask (0644) so other users cannot read it.
Reindent the HomeGuard struct / scoped_home function inside the #[cfg(test)] mod tests block (functionally correct, just hard to read before).