Root cause: PciDevice::open_io_ports never called acquire_iopl(),
so the first outl to 0xCF8 triggered #GP(0) when redox-drm tried
to scan virtio-gpu PCI capabilities.
- Add ensure_iopl_acquired() helper (thread-local Once)
- Call it in PciDevice::open_io_ports before any I/O
- Add P1-pci-open-io-ports-iopl.patch to recipe
- Mirror patch to local/patches/ for durability
Replaces the AcceptAnyKey stub with KnownHostsHandler that pins
server keys against ~/.config/tlc/known_hosts (or
$XDG_CONFIG_HOME/tlc/known_hosts).
Storage model: each entry stores the SHA-256 fingerprint of the host
key (64-char lowercase hex), not the raw base64 key bytes. This
sidesteps the absence of a PublicKey::key_data() round-trip in
russh 0.44 and keeps the file format self-describing — fingerprints
are what the user sees in 'ssh-keygen -lf', so a quick visual
diff is enough to spot a MITM.
Three matching outcomes:
Match — host + fingerprint in store, accept connection
Mismatch — host in store, fingerprint changed → SshHandlerError::KeyMismatch
Unknown — host not in store → TOFU: append + save + accept
A mismatch returns Err, which surfaces to the caller as
VfsError::Connection("ssh: ...") with the presented and stored
fingerprints in the message — the user sees a hard reject, never
a silent re-trust. Save failures during TOFU are non-fatal (the
in-memory append is enough for the current session); the user will
see the TOFU prompt again next connect.
Tests: 9 known_hosts + 2 SFTP error-display = 11 new tests.
Total: 1172 passing, 0 failing.
Process tab gains a new IO column sourced from /proc/[pid]/io
read_bytes and write_bytes, summing them as a single sortable value.
- New fields on ProcessInfo: io_read_kb, io_write_kb
- New method: ProcessInfo::io_total_kb()
- New helpers: read_io_bytes, write_io_bytes (silent on failure;
/proc/[pid]/io may require CAP_SYS_PTRACE for owned UID)
- New SortMode::Io variant inserted into the cycle
(Rss -> Cpu -> Io -> Pid -> Name)
- Updated render header: VIRT replaced by IO (RSS preserved)
- 4 new unit tests; total 80 pass
- Redox stripped binary: 4123496 bytes
- Linux smoke test confirms opencode dominates IO, kscreenlocker_g shows 0.0 KiB
Docs: local/docs/redbear-power-improvement-plan.md \xC2\xA746
Wires the v1.20 SMART data module into the Storage tab UI.
Each disk now shows a health badge (✓ PASSED / ✗ FAILED / error).
Implementation:
- App.smart: SmartInfo field + 11-tick refresh (paired with Storage)
- Conditional refresh (if self.smart.available guard — avoids
re-running smartctl if we already know it's missing)
- render_storage_panel: 4 SMART badge states
1. !available → '(SMART: install smartmontools)'
2. health.passed → ' ✓ PASSED'
3. !health.passed → ' ✗ FAILED'
4. health.error → ' (SMART: <error>)'
Linux host smoke test (this dev host without smartctl):
- Each disk shows '(SMART: install smartmontools)' hint
- No panic, graceful degradation
- Storage tab still works (no regression)
Performance: smartctl subprocess ~5-50ms per disk, 3 disks = 15-150ms
per 11-tick refresh (5.5 sec), well within budget.
76/76 tests pass (no new tests — UI integration only).
Cross-compile SHA256: ed804710fa834f4453a236aa034d50668b948b391ec1d2ccea294d438016d855.
Docs: improvement plan §45, CONSOLE-TO-KDE §3.3.2 v1.21,
RATATUI-APP-PATTERNS §13.14 + §14 (6400 LoC, 21 modules, 76 tests).
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.