Files
RedBear-OS/local/recipes/tui/tlc/PLAN.md
T
vasilito c54c4a2744 Phase 10b: Archive compress via system tools (MC parity)
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.
2026-07-08 19:32:32 +03:00

42 KiB
Raw Blame History

Twilight Commander (TLC) — Pure Rust Reimplementation Plan

Status: Sprints 15 all complete (2026-07-05):

  • Sprint 1 (A1A7): critical MC parity bugs
  • Sprint 2 (B1B8): high-priority polish
  • Sprint 3 (C1C18): medium polish
  • Sprint 4 / D-series (D1D8): dialogs + mouse + multi-cursor + spell check
  • Sprint 5 / E-series (E1): F9 menu restructure for MC parity
  • Sprint 5 / F-series (F1F4): chunked viewer rendering + xz2 + cleanup
  • Sprint 5 / G-series (G1G2): error dialog retry + SFTP open_write
  • Visual polish (V1V6): focused border, sparkline, size coloring, sub-cell bars, skin cache, disk-free
  • Warning cleanup: all 42 compiler warnings eliminated (zero-warning policy)
  • Stub fixes (W1W5): dead dialogs, Suspend, Connection/Encoding wiring, EditUserMenu, batch SkipAll
  • Visual/UX gap fixes (W6aW6d): 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 tctlc

The package was originally named tc ("Twilight Commander"). It was renamed to tlc on 2026-06-13 to avoid two collisions:

  1. Filesystem collision: /usr/bin/tc (iproute2's traffic-control binary) already exists on every Linux system. Building TLC produces target/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.
  2. crates.io collision: A third-party tc crate exists on crates.io for low-level traffic-control work. Renaming the package to tlc prevents future dependency confusion if we ever publish or use cargo 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

  • termion provides everything MC's tty layer did
  • ratatui provides 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 (A1A7 critical parity bugs) 5/5 Done 2026-07-05
Sprint 2 (B1B8 high-priority polish) 7/7 + B2 N/A Done 2026-07-05
Sprint 3 (C1C18 medium polish) 10/18 + 7 N/A Done 2026-07-05
Sprint 4 / D-series (D1D8) 8/8 Complete
Sprint 5 / E-series (E1) 1/1 Complete
Sprint 5 / F-series (F1F4) 4/4 Complete
Sprint 5 / G-series (G1G2) 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, LinuxSubshell in terminal/subshell.rs)
  • Mouse support — D5 (Sprint 4, MouseTerminal wrapper)

3.3 Sprint 3 items (C1C18) — 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.c exactly)
  • 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: SpellChecker with ~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::Compress dispatches to FileManager::compress_selected() which collects marked (or cursor) files, derives an archive name from the current directory, and shells out to tar -czf via start_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 via session.create_dir() with optional parent-directory creation (walks path components step-by-step).
  • remove: deletes files and directories via session.remove_file() / session.remove_dir() with recursive support (depth-first child removal).
  • rename: renames/moves via session.rename(oldpath, newpath).
  • open_read streaming: replaced session.read() (full-file buffer) with session.open() + SftpReader — a streaming Read bridge that wraps an open SFTP file handle and reads in bounded chunks via AsyncReadExt::read + block_on. Large files are never buffered in memory.
  • Fixed 2 pre-existing warnings (unused PathBuf import, 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. Uses word_wrap_line_length: usize field (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's editdraw.c status-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_lines field, ToggleRelativeLines Cmd, 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 (D1D4) — 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

  • ErrorDialog with path + error message
  • R / S / I / A shortcuts (MC letter bindings); Esc maps to Abort
  • 7 unit tests New PendingErrorOp struct 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

  • DisplayBits enum (3 variants) + from_config() parser
  • DisplayBitsDialog (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

  • VfsSettings struct (free_timeout: u32)
  • VfsSettingsDialog with 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

  • LearnKeysDialog captures 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 (D5D8) — 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 / ?1006h escape sequences
  • translate_mouse() converts MouseEvent::Press/Release/Hold
    • button into MouseAction enum
  • MouseAction::ScrollUp/ScrollDownPanel::cursor_up_n/ cursor_down_n (matches MC wheel behavior)
  • MouseAction::Click → selects the entry at the clicked row
  • MouseAction::DoubleClickPanel::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)grantptunlockpt
  • ioctl(TIOCGPTPEER) for slave fd (Linux ≥ 4.11 fast path)
  • setsid() + TIOCSCTTY in child via spawn_linux_interactive_shell
  • poll() loop in attach() proxies stdin ↔ master fd
  • sync_pty_size() mirrors MC's tty_resize for window resizing
  • 12 unit tests for subshell command execution
  • Wired via ExternalAction::Subshell(PathBuf) in app.rs

D7 Editor multi-cursor (b8aac3c9bc): TLC enhancement beyond MC (MC is single-cursor). Adds:

  • secondary_cursors: Vec<usize> field on Editor
  • add_secondary_cursor(byte_pos) — adds cursor (dedup + sort)
  • clear_secondary_cursors() — collapses to single cursor
  • insert_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:

  • SpellChecker with ~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 scan
  • Editor::toggle_spell_check() — enable/disable
  • 17 unit tests (13 in spell.rs + 4 in editor/mod.rs)
  • Architecture supports future runtime dictionary loading

2026-07-05 — Sprint 3 (C1C18 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 (B1B8 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 (A1A7 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_cmd handlers.
  • §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 of v.wrap toggle. Now .wrap(Wrap { trim: false }) is conditionally applied only when v.wrap is 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 SortDialog connected to Alt-Shift-T binding, Cmd::SortDialog variant, DialogState::Sort variant, 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_text gains show_ws param.

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-x in Long mode.
  • §30.4 SortReverse keybinding: Ctrl-Alt-T bound.
  • §30.5 ViewerNextFile/PrevFile dispatch: real viewer.open_next/prev calls.
  • §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 Y via statvfs.
  • §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:MM at cursor.
  • §30.13 Hardlink optimization: HardlinkTracker maps (dev, ino) → first destination; creates fs::hard_link for nlink ≥ 2.

2026-07-04 — Version sync: 1.0.0-beta0.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. P1P4 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 Clear returns 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 (A1A7) 5/5 Done 2026-07-05
2 High-priority polish (B1B8) 7/7 + B2 N/A Done 2026-07-05
3 Medium polish (C1C18) 10/18 + 7 N/A + 1 N/A Done 2026-07-05
4 D-series (D1D8) 8/8 Done 2026-07-05
V Visual polish (V1V6) 6/6 Done 2026-07-06
W Warning cleanup + stub fixes (W1W5) 5/5 Done 2026-07-06
W6 Visual/UX gap fixes (W6aW6d) 4/4 Done 2026-07-06

9.1 Sprint 4 Completion Summary

All 8 D-series items complete:

  • D1D4: 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 uses UnicodeWidthChar::width() instead of hardcoding 1; count_wrapped_rows() iterates UTF-8 chars instead of raw bytes
  • W6b: Render bracket match flash highlight — bracket_flash was computed but never rendered; now applies accent background style to matching brackets via push_rendered_text bracket_offsets parameter
  • W6c: Add search wrap-around notification — last_wrapped flag in both editor SearchState and viewer Search; "Search wrapped" message on wrap in both editor and viewer
  • W6d: Editor status bar tab width indicator — Tab:{width} in status_string(); hardcoded widget defaults verified as dead code (all rendering goes through render_popup which 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(), handles Ok(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 loopfm.tick_progress() runs in the timeout branch; re-render triggers every tick while pending_progress.is_some() or tick_progress completed

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