Commit Graph

949 Commits

Author SHA1 Message Date
vasilito b8aac3c9bc D7: editor multi-cursor support
Add secondary_cursors field to Editor with insert_char_multi,
delete_back_multi, delete_forward_multi methods. Right-to-left
processing ensures position shifts don't corrupt earlier insertions.

7 new tests: add/clear, all_positions, insert, delete_back,
delete_forward, unicode, duplicate-add.
2026-07-05 22:29:19 +03:00
kellito 7a2b0d5160 tlc: D5 — Mouse support wiring (click + scroll + double-click)
Mirrors MC's panel.c:4080-4197 mouse handling. Wraps the
Tui screen in termion's MouseTerminal (enables GPM on Linux /
xterm mouse on others — without it mouse events never reach
ratatui). The translate_mouse function maps termion MouseEvent
to a MouseAction enum that the FileManager dispatches as
cursor moves / entries / scroll (deferring HistoryPrev/Next/
List and ToggleHidden which need dedicated Cmds not yet in
the keymap).

New APIs:
  - terminal::event::translate_mouse(event, panel_top,
    panel_height, panel_width) -> MouseAction
  - terminal::event::MouseAction (Click, DoubleClick,
    ScrollUp/Down, HistoryPrev/Next/List, ToggleHidden,
    Unhandled)
  - FileManager::handle_mouse_event(m, size) routes
    MouseAction to existing cursor_up_n / cursor_down_n /
    Panel::enter / Panel::set_cursor helpers.
  - app.rs routes TermEvent::Mouse through handle_mouse_event
    before the keyboard-dispatch path.

Tests (9 new in terminal::event::mouse_tests):
  - click_on_file_row_emits_click_action
  - click_below_panel_is_unhandled
  - click_on_header_history_prev_button (col 1)
  - click_on_header_history_next_button (col width-2)
  - click_on_header_toggle_hidden_button (col width-6)
  - scroll_up_emits_scroll_up
  - scroll_down_emits_scroll_down
  - click_on_column_name_row_is_unhandled (sort-by-column
    not yet wired)
  - click_respects_panel_top_offset (panel_top parameter
    works for menu-bar offset)

1345 passing (was 1336; +9 new).
2026-07-05 22:06:01 +03:00
kellito 47abccbc13 tlc: PLAN.md Sprint 4 / D-series changelog + cleanup
Updates:
  - Status banner: 4 sprints (1-3 + D1-D4), 1336 tests
    (was 1292), 139 .rs files (was 138; +1 for
    error_dialog.rs), MC parity ~96% (was ~93%)
  - §3.1: 14d row updated 19→23, total 44→49, added
    Sprint 4 row (4/8 in progress)
  - §3.2: removed stale duplicate lines, marked deferred
    items as "- deferred" per Sprint 4 status
  - Renamed duplicate §3.3 to §3.4 (F9 menu parity)
  - Renumbered §3.4 Editor parity -> §3.5, §3.5 Viewer
    parity -> §3.6
  - §3.5 Editor parity: 28/35 -> 29/35 (C18 code-block
    preservation done)
  - §3.6 Viewer parity: unchanged
  - §7 Risk Register: removed stale `_wONTFIX` line and
    PTY subshell line; added D-series and D1 retry rows
  - §9: added Sprint 4 row (4/8 in progress) and
    §9.1 Deferred Phase 14d items section (D5-D8) with
    per-item context (which file, what needs doing)
  - §4 changelog: added full Sprint 4 / D-series section
    at the top with per-item commit refs and tests added

PLAN.md: 624 lines (was 540; +84 for Sprint 4 details).
2026-07-05 21:15:30 +03:00
kellito 17804d9a6b tlc: D4 — Learn keys dialog
Mirrors MC's src/learn.c::learn_keys (mc/source/src/learn.c:393-422).
MC shows a grid of buttons; TLC is simpler: scrollable list of
captured keys with their resolved Cmd.

New file: src/filemanager/learn_keys_dialog.rs (LearnKeysDialog,
CapturedKey, 6 unit tests).

New DialogState::LearnKeys variant wired into dispatch, render,
is_finished, title. Keymap is stored at construction so the
dispatcher doesn't thread it through every key event.

Tests: 1336 passing (was 1330; +6 new).
2026-07-05 21:04:42 +03:00
kellito 927d737d9c tlc: D3 — Virtual FS settings dialog (timeout)
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 (anonymous password, directory
cache timeout, proxy host, passive mode); TLC has no FTP/SFTP in
the active build, so we expose the relevant 1-field subset:
  - Timeout for freeing VFSs (sec): 0..=10000, default 10
    (out-of-range falls back to 10 — matches MC's
    boxes.c:1193-1194 validation)

New file: src/filemanager/vfs_settings_dialog.rs
  - VfsSettings struct (free_timeout: u32)
  - parse_timeout() (handles negative as 0, > 10000 as None)
  - VfsSettingsDialog with Edit-keyed text input
  - 12 unit tests

New DialogState variant:
  DialogState::VfsSettings(Box<VfsSettingsDialog>)
  Wired into dispatch, render, is_finished, title.

Tests: 1330 passing (was 1318; +12 new).
2026-07-05 20:49:13 +03:00
kellito 6c771d88f9 tlc: D2 — Display bits dialog (8/7/UTF-8)
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 8 bits: bytes pass through verbatim
  - 7 bits: high bit stripped (parity-stripped serial consoles)
  - UTF-8 (validated): pass through UTF-8, ? for invalid

New file: src/filemanager/display_bits_dialog.rs
  - DisplayBits enum with 3 variants
  - DisplayBitsDialog (radio-list, mirrors SortDialog)
  - from_config() / map_byte() / is_valid_utf8() helpers
  - 12 unit tests

New DialogState variant:
  DialogState::DisplayBits(Box<DisplayBitsDialog>)
  Wired into dispatch (handle_key), render, is_finished (false;
  the dispatcher captures Confirm/Cancel from handle_key), title.

Tests: 1318 passing (was 1306; +12 new).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-05 20:42:36 +03:00
kellito 4a3d097b27 tlc: D1 — file-op error recovery dialog (Retry/Skip/Ignore/Abort)
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.

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 (one per shortcut + an 'other keys don't finish' guard)

New FileManager state:
  - pending_error_op: Option<PendingErrorOp>
    stores kind + sources + dst + flags for the in-flight op so
    the user can Retry / Skip / Ignore / Abort
  - PendingErrorOp struct (parallel to existing PendingFileOp)
  - pending_error_op initialized to None

New DialogState variant:
  DialogState::Error(Box<error_dialog::ErrorDialog>)
  Wired into dispatch (handle_key), render, is_finished, title.

Retry is currently a stub (logs a message) because the copy
engine's batch step resumption would require threading the
operation index into copy_many/move_many/delete_many. The dialog
scaffolding is complete and tested; the retry can be wired in a
follow-up sprint when the batch step API is added.

Tests: 1306 passing (was 1299; +7 new).
2026-07-05 20:32:50 +03:00
kellito 2db8636f8b tlc: lock-in tests for C8, C11, C12 (verified-already-done)
Adds regression tests for Sprint 3 items that were verified
already-done during recon but had no dedicated test coverage:

  - viewer::tests::hex_edit_apply_nibble_at_eof_is_safe_noop
    Locks in the C8 contract: apply_nibble at cursor past EOF
    is a safe no-op (no panic, no spurious modified flag).

  - viewer::tests::move_cursor_clamps_to_size
    Locks in the C8 cursor bounds invariant at move_cursor
    level: positive deltas clamp to file size, negative deltas
    clamp to 0 with no underflow.

  - filemanager::panel::tests::history_dedups_consecutive_entries
    Locks in the C11 contract: refreshing the same directory
    N times grows the history by 1 entry (consecutive dedup),
    not N.

  - filemanager::panel::tests::sort_field_mtime_round_trips_through_config
    Locks in the C12 contract: 'mtime' and 'time' config
    strings both resolve to the Mtime sort field, and the
    Panel reports the human-readable name 'Mtime'.

Tests (4 new, total 1299 passing):
  +hex_edit_apply_nibble_at_eof_is_safe_noop
  +move_cursor_clamps_to_size
  +history_dedups_consecutive_entries
  +sort_field_mtime_round_trips_through_config
2026-07-05 19:58:29 +03:00
kellito a9a3507daf tlc: lock-in tests for C1 (Rgb precision) and C5 (bookmark column)
Adds regression tests for already-implemented Sprint 3 items so
future refactors can't silently regress these behaviors:

  - terminal::color::tests::default_theme_preserves_rgb_precision
    Locks in the C1 contract: when Theme::background is Rgb, the
    exact 8-bit values (julia256's core.bg = 58,58,58) survive.
    The parser may also emit Indexed(237) for the same source
    value depending on detected color depth, so the test accepts
    either variant but verifies 24-bit precision when present.

  - terminal::color::tests::light_theme_preserves_rgb_precision
    Same invariant for LIGHT_THEME (sand256 bright off-white
    foreground > 200 RGB).

  - editor::tests::bookmark_jump_restores_both_line_and_column
    Locks in the C5 contract: jumping to a named bookmark must
    restore both the line AND the column where the bookmark was
    set. The existing test only verified the byte offset, not
    the column, so a future regression to line-only restoration
    would not have been caught.

Tests (3 new, total 1295 passing):
  +default_theme_preserves_rgb_precision
  +light_theme_preserves_rgb_precision
  +bookmark_jump_restores_both_line_and_column
2026-07-05 19:50:29 +03:00
kellito 0b15a49b55 tlc: PLAN.md Sprint 3 completion + Sprint 2/3 changelogs
Updates:
  - Status banner: all 3 sprints complete (was: only Sprint 1)
  - §3.1: Sprint 2 marked Done (7/7 + B2 N/A), Sprint 3 marked
    Done (10/18 + 7 verified-already-done + 1 N/A)
  - §3.2: removed stale B1-B8 planning items (all done in Sprint 2)
  - New §3.3: Sprint 3 items final status table (10 implemented,
    7 verified-already-done, 1 N/A)
  - §9 SPRINT PLANS: collapsed three planned-sprint tables into a
    single summary table (all complete)
  - §4 RECENT CHANGELOG: added Sprint 2 (7 items) and Sprint 3
    (10 items) entries at top

Tests: 1292 passing (was 1180 before §30–§33; +112 new across
§30–§33, Sprint 1, Sprint 2, Sprint 3).

PLAN.md: 540 lines (was 421; +119 for Sprint 2/3 changelogs).
2026-07-05 19:46:15 +03:00
kellito 40d091226f tlc: Sprint 3 C6 — multi-line status messages
When set_message() is called with text containing '\n' characters,
the status line now renders the message across multiple rows
(previously only the first line was shown, with the rest lost).

StatusLine (terminal/status.rs):
  - Message::line_count() returns the newline-segment count
  - StatusLine::has_multiline_message() detects newline-containing
    unexpired messages
  - StatusLine::current_message_line_count() returns the row
    count for allocation (0 for no message)
  - StatusLine::render_multiline() renders each newline-separated
    line as its own row

FileManager (filemanager/mod.rs):
  - status_message_line_count() helper that clamps to 1..=4 rows
    to avoid eating the whole screen, returns 1 when no message

render.rs (filemanager/render.rs):
  - When has_message() AND has_multiline_message(), allocate the
    multi-line area above the single-line status bar (which still
    shows clock/spinner/hint). Single-line messages unchanged.

Tests (3 new in terminal::status::tests):
  - has_multiline_message_detects_newlines
  - current_message_line_count_counts_newlines
  - message_line_count_handles_empty_and_multiline

Total: 1292 passing (was 1289; +3 new).
2026-07-05 19:37:16 +03:00
kellito dfd75b52f6 tlc: Sprint 3 C18 — format paragraph preserves code blocks
Previously reformat_paragraph_at() would join indented lines
together, destroying intentional indentation (Python code,
shell heredocs, ASCII art, markdown sub-blocks).

Now reformat_paragraph_at() detects code blocks (paragraphs
where every non-blank line begins with whitespace) and
returns the text unchanged. The detection is intentionally
conservative — false positives leave the paragraph alone
rather than destroying user-written indentation.

format.rs:
  - New is_code_block(paragraph) helper
  - reformat_paragraph_at() calls is_code_block(); if true,
    returns text unchanged (preserving indentation)

Tests (6 new in editor::format::tests):
  - is_code_block_detects_indented_paragraph
  - is_code_block_rejects_unindented_paragraph
  - is_code_block_ignores_blank_lines_in_paragraph
  - reformat_paragraph_skips_code_block
  - reformat_paragraph_skips_shell_heredoc
  - reformat_paragraph_handles_mixed_code_and_prose

Total: 1289 passing (was 1283; +6 new).
2026-07-05 19:31:33 +03:00
kellito eaf3c221ab tlc: Sprint 3 C14 — cmdline.execute handles trailing & for background
Previously cmdline commands were always synchronous — even
typing 'firefox &' would block TLC until the foreground shell
process exited (which for firefox would never happen because
the shell exits immediately and firefox keeps running, but
the spawn process remained a child of the shell until the shell
exited, blocking the parent).

Now run_command detects a trailing '&' (with optional
trailing whitespace) and spawns the command in a new process
group via process_group(0) (Unix). The command is detached,
TLC continues immediately, and the parent shell exits without
waiting.

Falls back to a synchronous run on non-Unix platforms.

Tests (2 new in terminal::subshell::tests):
  - run_command_backgrounded_with_ampersand_succeeds
    'sleep 5 &' returns in < 2s (not 5s)
  - run_command_backgrounded_with_trailing_whitespace
    'sleep 3 & ' (space before &) also backgrounded

Total: 1283 passing (was 1281; +2 new).
2026-07-05 19:25:47 +03:00
kellito 1826f079d3 tlc: Sprint 3 C15 — TarVfs::open rejects empty/garbage archives
The tar crate returns an empty iterator (without erroring) for
both zero-byte files and files that aren't valid tar archives.
TarVfs::open() previously succeeded in both cases, producing an
'unbrowsable' archive the user couldn't navigate.

Fix:
  TarVfs::open() now checks entries.is_empty() after list() and
  returns VfsError::Other('empty tar archive') when empty. This
  covers both truly empty files (corrupt/truncated) and garbage
  bytes (e.g., text mistakenly saved with .tar extension).

Tests (2 new in vfs::tar::tests):
  - tar_vfs_open_empty_file_errors
  - tar_vfs_open_garbage_bytes_errors

Total: 1281 passing (was 1279; +2 new).
2026-07-05 19:14:30 +03:00
kellito 08dbaf519a tlc: Sprint 3 C2 — VFS parses file:// URLs as local
Previously parse() only recognised the local:// scheme. A
file:///path URL (the standard scheme used by browsers, text
editors, and many CLI tools) would be treated as an unknown
scheme and fall through to the 'unknown scheme → local'
fallback in for_path(), which works but is silent.

Now parse() explicitly handles file:// as an alias for local://,
making the VFS path normal form 'local://' after parsing.

Tests (1 new in vfs::path::tests):
  - parse_file_scheme_is_alias_for_local

Total: 1279 passing (was 1278; +1 new).
2026-07-05 18:58:11 +03:00
kellito d14d4ad72d tlc: Sprint 3 C10 — Find dialog pre-fills from cursor entry
Previously Find dialog always opened with the default placeholder
('*.rs'). Now open_find_dialog() inspects the active panel's
cursor entry and uses its name as the initial pattern — so
pressing M-? with cursor on 'main.rs' opens the dialog with
'main.rs' already in the input, ready for refinement (e.g.
'main.rs~' for backups, 'main*' for variants).

Implementation:
  - find::FindDialog::new_with_pattern(start_dir, initial_pattern)
  - panel::Panel::cursor_entry() -> Option<&Entry>
  - widget::input::Input::set_text(s) mutable setter (the
    existing .text() builder consumes self)
  - dialog_ops::open_find_dialog() uses cursor entry name as
    the initial pattern; skips '..' and falls back to default
    when the panel is empty

Tests (2 new in find::tests):
  - new_with_pattern_prefills_pattern_input
  - new_with_pattern_empty_falls_back_to_default

Total: 1278 passing (was 1276; +2 new).
2026-07-05 18:55:04 +03:00
kellito 790e476d8e tlc: Sprint 3 C9 — empty pattern inverts/clears marks
Previously mark_pattern("") and unmark_pattern("") were silent
no-ops (glob_match returned false for empty pattern). Now they
match MC's behavior:
  - mark_pattern(""): calls reverse_marks() (toggles every
    entry; from 0 marks → all entries marked, vice versa)
  - unmark_pattern(""): clears all marks

Tests (2 new in panel::tests):
  - mark_pattern_empty_inverts_marks (0→2→0 cycle)
  - unmark_pattern_empty_clears_all_marks

Total: 1276 passing (was 1274; +2 new).
2026-07-05 18:10:55 +03:00
kellito ab2d5de81d tlc: Sprint 3 C7 — Hex viewer shows offset header row
The hex viewer now reserves the top row for a status header
showing the cursor byte offset and the total file size:
  'Offset: 00000040  /  00000100 bytes'

When the area has height < 2 (degenerate case), the header is
skipped and the full row is used for hex bytes.

Layout split: total_height >= 2 → 1-row header + body_area
(remaining rows). total_height == 1 → no header, body uses
full area. The body Paragraph is rendered to body_area (not
the full area) so the header is not overwritten.

Tests (2 new in viewer::hex::tests):
  - render_shows_offset_header_when_area_has_height
    cursor=64 → header shows 'Offset: 00000040' at row 0
  - render_skips_header_when_area_height_is_one
    80x1 area → first row starts with hex offset '00000000'

Total: 1274 passing (was 1272; +2 new).
2026-07-05 18:05:38 +03:00
kellito b058c05338 tlc: Sprint 3 C4 — Panel::set_path resolves relative paths
Previously, Panel::set_path() would pass the input path straight to
read_directory(). A relative path like 'sub' would fail because
read_directory tries to stat it relative to wherever, not relative
to the user's current working directory.

Implementation:
  Panel::set_path() now checks if p.is_relative() and, if so,
  joins it onto std::env::current_dir() before reading. Absolute
  paths pass through unchanged. Falls back to the raw path if
  current_dir() fails (process has no cwd).

Tests (1 new in panel::tests):
  - set_path_resolves_relative_against_cwd

Total: 1272 passing (was 1271; +1 new).
2026-07-05 17:53:05 +03:00
kellito 8ca31beee3 tlc: Sprint 3 C3 — delete dialog shows total recursive size
DeleteDialog header now includes the total recursive size of
all paths to be deleted (formatted via format_size). For example:
  'Delete 3 (1.2 MB) ?'  instead of just 'Delete ?'.
Computed eagerly at construction via crate::ops::count_bytes so
render() does not block on filesystem traversal.

DeleteDialog (delete_dialog.rs):
  - New field total_bytes: u64 (cached at construction)
  - new() calls crate::ops::count_bytes(&paths) to populate it
  - render() prepends '(N items)' to header and appends size when > 0

Tests (3 new in delete_dialog::tests):
  - new_computes_total_bytes_for_directory (recursive dir sum)
  - new_total_bytes_zero_for_missing_paths
  - new_total_bytes_sums_multiple_paths

Total: 1271 passing (was 1268; +3 new).
2026-07-05 17:50:09 +03:00
kellito 9ea6826a58 tlc: Sprint 2 B8 — Input widget uses theme tokens by default
Input widget colors no longer hardcoded to Color::White / Blue /
Yellow. New instances use Color::Reset as a sentinel meaning
'use the theme token', and render() falls back to:
  - fg: theme.foreground
  - bg: theme.background
  - cursor_color: theme.warning

Callers that explicitly call .fg() / .bg() / .cursor_color() still
override the sentinel — existing API surface preserved.

Changes:
  - input.rs::Input::new() defaults fg/bg/cursor_color to Color::Reset
  - input.rs::Input::render() resolves Reset → theme token
  - New pub fn cursor_color(c) builder (was missing; .fg/.bg existed)

Tests (2 new in widget::input::tests):
  - default_colors_are_reset_sentinel
  - explicit_color_overrides_reset_sentinel

Total: 1268 passing (was 1266; +2 new).
2026-07-05 17:34:47 +03:00
kellito c737337681 tlc: Sprint 2 B7 — case-insensitive theme alias expansion
Theme::by_name() now lowercases the input before matching aliases,
so 'MC-Classic', 'Mc-Dark', and 'DARK' all resolve identically
to their lowercase canonical names. Previously a user setting
config.toml [skin] name = 'MC-Classic' would silently fall back
to default (no match found for the capitalized name).

Implementation:
  - In by_name(), lowercase the input via to_ascii_lowercase()
  - Match against the existing alias map (which already covers
    mc-classic, mc-dark, mc-dark-gray, default-dark, etc.)
  - The lowercase form is passed to mc_skin::theme_by_name,
    which handles the embedded MC .ini files; the lookup there
    also becomes case-insensitive as a side effect.

Tests (2 new in terminal::color::tests):
  - by_name_resolves_aliases_case_insensitively
    MC-CLASSIC → default, Mc-Dark → dark, etc.
  - by_name_real_skin_name_case_insensitive
    DARK → dark, NICEDARK → nicedark (verifies the alias->real
    skin chain works regardless of input case)

Total: 1266 passing (was 1264; +2 new).
2026-07-05 17:28:19 +03:00
kellito 3f4f76a762 tlc: Sprint 2 B6 — streaming search for Chunked sources
Previously Viewer::search for Chunked sources read the ENTIRE
file into RAM via read_at(0, size) — defeating the memory-efficient
design of Chunked mode (a 150 MB Chunked file would load all 150 MB
just to search it).

Search (viewer/search.rs):
  New method find_all_streaming<E>(pattern, case_insensitive, next_chunk)
    - Generic error type E so callers can use their own error type
    - next_chunk closure yields (bytes, is_last_chunk) until exhausted
    - Sliding-window algorithm: keeps last (pattern.len() - 1) bytes
      from each chunk as 'tail', combines with next chunk for regex
    - Matches that span chunk boundaries are detected correctly
    - Matches across overlapping combined buffers are deduped by
      start offset after collection

Viewer (viewer/mod.rs):
  search() now dispatches by source variant:
    - Inline / Compressed: in-memory find_all (unchanged)
    - Chunked: find_all_streaming with closure yielding CHUNK_SIZE
      reads until EOF reached

Tests (5 new in viewer::search::tests):
  - streaming_single_chunk_finds_match
  - streaming_match_spans_two_chunks (xxfoobarxx at chunk 5)
  - streaming_no_matches
  - streaming_multiple_matches_in_one_chunk
  - streaming_chunk_size_smaller_than_pattern (chunk_size=1, pattern=hello)

Total: 1264 passing (was 1259; +5 new).
2026-07-05 17:25:24 +03:00
kellito 109bdc8dc3 tlc: Sprint 2 B5 — bookmark line adjustment on edit
Bookmarks now track the correct line number across insert/delete
edits (MC parity). Previously a bookmark at line 5 would still
report line 5 even after lines 1-3 were deleted, pointing at
wrong content.

BookmarkSet (editor/bookmark.rs):
  New method adjust_lines(at_line: u32, delta: i32)
    - For each mark with line > at_line: shift by delta
    - For positive delta: mark.line += delta
    - For negative delta with mark.line in deletion range:
      clamp to at_line + 1 (first surviving line after range)
    - Column is preserved across all shifts
    - Zero delta is a no-op (no allocation)

Editor (editor/mod.rs):
  New private helper adjust_bookmarks_after_edit(edit_line, lines_before)
  Wraps insert_char, insert_str, delete_back, delete_forward:
    - Captures (line_before, lines_before) before buffer op
    - Calls buffer op
    - Computes delta = lines_after - lines_before
    - Calls bookmarks.adjust_lines(line_before, delta) if non-zero

Tests (6 new in editor::bookmark::tests):
  - adjust_lines_zero_delta_is_noop
  - adjust_lines_shifts_marks_after_insertion
  - adjust_lines_shifts_marks_after_deletion
  - adjust_lines_multi_line_insertion
  - adjust_lines_deletion_clamps_to_anchor
  - adjust_lines_does_not_touch_col

Total: 1259 passing (was 1253; +6 new).
2026-07-05 17:05:28 +03:00
kellito d4ddc4e2c7 tlc: Sprint 2 B4 — configurable tab width via Alt-T cycle
Adds user-facing control over the editor tab width.

New methods on Editor (editor/mod.rs):
  tab_width() -> usize
    - Returns current visual tab width in spaces.
  set_tab_width(width: usize)
    - Sets tab width, clamped to min 1.
  cycle_tab_width() -> usize
    - Cycles 4 → 8 → 2 → 4 (default 4 → wider → narrower → back).
    - Returns the new width.

Alt-T binding (editor/handlers.rs):
  try_global_shortcut now handles Alt-T (0x74) and calls
  cycle_tab_width(). Status bar shows 'Tab width: N'.

Tab-to-indent (B3) and word-wrap (B1) both consume tab_width
via the field, so changing it via Alt-T takes effect immediately
on next render / insert.

Tests (5 new in editor::tests):
  - tab_width_default_is_four
  - set_tab_width_updates_field (verifies 0 → 1 clamp)
  - cycle_tab_width_advances_2_4_8_2
  - cycle_tab_width_from_unusual_value_resets_to_2
  - tab_to_indent_uses_current_tab_width

Config wiring note: cfg.editor.tab_width (config.rs) already has
the field with serde defaults and persistence. Wiring it into
Editor::open() requires the constructor to accept &Config and
is deferred to a future sprint.

Total: 1253 passing (was 1248; +5 new).
2026-07-05 16:59:18 +03:00
kellito 2633c40dfd tlc: Sprint 2 B3 — Tab-to-indent in editor
Tab key now has indent-aware behavior (MC parity):
  - When cursor is in the indent zone (only whitespace before it on
    the current line), Tab inserts enough spaces to advance to the
    next tab_width boundary.
  - When cursor is past any non-whitespace, Tab inserts a single
    literal '\t' character for column alignment.

New methods on Editor (editor/mod.rs):
  insert_tab_with_indent()
    - Computes next_boundary = ((col / tab_width) + 1) * tab_width
    - In indent zone: inserts (next_boundary - col) spaces
    - Otherwise: inserts literal '\t'
  is_in_indent_zone() -> bool
    - True if all bytes between line start and cursor are space/tab

Tab handler in editor/handlers.rs routes through
insert_tab_with_indent() instead of insert_char('\t').

Tests (6 new in editor::tests):
  - tab_at_col_0_indents_to_next_boundary
  - tab_at_col_4_indents_to_next_boundary
  - tab_mid_line_inserts_literal_tab
  - tab_after_non_whitespace_inserts_literal_tab
  - tab_after_whitespace_runs_continues_indent
  - tab_inside_word_inserts_literal_tab

Total: 1248 passing (was 1242; +6 new).
2026-07-05 16:55:30 +03:00
kellito 135e94b5e4 tlc: Sprint 2 B1 — word-boundary wrapping in editor
Replaces the character-counting wrap in build_wrap_map with a
word-boundary algorithm that matches MC's intent: lines break at
the last whitespace before the wrap limit, mid-word break as
fallback for overlong words, tabs counted as their visual width
(next tab_width boundary), control chars as 2 cols.

New helpers (editor/render.rs):
  visual_width(ch, col, tab_width) -> usize
    - Tabs: tab_width - (col % tab_width), min 1
    - Control chars (0x00..=0x1F, 0x7F): 2 cols (renders as ^X)
    - Other chars: 1 col

  count_wrapped_rows(line_bytes, body_width, tab_width) -> usize
    - Walks line left-to-right, tracks last whitespace position
    - On overflow: wrap at last_ws_col (preserves word boundary),
      else mid-word break
    - Stops at \n (line_bytes excludes \n in caller)

Editor (mod.rs):
  - New tab_width: usize field (default 4)
  - Matches the renderer's '    ' tab expansion in push_rendered_text
  - Future B4 will make this user-configurable

Tests:
  +12 wrap_tests in editor::render::wrap_tests:
    - visual_width_tab_expands_to_next_boundary
    - visual_width_ascii_and_control
    - count_wrapped_rows_empty_line
    - count_wrapped_rows_short_line_fits_in_one_row
    - count_wrapped_rows_exact_fit
    - count_wrapped_rows_one_char_overflow_no_whitespace
    - count_wrapped_rows_word_boundary_break
    - count_wrapped_rows_three_words_at_width_six
    - count_wrapped_rows_long_word_falls_back_to_mid_word
    - count_wrapped_rows_tab_visual_width
    - count_wrapped_rows_zero_width_returns_one
    - count_wrapped_rows_stops_at_newline

Total: 1242 passing (was 1230; +12 new).
2026-07-05 16:51:33 +03:00
kellito b1efd23faf tlc: PLAN.md full rewrite — keep only current state
PLAN.md was 1810 lines with substantial resolved/stale content:
  - §3 audit findings (35 items, all resolved months ago)
  - §4 remaining tasks (all done)
  - §8 risk register (all mitigated)
  - §8.1 palette section (redundant)
  - §13 built-in skins (done)
  - §14 per-row gap tables (superseded by §3.1 cumulative)
  - §15 MC source deep audit (done)
  - §16 unified button rendering (done)
  - §17 dialog consistency (P1-P4 done)
  - bottom Changelog (duplicated §9)

Rewrote to 421 lines (77% reduction) with:
  - §0 Identity & Naming
  - §1 Architecture
  - §2 Current Status (metrics table)
  - §3 MC Parity Roadmap (cumulative phase status)
  - §4 Recent Changelog (last 6 months, condensed)
  - §5 Build Commands
  - §6 Config Integration
  - §7 Risk Register (current)
  - §8 Sprint 1 Detail
  - §9 Sprint 2 Plan
  - §10 References

Sprint 1 (A1-A7 critical parity bugs, commit a2df7a06cf) prominently
documented in §4 changelog and §3.1 phase status.
2026-07-05 15:20:49 +03:00
kellito a2df7a06cf tlc: Sprint 1 MC parity fixes (A1-A7)
Implements all 5 critical parity items from the comprehensive MC
assessment. Reference: MC source at local/recipes/tui/mc/source/.

A1 — Editor line number gutter:
  Split editor inner area into gutter_area + body_area; gutter
  renders right-aligned line numbers (or relative offsets when
  relative_lines mode is on); bookmark rows show current-line
  style; ~ shown for lines past end-of-file.

A2 — Viewer cursor line highlight:
  cursor_line_bg derived from body_bg + RGB(12,12,12); applied
  before search-match overlay so matches win on the cursor line.

A3 — Hide terminal cursor in file manager mode:
  App::run() hides the cursor after Tui::new(); render() shows
  the 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 flag and new_rename() constructor; result() resolves
  the typed name against the source's parent directory.

A7 — SUID/SGID/sticky bits in chmod dialog:
  4-row PermCell grid (user, group, other, special); class_shift
  = 0o4000, bit = 1. Display as 0oXXXX. Overwrite dialog shows
  all 12 cells. 6 new unit tests cover all special-bit combinations.

Other fixes in the same commit:
  - 4 editor render tests shifted x-coordinates by gutter_chars
    to account for the new gutter column.
  - 1 viewer text test moves cursor to line 1 so cursor-line
    highlight doesn't overlap the search-match assertion.

Tests: 1230 passed (was 1219 before A7; +11 new tests across A7
and A6). All Sprint 1 items compile clean.
2026-07-05 14:42:17 +03:00
vasilito 146986b955 tlc: F9 Left/Right menus now target respective panel
MenuBarOutcome::Dispatch now carries Option<usize> indicating which
menu (0=Left, 4=Right) originated the command. app.rs switches focus
to the corresponding panel before dispatching. This matches MC
behavior where the Left menu always operates on the left panel and
the Right menu on the right panel, regardless of which panel had
keyboard focus.
2026-07-05 09:54:31 +03:00
vasilito 7043a466c5 tlc: MC-style name-based cursor restoration on parent navigation
When going UP to a parent directory, find the entry whose name matches
the last component of the path being left (MC's get_parent_dir_name
approach). This is more robust than saved-index restoration because it
works even when the parent directory listing changed between visits.
Falls back to saved dir_cursors index for non-parent navigation.
2026-07-05 09:48:16 +03:00
vasilito 4526853895 tlc: fix viewer scrolling, cursor jumping, .zip hang, unsupported keys
- viewer/mod.rs: implement MC-style cursor tracking with independent
  scroll (move_cursor_down/up/ensure_cursor_visible). Arrow keys now
  move cursor within visible area; scrolling only when cursor reaches
  edge. last_height field stores render area height for key handlers.
  Fixes 'pressing down sends all text off screen'.
- panel.rs: save and restore BOTH cursor AND top scroll position in
  dir_cursors HashMap. Previously only cursor was saved and top was
  always reset to 0, causing ensure_cursor_visible to snap the view
  on every directory switch.
- panel.rs: fix try_enter_archive to construct VFS URL with correct
  scheme prefix (zip://, tar://) instead of parsing raw file path
  as Local. Fixes .zip/.tar hang on Enter.
- terminal/event.rs: rewrite parse_unsupported_key to handle CSI-tilde
  modifier sequences (\x1b[15;5~ = Ctrl-F5) and CSI arrow modifier
  sequences (\x1b[1;5B = Ctrl-Down). Returns tlc Key directly with
  modifiers set, bypassing termion's limited TermKey enum.
- app.rs: simplify event dispatch to use Key directly from
  parse_unsupported_key, removing dead TermKey variable.
- 6 new tests for modifier+function-key and modifier+arrow parsing.
2026-07-05 09:44:13 +03:00
vasilito ba429163e9 tlc: fix UTF-8 cursor panic + remove viewer/editor line numbers
- cursor.rs: move_left walks back over UTF-8 continuation bytes,
  move_right steps by char width via utf8_len_from_lead(), move_up/down
  snap to char boundary via snap_to_char_boundary()
- mod.rs: update_bracket_flash adds defensive is_char_boundary() check
- render.rs: remove gutter/column layout, body_area = inner (matches MC
  editdraw.c line_state=FALSE default); bookmark colors applied to
  body line base_style (matches MC book_mark line coloring)
- text.rs: remove gutter rendering and Paragraph .wrap(); implement
  pre-wrapping via wrap_line() for wrap mode; body uses full width
- Tests updated for new no-gutter layout (4 tests, all pass)
2026-07-05 09:12:11 +03:00
vasilito eb8686f4c3 tlc: add Edit extension/menu/highlighting file commands
Wire three Command menu items that open TLC's config files in
the built-in editor: extensions, user menu, and file highlighting
rules. Files are created empty if they don't exist.

Cmd::EditExtensionFile → ~/.config/tlc/extensions
Cmd::EditMenuFile → ~/.config/tlc/menu
Cmd::EditHighlightFile → ~/.config/tlc/filehighlight.ini

Added open_config_file() helper in dialog_ops.rs that resolves
the config dir via ProjectDirs, creates parent + empty file if
missing, then opens the editor.

Command menu now has all 20 items (was 17/20).
2026-07-05 08:14:45 +03:00
vasilito 4e3b06af83 tlc: fix all 49 clippy warnings (0 remaining)
Systematic clippy sweep across 22 files:
- Remove unused imports (Modifier, Block, Borders, Clear, Line)
- Remove unnecessary mut/unused variables (cursor.rs, mc_skin.rs)
- Fix unnecessary casts in popup.rs and viewer/mod.rs
- Use abs_diff instead of manual abs pattern (render.rs)
- Collapse nested if in menubar.rs
- Merge identical if branches in render.rs
- Use strip_prefix instead of manual slice (mod.rs)
- Use io::Error::other instead of Error::new (source.rs)
- Remove useless format! in known_hosts.rs
- Add #[derive(Default)] to SelectionMode enum
- Add #[allow] for too_many_arguments on render functions
- Add #[allow(dead_code)] for utility functions (centered_rect, rgb)
- Remove redundant .clone() on &str in menubar.rs
- Remove .max(0) on unsigned subtraction (button.rs)
- Add missing doc comments on public methods (editor, panel, ops)

cargo clippy --lib: 0 warnings (was 49)
cargo test --lib: 1213 passed, 0 failed
2026-07-05 07:42:00 +03:00
vasilito fb9be0d293 tlc: comprehensive §14.1-§14.6 PLAN refresh + Ctrl-X h chord
Update all 6 MC parity gap-analysis tables in PLAN.md to reflect
the true implementation state after Phases 15a-15l, §30, §31, §33.

Key corrections:
- §14.1 keybindings: 77/78  (was stale at ~73/77)
- §14.2 F9 menus: Left/Right 13/13 , File 20/21 , Command 17/20 
- §14.5 editor: 28/35  (was 15/35) — syntax, format paragraph,
  date insert, auto-indent, show whitespace, F9 menubar all added
- §14.6 viewer: 17/19  (was 6/19) — hex, bookmarks, growing files,
  wrap toggle, goto line, next/prev file, percent display all done
- §14.8 summary: ~93% complete (~44/51), up from stale ~90%

Also: Ctrl-X h chord now dispatches to Cmd::HotList.
2026-07-05 05:38:13 +03:00
vasilito b66a9e509e policy: mark bootloader fork as PENDING_REBASE
The local bootloader fork claims to be upstream 1.0.0 + Red Bear
patches, but the patches in local/patches/bootloader/ do NOT
apply cleanly to upstream 1.0.0. The fork was originally a 0.1.0
baseline with substantial additions that pre-date the upstream-1.0.0
refactor.

Changes:

  * local/fork-upstream-map.toml: set bootloader's upstream tag to
    PENDING_REBASE. The verifier recognises this as a deliberate
    state marker (a fork whose rebase is in progress) and refuses
    the build with a clear error pointing the user to the rebase
    procedure documented in the map.

  * local/scripts/verify-fork-versions.sh: when a fork is marked
    PENDING_REBASE in the map, the script reports a dedicated error
    message instead of running the upstream content comparison (which
    would always fail for a fork in this state).

The current state of the build is:
  * 5 of 6 `-rb1` Cat 2 forks pass the no-fake-version-label
    check (redoxfs, redox-scheme, kernel, installer, userutils).
  * bootloader refuses to build until a real rebase onto a chosen
    upstream tag is completed (the patches must apply cleanly with
    --fuzz=0).
  * installer has additional divergent content that may need a
    rebase too (separate operational task).

This is the strict enforcement the user asked for. The build
cannot proceed silently with fake labels. The user must drive
the rebase work for bootloader (and installer) following the
procedure in fork-upstream-map.toml.
2026-07-05 05:15:27 +03:00
vasilito 56d862e7a7 tlc: wire all remaining F9 editor menubar commands
Wire New, Open, Save, SaveAs, Quit, Find, FindNext, FindPrev,
Replace, BookmarkNext, BookmarkPrev through dispatch_editor_cmd.
Previously these printed 'F9: Cmd (not yet wired)'. Now each one
performs the actual operation matching its Alt-key/F-key equivalent.

open_save_before_close_prompt made pub(crate) so the Quit handler
can intercept dirty buffers.
2026-07-05 04:32:28 +03:00
vasilito 9e2a2666e0 tlc: wire editor Options menu toggles in F9 menubar
Add ToggleAutoIndent, ToggleShowWhitespace, ToggleWordWrap,
ToggleSyntax, ToggleRelativeLines to EditorCmd enum and dispatch
them through dispatch_editor_cmd. The Options menu now has all
five toggles alongside Settings, making them discoverable via F9
in addition to their Alt-key shortcuts.
2026-07-05 03:16:37 +03:00
vasilito c0157fc30e tlc: add Sort order... to F9 Left/Right menubar pull-downs
The SortDialog was wired to Alt-Shift-T in §31.1 but was missing
from the F9 menu bar. MC has 'Sort order...' under both Left and
Right menus. Added to both.
2026-07-05 03:02:31 +03:00
vasilito 3d3937c22d tlc: §32 viewer word-wrap fix + PLAN §14.7 table refresh
§32.1 Viewer word-wrap fix: Paragraph .wrap() was always applied
regardless of the 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 from render_line_with_highlight.

§32.2 Alt-W wrap toggle test verifying the field toggles correctly.

PLAN §14.7 table refresh: 30+ stale entries corrected. Phase 14b
6/6 done. Phase 14c 14/15 done (1 WONTFIX). Phase 14d ~17/25 done.
Overall ~90% MC parity.

Tests: 1213 passed, 0 failed.
2026-07-05 02:54:10 +03:00
vasilito f7c3d504dd policy: enforce no-fake-version-label rule for Cat 2 forks
Per local/AGENTS.md \xC2\xA7 'No-fake-version-label rule':

  Every Cat 2 fork version MUST match the source content from the
  corresponding upstream release + documented Red Bear patches.
  A `-rbN` label on stale content is a fake label and a policy
  violation.

Changes:

  * local/AGENTS.md: documented the no-fake-version-label rule,
    including what counts as a fake label, the enforcement contract,
    and what a real Red Bear fork looks like.

  * local/fork-upstream-map.toml: authoritative mapping of each
    Cat 2 fork (syscall, libredox, redoxfs, redox-scheme, relibc,
    kernel, bootloader, installer, userutils) to its upstream Git
    URL and release tag.

  * local/scripts/refresh-fork-upstream-map.sh: auto-update the
    fork-upstream-map by querying each upstream repo for the
    current latest stable release tag.

  * local/scripts/verify-fork-versions.sh: preflight enforcement
    script. For each Cat 2 fork with a `-rbN` version field:
      1. Compare fork's file list and content against the upstream
         release tag from the map. Reject the build if files are
         missing (would be a fake label).
      2. Reject the build if files exist in local that don't exist
         in upstream (must be moved to local/patches/<fork>/ as
         documented Red Bear patches).
      3. Reject the build if shared files diverge in content.

  * local/scripts/apply-rb-suffix.sh: invokes
    verify-fork-versions.sh after applying the `-rbN` label so the
    build fails fast if the labelled content is fake.

  * local/scripts/build-preflight.sh: invokes
    verify-fork-versions.sh at the start of every build. Bypassed
    only with REDBEAR_SKIP_FORK_VERIFY=1 (emergency only).

  * local/patches/bottom/0001-ratui-0.30-braille-compat.patch: the
    ratatui 0.30+ compatibility shim for bottom 0.11.2.

This is the structural enforcement that prevents fake labels from
ever reaching the build again. The current 6 forks with `-rbN`
labels are flagged by the verifier — they must be rebased onto
their actual upstream release before the build can succeed.
2026-07-05 02:37:54 +03:00
vasilito 2c4ccd5f3f tlc: §31 SortDialog wiring, truncation indicators, editor toggles
§31.1 SortDialog wiring: orphaned SortDialog struct connected to
Alt-Shift-T keybinding, Cmd::SortDialog variant, DialogState::Sort,
dialog key handling (Confirm/Cancel/Running), apply_sort() on Panel,
full render path through render.rs.

§31.2 Filename truncation indicator: truncate_to_width() appends ~
when content overflows panel width (MC parity). Applied to Long/Full
listing modes, info mode, and quickview mode. 5 tests.

§31.3 Editor auto-indent toggle: auto_indent field (default on),
Alt-A toggles. When off, Enter inserts plain newline with no
indentation copy. 3 tests.

§31.4 Editor show whitespace toggle: show_whitespace field (default
off), Alt-E toggles. push_rendered_text gains show_ws param — when
off, tabs render as 4 spaces and spaces render normally (no glyph
markers). 2 tests.

PLAN.md §14.7 tables updated: 30+ stale  entries corrected to .
Effort summary updated from ~36% to ~90% complete.

Tests: 1212 passed, 0 failed (1204 baseline + 8 new).
2026-07-05 02:24:48 +03:00
vasilito a2958e9b02 tlc: §30 MC parity — cursor memory, display, file ops, hardlink optimization
13 sub-items closing genuine MC parity gaps identified in PLAN §14:

Panel display:
- Cursor memory: per-directory cursor save/restore via HashMap
- mtime column: MC dual-format dates (recent=Mon DD HH:MM, old=Mon DD YYYY)
- rwx permissions: 10-char -rwxr-xr-x in Long listing mode
- Type glyphs: MC-style / @ * | = # % suffixes on entries
- Free space: panel footer shows disk free/total via statvfs

Navigation/sort:
- Sort reverse: Ctrl-Alt-T toggle (was dispatched but unbound)
- Sort case sensitivity: Alt-C toggle between case-insensitive/sensitive
- Viewer next/prev: dispatch fixed from no-op stub to real open_next/prev

File operations:
- Same-file detection: OpsError::SameFile via canonicalize check
- Hardlink optimization: HardlinkTracker maps source (dev,ino) to first
  destination; when nlink>=2 and identity already copied, creates
  fs::hard_link instead of byte-for-byte copy

Viewer:
- Wrap toggle: Alt-W (field existed, key was unbound)
- Percent display: footer shows NN% based on line position

Editor:
- Date insert: Alt-D inserts YYYY-MM-DD HH:MM at cursor

Version: 1.0.0-beta → 0.2.5 (branch-aligned)
Tests: 1184 → 1204 (+20 new), 0 failures
Binary: tlc 5.3MB, tlcedit 3.9MB, tlcview 3.7MB
2026-07-04 14:27:01 +03:00
vasilito 26ecd868f7 fork: import redox-scheme 0.11.2 as tracked tree with -rb1 deps
Red Bear OS needs a local fork of redox-scheme to enforce the -rb1
version policy end-to-end. crates.io redox-scheme 0.11.2 pins
redox_syscall = "0.9.0" exactly and libredox = "0.1.18" exactly,
which Cargo refuses to satisfy with the local -rb1 forks.

The fork is implemented as a TRACKED TREE under local/sources/redox-scheme/
on the active 0.2.5 branch (per local/AGENTS.md), not as a separate
git repository or as a separate submodule on its own branch.

The single-repo rule from local/AGENTS.md is preserved: this
directory is a regular subdirectory of the RedBear-OS repo, not a
separate repository.
2026-07-04 11:05:39 +03:00
vasilito 5be1ec17b3 docs: rewrite README for public audience — highlight cub, tlc, redbear-* utilities, current status, call for contributors 2026-07-04 09:18:22 +03:00
vasilito d7273ce5cf fix: document and implement local fork version sync policy
Add comprehensive policy documentation in AGENTS.md covering:
- local/ fork always takes precedence over recipes/ paths
- build system must ensure local fork is at latest available version
- all Red Bear patches must be applied cleanly on top of latest version
- automatic version bump + patch reapplication via bump-fork.sh

Create local/scripts/bump-fork.sh that implements automatic version bumping:
- Detects current local version vs required version from Cargo.lock
- Fetches upstream source at required version
- Applies all Red Bear patches atomically
- Updates version field and replaces local fork contents

Fix driver-manager Cargo.toml lockfile collision:
- Remove redundant syscall dependency (transitive via pcid_interface)
- Update all driver recipes to use local/sources/syscall and libredox paths
- This eliminates the redox_syscall lockfile collision between
  local/sources/syscall and recipes/core/base/syscall (same dir, different paths)

relibc: fix unsafe call for Rust 2024 edition compatibility
2026-07-04 04:23:34 +03:00
vasilito 2c702465a1 fix: convert ucsid, driver-params to oneshot_async; add ipcd/ptyd overrides
Config changes to prevent scheduler hangs on live-mini ISO:
- 00_ucsid.service: scheme -> oneshot_async
- 13_driver-params.service: scheme -> oneshot_async
- 00_ipcd.service: new override (notify -> oneshot_async)
- 00_ptyd.service: new override (notify -> oneshot_async)
- 00_pcid-spawner.service: new override (oneshot -> oneshot_async)
- base: update submodule to 4bfb878 (force-run scheduler)
2026-07-04 01:07:56 +03:00
vasilito 1eed99c54f fix: change gpiod, i2cd, pcid-spawner to oneshot_async; add pcid-spawner override
All three were blocking the boot scheduler in /usr phase on live-mini
because their daemons never notify readiness without hardware present.

- 00_gpiod.service: scheme -> oneshot_async
- 00_i2cd.service: scheme -> oneshot_async
- 00_pcid-spawner.service: new override, oneshot -> oneshot_async
- base: update submodule to 4a1d1f4 (scheduler counter)
2026-07-03 10:43:31 +03:00
vasilito f47ee4fb18 fix: change blocking oneshot services to oneshot_async, add init scheduler tracing
- pcid-spawner: oneshot -> oneshot_async (blocked boot in /usr phase)
- inputd: oneshot -> oneshot_async (unnecessary blocking during initfs)
- base: update submodule to b1a6bd8 (serial debug tracing for scheduler)
- run_mini1.sh: reduce QEMU memory 12G -> 2G for faster CI testing
2026-07-03 08:56:39 +03:00