fix(tui): always render selected columns; stop dropping by terminal width#497
Conversation
…idth When the natural table width exceeded the terminal, select_columns() was removing whole low-priority columns (LastCommit, Hash, Owner, Age, ...). This produced confusing UX: columns popped in and out mid-sync as column_content_width widened on streamed data, terminal resizes silently changed the visible set, and the auto-decided defaults diverged from what explicit --columns rendered. Render the full intended column set unconditionally and let fit_widths_to_available shrink Branch / Path / LastCommit with ellipses, accepting overflow rather than removing data — mirroring the blocking-list renderer's tabled::Width::truncate(...).priority(Priority::max(true)). Behavior note: phased commands (sync, clone, prune, repo remove) without --columns now always show the Hash column. Pre-fix it was the first column dropped on most realistic widths, so most users have never seen it. YAML regression scenarios cannot exercise this path — the manual-test runner pipes stdio, so terminal_size returns None and the renderer's narrow-width branches never fire. Regression is pinned via two TestBackend tests in render.rs instead. Fixes #494 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
/claude review |
|
Claude finished @avihut's task in 3m 13s —— View job Code Review: fix(tui): always render selected columns; stop dropping by terminal width
Overall assessment: The fix is correct and well-scoped. The Issues1. Stale
|
- Strip orphaned "Priority N (always shown)" notes from Column enum
variants. The priority() method that consumed them was deleted in this
PR, and "always shown" is now inaccurate for Branch (shrinkable to min).
Replace Size's doc with a why-it's-opt-in note pointing at ALL_COLUMNS.
- Add all_columns_never_includes_size unit test to replace the deleted
size_excluded_from_default_responsive_columns. Size triggers a
filesystem walk; pin the invariant that it stays out of the default set.
- Comment the phased regression test to clarify it verifies header
*presence* (substring), not pixel-level fit — the table technically
overflows at width=100 by design ("accept overflow rather than
dropping data").
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cells The `daft list --columns +size` TOTAL summary row's value silently truncates when the formatted total is wider than any per-row cell — `10.2G` renders as `10.2`, losing the unit suffix. Two paths, two distinct mechanisms: - **TUI** (`render.rs` + `columns.rs::column_content_width`): the summary row is appended to `all_rows` AFTER `column_content_width` computes natural widths from `state.live.rows` only. The Size column gets sized to the widest data cell; the wider TOTAL cell then truncates against that width via ratatui's `Constraint::Length`. - **Blocking** (`list.rs::print_table`): tabled's `Builder` sees the TOTAL row, so the column's natural width fits it. But `Width::truncate(term).priority(Priority::max(true))` shrinks the widest column when the table needs to fit a narrow terminal — and once Branch/Path hit their floor, Size becomes a candidate. `MinWidth` / `Width::increase` doesn't help because `Width::truncate` derives floors from `EmptyRecords` (padding only), not from `MinWidth` settings. Verified empirically. Fixes: - `column_content_width` gains an `extra_width: u16` parameter; the call site passes the formatted TOTAL width for Size. `fit_widths_to_available` already excludes Size from shrink candidates, so once the natural width is right, no further fitting work is needed. - The TUI's summary-row build is refactored to reuse the `total_size` String computed once for the natural-width hint, keeping the displayed cell and the column width in lockstep. - A custom `PriorityMaxExcept` Peaker replaces `Priority::max(true)` in the blocking path when the Size column is selected. It honors `mins` so the shrink loop terminates cleanly. Tests: - `column_content_width` + `extra_width`: 3 unit tests (wider, narrower, Status floor preserved). - Blocking path: 3 `PriorityMaxExcept` algorithmic tests + 3 `size_column_index` wire-up tests + a `format_human_size` no-whitespace contract test. - TUI regression: a `TestBackend` test in `render.rs` that constructs three 5 GiB worktrees (TOTAL `15.0G`, per-row `5.0G`) and asserts the full `15.0G` string appears in the rendered buffer. Verified to fail pre-fix (renders `15.0`). - YAML smoke scenario `list/size-total.yml` for end-to-end wire-up. Note: the manual-test runner pipes stdio so `terminal_size()` returns `None`, meaning `Width::truncate` doesn't fire — true truncation regression coverage lives in unit + TestBackend tests, mirroring how #497 handled the same constraint. The other fixed-width metric columns (`Hash`, `Changes`, `Base`, `Remote`, `Age`, `Owner`) have the same class of bug under `Priority::max(true)` in very narrow terminals — left out of scope; noted in `PriorityMaxExcept`'s docstring. Fixes #501 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cells The `daft list --columns +size` TOTAL summary row's value silently truncates when the formatted total is wider than any per-row cell — `10.2G` renders as `10.2`, losing the unit suffix. Two paths, two distinct mechanisms: - **TUI** (`render.rs` + `columns.rs::column_content_width`): the summary row is appended to `all_rows` AFTER `column_content_width` computes natural widths from `state.live.rows` only. The Size column gets sized to the widest data cell; the wider TOTAL cell then truncates against that width via ratatui's `Constraint::Length`. - **Blocking** (`list.rs::print_table`): tabled's `Builder` sees the TOTAL row, so the column's natural width fits it. But `Width::truncate(term).priority(Priority::max(true))` shrinks the widest column when the table needs to fit a narrow terminal — and once Branch/Path hit their floor, Size becomes a candidate. `MinWidth` / `Width::increase` doesn't help because `Width::truncate` derives floors from `EmptyRecords` (padding only), not from `MinWidth` settings. Verified empirically. Fixes: - `column_content_width` gains an `extra_width: u16` parameter; the call site passes the formatted TOTAL width for Size. `fit_widths_to_available` already excludes Size from shrink candidates, so once the natural width is right, no further fitting work is needed. - The TUI's summary-row build is refactored to reuse the `total_size` String computed once for the natural-width hint, keeping the displayed cell and the column width in lockstep. - A custom `PriorityMaxExcept` Peaker replaces `Priority::max(true)` in the blocking path when the Size column is selected. It honors `mins` so the shrink loop terminates cleanly. Tests: - `column_content_width` + `extra_width`: 3 unit tests (wider, narrower, Status floor preserved). - Blocking path: 3 `PriorityMaxExcept` algorithmic tests + 3 `size_column_index` wire-up tests + a `format_human_size` no-whitespace contract test. - TUI regression: a `TestBackend` test in `render.rs` that constructs three 5 GiB worktrees (TOTAL `15.0G`, per-row `5.0G`) and asserts the full `15.0G` string appears in the rendered buffer. Verified to fail pre-fix (renders `15.0`). - YAML smoke scenario `list/size-total.yml` for end-to-end wire-up. Note: the manual-test runner pipes stdio so `terminal_size()` returns `None`, meaning `Width::truncate` doesn't fire — true truncation regression coverage lives in unit + TestBackend tests, mirroring how #497 handled the same constraint. The other fixed-width metric columns (`Hash`, `Changes`, `Base`, `Remote`, `Age`, `Owner`) have the same class of bug under `Priority::max(true)` in very narrow terminals — left out of scope; noted in `PriorityMaxExcept`'s docstring. Fixes #501 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…cells (#507) The `daft list --columns +size` TOTAL summary row's value silently truncates when the formatted total is wider than any per-row cell — `10.2G` renders as `10.2`, losing the unit suffix. Two paths, two distinct mechanisms: - **TUI** (`render.rs` + `columns.rs::column_content_width`): the summary row is appended to `all_rows` AFTER `column_content_width` computes natural widths from `state.live.rows` only. The Size column gets sized to the widest data cell; the wider TOTAL cell then truncates against that width via ratatui's `Constraint::Length`. - **Blocking** (`list.rs::print_table`): tabled's `Builder` sees the TOTAL row, so the column's natural width fits it. But `Width::truncate(term).priority(Priority::max(true))` shrinks the widest column when the table needs to fit a narrow terminal — and once Branch/Path hit their floor, Size becomes a candidate. `MinWidth` / `Width::increase` doesn't help because `Width::truncate` derives floors from `EmptyRecords` (padding only), not from `MinWidth` settings. Verified empirically. Fixes: - `column_content_width` gains an `extra_width: u16` parameter; the call site passes the formatted TOTAL width for Size. `fit_widths_to_available` already excludes Size from shrink candidates, so once the natural width is right, no further fitting work is needed. - The TUI's summary-row build is refactored to reuse the `total_size` String computed once for the natural-width hint, keeping the displayed cell and the column width in lockstep. - A custom `PriorityMaxExcept` Peaker replaces `Priority::max(true)` in the blocking path when the Size column is selected. It honors `mins` so the shrink loop terminates cleanly. Tests: - `column_content_width` + `extra_width`: 3 unit tests (wider, narrower, Status floor preserved). - Blocking path: 3 `PriorityMaxExcept` algorithmic tests + 3 `size_column_index` wire-up tests + a `format_human_size` no-whitespace contract test. - TUI regression: a `TestBackend` test in `render.rs` that constructs three 5 GiB worktrees (TOTAL `15.0G`, per-row `5.0G`) and asserts the full `15.0G` string appears in the rendered buffer. Verified to fail pre-fix (renders `15.0`). - YAML smoke scenario `list/size-total.yml` for end-to-end wire-up. Note: the manual-test runner pipes stdio so `terminal_size()` returns `None`, meaning `Width::truncate` doesn't fire — true truncation regression coverage lives in unit + TestBackend tests, mirroring how #497 handled the same constraint. The other fixed-width metric columns (`Hash`, `Changes`, `Base`, `Remote`, `Age`, `Owner`) have the same class of bug under `Priority::max(true)` in very narrow terminals — left out of scope; noted in `PriorityMaxExcept`'s docstring. Fixes #501 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Stops the TUI from silently dropping low-priority columns (LastCommit, Hash, Owner, Age, ...) when the natural table width exceeds the terminal. The full intended column set now renders every time;
fit_widths_to_availableshrinks Branch / Path / LastCommit with ellipses and accepts overflow, mirroring the blocking-list renderer'stabled::Width::truncate(...).priority(Priority::max(true)).Affects
daft list(LiveTable),daft sync,daft clone,daft prune,daft repo remove. All five flow throughsrc/output/tui/render.rs::render_tableso the fix is centralized to one match-expression collapse.Behavior change worth surfacing
For phased commands (
sync,clone,prune,repo remove) with no--columnsflag, the Hash column is now always visible. Pre-fix it was the first column dropped on most realistic terminal widths, so most users have never seen it by default. Consistent with the spec — flagging here in case reviewers want to push back.A pre-existing asymmetry is preserved:
daft syncwith no flag falls back toALL_COLUMNS(includes Hash);daft sync --columns +sizeuses the modifier-mode baseListColumn::tui_defaults()which doesn't include Hash. Out of scope for this PR; can be reconciled in a follow-up.AC deviation
Issue acceptance criterion #4 asked for a YAML regression scenario verifying narrow-terminal behavior. YAML can't exercise this path:
xtask/src/manual_test/runner.rsruns commands viaCommand::new("bash").output()with piped stdio, soterminal_sizereturnsNoneand the renderer's narrow-width branches never fire. SettingCOLUMNS=60in scenarioenv:doesn't help —terminal_sizereads ioctl, not env. Replaced with twoTestBackendtests inrender.rs::teststhat exerciserender_tabledirectly at a constrained width with a long branch name (forcingfit_widths_to_availableto shrink Branch from natural to min while keeping all 11 columns).Cleanup deferred
columns_explicit: boolis threaded throughlive_table.rs,operation_table.rs,state.rs,list_live.rs,prune.rs,clone.rs,sync.rs,repo/remove.rs. After this fix no render-layer logic branches on it. Marked with// Unused by render after #494; pending removal in a follow-up.comments on the two config-struct fields; full rip is a separate PR.Test plan
mise run fmtcleanmise run clippyclean (zero warnings)mise run test:unit— 1701 pass, 0 fail (4 deletedcolumn_selection_*tests replaced by 2 TestBackend regression tests inrender.rs)test-plans/render-all-columns.md: 3 widths (60/100/160) × 2 commands (daft list,daft sync), plus mid-syncresize regression checkFixes #494
🤖 Generated with Claude Code