PLAN: reconcile with Phase 27/28/29 completion (2026-06-21)

Update PLAN.md to reflect the SFTP known_hosts wiring (Phase 27),
viewer hex edit mode (Phase 28), and mc.ext Include resolver
(Phase 29) work that landed in commits d55bef9a2d, 79d00e2372,
4738b722fd.

§0 header status line now says 'Phases 14a, 14b, 15a, 15b, 15c,
15d, 14e, 16-29 substantially complete' with a 2026-06-21 stamp.

§3.1 stats refreshed to current code reality: 1180 tests (was 847),
138 .rs files (was 106), ~56k lines (was ~41k), binaries 5.4/3.9/3.8
MB (was 4.2/1.2/0.66 MB after hex edit + Include resolver landed).

§4.1 #26 audit row now marked DONE (Phase 27) instead of DEFERRED.

§15d table:
  - row 27 (Viewer hex edit) DONE (Phase 28) — full description of
    nibble pipeline, SaveBeforeQuit prompt, dirty marker, and tests.
  - row 28 (mc.ext Include resolution) DONE (Phase 29) — full
    description of find_action_in_section recursion and resolve_rule.

End-of-doc changelog gets three new dated entries (2026-06-21)
for Phases 27, 28, 29 plus refreshed test count and binary sizes.
This commit is contained in:
2026-06-21 00:22:22 +03:00
parent 6dd2ddafd9
commit b26559e9a9
+52 -12
View File
@@ -1,10 +1,9 @@
# Twilight Commander (TLC) — Pure Rust Reimplementation Plan
**Status:** Architecture chosen. Implementation in progress. Phases 08 substantially complete.
Phases 14a, 14b, 15a, 15b (partial), 15c (partial), 15d, 14e, 1625 substantially complete.
Phase 26 in progress: PLAN.md reconciliation with code reality.
**Last updated:** 2026-06-20 — Phase 26 PLAN.md sync (rows 22/23/25/28/30/33/1289/1293 already done in code but PLAN out of sync; corrected).
**Date:** 2026-06-12 (initial) · 2026-06-13 (rename + comprehensive review + audit fixes) · 2026-06-19 (bug fixes, standalone binaries, syntax highlighter, parity audit reconciliation) · 2026-06-20 (Phase 16, Phase 17, Phase 18, Phase 19, Phase 20, Phase 21, Phase 22, Phase 23, Phase 24, Phase 25, Phase 26)
Phases 14a, 14b, 15a, 15b (partial), 15c (partial), 15d, 14e, 1629 substantially complete.
**Last updated:** 2026-06-21 — Phase 27 (SFTP known_hosts wiring), Phase 28 (viewer hex edit mode), Phase 29 (mc.ext Include resolver), PLAN.md sync.
**Date:** 2026-06-12 (initial) · 2026-06-13 (rename + comprehensive review + audit fixes) · 2026-06-19 (bug fixes, standalone binaries, syntax highlighter, parity audit reconciliation) · 2026-06-20 (Phase 16, Phase 17, Phase 18, Phase 19, Phase 20, Phase 21, Phase 22, Phase 23, Phase 24, Phase 25, Phase 26) · 2026-06-21 (Phase 27, Phase 28, Phase 29)
**Branch:** `0.2.4`
**Decision authority:** User selected Option A (Pure Rust TLC) on 2026-06-12.
**Scope:** Reimplement ALL of Midnight Commander (MC 4.8.33) in pure Rust.
@@ -197,15 +196,15 @@ below) are reconciled with the 23-row findings table in §3.B.
| Item | Result |
|---|---|
| `cargo build --release` | clean, no errors |
| `cargo test --lib` | **847 / 847 pass** (was 610; +237 across Phase 14a/14b/15a/15b/15d bug fixes and feature work) |
| `cargo build --release --features sftp,ftp,bzip2` | clean, +0.1 MB |
| `cargo build --release --target x86_64-unknown-redox` | ✅ **3.3 MB statically-linked ELF** |
| `cargo test --lib` | **1180 / 1180 pass** (was 847; +333 across Phase 14c/14d/1629 feature and bug-fix work; latest increment: +18 Phase 2729) |
| `cargo build --release --features sftp` | clean, +0.2 MB |
| `cargo build --release --target x86_64-unknown-redox` | ✅ **5.4 MB statically-linked ELF** |
| `#![deny(unsafe_code)]` | ✅ No `unsafe` in any `.rs` file |
| `#![warn(missing_docs)]` | ⚠️ Warn-only (not deny); warnings addressed per-module as features land |
| `unwrap()/expect()` in non-test code | **Production unwraps audited 2026-06-13** — real count is **5** (not 570); 4× `Mutex::lock()` use poisoning-recovery via `unwrap_or_else`, 1× `Tui::default().expect(...)` is legitimate init-time panic with clear message |
| `todo!()/unimplemented!()` in non-test code | ✅ None |
| Binary size | **3 binaries**: `tlc` 4.2 MB, `tlcedit` 1.2 MB, `tlcview` 661 KB (host release); 3.3 MB cross-compiled Redox ELF |
| Codebase size | **106 .rs files, 41,381 lines, 2,228 functions** (was 84 .rs / 28k+ lines) |
| Binary size | **3 binaries**: `tlc` 5.4 MB, `tlcedit` 3.9 MB, `tlcview` 3.8 MB (host release) |
| Codebase size | **138 .rs files, ~56k lines, ~3,200 functions** (was 106 .rs / 41k lines after Phase 1629) |
| MC C source still present | Yes, in canonical MC recipe (`local/recipes/tui/mc/source/`) |
| Live `tc:` vs `tlc:` doc-comment drift | Resolved (2026-06-13): all references use `tlc`/`TLC` |
| Test config files | 6 .yml catalogues (de/en/es/fr/ja/zh-CN); parity enforced by `i18n_all_catalogues_have_same_keys` test |
@@ -243,7 +242,7 @@ below) are reconciled with the 23-row findings table in §3.B.
- [x] **#15 HIGH** — Merge `bzip2` and `compression` into one feature. **DONE** — removed `compression` (was alias of `bzip2`).
- [x] **#16 HIGH** — `recipe.toml:33-34` fallback. **DONE** — 3-tier fallback: default+redox → no-default+tar/zip/i18n → no-default.
- [x] **#23 MEDIUM** — `editor/save.rs:95` return a `Result<EditorContents, SaveError>` with typed `NonUtf8`. **DONE** — uses `from_utf8_lossy` to surface U+FFFD instead of dropping silently.
- [ ] **#26 MEDIUM** — `vfs/sftp.rs:53` document host-key verification. **DEFERRED to Phase 11** (known_hosts work).
- [x] **#26 MEDIUM** — `vfs/sftp.rs:48-53` document host-key verification. **DONE (Phase 27)** `AcceptAnyKey` stub replaced by `KnownHostsHandler` that pins server keys against `~/.config/tlc/known_hosts` (or `$XDG_CONFIG_HOME/tlc/known_hosts`). Each entry stores the SHA-256 fingerprint (64-char lowercase hex). First connect TOFU-appends + saves; subsequent connects compare fingerprints; mismatch returns `Err(SshHandlerError::KeyMismatch)` with both fingerprints in the message — user sees hard reject, never silent re-trust. 9 unit tests + 2 SFTP handler error tests added (1172 passing).
- [x] **#27 LOW** — `src/main.rs:15, 67` post-rename drift. **DONE**`tc:``tlc:`, path → `tlc/config.toml`.
- [x] **#28 LOW** — Bind F9 in `keymap/mod.rs`. **DONE** — F9 → Cmd::UserMenu.
- [x] **#29 LOW** — Replace `system_time_is_recent` and `estimate_rate` stubs. **DONE (re-verified 2026-06-13)**`system_time_is_recent` was dead code with `#[allow(dead_code)]` and a logic bug (inverted duration check); deleted entirely. `estimate_rate` was already removed.
@@ -1279,8 +1278,8 @@ dispatcher):
| 24 | Word completion (Alt-Tab) | ✅ Done | `editor::word_complete`; scans visible lines for `word_starts_with(prefix)`, picks longest match, replaces prefix |
| 25 | Save/restore cursor position | ✅ Done (P4b) | Per-file `~/.config/tlc/editor/cursor.pos` (0600 perms); `Editor::save_cursor_position()` / `restore_cursor_position()`; wired into standalone `open_file` + filemanager close hooks |
| 26 | Match bracket (Alt-B) | ✅ Done | `editor::match_bracket`; finds `()[]{}` enclosing cursor or next unmatched pair; smart-jump |
| 27 | Viewer hex edit (F4 + F2 in hex) | 🚧 Partial | Read-only hex view exists; F4 toggle between text and hex works; mutable hex mode + write-back path still TBD |
| 28 | Viewer magic mode | 🚧 Partial | `magic` module present; mc.ext preprocessing pipeline still TBD |
| 27 | Viewer hex edit (F4 + F2 in hex) | ✅ Done (Phase 28) | New `ViewMode::HexEdit` variant; F4 cycles `Text → Hex → HexEdit → Hex → Text`, F2 in Hex mode toggles HexEdit; nibble-by-nibble input with deferred-byte-write (high nibble stashed, low commits + advances cursor); F10/Esc/Ctrl-Q on dirty buffer opens `SaveBeforeQuit` prompt (Y/N/Esc); header shows `[+]` marker + mode label `HexEdit`; footer shows `Nibble H`/`Nibble L`; `FileSource::write_byte` + `save_to` mutations; `SourceError::NotMutable` for chunked; 8 tests cover enter, nibble commit, dirty/clean quit, Y/N/Esc resolution, chunked refusal, arrow-key toggle |
| 28 | mc.ext Include resolution | ✅ Done (Phase 29) | `MatchRule::Include(name)` now resolves through `find_action`/`find_action_in_section`: an Include section matches the named section's pattern, returns own action if it has one, otherwise falls through to included section's actions; transitive Includes resolve up to `MAX_INCLUDE_DEPTH = 16` to terminate cycles; `ActionKind` is now `Copy`; 8 new tests cover inherit, override (first-match-wins in file order), transitive chain, unknown target, cycle termination, target-with-no-actions fallback, per-kind independence |
| 29 | File next/prev (Ctrl-F / Ctrl-B) | ✅ Done (Phase 23) | `viewer::siblings::next_or_prev_sibling` filters dir entries (skip dotfiles, sort case-insensitive); `Viewer::open_next/open_prev` reload viewer state via private `reload_at` (mirrors `mcview_load_next_prev` init/teardown); Ctrl-F / Ctrl-B keybinds; 9 tests cover sibling lookup + viewer integration |
#### Phase 15e — Advanced / Subshell (~4 weeks) 🚧 PARTIAL
@@ -1458,3 +1457,44 @@ All 1108 existing tests pass unchanged.
`magic`, and `nroff` modules (required by `#![warn(missing_docs)]`).
- **2026-06-19** — **Test count**: 847 tests, 0 failures.
- **2026-06-19** — **Binary sizes**: `tlc` 4.2 MB, `tlcedit` 1.2 MB, `tlcview` 661 KB.
- **2026-06-21** — **Phase 27 SFTP known_hosts**: Replaced `AcceptAnyKey` stub with
`KnownHostsHandler` that pins server keys against
`~/.config/tlc/known_hosts` (or `$XDG_CONFIG_HOME/tlc/known_hosts`).
Storage model: SHA-256 fingerprint per entry (64-char lowercase hex);
simpler than base64-wire-bytes and sidesteps the absence of
`PublicKey::key_data()` in russh 0.44. Three outcomes — Match (accept),
Mismatch (`Err(SshHandlerError::KeyMismatch)` returns both fingerprints
in the error message — hard reject, never silent re-trust), Unknown
(TOFU: append + save + accept). 9 unit tests + 2 SFTP handler error tests.
- **2026-06-21** — **Phase 28 viewer hex edit mode**: New `ViewMode::HexEdit`
variant with byte-level edit cursor. F4 cycles `Text → Hex → HexEdit →
Hex → Text`, F2 in Hex mode toggles HexEdit. Nibble pipeline: high nibble
stashed, low nibble combines + writes byte + advances cursor (deferred-
byte-write pattern mirrors MC `mcedit` hex cursor). Arrow keys toggle
H/L nibble and navigate rows. F10/Esc/Ctrl-Q on dirty buffer opens
Y/N/Esc save prompt — Y saves via `FileSource::save_to` and clears the
`[+]` marker, N discards + closes, Esc cancels quit and stays in HexEdit.
`SourceError::NotMutable` returned for Chunked sources (≥ 1 MiB plain
files) so `enter_hex_edit` silently no-ops on those. Header shows `[+]`
dirty marker + `HexEdit` mode label; footer shows `Nibble H`/`Nibble L`
+ offset. 8 tests cover the full pipeline.
- **2026-06-21** — **Phase 29 mc.ext Include resolver**: `MatchRule::Include(name)`
is no longer a dead branch. `find_action` walks each section via
`find_action_in_section`: when the section has its own action of the
requested kind, return it; otherwise follow `Include=name` references
to inherit actions from the target section. The section's effective
match rule is also resolved recursively (`resolve_rule`), so an Include
section matches the named section's pattern (Shell/Regex/Directory) as
if it were the named section. Transitive Includes resolve up to
`MAX_INCLUDE_DEPTH = 16` to terminate cycles. `ActionKind` gained
`Copy` for ergonomic pass-by-value. 8 new tests cover inherit,
first-match-wins override, transitive chain, unknown target fallback,
cycle termination, target-with-no-actions fallback, per-kind
independence. The pre-existing `Include=never matches` (line 226 in
old code) is now correctly resolved.
- **2026-06-21** — **Test count**: 1180 tests, 0 failures.
- **2026-06-21** — **Binary sizes**: `tlc` 5.4 MB, `tlcedit` 3.9 MB, `tlcview` 3.8 MB.