Adds the smart.rs module for disk health monitoring. Since
smartctl is not installed on most systems (this dev host has it
absent), v1.20 implements the module with three-tier graceful
degradation per the zero-stub policy.
New module smart.rs (222 lines, 7 unit tests):
- SmartInfo struct with available + per-disk health records
- SmartHealth struct with passed + attributes + error
- SmartAttribute struct with id + name + value + worst + threshold + raw
- SmartInfo::smartctl_available() — checks smartctl --version
- SmartInfo::read(disks) — orchestrates per-disk smartctl -A -H
- parse_smartctl_output(text) — extracts passed/failed + attrs
- parse_attribute_line(line) — single 10-field SMART attribute
- parse_smart_value(s) — handles both hex (0x33) and decimal
- health_for(disk_name) — convenience accessor
Three-tier graceful degradation:
1. smartctl missing → available=false, disks=[]
2. smartctl errors per disk → error captured in SmartHealth
3. NVMe permission issues → error message, no fabrication
Updated main.rs: mod smart declaration.
76/76 tests pass (5 bench + 12 sensor + 13 network + 12 storage +
20 process + 7 pid_detail + 7 smart).
Linux host smoke test (this dev host without smartctl):
- available=false (graceful, no panic)
- Storage tab still works (no regression)
Cross-compile SHA256 unchanged from v1.19 (smart.rs is dead code
on Redox — compiles but never called).
Docs: improvement plan §44, CONSOLE-TO-KDE §3.3.2 v1.20,
RATATUI-APP-PATTERNS §13.14 + §14 (6360 LoC, 21 modules, 76 tests).
Format-paragraph was already implemented (Editor::format_paragraph
in src/editor/mod.rs + Alt-P keybind wired in handlers.rs at
0x70 within the Alt-modifier match arm). The function uses
editor::format::reformat_paragraph_at which walks contiguous
non-blank lines and re-flows them at DEFAULT_WRAP_WIDTH (72).
Undoable via begin_undo_group / end_undo_group pair.
This phase adds an end-to-end handler test that exercises the
keybinding path: insert a long paragraph, press Alt-P, verify
the buffer is marked modified. Pre-existing format.rs unit tests
cover the formatting details (wrap widths, blank-line
boundaries, undo); this test verifies the integration.
PLAN.md §15d row 32 marked Done.
Tests: 1154 passed (was 1153, +1). Release binaries build clean.
Closes the v1.13 §37.6 forward-work item (the last one). Process
tab now supports case-insensitive substring filtering on the
process name (comm).
Implementation summary:
- New App.process_filter: String field
- Hotkey 'f' opens text-input mode (pattern reused from refresh-
interval input):
- chars → push to filter buffer
- Backspace → pop
- Enter → commit filter, flash match count
- Esc → discard buffer + clear filter
- Filter applied in render_process_panel:
- For each process, skip if non-empty filter doesn't match
(case-insensitive substring on comm)
- Header line shows filter indicator + hint
- Helper proc_filter_match_count(app) for status message
- 4 new unit tests (case insensitive + substring + no match + empty)
- 62/62 tests pass
Build: clean
Cross-compile SHA256: 12913dedc9b0ea58ed3e7418527da34c903f70be703b8676e4273042c73ac875
Docs: improvement plan §42, CONSOLE-TO-KDE §3.3.2 v1.18,
RATATUI-APP-PATTERNS §13.14 + §14 (5840 LoC, 62 tests).
Mirrors MC's edit_sort_cmd flow (CK_Sort / M-F8 →
src/editor/editcmd.c::edit_sort_cmd):
1. Require a marked block; surface error message if no
selection is active.
2. Stash the active selection to a unique temp file under
/tlc-sort-<nanos>/in.txt (per-call unique dir to
avoid races between concurrent sort runs).
3. Run via 'sh -c sort <opts> <in> > <out>' so user-supplied
options pass through verbatim (matches MC g_strconcat).
Empty options means default lexical sort.
4. On non-zero exit: surface exit code + first stderr line.
5. On success: delete the original selection, insert the
sorted output at the cursor, mark buffer modified, and
surface a status line ('Sort: ok (N bytes, M lines)').
Components:
src/editor/mode.rs — new PromptKind::Sort variant.
src/editor/handlers.rs — Alt-F8 keybind (errors if no
selection, otherwise opens the sort prompt); commit_prompt
routes PromptKind::Sort to Editor::sort_block(options).
src/editor/render.rs — title ('Run sort') + label ('sort
options (empty for default)') + mode tag (' [Sort]') for
the prompt.
src/editor/mod.rs — Editor::sort_block(options).
Tests: 1153 passed (was 1150, +3):
- sort_block_sorts_active_selection (apple/banana/cherry)
- sort_block_with_no_selection_reports_error
- sort_block_with_reverse_flag (-r → c/b/a)
Release binaries build clean.
PLAN.md §15d row 32a marked Done.
Mirrors Midnight Commander's
MC src/viewer/actions_cmd.c::mcview_load_next_prev (CK_FileNext
/ CK_FilePrev).
Components:
src/viewer/siblings.rs (new) — pure helper next_or_prev_sibling:
reads current file's parent directory, filters out hidden
files (dot-prefixed), sorts case-insensitive, locates current
by file_name, returns next (direction=+1) or prev (direction=-1)
entry. Returns None at directory boundaries or on I/O error.
6 unit tests cover next/prev/last/first/hidden/no-parent.
src/viewer/mod.rs — Viewer::open_next / Viewer::open_prev
public methods that look up the sibling and reload viewer
state via a private reload_at helper (mirrors MC's
mcview_init/mcview_done pair around mcview_load). Source
errors are converted to std::io::Error so the Result type
matches the existing open() signature.
src/viewer/mod.rs — Ctrl-F / Ctrl-B keybinds in handle_key.
Each delegates to open_next / open_prev.
PLAN.md §15d row 29 marked Done; status bumped to Phase 23.
Tests: 1150 passed (was 1141, +9: 6 siblings module tests +
3 viewer integration tests covering open_next, open_prev,
Ctrl-F/Ctrl-B keybinds). Release binaries build clean.
Replaces the Phase 21 TODO stub with the full MC
edit_user_menu flow:
src/editor/mod.rs — Editor::run_user_menu_command(raw_command):
1. Expand percent variables via PercentCtx::for_file
(MC: expand_format).
2. Stash active selection to clipfile (MC: edit_save_block).
Block file path exposed via %b for downstream expansion.
3. Run via 'sh -c <expanded>' so user can use pipes,
redirections, globs (matches MC user_menu_cmd shell-out).
4. On non-empty stdout: delete any selection, then
insert_str at cursor position (MC: edit_insert_file).
Buffer cursor is synced to editor cursor after
delete_selection so the insert lands at the right spot.
5. Status line: 'Menu: ok (N bytes inserted)' on success,
'Menu: exit N, stderr: ...' on failure.
src/editor/handlers.rs — replaces TODO with call to
self.run_user_menu_command(command).
Tests: 1141 passed (was 1137, +4):
- inserts stdout at cursor
- replaces active selection (cursor sync verified)
- reports non-zero exit status
- expands %f to current file path via cat %f
Release binaries build clean.
Mirrors Midnight Commander's editor user-menu flow (MC
src/editor/edit.c::edit_user_menu + CK_UserMenu).
Architecture reuses the existing filemanager usermenu infra:
filemanager::usermenu::UserMenuDialog is constructed with
condition='edit' so the same ~/.config/tlc/menu file works
in both filemanager (condition='view') and editor contexts.
Components:
src/editor/usermenu.rs (new) — EditorUserMenu wrapper that
owns the dialog + cursor snapshot + selection flag +
clipfile path. Plus reuses
PercentCtx::for_file; default_block_file() resolves
/tlc/editor.block or ~/.config/tlc/editor.block.
src/editor/menubar.rs — adds EditorCmd::UserMenu and
EditorCmd::EditUserMenu variants (cross-referenced MC
CK_UserMenu + CK_EditUserMenu).
src/editor/mod.rs — new usermenu_session: Option<EditorUserMenu>
field, init None in both constructors, Editor::open_user_menu
method, dispatch_editor_cmd wires UserMenu/EditUserMenu
variants (EditUserMenu surfaces path via status message —
full Open wiring deferred to follow-up).
src/editor/handlers.rs — F11 keybind opens user menu (F11 is
MC's editor UserMenu binding per misc/mc.default.keymap;
F2 in the editor is reserved for Save). Routing at top of
handle_key intercepts Running/Cancel/Execute outcomes;
Execute surfaces the command in the status line — full
selection-stash + stdout-capture is deferred TODO.
Tests: 1137 passed (was 1132, +5: 4 editor::usermenu module
tests covering expand, default_block_file, EditorUserMenu
construction, key routing; 1 handler test for F11 open + Esc close).
Closes the v1.13 §37.6 forward-work item. Process tab now shows
real-time CPU usage per process, computed from the delta of
total CPU ticks between successive 13th-tick refreshes.
Source code already landed (process.rs + app.rs + render.rs).
This commit captures full v1.14 docs:
- Improvement plan §38 (CPU% in Process Tab)
- CONSOLE-TO-KDE §3.3.2 v1.14
- RATATUI-APP-PATTERNS §13.14 (audit table +14) + §14 (19 modules, 47 tests)
Implementation summary:
- New cpu_pct: f64 field on ProcessInfo
- New ProcInfo::read_with_cpu_pct(prev, dt_secs, num_cpus)
- Wall-clock dt (SystemTime) — accurate even when TUI pauses
- saturating_sub on ticks prevents underflow if now < prev
- num_cpus from self.cpus.len() (Per-CPU detection result)
- 4 new unit tests (formula + zero + underflow + dt=0)
- 47/47 total tests pass
Math sanity check (verified by unit test):
utime=100→200, stime=50→80, dt=2sec, num_cpus=4
delta = 280-150 = 130 ticks / 2 sec = 65 ticks/sec
CPU% = 65 / 4 cpus * 100 = 1625.0%
Cross-compile SHA256: d46cd66b8e158e2327839ef502879951877a5500d4a40807d3dbc72ed7397231.
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}.