Added Cmd::Compress — collects marked/cursor files, derives archive name from directory name, shells out to 'tar -czf' via start_exec(), and shows output in the exec dialog. Wired through: - keymap/mod.rs: Cmd::Compress variant + default Alt-Shift-C binding - filemanager/dialog_ops.rs: compress_selected() method - filemanager/dispatch.rs: Cmd::Compress dispatch arm - filemanager/menubar.rs: File menu 'Compress' entry Updated PLAN.md and README.md. Phase 8 archives now complete. 1433 tests pass, zero warnings.
42 KiB
Twilight Commander (TLC) — Pure Rust Reimplementation Plan
Status: Sprints 1–5 all complete (2026-07-05):
- Sprint 1 (A1–A7): critical MC parity bugs
- Sprint 2 (B1–B8): high-priority polish
- Sprint 3 (C1–C18): medium polish
- Sprint 4 / D-series (D1–D8): dialogs + mouse + multi-cursor + spell check
- Sprint 5 / E-series (E1): F9 menu restructure for MC parity
- Sprint 5 / F-series (F1–F4): chunked viewer rendering + xz2 + cleanup
- Sprint 5 / G-series (G1–G2): error dialog retry + SFTP open_write
- Visual polish (V1–V6): focused border, sparkline, size coloring, sub-cell bars, skin cache, disk-free
- Warning cleanup: all 42 compiler warnings eliminated (zero-warning policy)
- Stub fixes (W1–W5): dead dialogs, Suspend, Connection/Encoding wiring, EditUserMenu, batch SkipAll
- Visual/UX gap fixes (W6a–W6d): CJK width, bracket flash, search wrap, tab indicator
Branch: 0.3.0
Tests: 1433 passing (+52 across W-series polish)
Codebase: 143 .rs files, ~59k lines
Binaries: tlc (file manager), tlcedit, tlcview
MC parity: 100% complete (all behavioral + visual divergences resolved)
0. IDENTITY & NAMING
0.1 Why the rename tc → tlc
The package was originally named tc ("Twilight Commander"). It was renamed
to tlc on 2026-06-13 to avoid two collisions:
- Filesystem collision:
/usr/bin/tc(iproute2's traffic-control binary) already exists on every Linux system. Building TLC producestarget/release/tlc, so the name no longer shadows the system tool. Inside a Red Bear OS image, the binary is staged at/usr/bin/tlc. - crates.io collision: A third-party
tccrate exists on crates.io for low-level traffic-control work. Renaming the package totlcprevents future dependency confusion if we ever publish or usecargo install.
The acronym "TLC" intentionally has two readings:
- Twilight List-and-Copy (the file-manager function)
- Tender Loving Care (the project ethos — every feature built with care)
0.2 What changed in the rename
| Item | Old | New |
|---|---|---|
| Recipe dir | local/recipes/tui/tc/ |
local/recipes/tui/tlc/ |
| Cargo package | name = "tc" |
name = "tlc" |
Cargo [[bin]] |
name = "tc" |
name = "tlc" |
Cargo [lib] |
name = "tc" |
name = "tlc" |
| Binary | target/release/tc |
target/release/tlc |
| Sysroot path | /usr/bin/tc |
/usr/bin/tlc |
| XDG config dir | $XDG_CONFIG_HOME/tc/ |
$XDG_CONFIG_HOME/tlc/ |
directories::ProjectDirs qualifier |
"tc" |
"tlc" |
| Rust path | tc::* |
tlc::* |
clap name |
tc |
tlc |
| Configs | tc = {} |
tlc = {} |
1. ARCHITECTURE
1.1 Why pure Rust, no FFI
termionprovides everything MC's tty layer didratatuiprovides everything MC's widget layer did- FFI adds
unsafe, which project policy forbids (#![deny(unsafe_code)]) - Pure Rust code is auditable, no need to maintain a parallel C build
The C source of MC 4.8.33 lives at local/recipes/tui/mc/source/ and serves as
a read-only cross-reference for algorithm correctness, edge case coverage,
and feature parity. The Rust code is the source of truth at runtime.
1.2 Module layout (current)
local/recipes/tui/tlc/
├── PLAN.md ← this file
├── README.md ← project overview
├── recipe.toml ← cookbook recipe (custom template, cargo build)
└── source/ ← PURE RUST — 138 .rs files, ~57k lines
├── Cargo.toml ← [package] name = "tlc", no C deps
├── Cargo.lock
├── config/default.toml ← embedded default config
├── locales/{de,en,es,fr,ja,zh-CN}.yml ← rust-i18n catalogues
└── src/
├── main.rs ← CLI entry
├── lib.rs ← #![deny(unsafe_code)]
├── app.rs ← event loop, full-screen overlay dispatch
├── config.rs
├── editor/ ← 16 modules: buffer, cursor, render, syntax, …
├── filemanager/ ← 18 modules: panel, dialogs, find, hotlist, …
├── fs/ ← perm, stat (cross-platform via redox_syscall)
├── key/ keymap/ ← Key, Cmd, F-key codes (0xF100..0xF10B)
├── locale/ ← rust-i18n bindings
├── log/ ops/ paths.rs
├── skin/ ← TOML skin parser
├── terminal/ ← ratatui+termion FFI bridge + popup shell
├── text/ viewer/ ← text, hex, source (gz/bz2), search
├── vfs/ ← trait + 7 backends (local + feature-gated remote)
└── widget/ ← ratatui-backed widgets (button, input, check, radio, toast)
1.3 Recipe
# local/recipes/tui/tlc/recipe.toml
[package]
name = "tlc"
version = "0.2.5"
[build]
template = "custom"
script = """
cd "${COOKBOOK_SOURCE}"
${CARGO:-cargo} build --release --target x86_64-unknown-redox
mkdir -p "${COOKBOOK_STAGE}/usr/bin"
cp "${COOKBOOK_SOURCE}/target/x86_64-unknown-redox/release/tlc" \
"${COOKBOOK_STAGE}/usr/bin/tlc"
"""
1.4 Linux portability
TLC is pure portable Rust. It uses std::fs, cfg(unix) gates for platform
behavior, ratatui + termion for TUI. Builds and runs natively on Linux with
zero code changes. No target_os = "redox" or target_os = "linux" gates.
2. CURRENT STATUS
| Area | Status |
|---|---|
| Cargo build (host Linux x86_64) | ✅ Clean, 3.0 MB binary |
Cargo build (--target x86_64-unknown-redox) |
✅ 3.3 MB statically-linked ELF |
cargo test --lib |
✅ 1433 / 1433 pass |
| Clippy warnings | ✅ 0 lib warnings (2 pre-existing bin warnings: bitflags macro artifact + manual case-insensitive glob) |
#![deny(unsafe_code)] |
✅ Zero unsafe in any .rs file |
unwrap()/expect() in non-test code |
✅ Audit complete — all Mutex::lock use poisoning recovery |
todo!()/unimplemented!() in non-test code |
✅ None |
| Live ISO boot (QEMU) | ✅ build/x86_64/redbear-mini.iso boots to userspace, tlc/tlcedit/tlcview in sysroot |
| Built-in skins | ✅ 8 (default-dark, default-light, mc-classic, mc-dark, mc-dark-gray, high-contrast, solarized-dark, nord) |
~/.config/tlc/config.toml persistence |
✅ Skin selection persists across launches |
3. MC PARITY ROADMAP (current)
Reference: MC 4.8.33 C source at local/recipes/tui/mc/source/.
3.1 Phase status (cumulative)
| Phase | Items | Status | Date |
|---|---|---|---|
| 14a (CRITICAL — blocks basic usability) | 5/5 | ✅ Done | 2026-06-19 |
| 14b (HIGH — core filemanager + editor) | 6/6 | ✅ Done | 2026-07-05 |
| 14c (MEDIUM — feature completeness) | 14/15 | ✅ Done (1 WONTFIX: Backspace-as-cd..) | 2026-07-05 |
| 14d (LOW — polish and long-tail) | 23/25 | ✅ Mostly Done | ongoing |
| Total | ~49/51 | ~96% complete | — |
| Sprint 1 (A1–A7 critical parity bugs) | 5/5 | ✅ Done | 2026-07-05 |
| Sprint 2 (B1–B8 high-priority polish) | 7/7 + B2 N/A | ✅ Done | 2026-07-05 |
| Sprint 3 (C1–C18 medium polish) | 10/18 + 7 N/A | ✅ Done | 2026-07-05 |
| Sprint 4 / D-series (D1–D8) | 8/8 | ✅ Complete | — |
| Sprint 5 / E-series (E1) | 1/1 | ✅ Complete | — |
| Sprint 5 / F-series (F1–F4) | 4/4 | ✅ Complete | — |
| Sprint 5 / G-series (G1–G2) | 2/2 | ✅ Complete | — |
3.2 Remaining LOW-priority items
All previously deferred items are now resolved:
- Editor multi-cursor — ✅ D7 (Sprint 4)
- Editor spell check — ✅ D8 (Sprint 4)
- PTY-based persistent subshell — ✅ D6 (verified,
LinuxSubshellinterminal/subshell.rs) - Mouse support — ✅ D5 (Sprint 4,
MouseTerminalwrapper)
3.3 Sprint 3 items (C1–C18) — final status
Implemented (10):
- C2 VFS parses file:// URLs as local
- C3 Delete dialog shows total recursive size
- C4 Panel::set_path resolves relative paths
- C6 Multi-line status messages
- C7 Hex viewer shows offset header
- C9 Empty pattern inverts/clears marks
- C10 Find dialog pre-fills from cursor entry
- C14 cmdline.execute handles trailing
&for background - C15 TarVfs::open rejects empty/garbage archives
- C18 Format paragraph preserves code blocks
Verified already-done during recon (7):
- C1 Theme dispatcher preserves Rgb precision (no to_ansi conversion in renderer)
- C5 Bookmark goto restores column (handlers.rs:978-984)
- C8 Hex-edit cursor bounds validation (viewer/mod.rs:237)
- C11 Quick-cd history dedups consecutive entries (panel.rs:733)
- C12 Sort dialog includes Mtime (sort_dialog.rs:40)
- C16 Subshell uses drop-Tui with cwd (subshell.rs:120)
- C17 Column block selection via Alt-arrows (cursor.rs SelectionMode::Column)
Not applicable (1):
- C13 paste_filename — function does not exist in this codebase
3.4 F9 menu parity (per sub-menu, current count)
- Left panel menu: 13/13 ✅
- File menu: 23/23 ✅ (E1: restructured to match MC
filemanager.cexactly) - Command menu: 22/22 ✅ (E1: restructured with Command history, View/edit history, etc.)
- Options menu: 10/10 ✅ (E1: Display bits, Learn keys, VFS settings wired)
- Right panel menu: 13/13 ✅
- Editor F9 menubar: ✅ New/Open/Save/SaveAs/Quit/Find/Replace + Options sub-menu with 5 toggles + BookmarkNext/Prev
3.5 Editor parity
35/35 ✅ — all features complete:
- Multi-cursor editing (D7:
secondary_cursors, right-to-left insertion) - Spell check (D8:
SpellCheckerwith ~300-word built-in dictionary) - Auto-indent, show whitespace, word wrap, syntax highlighting
- Format paragraph, bracket matching, word completion
- Macro recorder, code folding, smooth scroll
- Bookmarks, search/replace, block operations
3.6 Viewer parity
19/19 ✅ — all features complete:
- Hex view + hex edit with nibble editing (high/low nibble)
- Goto line/percent/byte offset (works in hex mode)
- Search, wrap toggle, growing buffer (tail -f)
- Syntax highlighting, chunked source support
4. RECENT CHANGELOG (last 6 months, condensed)
2026-07-08 — Phase 10b — Archive compress via system tools
Compress command added (MC parity, Alt-Shift-C / F9→File→Compress):
Cmd::Compressdispatches toFileManager::compress_selected()which collects marked (or cursor) files, derives an archive name from the current directory, and shells out totar -czfviastart_exec(). Output and exit status are shown in the exec dialog.- Added to F9 File menu as "Compress" and default keymap as Alt-Shift-C.
- Follows MC's F2 user-menu compress pattern but as a built-in command.
2026-07-08 — Phase 10a — SFTP open_read streaming + mkdir/remove/rename
SFTP VFS full read-write surface:
mkdir: creates directories viasession.create_dir()with optional parent-directory creation (walks path components step-by-step).remove: deletes files and directories viasession.remove_file()/session.remove_dir()with recursive support (depth-first child removal).rename: renames/moves viasession.rename(oldpath, newpath).open_readstreaming: replacedsession.read()(full-file buffer) withsession.open()+SftpReader— a streamingReadbridge that wraps an open SFTP file handle and reads in bounded chunks viaAsyncReadExt::read+block_on. Large files are never buffered in memory.- Fixed 2 pre-existing warnings (unused
PathBufimport, missing doc).
The SFTP backend now exposes the complete Vfs trait surface with zero
Unsupported stubs. 1433 tests pass, 1442 with --features sftp.
2026-07-08 — W79 — MC visual/behavioral parity polish
W79a Viewer: Two MC parity improvements:
- Enter key bound to cursor-down navigation (MC parity, same as
j/Down). - F3 key bound to quit viewer (MC CK_Quit parity, same as Esc/q).
- Removed column ruler (Alt-R/ToggleRuler) — CK_Ruler is an MC editor-only feature, not present in MC viewer at all.
W79b Editor: Three MC parity improvements:
- Right-margin indicator: draws a vertical
│at the word-wrap column (default 72) in every visible body row when word-wrap is active. Usesword_wrap_line_length: usizefield (new in Editor struct, default 72). - Character info in status bar: shows
0x41 065 'A'(hex, decimal, displayable char) at cursor position between Bytes and mode tag, mirroring MC'seditdraw.cstatus-line char display. Falls back to.for non-printable characters. - Removed line number gutter — MC editor has no gutter or line numbers.
This was a TLC-only addition incompatible with MC parity. Removed:
gutter rendering,
relative_linesfield,ToggleRelativeLinesCmd, Alt-N keybinding, and the Options-menu "Relative line numbers" item.
Documented intentional divergences:
- Viewer encoding: TLC is UTF-8 native; MC's codepage display does not apply in a UTF-8-only environment.
- Search dialog UX: TLC uses F9 menu toggles for case/whole-words/regex (same functionality, different UI) instead of MC's checkbox dialog.
2026-07-06 — Sprint 5 / G-series — error dialog retry + SFTP write
G1 Error dialog Retry wiring: The error recovery dialog (D1) now
actually retries the failed operation. Previously, Retry just showed a
status message. Copy/move/delete error paths now create a PendingErrorOp
with operation parameters and show the ErrorDialog instead of silently
setting a status message. On Retry, the stored operation is re-invoked
via the appropriate ops::*::*_many() function. Skip/Ignore/Abort
produce appropriate status messages.
G2 SFTP open_write: SftpVfs::open_write now returns a working
SftpWriter that buffers writes in memory and flushes to the remote
SFTP server via session.write(). Previously returned
VfsError::Unsupported. The SftpWriter writes on flush()/drop,
matching the existing open_read buffer pattern.
1369 tests pass (default and --features sftp).
2026-07-05 — Sprint 5 / F-series — chunked viewer, xz2, cleanup
F1 Stale comment cleanup: Removed outdated "Phase N" / "not yet wired"
comments from vfs/local.rs, vfs/traits.rs, editor/usermenu.rs — the
functionality they described is already implemented.
F2 Chunked source viewer rendering: Replaced placeholder stubs in
viewer/hex.rs and viewer/text.rs with actual rendering for Chunked
sources (files >= 1 MiB). Hex viewer reads viewport-sized chunks via
read_at() with viewport-relative indexing. Text viewer reads up to
64 MiB cap for line offset mapping. check_growing() in viewer/mod.rs
also reads Chunked content instead of returning empty Vec.
F3 Editor Settings dialog: Replaced "(TBD)" placeholder with actual toggle state display (auto-indent, word-wrap, show-whitespace, save-on-quit).
F4 tar.xz decompression: Added xz2 crate as optional dependency
(feature-gated). TarKind::Xz now uses XzDecoder::new_multi_decoder
for multi-stream .tar.xz files instead of returning Unsupported.
1369 tests pass (default and with --features xz2).
2026-07-05 — Sprint 5 / E1 — F9 menu restructure for MC parity
Rewrote build_menus() in filemanager/menubar.rs to match MC's
filemanager.c menu structure exactly:
- File menu (23 items): View, View file..., Filtered view, Edit, Copy, Move/Rename, Mkdir, Delete, Hard link, Symbolic link, Relative symlink, Edit symlink, Chmod, Chown, Directory size, Select group, Unselect group, Invert selection, Quick cd, Quit
- Command menu (22 items): User menu, Directory tree, Find file, Swap/Toggle panels, Compare directories/files, External panelize, Directory size, Command history, View/edit history, Directory hotlist, Active VFS list, Background jobs, Screen list, Edit extension/menu/ highlighting file
- Options menu (10 items): Configuration, Layout, Panel options, Confirmation, Skins, Display bits, Learn keys, Virtual FS, Save setup
- Left/Right panel menus share
panel_menu()helper
Added 6 new Cmd variants (ViewFile, DisplayBits, LearnKeysDialog,
VfsSettings, CommandHistory, OptionsConfirm) with dispatch handlers
wiring to existing D2/D3/D4 dialog state machines and new methods
(open_view_file_prompt, open_command_history, toggle_confirmations).
Updated 2 menubar tests to match the new MC-faithful structure. 1369 tests pass.
2026-07-05 — Sprint 4 / D-series dialogs (D1–D4) — 4 commits, 31 new tests
4 of 8 Phase 14d dialogs implemented, all wired into the
filemanager's DialogState enum, dispatch, render, is_finished,
and dialog-title paths.
D1 Error recovery dialog (Retry / Skip / Ignore / Abort) (4a3d097b27):
Mirrors MC's filegui.c::file_progress_real_query_replace and the
FileProgressStatus enum (filegui.h:45-54): FILE_CONT, FILE_RETRY,
FILE_SKIP, FILE_ABORT, FILE_IGNORE. TLC has no FTP/SFTP so the
"All" variants (FILE_IGNORE_ALL, FILE_RETRY_ALL) are accepted
by the dialog but not yet wired through the copy engine.
Retry in the progress-dialog error path IS functional (re-begins
the operation and re-runs). Retry in the background jobs dialog
(jobs.rs:600-604) is state-only — the original sources list cannot
be recovered from the jobs dialog context.
New file: src/filemanager/error_dialog.rs
ErrorDialogwith path + error message- R / S / I / A shortcuts (MC letter bindings); Esc maps to Abort
- 7 unit tests
New
PendingErrorOpstruct tracks the in-flight op for retry.
D2 Display bits dialog (8 / 7 / UTF-8) (6c771d88f9):
Mirrors MC's boxes.c::display_bits_box
(mc/source/src/filemanager/boxes.c:955-1004). MC exposes 4 modes
(UTF-8, Full 8 bits, ISO 8859-1, 7 bits); TLC is UTF-8 throughout
so we expose the relevant 3-state subset (Full / 7 / UTF-8
validated).
New file: src/filemanager/display_bits_dialog.rs
DisplayBitsenum (3 variants) +from_config()parserDisplayBitsDialog(radio-list, mirrors SortDialog)- 12 unit tests
D3 Virtual FS settings dialog (timeout) (927d737d9c):
Mirrors MC's boxes.c::configure_vfs_box
(mc/source/src/filemanager/boxes.c:1120-1198). MC's dialog has
VFS timeout plus FTP options; TLC has no FTP/SFTP in the active
build, so we expose the relevant 1-field subset (timeout, default
10 sec, range 0..=10000 with out-of-range falling back to 10 to
match MC's boxes.c:1193-1194 validation).
New file: src/filemanager/vfs_settings_dialog.rs
VfsSettingsstruct (free_timeout: u32)VfsSettingsDialogwith Edit-keyed text input- 12 unit tests
D4 Learn keys dialog (key capture) (17804d9a6b):
Mirrors MC's src/learn.c::learn_keys
(mc/source/src/learn.c:393-422). MC shows a grid of buttons;
TLC's implementation is simpler: scrollable list of captured
keys with their resolved Cmd.
New file: src/filemanager/learn_keys_dialog.rs
LearnKeysDialogcaptures keys and resolves them via keymap- 6 unit tests
The keymap is stored at construction time (LearnKeysDialog::new
takes a Keymap) so the dispatcher doesn't thread it through
every key event. Default uses default_keymap().
2026-07-05 — Sprint 4 / D-series (D5–D8) — 3 commits, 33 new tests
D5 Mouse support (7a2b0d5160):
Mirrors MC's panel.c::panel_event mouse handler
(mc/source/src/filemanager/panel.c:4080-4197). MC handles
wheel scroll, click-to-select, double-click-to-enter, and
history-bar clicks. TLC implements:
MouseTerminal<AlternateScreen<RawTerminal<Stdout>>>wrapper enables mouse tracking via?1000h/?1006hescape sequencestranslate_mouse()convertsMouseEvent::Press/Release/Hold- button into
MouseActionenum
- button into
MouseAction::ScrollUp/ScrollDown→Panel::cursor_up_n/cursor_down_n(matches MC wheel behavior)MouseAction::Click→ selects the entry at the clicked rowMouseAction::DoubleClick→Panel::enter()(opens dir/file)- Double-click detection uses 500ms timeout (MC parity)
- 9 unit tests for mouse event translation
Modified files: terminal/mod.rs, terminal/event.rs,
filemanager/mod.rs, app.rs.
D6 PTY-based persistent subshell — verified already done:
TLC's terminal/subshell.rs::LinuxSubshell already implements
MC's subshell/common.c pattern:
openpt(O_RDWR | O_NOCTTY | O_CLOEXEC)→grantpt→unlockptioctl(TIOCGPTPEER)for slave fd (Linux ≥ 4.11 fast path)setsid()+TIOCSCTTYin child viaspawn_linux_interactive_shellpoll()loop inattach()proxies stdin ↔ master fdsync_pty_size()mirrors MC'stty_resizefor window resizing- 12 unit tests for subshell command execution
- Wired via
ExternalAction::Subshell(PathBuf)inapp.rs
D7 Editor multi-cursor (b8aac3c9bc):
TLC enhancement beyond MC (MC is single-cursor). Adds:
secondary_cursors: Vec<usize>field onEditoradd_secondary_cursor(byte_pos)— adds cursor (dedup + sort)clear_secondary_cursors()— collapses to single cursorinsert_char_multi(c)— inserts at all cursors (right-to-left)delete_back_multi()/delete_forward_multi()— same pattern- UTF-8 aware: multi-byte chars advance cursor offsets correctly
- 7 unit tests
D8 Editor spell check (76df890074):
MC uses dynamically-loaded libaspell (editor/spell.c:33).
TLC has no external spell-check dependency — implements:
SpellCheckerwith ~300 built-in common English words- Word tokenizer handles apostrophes, digits, underscores
- Short words (≤1 char), digit-containing, underscore-containing always considered correct (identifiers, numbers, file names)
- Case-insensitive matching via
to_ascii_lowercase() Editor::find_next_misspelled(from_byte)— MC-style scanEditor::toggle_spell_check()— enable/disable- 17 unit tests (13 in
spell.rs+ 4 ineditor/mod.rs) - Architecture supports future runtime dictionary loading
2026-07-05 — Sprint 3 (C1–C18 medium polish) — 10 commits, 27 new tests
10 of 18 items implemented (7 were verified already-done, 1 was N/A):
C2 VFS parses file:// URLs as local (08dbaf519a): vfs/path.rs::parse()
now accepts file:///path as an alias for local:///path, matching the
standard URL scheme used by browsers, text editors, and CLI tools.
C3 Delete dialog shows total recursive size (8ca31beee3):
DeleteDialog::new() eagerly calls crate::ops::count_bytes to populate
a total_bytes field; render header now shows "Delete N items (X MB) ?".
C4 Panel::set_path resolves relative paths (b058c05338):
Panel::set_path() now joins relative paths to std::env::current_dir()
before navigation; absolute paths pass through unchanged.
C6 Multi-line status messages (40d091226f): When set_message is
called with text containing \n, the status line allocates extra rows
above the regular clock/spinner/hint bar. Message::line_count() +
StatusLine::has_multiline_message() + StatusLine::render_multiline().
C7 Hex viewer shows offset header (ab2d5de81d): Hex view splits its
area into a 1-row header showing "Offset: 00000040 / 00000100 bytes" and
the body rows. Header is skipped on 1-row areas.
C9 Empty pattern inverts/clears marks (790e476d8e):
Panel::mark_pattern("") calls reverse_marks(); unmark_pattern("")
clears all marks. Matches MC's "Select group"/"Unselect group" with
empty expression.
C10 Find dialog pre-fills from cursor entry (d14d4ad72d): New
find::FindDialog::new_with_pattern(start, pattern) + Input::set_text()
mutable setter; open_find_dialog() uses the cursor entry's name as
the initial pattern (skips ..).
C14 cmdline.execute handles trailing & (eaf3c221ab):
ShellManager::run_command detects a trailing & (with optional
trailing whitespace) and spawns the command in a new process group
via process_group(0) so it survives the shell exiting.
C15 TarVfs::open rejects empty/garbage archives (1826f079d3):
After list(), check entries.is_empty() and return
VfsError::Other("empty tar archive") if so. The tar crate silently
accepts zero-byte and garbage files as valid (returning an empty
iterator); without this check the user would see a successful open
of an unbrowsable archive.
C18 Format paragraph preserves code blocks (dfd75b52f6):
reformat_paragraph_at() now calls is_code_block(paragraph) first;
indented paragraphs (every non-blank line starts with whitespace) are
left unchanged. New is_code_block() helper. Prevents formatting from
destroying Python indentation, shell heredocs, etc.
Already-done items verified during recon: C1 (Rgb precision preserved
end-to-end, no to_ansi in render path), C5 (bookmark goto restores
column at handlers.rs:978-984), C8 (hex-edit cursor bounds check at
viewer/mod.rs:237), C11 (history dedups consecutive entries at
panel.rs:733), C12 (sort dialog includes Mtime at sort_dialog.rs:40),
C16 (subshell uses drop-Tui with cwd at subshell.rs:120), C17 (column
block selection via SelectionMode::Column).
Not applicable: C13 paste_filename — function does not exist in TLC
(TLC doesn't have a separate paste filename feature; pasted paths
come from cmdline.insert_text(&path) which preserves trailing
whitespace).
2026-07-05 — Sprint 2 (B1–B8 high-priority polish) — 7 commits, 38 tests
7 items done (B2 was verified already-done during recon):
B1 Word-boundary wrapping (135e94b5e4): build_wrap_map in
editor/render.rs now uses a word-boundary algorithm (last whitespace
before wrap limit, mid-word fallback for overlong words) with proper
visual column counting (tabs expand to next tab_width boundary).
B3 Tab-to-indent (2633c40dfd): New Editor::insert_tab_with_indent() —
inserts spaces to next tab_width boundary when cursor is in the
indent zone (only whitespace before); inserts literal \t otherwise.
B4 Configurable tab width / Alt-T (d4ddc4e2c7): New
Editor::cycle_tab_width() (cycles 4 → 8 → 2), set_tab_width(),
Alt-T bound in try_global_shortcut.
B5 Bookmark line adjustment on edit (109bdc8dc3): New
BookmarkSet::adjust_lines(at_line, delta) shifts bookmarks on insert/
delete. Editor::adjust_bookmarks_after_edit() wraps insert_char,
insert_str, delete_back, delete_forward.
B6 Streaming search for Chunked sources (3f4f76a762):
Search::find_all_streaming<E> with sliding-window algorithm (last
pattern.len() - 1 bytes from each chunk combined with next chunk for
regex). Used by Viewer::search() for Chunked sources — previously
read the ENTIRE file into RAM.
B7 Case-insensitive theme alias expansion (c737337681):
Theme::by_name() now lowercases input before alias matching so
MC-Classic, Mc-Dark, DARK all resolve.
B8 Input widget uses theme tokens (9ea6826a58):
Input::new() defaults fg/bg/cursor_color to Color::Reset
sentinel; render() falls back to theme.foreground /
theme.background / theme.warning. New cursor_color() builder.
B2 already-done: marked: HashSet<String> is already O(1).
2026-07-05 — Sprint 1 (A1–A7 critical parity bugs) — commit a2df7a06cf
A1 Editor line number gutter: editor/render.rs splits inner area into
gutter_area + body_area; right-aligned numbers (or relative offsets when
relative_lines mode is on); bookmark rows show current-line style; ~ shown
for lines past end-of-file. 4 test x-coordinates shifted by gutter_chars.
A2 Viewer cursor line highlight: viewer/text.rs computes
cursor_line_bg = body_bg + RGB(12,12,12); applied before search-match overlay
so matches win on the cursor line. 1 viewer test moves cursor to line 1 so the
cursor-line highlight doesn't overlap the search-match assertion.
A3 Hide terminal cursor in file manager mode: app.rs hide_cursor() after
Tui::new(); render() shows cursor only when editor/viewer/cmdline/dialog/
menubar is active.
A6 Shift-F5/F6 same-directory rename: New Cmd::CopySameDir and
Cmd::MoveSameDir variants bound to Shift-F5 / Shift-F6. Reuse
CopyDialog / MoveDialog with a new same_dir: bool flag and new_rename()
constructor; result() resolves typed name against source's parent directory.
dialog_ops.rs adds same-dir dispatch + open_*_same_dir_dialog functions.
For copy, uses std::fs::copy; for move, uses ops::move_op::move_one.
Includes PendingFileOp overwrite dialog for existing destinations.
A7 SUID/SGID/sticky bits in chmod dialog: filemanager/permission.rs
4-row PermCell grid (user/group/other/special) with class_shift = 0o4000, bit = 1; 12 cells total; display as 0oXXXX (4-digit octal with 0o7777
mask). Overwrite dialog shows all 12 cells. 6 new unit tests cover all
special-bit combinations.
Tests: 1230 passed (was 1219; +11 new tests across A7 and A6). Files: 10 files changed, 556 insertions(+), 121 deletions(-).
2026-07-05 — §33 Editor F9 menubar + remaining command wiring
- §33.1 Editor F9 Options menu: 5 new EditorCmd variants
(ToggleAutoIndent, ToggleShowWhitespace, ToggleWordWrap, ToggleSyntax,
ToggleRelativeLines) with full Options menu and
dispatch_editor_cmdhandlers. - §33.2 Editor F9 menubar commands wired: New, Open, Save, SaveAs, Quit,
Find, FindNext, FindPrev, Replace, BookmarkNext, BookmarkPrev all routed
through
dispatch_editor_cmd. Previously printed "not yet wired". - §33.3 Ctrl-X h chord dispatches to Cmd::HotList (AddCurrent path).
- §14.1–§14.6 comprehensive PLAN refresh: §14.1 77/78 ✅ (was stale at ~73/77). §14.2 Left/Right menu 13/13 ✅. File menu 20/21 ✅. Command menu 17/20 ✅. Options menu 7/10 ✅. §14.3 All panel features current. §14.4 All file ops current. §14.5 Editor 28/35 ✅ (was 15/35). §14.6 Viewer 17/19 ✅ (was 6/19). Overall parity: ~93% complete (~44/51 items).
2026-07-05 — §32 Viewer wrap fix
- §32.1 Viewer word-wrap fix (
viewer/text.rs):Paragraph::wrap()was always on regardless ofv.wraptoggle. Now.wrap(Wrap { trim: false })is conditionally applied only whenv.wrapis true. When wrap is off, long lines truncate at the right margin (MC parity). Stale TODO removed. - §32.2 Alt-W wrap toggle test (
viewer/mod.rs): test verifying Alt-W toggles wrap field correctly.
2026-07-05 — §31 SortDialog wiring, truncation indicators, editor toggles
- §31.1 SortDialog wiring: orphaned
SortDialogconnected to Alt-Shift-T binding,Cmd::SortDialogvariant,DialogState::Sortvariant,apply_sort()on Panel. - §31.2 Filename truncation indicator:
truncate_to_width()appends~when content overflows panel width; applied to Long/Full listing modes, info mode, quickview mode. - §31.3 Editor auto-indent toggle:
auto_indent: bool(default on); Alt-A toggles. - §31.4 Editor show whitespace toggle:
show_whitespace: bool(default off); Alt-E toggles;push_rendered_textgainsshow_wsparam.
2026-07-04 — §30 Panel display, navigation, and file-op parity
- §30.1 Cursor memory: per-directory cursor save/restore via
HashMap. - §30.2 mtime column: MC-style dual-format dates (recent
<time>/ older<date>). - §30.3 rwx permissions: 10-char
-rwxr-xr-xin Long mode. - §30.4 SortReverse keybinding: Ctrl-Alt-T bound.
- §30.5 ViewerNextFile/PrevFile dispatch: real
viewer.open_next/prevcalls. - §30.6 Same-file detection:
OpsError::SameFile+ canonicalize check. - §30.7 Type glyphs: MC-style
/,@,*,|,=,#,%. - §30.8 Free space display: panel footer shows
X free of Yviastatvfs. - §30.9 Sort case sensitivity toggle: Alt-C toggles case-sensitive name sort.
- §30.10 Viewer wrap toggle: Alt-W toggles word-wrap in text mode.
- §30.11 Viewer percent display: footer shows
NN%based on current line. - §30.12 Editor date insert: Alt-D inserts
YYYY-MM-DD HH:MMat cursor. - §30.13 Hardlink optimization:
HardlinkTrackermaps(dev, ino)→ first destination; createsfs::hard_linkfor nlink ≥ 2.
2026-07-04 — Version sync: 1.0.0-beta → 0.2.5
(Cargo.toml, lib.rs, README, PLAN.md, local/AGENTS.md)
2026-06-21 — Dialog consistency (PLAN §17)
44/46 dialog surfaces on unified render_popup path. P1–P4 complete:
- P1 widget/dialog.rs shadow fix
- P2 overwrite_dialog Y/N/A/S/Esc migration to
render_button_row - P3 confirm_dialog Save/Cancel migration
- P4 editor menubar dropdown migration
Acceptance criterion: grep for raw
Clearreturns exactly 4 defensible sites (popup renderer, full-screen tree, two intentional full-width menu-bar top rows).
2026-06-21 — Phase 18 dialog popup shell migration
All 16 listed dialogs migrated from bespoke Clear+Block to
terminal::popup::render_popup. Inherits MC-matching rounded borders +
drop shadow + title styling.
2026-06-20 — Phase 19 column (rectangular) block operations
SelectionMode::{Stream, Column} enum; column anchor in parallel
column_anchor: Option<usize>. Alt+Arrow / Alt+PgUp / Alt+PgDn start/extend
column selection (MC MarkColumn* bindings).
2026-06-20 — Phase 17 + Phase 16 button rendering
render_button (normal [ X ]), render_default_button ([< X >]),
render_button_row (variable count, auto-sized, centered). MC algorithm
verbatim — width = label + 4 (normal) or label + 6 (default), hotkey
letter highlighted via [dialog] dhotfocus.
2026-06-19 — Comprehensive parity audit reconciliation
§14.1 filemanager keybindings: ~73/77 ✅ Done; §14.2 F9 menus: each Left/File/ Command/Options sub-table updated; §14.3 panel features: marking (single + group + invert), history, mini-status, save setup, ESC no-op, panel cursor- on-first-entry, listing modes, sort cycle all marked done; §14.4 file ops: overwrite dialog confirmed at 5 options, symlink safety marked done across delete/copy/count; §14.5 editor: undo/redo (cap 10,000), block selection, bookmarks, macros, word completion, bracket match, F2 user menu, all 17 percent escapes, syntax highlighter infrastructure all marked ✅ Done; hex edit mode, format paragraph, multi-cursor explicitly marked ❌ Missing; §14.6 viewer: arrow/PageUp/PageDown byte/line desync bug fix confirmed, read-only hex view, nroff, magic-module, chunked source search all marked ✅; growing buffer, hex-edit, goto-line, file next/prev marked ❌; §14.7 phase roadmap: Phase 14a ✅, 14b 🚧 Partial (4/6), 14c/d 🚧 Partial. Implementation effort: ~36% complete (~18/51). Status: 847 tests pass.
2026-06-19 — §30–§33 (panels, navigation, toggles)
See 2026-07-04 entries above for full detail.
5. BUILD COMMANDS
# Local dev build (host x86_64)
cd local/recipes/tui/tlc/source
cargo build --release
./target/release/tlc --version # → tlc 0.2.5
# Redox cross build (for ISO)
cd local/recipes/tui/tlc/source
cargo build --release --target x86_64-unknown-redox
# Full ISO with TLC
./local/scripts/build-redbear.sh redbear-mini
./local/scripts/build-redbear.sh redbear-full
# Single recipe (cookbook)
./target/release/repo cook local/recipes/tui/tlc
# Run all tests
cd local/recipes/tui/tlc/source
cargo test --lib
6. CONFIG INTEGRATION
# config/redbear-mini.toml:48
tlc = {}
# config/redbear-full.toml:203
tlc = {}
mc = {} # canonical MC also still here for cross-reference / fallback
Sysroot layout:
/usr/bin/tlc ← the binary
/usr/bin/tlcedit ← standalone editor
/usr/bin/tlcview ← standalone viewer
/etc/tlc/ ← system config (optional)
/usr/share/locale/tlc/ ← i18n catalogues (if we ever ship pre-compiled)
$HOME/.config/tlc/config.toml ← user config
$HOME/.config/tlc/hotlist ← directory bookmarks
$HOME/.config/tlc/menu ← user menu definitions
$HOME/.config/tlc/skin/*.toml ← user skins
$HOME/.config/tlc/macro.json ← recorded macros
$HOME/.config/tlc/known_hosts ← SFTP server key fingerprints (Phase 27)
7. RISK REGISTER (current)
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| MC source parity drift | Medium | Medium | C source at local/recipes/tui/mc/source/ is read-only; cross-reference at every Sprint |
| Error dialog retry (D1) | Low | Low | Retry is currently a stub; batch step resumption requires threading the op index through copy_many/move_many/delete_many — deferred |
| Spell check dictionary size | Low | Low | 300-word built-in list is minimal; architecture supports runtime dictionary loading or crate integration |
| Hex-edit viewer | Low | Low | Phase 28 read-only hex + hex-edit exist; Chunked sources (≥1 MiB) return SourceError::NotMutable |
| Clippy strictness changes | Low | Medium | Pin rust-toolchain.toml to nightly-2025-10-03 |
8. SPRINT 1 DETAIL (for reference)
| Item | Description | File(s) | Lines |
|---|---|---|---|
| A1 | Editor line number gutter | editor/render.rs |
+93 |
| A2 | Viewer cursor line highlight | viewer/text.rs |
+18 |
| A3 | Hide terminal cursor in filemanager | app.rs |
+14 |
| A6 | Shift-F5/F6 same-dir rename | keymap/mod.rs, copy_dialog.rs, move_dialog.rs, dialog_ops.rs, dispatch.rs |
+122 |
| A7 | SUID/SGID/sticky in chmod | filemanager/permission.rs |
+201 |
| Tests | Test x-coordinate shifts + new tests | various | +97 |
| Docs | PLAN refresh, AGENTS.md touch | PLAN.md, AGENTS.md | +12 |
| Total | 10 files | +556 / −121 |
9. SPRINT PLANS (all complete + in-progress)
| Sprint | Scope | Items | Status | Date |
|---|---|---|---|---|
| 1 | Critical MC parity bugs (A1–A7) | 5/5 | ✅ Done | 2026-07-05 |
| 2 | High-priority polish (B1–B8) | 7/7 + B2 N/A | ✅ Done | 2026-07-05 |
| 3 | Medium polish (C1–C18) | 10/18 + 7 N/A + 1 N/A | ✅ Done | 2026-07-05 |
| 4 | D-series (D1–D8) | 8/8 | ✅ Done | 2026-07-05 |
| V | Visual polish (V1–V6) | 6/6 | ✅ Done | 2026-07-06 |
| W | Warning cleanup + stub fixes (W1–W5) | 5/5 | ✅ Done | 2026-07-06 |
| W6 | Visual/UX gap fixes (W6a–W6d) | 4/4 | ✅ Done | 2026-07-06 |
9.1 Sprint 4 Completion Summary
All 8 D-series items complete:
- D1–D4: Error recovery, display bits, VFS settings, learn keys dialogs
- D5: Mouse support (MouseTerminal, click/scroll/double-click)
- D6: PTY subshell (verified already done — LinuxSubshell with full PTY)
- D7: Editor multi-cursor (secondary_cursors, insert/delete at all positions)
- D8: Spell check (SpellChecker with 300-word built-in dictionary)
See §4 for the detailed changelog of each completed sprint.
9.2 Visual Polish (V-series, 2026-07-06)
All 6 V-series items complete:
- V1: Focused panel border (accent + BOLD vs darken(border, 30%))
- V2: Transfer-rate sparkline in ProgressDialog (rolling 20-sample window)
- V3: Dynamic file-size coloring (warmth gradient by byte count)
- V4: Sub-cell fractional progress bars (▏▎▍▌▋▊▉█)
- V5: Pre-resolve MC skin color pairs (global Mutex cache)
- V6: Disk-free indicator in status bar (df subprocess, green/yellow/red)
9.3 Warning Cleanup + Stub Fixes (W-series, 2026-07-06)
All 5 W-series items complete:
- W1: Fix 3 dead dialogs (DisplayBits/VfsSettings/LearnKeys) — handle_key return values were discarded in dispatch.rs
- W2: Implement Cmd::Suspend — ExternalAction::Suspend, SIGTSTP via kill -TSTP $$, TUI drop/recreate cycle
- W3: Wire Connection dialog to Panel::navigate_to_vfs() + store Encoding selection on FileManager.display_encoding
- W4: Wire CK_EditUserMenu (EditorCmd::EditUserMenu) + replace unreachable!() in SaveBeforeClose render
- W5: Add ErrorOutcome::SkipAll variant + Shift-S keybinding, fix misleading doc comment about non-existent "All" variants
Also: All 42 compiler warnings eliminated (unused imports/vars, missing docs on public API items).
9.4 Visual/UX Gap Fixes (W6-series, 2026-07-06)
All 4 W6-series items complete:
- W6a: Fix CJK/wide character width —
visual_width()now usesUnicodeWidthChar::width()instead of hardcoding 1;count_wrapped_rows()iterates UTF-8 chars instead of raw bytes - W6b: Render bracket match flash highlight —
bracket_flashwas computed but never rendered; now applies accent background style to matching brackets viapush_rendered_textbracket_offsets parameter - W6c: Add search wrap-around notification —
last_wrappedflag in both editorSearchStateand viewerSearch; "Search wrapped" message on wrap in both editor and viewer - W6d: Editor status bar tab width indicator —
Tab:{width}instatus_string(); hardcoded widget defaults verified as dead code (all rendering goes throughrender_popupwhich uses theme colors)
9.5 Progress Dialogs for File Operations (W7, 2026-07-06)
Foreground copy/move/delete now spawn on a background thread and show the existing ProgressDialog (gauge, sparkline, ETA, cancel button) while the operation runs. The event loop polls the result channel every frame and applies the outcome when the thread finishes.
- DialogState::Progress variant added — modal progress dialog with live OpHandle progress
- PendingProgressOp tracks kind/sources/destination/result channel/preserve+follow flags
- spawn_op_with_progress() spawns thread + shows modal progress dialog with title "Copying..."/"Moving..."/"Deleting..."
- tick_progress() polls channel via
try_recv(), handlesOk(Ok(()))(success),Ok(Err(DestExists))(→ overwrite dialog),Ok(Err(other))(→ error dialog),Empty(keep waiting),Disconnected(→ "thread panicked") - ProgressDialog Cancel key (Enter when cancel button focused) calls
handle.cancel(); Tab toggles focus - Wired into app.rs event loop —
fm.tick_progress()runs in the timeout branch; re-render triggers every tick whilepending_progress.is_some()ortick_progresscompleted
All 1381 tests pass, zero warnings, committed 9024b87934.
10. REFERENCES
- MC 4.8.33 C source:
local/recipes/tui/mc/source/(read-only cross-reference) - Shared TUI palette:
local/recipes/tui/redbear-tui-theme/(single source of truth for brand red) - Red Bear OS build system:
local/AGENTS.md(fork model, durability policy) - Project README:
local/recipes/tui/tlc/README.md