Skip to content

feat(tui): collapsible groups in the tasks pane - #527

Merged
emal-avala merged 8 commits into
mainfrom
feat/pane-collapse
Jul 27, 2026
Merged

feat(tui): collapsible groups in the tasks pane#527
emal-avala merged 8 commits into
mainfrom
feat/pane-collapse

Conversation

@emal-avala

Copy link
Copy Markdown
Member

Summary

Closes the remainder of D4-22. #512 unified agents and background jobs into one pane with group headings; this adds the collapsible part.

The pane grew a second group when background tasks landed in it. With a few agents and a few jobs it fills the strip, and the half you are not watching crowds out the half you are.

Space folds the selected group:

▾ agents
  ● working
    [general-purpose] explore the parser
▸ background (3)

A folded heading shows its count, so collapsing never hides how much is behind it.

Two things that would otherwise make this feel broken

The pane is sized from the collapsed row count. pane_rows_collapsed replaces the sizing input, so folding actually frees vertical space. Sizing from the full list would leave the strip exactly as tall and the feature would do nothing visible — a_collapsed_group_costs_only_its_heading asserts the height genuinely drops, by exactly the folded rows.

The selection skips folded rows. tasks_select walks visible_indices, and toggle_selected_group moves the selection to the first still-visible row when you collapse the group it was in. Without that the selection sits on something off-screen and the pane looks unresponsive to arrow keys — which is indistinguishable from a hang.

Key choice

Space rather than a chord: this branch only runs with an empty composer, so it is not a space the user is trying to type. Same guard shape the existing pane keys use, and it yields to the queue pane exactly as they do.

Verification

  • a_collapsed_group_costs_only_its_heading, pane_rows_matches_the_uncollapsed_count — sizing
  • folded_rows_are_not_selectable, folding_everything_leaves_nothing_selectable — selection domain
  • collapsing_moves_a_selection_off_a_hidden_row, selection_steps_over_a_folded_group, unfolding_restores_the_group — selection behaviour
  • a_folded_group_hides_its_rows_and_shows_its_size — renders through TestBackend and asserts the row text disappears, the ▸ agents (1) marker appears, and the other group is untouched

652 bin tests pass; clippy --all-targets -- -D warnings and fmt --check clean.

The pane grew a second group when background tasks landed in it. With a
few agents and a few jobs it fills the strip, and the half you are not
watching crowds out the half you are.

Space folds the selected group. A folded heading shows the count, so
collapsing never hides how much is behind it.

Two things that would otherwise make this feel broken:

The pane is sized from the collapsed row count, so folding actually
frees space. Sizing from the full list would leave the strip the same
height and buy nothing.

The selection skips folded rows, and collapsing the group that holds the
selection moves it to the first row still visible. Otherwise the
selection sits on something off-screen and the pane looks unresponsive.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ede3a5d791

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/cli/src/ui/modern/app.rs Outdated
Comment on lines +2248 to +2250
self.collapsed_groups.push(source);
let visible = super::tasks::visible_indices(&self.tasks, &self.collapsed_groups);
self.tasks_selected = visible.first().copied().unwrap_or(0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep collapsed group headings selectable

When two source groups are present, collapsing the selected group immediately moves tasks_selected into the other group, while tasks_select only traverses visible task rows. The collapsed heading can therefore never be selected again, so pressing Space cannot unfold that group without first collapsing every remaining visible group and relying on the hidden fallback index. Keep group headings in the selection model, or otherwise retain an explicit selection for the collapsed heading.

Useful? React with 👍 / 👎.

Comment on lines +763 to +764
if group_folded {
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve task indices when omitting folded rows

On a narrow terminal where the pane overflows, folding a source that precedes the selected source removes its tasks from task_ends, but tasks_selected remains an absolute index into app.tasks. The overflow code later calls task_ends.get(app.tasks_selected) and falls back to row zero, so moving down through the remaining group can scroll the selected marker off-screen instead of windowing around it. Store rendered row ends by original task index or translate the selection to its visible ordinal.

Useful? React with 👍 / 👎.

Folding a group moved the selection into the other group, and Up/Down
only walked unfolded rows, so the folded group's heading could never be
reached again to unfold it. A collapsed group now keeps exactly one
selectable row — its heading — the marker renders there, and Enter on it
unfolds instead of opening a hidden row's output.

The overflow window keyed rendered-row ends by absolute task index, so a
folded group ahead of the selection shifted the lookup and scrolled the
selected task off screen. Rows are anchored to their original task index
now, and a visible folded heading accounts for the rows behind it rather
than double-counting them in "+n more".
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — both P2 findings on ede3a5d are fixed in b6efd9a.

Keep collapsed group headings selectable (app.rs): a collapsed group now contributes exactly one selectable row — its first task index, which the heading stands in for (tasks::selectable_indices / is_folded_heading). Folding keeps the selection on the group it just folded instead of moving it into another group, the marker renders on the heading, and Enter on a folded heading unfolds it rather than opening the output of a row the user cannot see. keep_task_selection snaps to the heading after a re-sort so a finishing task cannot strand the selection on a folded non-first row.

Preserve task indices when omitting folded rows (render.rs): rendered-row ends are no longer a dense vec indexed by absolute task index. Each row is a RowAnchor { task, end, covers } keyed by its original index into app.tasks, so a folded group ahead of the selection no longer shifts the lookup or falls back to row zero. A folded heading that survives the window carries covers = group size, so its rows are accounted for by the heading (which already prints (n)) instead of being double-reported in +n more.

Regression tests, each verified to fail against the previous behaviour: overflow_window_follows_selection_past_a_folded_group, stepping_through_a_group_after_a_folded_one_stays_visible, a_visible_folded_heading_accounts_for_its_own_rows, a_folded_heading_shows_the_selection_marker, a_folded_group_can_still_be_selected_and_unfolded, navigation_returns_to_a_folded_heading, selection_steps_over_a_folded_groups_members, drill_in_on_a_folded_heading_unfolds_instead, a_resort_keeps_a_folded_selection_on_the_heading, plus selectable_indices / is_folded_heading unit tests.

Full gate green: cargo check --all-targets, cargo test --all-targets (only the 3 known environmental bwrap_* sandbox failures on this box), cargo clippy --all-targets -- -D warnings, cargo fmt --all -- --check. Headline text still goes through escape_deceptive; mask_version in snapshot.rs untouched and no golden regenerated.

@mintlify

mintlify Bot commented Jul 27, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
agentcode 🟡 Building Jul 27, 2026, 6:33 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6efd9a8ee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// it accounts for every task behind it.
anchors.push(RowAnchor {
task: idx,
end: lines.len() - 1,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep later folded headings visible in short panes

When the pane has only one content row plus the overflow indicator (for example, a narrow 14-row terminal) and the selected folded group is not the first group, this anchor points at the heading itself, but the windowing below computes sel_start = sel_end - 1 under the assumption that every anchor ends on a task headline. The resulting window shows the preceding blank/task line instead of the selected heading, so the marker disappears and the user must unfold the group blindly. Record the anchor's actual start or distinguish single-line folded headings when selecting the window offset.

Useful? React with 👍 / 👎.

A folded group heading is a single line, but the overflow window derived
the marker row as `end - 1` — the shape of a two-line status+headline
task row. When the selected folded group was not the first one, that
stepped onto the blank separator above the heading, so on a short pane
the selected group scrolled off screen and had to be unfolded blind.

Each row now records the line its marker sits on rather than deriving it,
so short-pane windowing keeps a selected folded heading visible at every
pane height.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — P2 "Keep later folded headings visible in short panes" fixed in 2fea1c3.

Confirmed the bug first: with 12 agent rows expanded and a folded background group selected, the heading was off screen at every pane height from 8 to 15 rows.

Root cause was the assumption that every anchor is a two-line status+headline pair, so the marker row could be derived as end - 1. A folded heading is a single line, and for any group after the first there is a blank separator directly above it — that is the line the window landed on. (For the first group the heading is at line 0, so the off-by-one saturated and stayed invisible.)

RowAnchor now records start alongside end — the status line for a task row, the heading line itself for a folded group — and the short-pane windowing uses it instead of subtracting one. Both the max_rows == 1 branch and the visible_h == 1 branch read it, which is where the failure showed up; visible_h >= 2 anchors on end as before.

Regression tests: a_selected_folded_heading_survives_every_pane_height sweeps heights 8..=22 with the folded group second and asserts ❯▸ background (3) is rendered — verified to fail at height 8 against the previous derivation. a_selected_task_row_keeps_its_marker_at_every_pane_height sweeps the same range for an ordinary selected task row so the fix cannot regress the two-line case.

Full gate green: cargo check --all-targets, cargo test --all-targets (only the 3 known environmental bwrap_* sandbox failures on this box), cargo clippy --all-targets -- -D warnings, cargo fmt --all -- --check. mask_version in snapshot.rs untouched, no golden regenerated.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: 2fea1c3a57

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

… collapse work

#519 (model checklist) and #520 (palette routing) landed on the same
pane. Four conflict sites, resolved as:

- app.rs: additive on both sides — `collapsed_groups` alongside `todos`
  and `conversation_epoch`.
- pane sizing: `pane_rows_with_todos` now also takes the collapsed
  groups, so one computation accounts for both the checklist and folded
  rows. Keeps main's saturating `u16::try_from` — a model-authored
  checklist has no length limit and `as u16` would wrap a huge plan to a
  one-row request.
- group heading: keeps the fold marker, the `(n)` count and the
  selection caret, with main's `palette().muted` instead of the
  hardcoded colour #520 removed.
- overflow accounting: keeps main's footer-earns-its-row logic and
  reads the row anchors, so a surviving folded heading still accounts
  for its whole group instead of double-reporting those rows in
  "+n more". Checklist rows keep their own separate count.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — merged main (843651a). #519 (model checklist) and #520 (palette routing) landed on this same pane; four conflict sites, all in the tasks pane:

  1. app.rs — additive on both sides: collapsed_groups kept alongside todos and conversation_epoch.
  2. Pane sizingpane_rows_with_todos now takes the collapsed groups too, so a single computation accounts for both the checklist and the folded rows; previously it called pane_rows, which ignores collapse, and folding would have freed nothing. Kept main's saturating u16::try_from(...) rather than my as u16: a model-authored checklist has no length limit and a truncating cast wraps a huge plan round to a one-row request.
  3. Group heading — kept the fold marker, the (n) count and the selection caret, but with main's palette().muted in place of the hardcoded Color::DarkGray that fix(tui): route chrome colours through the palette #520 removed. the_frame_uses_only_theme_colours passes.
  4. Overflow accounting — kept main's footer-earns-its-row logic (no +0 more) and repointed it at the row anchors, so a folded heading that survives the window still accounts for its whole group instead of double-reporting those rows in +n more. Checklist rows keep their own separate count, as main has them.

New test for the interaction: collapsing_still_shrinks_the_pane_when_a_checklist_is_present — folding frees exactly the rows it hides while the checklist stays accounted for.

Snapshot invariants verified after the merge: mask_version still present in snapshot.rs (2 references) and all four goldens are byte-identical to main and still read agent-code x.y.z. No golden was regenerated — none needed to change.

Full gate green: cargo check --all-targets, cargo test --all-targets, cargo clippy --all-targets -- -D warnings, cargo fmt --all -- --check. Only the 3 known environmental bwrap_* sandbox failures on this box.

One unrelated note: services::background::tests::spawn_shell_completes_and_surfaces_exactly_once (crates/lib, untouched by this PR) failed once under full-suite parallelism and then passed 10/10 isolated and 4/4 full runs. Flagging it as a pre-existing timing flake, not something this branch introduces.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 843651aac6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/cli/src/ui/modern/run.rs Outdated
// not a space the user is trying to type.
(m, KeyCode::Char(' '))
if m.is_empty()
&& app.tasks_visible()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let checklist-only panes pass Space to the composer

When a TodoUpdate makes the pane visible without creating any task rows, this guard still captures Space, but toggle_selected_group() immediately returns because there is no selected group. The keystroke is therefore silently discarded instead of reaching insert_char, unlike the arrow and Enter bindings that deliberately use tasks_nav_active() to avoid swallowing keys in checklist-only panes; use the same predicate here.

Useful? React with 👍 / 👎.

A `TodoUpdate` makes the tasks pane visible without creating any task
rows. The fold binding still claimed Space there, and
`toggle_selected_group` returned immediately with no group selected, so
the keystroke was dropped instead of reaching the composer. Gate it on
`tasks_nav_active` like the arrow and Enter bindings already do.

Also restores the Enter binding's comment, which the fold arm had been
inserted into the middle of.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — P2 "Let checklist-only panes pass Space to the composer" fixed in fb5d0ea.

Correct catch, and it is a merge-interaction bug: #519 made a TodoUpdate able to show the pane with no task rows at all, and it introduced tasks_nav_active() for exactly that case. My fold binding predated it and used tasks_visible(), so in a checklist-only pane Space was claimed, toggle_selected_group() returned immediately with nothing selected, and the keystroke was dropped instead of reaching the composer. The arm now uses tasks_nav_active() like the ↑/↓ and Enter arms.

Also restored the Enter binding's comment: my fold arm had been inserted between its first line and the rest, so "Retained prompts win the empty Enter: after an aborted turn the" was left dangling above the Space arm and the Enter arm started mid-sentence. The fold arm now sits above that comment with its own.

Regression test a_checklist_only_pane_passes_space_to_the_composer, alongside the existing a_checklist_only_pane_leaves_prompt_history_reachable: with a checklist and no tasks, Space reaches the composer; once a task row exists the pane claims it again and folds the group. Verified it fails against the tasks_visible() guard.

Full gate green: cargo check --all-targets, cargo test --all-targets, cargo clippy --all-targets -- -D warnings, cargo fmt --all -- --check. Only the 3 known environmental bwrap_* sandbox failures on this box.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: fb5d0eaeaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Three conflicts, all the same shape: this branch and main each added
a method or test at the same anchor, with both sides ending mid-item
so they shared one closing brace. Unioning naively fused two items
into one; each side is closed explicitly instead.

- app.rs: snap_selection_to_selectable (here) and captured_cards (#526)
- render.rs: the folded-group frame test and #526's drill-in test
- tasks.rs: the collapse row-count tests and #526's capture tests

mask_version intact (2 refs), goldens still read agent-code x.y.z, no
regeneration needed. 739 bin tests pass.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — merged main (which absorbed #507, #522, #526). Three conflicts, all the same shape: this branch and main each added a method or test at the same anchor with both sides ending mid-item, so they shared one closing brace — each side is now closed explicitly rather than fused. Kept: snap_selection_to_selectable alongside #526's captured_cards, and both PRs' frame/capture tests. mask_version intact and goldens unchanged (agent-code x.y.z), no regeneration needed. 739 bin tests pass; clippy and fmt clean.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b5afe32d10

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

#[test]
fn a_folded_group_hides_its_rows_and_shows_its_size() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore the displaced prompt-mode test annotation

Inserting this function immediately after the pre-existing #[test] makes that attribute apply to the new folding test, while normal_mode_is_visible_in_the_prompt below is now an unannotated helper and is silently omitted from the test suite because the CLI crate allows dead code. Add a separate #[test] before the original function so its vi-mode prompt rendering coverage continues to run.

Useful? React with 👍 / 👎.

The main merge inserted the folding test directly after an existing
`#[test]`, so that attribute bound to the new function and
`normal_mode_is_visible_in_the_prompt` (vi-mode prompt rendering)
became an unannotated helper that no longer ran. The crate root allows
dead code for its public API surface, so nothing warned.

`copy_reports_no_assistant_when_empty` in app.rs was dead the same way
and already dead on main; both pass now that they run.

The pane modules' test modules opt back in with `#![deny(dead_code)]`,
which turns a lost `#[test]` into a compile error instead of a test that
silently stops running.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — P2 "Restore the displaced prompt-mode test annotation" fixed in b726dc4.

Correct and well spotted. The merge dropped the folding test in directly after an existing #[test], so that attribute bound to the new function and normal_mode_is_visible_in_the_prompt (#522's vi-mode prompt rendering coverage) became an unannotated helper. #![allow(dead_code)] at the crate root — there for the public API surface — meant nothing warned. It has its own #[test] back and passes.

Two follow-ons from auditing the same class:

  • I diffed every #[test]/#[tokio::test] function name in the tree against main to check nothing else had been dropped. One more turned up: copy_reports_no_assistant_when_empty in app.rs, dead the same way and already dead on main, so pre-existing rather than something this branch caused. It passes once it runs, so I restored its attribute here rather than leave known-dead coverage behind; happy to split it out if you would rather keep this PR strictly to the pane. The only other differences were tests new on main from feat(review): review targets, merge-base resolution and a rubric #529, which this branch has not merged.
  • Rather than guard this with a source-scanning meta-test, the four pane test modules now carry #![deny(dead_code)], which overrides the crate-root allow for those modules only. A test that loses its #[test] is unreachable, so this makes it a compile error. Verified: removing the attribute again fails the build with "function normal_mode_is_visible_in_the_prompt is never used" instead of silently skipping the test.

Bin test count goes 739 → 741, matching the two restored tests.

Full gate green: cargo check --all-targets, cargo test --all-targets, cargo clippy --all-targets -- -D warnings, cargo fmt --all -- --check. Only the 3 known environmental bwrap_* sandbox failures on this box. mask_version present in snapshot.rs and all four goldens byte-identical to main; none regenerated.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b726dc499c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/cli/src/ui/modern/run.rs Outdated
Comment on lines +1445 to +1449
(m, KeyCode::Char(' '))
if m.is_empty()
&& app.tasks_nav_active()
&& !app.show_queue_pane
&& app.input.is_empty() =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let Space reach folding in vi normal mode

When vi editing is enabled and the empty composer is in Normal mode, the earlier bare-character dispatch consumes KeyCode::Char(' '), calls the no-op vi_normal_key(' '), and returns before this arm. As a result, the documented fold/unfold key does nothing for vi-mode users even though arrows and Enter still control the pane; route this contextual Space action before normal-mode character handling or explicitly let it fall through under these pane conditions.

Useful? React with 👍 / 👎.

Vi normal mode owns bare characters, so its dispatch arm consumed
Space, called the no-op `vi_normal_key(' ')` and returned before the
fold binding. Vi users got a documented key that did nothing while
their arrows and Enter still drove the pane.

Space now falls through on an empty composer, as Backspace and Enter
already do — as a motion it moves right, which does nothing on an empty
line. Both dispatch sites ask one shared `space_folds_group` predicate
so the conditions cannot drift apart.
@emal-avala

Copy link
Copy Markdown
Member Author

@codex review — P2 "Let Space reach folding in vi normal mode" fixed in b4e2682.

Right again, and the third interaction this branch has had with a PR that landed underneath it (#519 checklist, #522 vi mode, #526 captured cards). Vi normal mode owns bare characters, so its arm consumed Char(' '), ran the no-op vi_normal_key(' ') and returned before the fold arm — vi users had a documented key that did nothing while their arrows and Enter still drove the pane.

Fixed the way that arm already handles the same problem for Backspace and Enter: fall through on an empty composer. As a vi motion Space moves right, which does nothing on an empty line, so normal mode gives up nothing real.

Both dispatch sites now ask a single App::space_folds_group() predicate (tasks_nav_active() && !show_queue_pane && input.is_empty()) rather than repeating the condition. That is deliberate: the previous two findings here were both a guard in one place disagreeing with a guard in another, and a duplicated condition across the vi arm and the fold arm is exactly the shape that drifts.

Regression tests: vi_normal_mode_still_folds_with_space (normal mode, empty composer, pane with a task — Space folds and unfolds, nothing leaks into the composer) and vi_normal_mode_keeps_space_as_a_motion_with_text (with text in the composer, Space stays a motion and does not fold). Verified against the previous dispatch: the first fails, the second still passes, so the fix does not steal Space from vi editing.

Bin tests 741 → 743. Full gate green: cargo check --all-targets, cargo test --all-targets, cargo clippy --all-targets -- -D warnings, cargo fmt --all -- --check. Only the 3 known environmental bwrap_* sandbox failures on this box.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. More of your lovely PRs please.

Reviewed commit: b4e26824cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@emal-avala
emal-avala merged commit d8087e5 into main Jul 27, 2026
15 checks passed
@emal-avala
emal-avala deleted the feat/pane-collapse branch July 27, 2026 15:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant