# tvision-rs — post-port backlog (the PORT-ORDER successor)

> All 92 PORT-ORDER rows are ✅. This doc orders the **remaining TODOs** the
> port deliberately deferred (audited 2026-06-10 against every `TODO` marker in
> the tree + HANDOVER's standing deferrals). Same contract as PORT-ORDER:
> **the lowest unmarked row in the active phase is the work**, ✅ = done & on
> `main`, tags pick the implementer model (FOUNDATION → Opus/main thread,
> MECHANICAL → Sonnet). When a row lands: tick it here, add the
> IMPLEMENTATION-LOG entry, update HANDOVER.
>
> **The architecture rule for this run:** most open TODOs cluster behind a few
> missing seams. Phase A builds those seams *once*, with a design note in
> `docs/design/` and a PORTING-GUIDE D-rule where behavior deviates from the
> C++; Phase B then adopts them mechanically per widget. Never hand-roll a
> per-widget variant of a Phase-A seam.
>
> **Run-scope decisions (user, 2026-06-10):** the big editor sub-features are
> Phase C — *backlogged, not in this run*. The theme verification pass (A4)
> runs **early, in parallel** with A1–A3.

## Phase A — FOUNDATION seams (serial, except A4 ∥)

| row | item | tag | unblocks | notes |
|---|---|---|---|---|
| A1 ✅ | **CommandSet allowlist → denylist flip** | FOUNDATION 🔴 | B1 | `src/command.rs` `CommandSet` is an allowlist (default-empty → everything disabled); C++ `tview.cpp::initCommands` is **enabled-by-default** with a 5-command denylist (cmZoom/cmClose/cmResize/cmNext/cmPrev) + every command `>255` always enabled. Flip `has` to `!disabled.contains`, seed startup with the 5 window commands disabled, shrink `program.rs::default_command_set` accordingly (the file-dialog bandaid entries disappear). Add the **`Context` command-set query** + `cmCommandSetChanged` broadcast plumbing (C++ `commandSetChanged` flag) so views can gray themselves. |
| A2 ✅ | **`show()→resetCurrent` cascade at insert** | FOUNDATION 🔴 | — | C++ `TView::setState(sfVisible)` calls `owner->resetCurrent()` for `ofSelectable`. tvision-rs's ctx-less `Group::insert` skips it; compensated in **three** call sites (`exec_view`, `HistoryWindow::select_child`, `Desktop::insert_and_focus`). Establish currency at insert/show time and collapse the compensations. See memory `show-resetcurrent-cascade-gap`; partial fix landed e8d82f2. |
| A3 ✅ | **D9 press-and-hold / auto-repeat capture seam** | FOUNDATION | B2 | Generalize `window.rs::DragCapture` + the button hold-capture (9aa291d) into one reusable capture: track-while-held, fire-on-release-inside, optional timer-driven auto-repeat (scrollbar arrows). Design note required; this is the C++ `while(mouseEvent(...))` inner-loop successor. |
| A4 ✅ | **Theme chain verification pass** (∥ with A1–A3) | FOUNDATION | B-theme leaves | Per memory `theme-trust-the-chain`: every "provisional" `theme.rs` value gets derived from the **literal C++ palette chain** and the chain documented inline (menu :637, status-line :651, label :604, input :615, cluster/static :569, indicator :577). Plus the **cyan window scheme** (`frame.rs:40,175,223`, `window/window.rs:46,53,256` — blue fallback today), the window-scheme list remap (`theme.rs:552`), and the history palette remaps (`history.rs:180,363,547`). Snapshot churn expected — review every changed `.snap` against the chain. |
| A5 ✅ | **Phased key dispatch (preProcess/focused/postProcess)** | FOUNDATION | B4 | C++ `TGroup::handleEvent` routes `evKeyDown` through phases; tvision-rs has no phase concept, blocking every "plain-hotkey postProcess accelerator" TODO (`static_text.rs:522`, `button.rs:445`, `cluster.rs:533`). Small seam; design note required. |
| A6 ✅ | **OS clipboard integration** | FOUNDATION | B3, C9 | **User directive (2026-06-10): tvision-rs integrates with the OS clipboard by default — the internal string buffer is a last-resort fallback only, never the default.** Backend `Clipboard` seam: OSC 52 write (works over SSH; `crossterm_backend.rs:159`) and/or a native provider (e.g. `arboard`), with capability detection choosing the best available; paste arrives via bracketed paste (B7's event). magiblot tvision integrates with the system clipboard — this is also the *faithful* choice. Design note required (provider selection + fallback order). |

## Phase B — MECHANICAL fan-out (parallel worktree batches once their seam is ✅)

| row | item | needs | sites |
|---|---|---|---|
| B1 ✅ | command-graying adoptions | A1 | **COMPLETE (`680aabc`):** button `cmCommandSetChanged` arm sets `state.disabled = !ctx.command_enabled(command)`; initial graying on startup via `command_set_changed: true` in `Program::new`. InputLine `can_update_commands`/`update_commands` pushed from `handle_event` tail + `set_state` transition. |
| B2 ✅ | press-and-hold adoptions | A3 | **COMPLETE (`90fc0ce`,`de1c0f0`,`62bbd15`,`eb7648d`,`694963d`):** all 8 — scrollbar, inputline, cluster, frame, listviewer, outline, statusline (+ first-class pre-route deferreds in the pump), editor (+ the untracked-wheel fidelity fix) |
| B3 ✅ | InputLine clipboard | A6 | **COMPLETE (`680aabc`):** cmCut/cmCopy write to backend clipboard via `ctx.set_clipboard`; cmPaste uses `Deferred::InputLinePaste` broker (pump reads clipboard, calls `paste_text`). `paste_text` bulk-inserts with `save_state`/`check_valid`/`max_len` clamp + scroll-follow + `sync_cursor`. |
| B4 ✅ | accelerator adoptions + ctrlToArrow | A5 | **landed WITH A5** (`43c9d30`): button/label plain-letter postProcess accelerators, cluster accelerator scan, ctrl_to_arrow for cluster + scrollbar |
| B5 ✅ | resize republish family | — | **COMPLETE (`c917b4b`):** `View::on_bounds_changed` hook (pump post-ChangeBounds); `Scroller::on_bounds_changed` (set_limit); `list_viewer::on_bounds_changed` free fn (vbar pgStep=size.y, hbar pgStep=size.x/numCols) adopted by ListBox/SortedListBox/HistoryViewer/FileList/DirListBox; Outline uses Scroller formula; `Window::locate` re-pushes `frame.set_zoomed`; `KeyboardResizeCapture` (arrows/Ctrl/Home/End/PgUp/PgDn/Enter/Esc); cmResize enabled in `set_state`. |
| B6 ✅ | FileDialog finishers | — | **COMPLETE (`6ae0222`):** `wfGrow` for FileDialog + ChDirDialog via `Dialog::set_flags`; "21st-century percentages" screen-relative resize deferred to first `handle_event` (`needs_screen_resize` guard, `ctx.owner_size()`); `SearchRec` attr/size/time populated from `std::fs` metadata (`FA_DIREC` for dirs, `pack_dos_time` DOS ftime packing). |
| B7 ✅ | backend terminal lifecycle | — | **User confirm (2026-06-10): a TV program must not hand-roll terminal setup — C++ `TApplication`/`TScreen` does it in the ctor.** Move raw-mode/alt-screen/mouse-capture from `examples/hello.rs:218` into `CrosstermBackend` (RAII: setup at construction, teardown in `Drop` + a panic hook so a crashed app restores the terminal) and strip it from `hello.rs`. Also: paste event (`crossterm_backend.rs:313`), focus events (`:317`). Can run early — independent of Phase A. |
| B8 ✅ | small singletons | — | **COMPLETE (`dae38c1`):** `max_len` clamp on `InputLine::set_value` (UTF-8-safe truncation, mirrors paste_text); `valid-select` (`ctx.request_focus(id)` before return false, faithful to C++ `select()`). Timer-payload pre-resolved (`Event::Timer(id)` already in pump idle). `init/doneHistory` + help-ctx propagation remain standing deferrals (need history subsystem port + OneOf status line respectively). |

## Phase C — backlogged feature work (NOT this run; own run each)

| row | item | notes |
|---|---|---|
| C1 ✅ | Find/Replace dialogs | **COMPLETE (`b388492`):** `Deferred::OpenFindDialog`/`OpenReplaceDialog` + `Context::open_*_dialog` (the OpenSaveAsDialog seam); pump drain builds the `tvedit2.cpp` dialogs verbatim (Find 38×12, Replace 40×16, both with `THistory` arrows + `CheckBoxes` options, pre-filled); `ModalCompletion::FindPick`/`ReplacePick` read text via `field_text` + options via `CheckBoxes` downcast, write back (FindPick clears, ReplacePick sets `EF_DO_REPLACE`), re-inject `cmSearchAgain`. `Editor::do_search_replace(ctx)` is faithful `doSearchReplace` with the prompt-on-replace dialog routed through `request_message_box` (answer stored in `pending_replace_answer` via `set_modal_answer`, consumed on the re-run). `CheckBoxes::as_any_mut` off the delegate skip list. |
| C2 ✅ | Editor right-click context menu | **COMPLETE (`2ee829c`):** `MouseDown` right-button arm builds 4-item `Menu` (Cut/Copy/Paste/Undo, `kbShiftDel`/`kbCtrlIns`/`kbShiftIns`/`kbCtrlU`) and calls `popup_menu()` (row-52). Global pos = `m.position + self.abs_origin`. No new seam needed. |
| C3 ✅ | Internal-clipboard editor | **COMPLETE (C3 commit):** see IMPLEMENTATION-LOG. |
| C4 ✅ | D10 dialog gather/scatter group-walk | **COMPLETE (`5f57bb7`):** `Group::gather_data`/`scatter_data`; `View::set_value_ctx` seam; `ListBox::set_value_ctx` → `focus_item_num`; macro forwarder. Walk order = `children.iter()` (C++ `last→prev` = forward). |
| C5 ✅ | cmQuit veto / saveAs modified-close inline drives | **COMPLETE (`7923ac9`):** `valid_end` inline-drive loop (OpenMessageBox + OpenSaveAsDialog); `validate_modal_close` extended likewise; `drive_save_as_inline` helper; 4 new tests; LIMITATION comment removed. |
| C6 ✅ | `cmDosShell` | **COMPLETE (`386eb84`):** `Backend::suspend()`/`resume()` (no-op defaults; CrosstermBackend calls `restore_terminal()`/re-enter); `Renderer::invalidate_all()`; `program_handle_event` threaded with renderer; `Command::DOS_SHELL` arm: suspend → writeShellMsg → `raise(SIGTSTP, cfg(all(unix,not(test))))` → resume → invalidate_all. `libc` dep added. |
| C7 ✅ | help-ctx refresh / `OneOf` status line | **COMPLETE (`8c9bf85`+`20871fd`):** `View::get_help_ctx()` trait method (default: `self.state().get_help_ctx()`); forwarder in `tvision-macros/src/specs.rs`; `delegate_view` spy updated (27 methods). `Program::pump_once` idle arm wires `TStatusLine::update()`: reads `captures.top_modal_view()` as tvision-rs's `TheTopView` equivalent, calls `v.get_help_ctx()` on the modal, forwards to `sl.set_help_ctx(top_ctx)`. `set_help_ctx` now idempotent (early-return guard mirrors C++ `if(helpCtx!=h)`). OneOf defs switch automatically when a modal with a matching `helpCtx` is the top view. |
| C8 ✅ | Theme editor | **COMPLETE (`f38c8d3`):** `Theme::set_style`/`Role::name`/`pub(crate) const ALL`/`ROLE_COUNT`; `Deferred::OpenColorDialogForRole` + `ModalCompletion::ThemeColorPick`/`ThemeEdit`; `Program::set_theme` + `theme_editor()`; `ThemeEditorBody` (new `src/dialog/theme_editor.rs`): scrollable list of 75 roles with fg/bg swatches + "AaBb" preview, arrow/PgUp/PgDn/Home/End nav, F/B hotkeys. 1145 → 1152 tests. |
| C9 ✅ | kbPaste / bracketed-paste multi-char insert | **COMPLETE (`95e0f47`):** `Event::Paste(String)` variant (removes `Copy` from `Event`; all existing `match *ev` arms bind Copy fields and compile unchanged; 3 full-copy sites get `.clone()`). `EnableBracketedPaste` wired at setup/resume; `DisableBracketedPaste` at restore. `HeadlessHandle::push_paste`. Editor `Event::Paste` arm: `mem::take` + `ev.clear()` + `insert_text`. 1152 → 1154 tests. |

## Latent edge notes (keep as TODOs; fix opportunistically)
`input_line.rs:334` auto-fill shrink clamp (D13 hazard, no auto-fill validator
exists yet) · `editor.rs:899` setBufSize shrink · `:910` OOM path (Vec
infallible) · `:1766` charScan.scanCode · `:2125` fexpand nuance · `:2170`
efBackupFiles · `edReadError` on load (ctor has no ctx) ·
`menu_session.rs:1159` TMenuPopup Ctrl+letter (dead under the capture-stack
model, documented).

`initHistory`/`doneHistory` — **resolved**: moot because the store uses a
`thread_local! Vec` that auto-initializes/auto-drops (row 54 deviation). The
stale `TODO(history)` comments in `application.rs` were removed.
