fix(tui): label pending input delivery modes
Harvested from PR #2532 by @cyq1017. Pending input rows now distinguish steer-pending, rejected-steer, and queued-follow-up states, with continuation rows aligned under the delivery label. Refs #2054; leaves the broader cancel/edit affordance work open. Co-authored-by: cyq1017 <61975706+cyq1017@users.noreply.github.com>
This commit is contained in:
+5
-1
@@ -68,10 +68,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
expandable transcript row by default, while running, failed, shell, patch,
|
||||
review, diff, and other risky tool cells remain visible. The setting
|
||||
`tool_collapse = "compact" | "expanded" | "calm"` controls the behavior.
|
||||
- Pending-input preview rows now label delivery mode explicitly as steer
|
||||
pending, rejected steer, or queued follow-up, with wrapped continuation rows
|
||||
aligned under the label so busy-turn input state is easier to read (#2054).
|
||||
|
||||
### Community
|
||||
|
||||
Thanks to **@cyq1017** for the restore-listing implementation (#2513),
|
||||
Thanks to **@cyq1017** for the restore-listing implementation (#2513) and
|
||||
pending-input delivery-mode label work (#2532, #2054),
|
||||
**@wywsoor** for the broader macOS/iTerm rollback UX report (#2494),
|
||||
**@HUQIANTAO** for the `web_run` lock-splitting work (#2502) and turn-metadata
|
||||
prefix-cache stability work (#2517), **@xyuai** for canonical CodeWhale
|
||||
|
||||
@@ -1548,7 +1548,8 @@ pub struct App {
|
||||
/// cancelled cleanly). Surfaced in the pending-input preview so the user
|
||||
/// knows the steer was deferred to end-of-turn. Today no engine path
|
||||
/// produces these; the field is scaffolding for a future signalling
|
||||
/// channel and the bucket renders identically when populated.
|
||||
/// channel and the bucket renders with a rejected-steer label when
|
||||
/// populated.
|
||||
pub rejected_steers: VecDeque<String>,
|
||||
/// Legacy resend flag for pending steer recovery.
|
||||
pub submit_pending_steers_after_interrupt: bool,
|
||||
|
||||
@@ -6491,7 +6491,8 @@ async fn handle_plan_choice(
|
||||
/// - `pending_steers` — typed during a running turn + Esc; held until the
|
||||
/// abort lands and gets resubmitted as a fresh merged turn.
|
||||
/// - `rejected_steers` — engine declined a mid-turn steer (scaffolding;
|
||||
/// no engine path produces these yet but the bucket renders identically).
|
||||
/// no engine path produces these yet but the bucket renders with a distinct
|
||||
/// rejected-steer label).
|
||||
/// - `queued_messages` — Enter while busy (offline-mode FIFO); drained at
|
||||
/// end-of-turn.
|
||||
fn build_pending_input_preview(app: &App) -> PendingInputPreview {
|
||||
|
||||
@@ -24,6 +24,9 @@ use crate::tui::widgets::Renderable;
|
||||
|
||||
/// Per-item line cap before we collapse the rest into a `…` overflow row.
|
||||
const PREVIEW_LINE_LIMIT: usize = 3;
|
||||
const PENDING_STEER_PREFIX: &str = " ↳ Steer pending: ";
|
||||
const REJECTED_STEER_PREFIX: &str = " ↳ Rejected steer: ";
|
||||
const QUEUED_MESSAGE_PREFIX: &str = " ↳ Queued follow-up: ";
|
||||
|
||||
/// Description of the keybinding the hint line at the bottom should advertise
|
||||
/// for the "edit last queued message" action.
|
||||
@@ -109,14 +112,38 @@ impl PendingInputPreview {
|
||||
&mut lines,
|
||||
Line::from(vec![Span::raw("• "), Span::raw("Pending inputs")]),
|
||||
);
|
||||
let pending_steer_indent = continuation_indent(PENDING_STEER_PREFIX);
|
||||
for steer in &self.pending_steers {
|
||||
push_truncated_item(&mut lines, steer, width, dim, " ↳ ", " ");
|
||||
push_truncated_item(
|
||||
&mut lines,
|
||||
steer,
|
||||
width,
|
||||
dim,
|
||||
PENDING_STEER_PREFIX,
|
||||
&pending_steer_indent,
|
||||
);
|
||||
}
|
||||
let rejected_steer_indent = continuation_indent(REJECTED_STEER_PREFIX);
|
||||
for steer in &self.rejected_steers {
|
||||
push_truncated_item(&mut lines, steer, width, dim, " ↳ ", " ");
|
||||
push_truncated_item(
|
||||
&mut lines,
|
||||
steer,
|
||||
width,
|
||||
dim,
|
||||
REJECTED_STEER_PREFIX,
|
||||
&rejected_steer_indent,
|
||||
);
|
||||
}
|
||||
let queued_message_indent = continuation_indent(QUEUED_MESSAGE_PREFIX);
|
||||
for message in &self.queued_messages {
|
||||
push_truncated_item(&mut lines, message, width, dim_italic, " ↳ ", " ");
|
||||
push_truncated_item(
|
||||
&mut lines,
|
||||
message,
|
||||
width,
|
||||
dim_italic,
|
||||
QUEUED_MESSAGE_PREFIX,
|
||||
&queued_message_indent,
|
||||
);
|
||||
}
|
||||
if !self.queued_messages.is_empty() {
|
||||
lines.push(Line::from(vec![Span::styled(
|
||||
@@ -154,6 +181,10 @@ impl Renderable for PendingInputPreview {
|
||||
}
|
||||
}
|
||||
|
||||
fn continuation_indent(prefix: &str) -> String {
|
||||
" ".repeat(display_width(prefix))
|
||||
}
|
||||
|
||||
fn push_section_header(lines: &mut Vec<Line<'static>>, header: Line<'static>) {
|
||||
lines.push(header);
|
||||
}
|
||||
@@ -423,6 +454,51 @@ mod tests {
|
||||
assert!(rows.iter().any(|r| r.contains("↑")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_input_rows_label_each_delivery_mode() {
|
||||
let mut preview = PendingInputPreview::new();
|
||||
preview.pending_steers.push("steer".to_string());
|
||||
preview.rejected_steers.push("rejected".to_string());
|
||||
preview.queued_messages.push("queued".to_string());
|
||||
|
||||
let rows = render_to_string(&preview, 80);
|
||||
|
||||
assert!(
|
||||
rows.iter().any(|row| row.contains("Steer pending: steer")),
|
||||
"missing pending-steer label: {rows:?}"
|
||||
);
|
||||
assert!(
|
||||
rows.iter()
|
||||
.any(|row| row.contains("Rejected steer: rejected")),
|
||||
"missing rejected-steer label: {rows:?}"
|
||||
);
|
||||
assert!(
|
||||
rows.iter()
|
||||
.any(|row| row.contains("Queued follow-up: queued")),
|
||||
"missing queued-follow-up label: {rows:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrapped_pending_input_aligns_continuation_under_label() {
|
||||
let mut preview = PendingInputPreview::new();
|
||||
preview
|
||||
.queued_messages
|
||||
.push("alpha beta gamma delta epsilon zeta".to_string());
|
||||
|
||||
let rows = render_to_string(&preview, 34);
|
||||
|
||||
assert!(rows[1].contains("Queued follow-up: alpha"));
|
||||
assert!(
|
||||
rows[2].starts_with(&continuation_indent(QUEUED_MESSAGE_PREFIX)),
|
||||
"continuation should align under label: {rows:?}"
|
||||
);
|
||||
assert!(
|
||||
!rows[2].trim().is_empty(),
|
||||
"continuation should keep wrapped text: {rows:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_truncates_to_three_visible_lines() {
|
||||
let mut preview = PendingInputPreview::new();
|
||||
|
||||
@@ -50,6 +50,7 @@ harvest/stewardship commits:
|
||||
| #2736 sub-agent model inheritance | Locally harvested with explicit-override and provider-shaping tests. | Tool-agent routing now inherits the parent runtime model instead of hard-coding `deepseek-v4-flash`, while explicit DeepSeek-style tool-agent overrides still win. The `reasoning_effort = off` fast lane is covered by strict OpenAI-like provider request-shaping tests. Credit @h3c-hexin; comment/close the original after the integration branch is public. |
|
||||
| #2737 configured `skills_dir` discovery | Locally harvested with explicit-config precedence. | The system prompt now unions workspace-discovered skills and configured `skills_dir` skills instead of treating the configured directory as a fallback. Explicit configured skills are inserted before global defaults so they are not lost behind a large global skill library. Credit @h3c-hexin; comment/close the original after the integration branch is public. |
|
||||
| #2738 dense tool-call transcript collapse | Locally harvested with expansion, cache-key, and safety fixes. | Successful read/search/list-style tool runs collapse by default once they cross the density threshold; failures, running cells, shell/exec, patch/write/edit/delete, diff preview, plan update, and review cells stay visible. Users can expand a group with Enter/Space/mouse and can set `tool_collapse = "compact" | "expanded" | "calm"`. Credit @idling11 and issue #2692; comment/close the original after the integration branch is public. |
|
||||
| #2532 pending-input delivery-mode labels | Locally re-harvested for #2054. | Pending-input preview rows now label steer-pending, rejected-steer, and queued-follow-up delivery modes, and wrapped continuation rows align under the label. `cargo test -p codewhale-tui --bin codewhale-tui --locked pending_input_preview -- --nocapture` passed. Credit @cyq1017; #2054 remains open for cancel/edit-mode affordance clarity. |
|
||||
| #2636 project-context mtime cache | Defer direct merge; harvest only after cache key/signature is widened. | Must include constitution changes, auto-generated context deletion, canonical path equivalence, and overwrite detection before landing. |
|
||||
| #2634 HarmonyOS port | Locally harvested with additional Nix-chain clearance; keep credited and do not close until the integration branch is public. | User-supplied MatePad Edge demo (`https://bilibili.com/video/av116689597368905`) confirms real-device interest. Added env-driven OpenHarmony SDK setup, OHOS platform guards/fallbacks, self-update disablement, and OHOS target gating for Starlark execpolicy parsing plus PTY support so published OHOS builds do not pull `nix` 0.28 through `rustyline` or `portable-pty`. `cargo check --workspace --all-features --locked`, focused PTY/clipboard tests, and `cargo tree --locked -p codewhale-tui --target aarch64-unknown-linux-ohos -i nix@0.28.0` passed; full OHOS target check is blocked on this host because `OHOS_NATIVE_SDK`/target CC/sysroot are not configured and `ring` cannot find `assert.h`. |
|
||||
| #2687 append-only mode/approval prompt | Defer direct merge; draft has compile failures and Plan-mode prompt correctness risks. | Any future harvest must keep stable `message[0]` genuinely mode-agnostic, preserve mode/approval suffixes after capacity replans, and distinguish external overrides from persisted generated prompts. |
|
||||
@@ -74,7 +75,7 @@ v0.9 branch so the remaining Windows/manual checks are explicit.
|
||||
| Sub-agent timeout and trust model (#1806, #719) | Fixed or covered in current branch. | `heartbeat_timeout_secs` clamp/default test passed, and `agent_open_description_explains_fresh_vs_forked_context_and_trust_model` asserts that sub-agent results are self-reports. |
|
||||
| Sub-agent checkpoint/resume (#2029) | Still release-blocking. | Session projection/transcript handles exist, but no checkpoint/continue status or resume contract has landed. Needs a child checkpoint/timeout/resume test that preserves policy and completes. |
|
||||
| Live shell/session liveness (#1786) | Partially fixed, still release-blocking. | Shell containment and turn-liveness tests exist, but orphaned PID/session-load reaping and long-running shell LIVE-state recovery remain open. Needs stale PID reaping and live-state regression coverage. |
|
||||
| Queued/live input feedback (#2054) | Partially covered; UX clarity still blocking. | `cargo test -p codewhale-tui --bin codewhale-tui --locked queued -- --nocapture` passed for queued-message recovery/editing, but pending rows still need clear delivery-mode labels and cancel/edit-mode clarity tests. |
|
||||
| Queued/live input feedback (#2054) | Partially covered; UX clarity still blocking. | Queued-message recovery/editing and pending-input delivery-mode labels are covered by `queued` and `pending_input_preview` focused tests. Still needs cancel/edit-mode affordance clarity and a repro for accidentally entering queued-draft edit while a turn is loading. |
|
||||
| Prompt/UI calmness (#1191) | Defer or narrow. | No release-blocking regression evidence yet; keep as polish unless a current user-facing prompt/UI failure is identified. |
|
||||
|
||||
## PR Harvest Queue
|
||||
|
||||
Reference in New Issue
Block a user