docs(comments): rewrite comments across all crates to the guidelines

Sweep every first-party crate source (1956 .rs files) to the project comment
guidelines: delete redundant restatements, decorative banners, change
narration, and end-of-line comments; keep and tighten the crucial ones
(invariants, bug rationale, SAFETY blocks, ported-source attribution).

No functional code changed. Every edit is proven comment-only against the
prior tree by a comment-stripping lexer (string/char/raw-string aware) plus a
separate doctest-fence check. Where removing a comment made rustfmt or clippy
want to re-lay-out adjacent code, the minimal triggering comment is restored so
code tokens stay byte-identical.

Gates green: cargo fmt --all --check (0 diffs), cargo check and cargo clippy
--workspace --all-targets (0 warnings).

Adds scripts/check_codegen_comment_guidelines.py — the enforcement gate for
these guidelines (flags banners, end-of-line comments, change narration, and
commented-out code).
This commit is contained in:
ZacharyZhang-NY committed 2026-07-23 16:55:39 -04:00
1 parent ff0fb56c67
commit a02b555e66
1458 files changed
+10729 -21750

No files matched your search

@@ -2,8 +2,6 @@
use super::*;
// ── agent-bound kinds (bash) ─────────
/// A bash command typed while a turn is RUNNING takes the
/// server-authoritative immediate path (Effect + optimistic echo, no local
/// queue entry).
@@ -23,9 +21,7 @@ fn bash_while_running_is_server_authoritative() {
}
other => panic!("expected immediate SendBashCommand, got {other:?}"),
};
// Not in the local queue.
assert_eq!(app.agents[&id].session.queue_len(), 0);
// Optimistic echo present with kind="bash".
let q = app
.shared_prompt_queue("test-session")
.expect("echo present");
@@ -54,8 +50,8 @@ fn auth_complete_triggers_bundle_status_fetch() {
);
assert!(matches!(app.auth_state, AuthState::Done));
// Pager only refreshes the on-disk catalog snapshot; the actual
// bundle download now runs inside the shell post-auth.
// Pager only refreshes the on-disk catalog snapshot; the bundle
// download runs inside the shell post-auth.
assert!(
effects
.iter()
@@ -13,8 +13,6 @@ fn last_system_text(app: &AppView) -> String {
}
}
// ── /usage dispatch tests ───────────────────────────────────
#[test]
fn show_usage_returns_fetch_usage_effect() {
let mut app = test_app_with_agent();
@@ -1398,9 +1398,9 @@ fn dashboard_plan_description_transforms_snapshot_and_chip_ranges() {
);
}
/// The sessions picker modal was removed; `/sessions` survives as an alias
/// of `/dashboard`. It must resolve to the dashboard command and inherit
/// the dashboard feature-flag gate (hidden by canonical name, fail-closed).
/// `/sessions` is an alias of `/dashboard`; it must resolve to the
/// dashboard command and inherit its feature-flag gate (hidden by
/// canonical name, fail-closed).
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn dashboard_slash_sessions_aliases_dashboard() {
@@ -1871,7 +1871,7 @@ fn dashboard_deferred_plan_mode_applied_on_session_created() {
}
/// Any non-empty prompt — even a single character — dispatches a
/// new session (the old 4-char floor was relaxed to 1 char).
/// new session.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn dashboard_dispatch_single_char_creates_session() {
@@ -1971,10 +1971,7 @@ fn dashboard_dispatch_with_top_level_selection_creates_new_session() {
);
}
// -----------------------------------------------------------------
// Enter / Ctrl+S behaviour matrix.
//
// Pins the contract the user spelled out (Ctrl+S is "send + open";
// Enter / Ctrl+S behaviour matrix (Ctrl+S is "send + open";
// Shift/Alt+Enter insert a newline):
//
// button + empty prompt + Enter → Create + open detail
@@ -1987,11 +1984,6 @@ fn dashboard_dispatch_with_top_level_selection_creates_new_session() {
// The dispatch input always spawns a NEW session — a selected row is
// the navigation cursor (Enter on an empty prompt opens it), never a
// reply target.
//
// Tests drive the whole stack — the state handler emits an
// action, the dispatcher runs, and we assert the resulting
// view + selection + attached_agent.
// -----------------------------------------------------------------
/// 2 — Button focused + non-empty + Enter → new session,
/// STAY on the dashboard, no attached_agent.
@@ -2174,10 +2166,8 @@ fn dashboard_attach_top_level_switches_to_agent_view() {
);
}
/// Attach routes through `focus_row`, so a previously selected
/// section header is cleared — the row and section cursors stay
/// mutually exclusive (a bare `selected` assignment used to leave
/// both active).
/// Attach routes through `focus_row`, so any selected section header
/// is cleared — the row and section cursors stay mutually exclusive.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn dashboard_attach_clears_selected_section() {
@@ -2321,11 +2311,10 @@ fn dashboard_attach_subagent_lazily_replays_deferred_transcript() {
/// No auto-attached popup. The user reaches an agent's view by
/// pressing Enter on its row.
///
/// Opening from an agent now lands in NEW-SESSION mode (the
/// Opening from an agent lands in NEW-SESSION mode (the
/// `[+ New Agent]` button focused, no row selected) so typing +
/// Enter dispatches a brand new agent. Previously the dashboard
/// pre-seeded `selected` to the came-from agent, which armed reply
/// mode and trapped the user replying to that one agent.
/// Enter dispatches a brand new agent, rather than pre-seeding
/// `selected` to the came-from agent and arming reply mode.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn dashboard_open_does_not_auto_attach_to_focused_agent() {
@@ -2439,12 +2428,9 @@ fn dashboard_dispatch_after_open_from_agent_spawns_new_sessions() {
);
}
/// Opening from Welcome leaves the `[+ New Agent]`
/// button as the default focus. Previously the dashboard
/// seeded selection to the first agent so Enter would attach
/// without navigating; with the button taking that role,
/// selection stays empty and the button signals what Enter
/// (on an empty prompt) will do.
/// Opening from Welcome leaves the `[+ New Agent]` button as the
/// default focus: selection stays empty, and the button signals
/// what Enter (on an empty prompt) will do.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn dashboard_open_from_welcome_focuses_new_agent_button() {
@@ -2468,9 +2454,6 @@ fn dashboard_open_from_welcome_focuses_new_agent_button() {
);
}
// -----------------------------------------------------------------
// Dashboard mouse-wheel scrolling
//
// Mouse wheel is intentionally decoupled from the selected row:
// scrolling only moves the viewport, leaving `selected` alone.
// `DashboardState::handle_scroll` flags
@@ -2478,7 +2461,6 @@ fn dashboard_open_from_welcome_focuses_new_agent_button() {
// `clamp_viewport` skips its snap-to-selection pull-back so the
// viewport can travel past the cursor. Selection-driven nav
// (arrows, click) clears the flag and re-engages the snap.
// -----------------------------------------------------------------
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
@@ -3364,7 +3346,8 @@ fn dashboard_toggle_auto_approve_flips_yolo_on_selected_agent() {
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn dashboard_toggle_auto_approve_with_no_selection_toasts() {
let mut app = test_app(); // no agent
// no agent
let mut app = test_app();
open_dashboard(&mut app);
let effects = dispatch_dashboard_toggle_auto_approve(&mut app);
assert!(effects.is_empty());
@@ -3439,13 +3422,9 @@ fn dashboard_rename_end_to_end_top_level_row() {
);
}
/// `DashboardCancelRename` emits no effects and
/// leaves `display_name` untouched. Previously named
/// `dashboard_rename_cancel_via_esc_does_not_emit_effect`
/// but that name implied Esc keystroke routing — the test
/// actually dispatches `Action::DashboardCancelRename` directly.
/// The Esc-keystroke routing is now pinned by the sibling test
/// `dashboard_rename_esc_keystroke_routes_to_cancel`.
/// `DashboardCancelRename` emits no effects and leaves `display_name`
/// untouched. Esc-keystroke routing to this action is pinned separately
/// by `dashboard_rename_esc_keystroke_routes_to_cancel`.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn dashboard_rename_cancel_action_emits_no_effect() {
@@ -3809,19 +3788,14 @@ fn dashboard_close_shortcuts_help_clears_modal() {
assert!(app.dashboard.as_ref().unwrap().shortcuts_modal.is_none());
}
// -----------------------------------------------------------------
// `[+ New Agent]` button
//
// The header button is the default cursor target when no row
// is selected. Up-arrow from the first row, Esc deselect,
// dashboard-open-from-welcome, and `reanchor_selection`-drops
// all land here. Enter-with-empty-prompt and click both
// dispatch `DashboardCreateNewAgentWithDetail`, which
// creates a session AND switches into detail view. Enter
// with a NON-empty prompt falls through to
// `DashboardDispatch`, which creates the session but stays
// `[+ New Agent]` button: the header button is the default cursor
// target when no row is selected. Up-arrow from the first row, Esc
// deselect, dashboard-open-from-welcome, and `reanchor_selection`-drops
// all land here. Enter-with-empty-prompt and click both dispatch
// `DashboardCreateNewAgentWithDetail`, which creates a session AND
// switches into detail view. Enter with a NON-empty prompt falls
// through to `DashboardDispatch`, which creates the session but stays
// on the dashboard.
// -----------------------------------------------------------------
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
@@ -3986,9 +3960,7 @@ fn dashboard_filter_state_known_token_via_dispatch() {
));
}
// -----------------------------------------------------------------
// Real extract_recent_lines coverage.
// -----------------------------------------------------------------
/// count=0 returns empty even when scrollback has entries.
#[test]
@@ -4136,9 +4108,7 @@ fn extract_recent_lines_strips_ansi() {
assert!(out[0].contains("evil"));
}
// -----------------------------------------------------------------
// Stop-confirm full lifecycle.
// -----------------------------------------------------------------
/// A press > 2s after the first re-arms (does NOT close).
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
@@ -4254,7 +4224,8 @@ fn dashboard_permission_select_drops_stale_request() {
last_response_truncated: false,
question: Some("q?".into()),
options: vec![("allow".into(), "Allow".into())],
request_id: Some(123), // mismatched id
// mismatched id
request_id: Some(123),
reject_option: None,
},
));
@@ -4687,7 +4658,8 @@ fn dashboard_question_answer_sends_and_clears() {
let effects = dispatch_dashboard_question_answer(
&mut app,
crate::views::dashboard::DashboardRowId::TopLevel(AgentId(0)),
Some(1), // pick "Postgres"
// pick "Postgres"
Some(1),
String::new(),
);
assert!(effects.is_empty());
@@ -4745,7 +4717,8 @@ fn dashboard_question_answer_walks_multiple_questions() {
let fields = compute_peek_fields(&row, &app.agents).expect("ask surfaced");
assert!(fields.question.as_deref().unwrap().starts_with("(1/2)"));
assert!(fields.request_id.is_none());
assert_eq!(fields.reject_option, Some(2)); // 2 options + "Other"
// 2 options + "Other"
assert_eq!(fields.reject_option, Some(2));
// Plant a peek with a stale draft to verify the advance reset.
if let Some(d) = app.dashboard.as_mut() {
let mut p = PeekPanelState::new(row.clone(), fields);
@@ -4787,7 +4760,8 @@ fn dashboard_peek_auto_opens_for_selected_row() {
let mut app = test_app_with_agent();
mark_agent_nonempty(&mut app, AgentId(0));
open_dashboard(&mut app);
let area = Rect::new(0, 0, 80, 24); // tall enough for the peek
// tall enough for the peek
let area = Rect::new(0, 0, 80, 24);
let reg = crate::actions::ActionRegistry::defaults();
// Select a row, then render → the peek opens by default.
@@ -4972,7 +4946,7 @@ fn build_rows_keeps_pinned_empty_local_session() {
assert_eq!(rows.len(), 1, "a pinned empty session is kept");
}
// -- Conversation-origin roster rows (chat-mode dashboard fallback) ----
// Conversation-origin roster rows (chat-mode dashboard fallback).
#[test]
fn dashboard_attach_roster_focuses_existing_local_agent() {
@@ -373,7 +373,6 @@ fn authenticating_seq(app: &AppView) -> u64 {
ref other => panic!("expected Authenticating, got {other:?}"),
}
}
/// Extract text from the last system message in an agent's scrollback.
fn last_system_text(app: &AppView, id: AgentId) -> String {
system_text_from_end(app, id, 0)
}
@@ -648,8 +647,6 @@ fn open_session_picker_with(
pending_delete: None,
});
}
/// Toast strings match the expected format and contain on/off
/// status.
fn read_toast(app: &AppView) -> String {
let agent = app.agents.get(&AgentId(0)).expect("agent must exist");
agent
@@ -748,7 +745,6 @@ fn agent_scrollback_len(app: &AppView) -> usize {
app.agents.get(&AgentId(0)).unwrap().scrollback.len()
}
use crate::scrollback::blocks::UserPromptBlock;
/// Helper: open the dashboard against an existing `app`.
fn open_dashboard(app: &mut AppView) {
let _ = dispatch_open_dashboard(app);
}
@@ -792,12 +788,10 @@ fn dashboard_row_order(app: &AppView) -> Vec<crate::views::dashboard::DashboardR
/// Build a synthetic `PermissionViewState` with the given id and
/// options. Pushes it to the agent's permission_queue.
///
/// Returns the response receiver so tests can verify
/// the response was actually `send`'d through the oneshot. The
/// previous version dropped the receiver (`_rx`), which let
/// "happy-path" tests assert the queue was popped but masked
/// regressions where the pop happened without the corresponding
/// send.
/// Returns the response receiver so tests can verify the response
/// was actually `send`'d through the oneshot — a dropped receiver
/// would let "happy-path" tests assert the queue was popped while
/// masking regressions where the pop happens without a send.
fn push_synthetic_permission(
agent: &mut crate::app::agent_view::AgentView,
id: usize,
@@ -147,8 +147,6 @@ fn accept_word_select_tip_no_op_when_tip_not_showing() {
);
}
// ── /plan slash command tests ─────────────────────────────────────
#[test]
fn slash_plan_no_args_not_in_plan_enters_plan_mode() {
let mut app = test_app_with_agent();
@@ -296,7 +294,6 @@ fn set_plan_mode_mutates_only_active_agent_not_others() {
);
}
// ----------------------------------------------------------------
// `set_yolo_mode` dispatcher unit tests (security-relevant)
//
// SHELL-owned, but with rollback semantics: a disk-write failure
@@ -337,7 +334,6 @@ fn set_plan_mode_mutates_only_active_agent_not_others() {
// - Failure toast: "✗ Could not save permission_mode: {error}"
// — exact format pinned via `assert_eq!` in
// `rollback_permission_mode_reverts_state_no_effect`.
// ----------------------------------------------------------------
/// Slash gate sync: both toggles stay offered while modes change; only the
/// auto feature gate suppresses `/auto`.
@@ -453,10 +449,8 @@ fn set_yolo_mode_off_to_on_emits_persist_with_rollback() {
crate::app::actions::PermissionModePersist::WithRollback("ask"),
"rollback must revert to the prior canonical (was 'ask')"
);
// Explicit session_id assertion
// (previously hidden behind `..` — a regression that
// dropped session_id silently broke the ACP
// notification gate at effects.rs).
// If dropped, session_id silently breaks the ACP
// notification gate at effects.rs.
assert!(
session_id.is_some(),
"session_id must be threaded through for ACP notification gating"
@@ -892,7 +886,7 @@ fn set_yolo_mode_on_drains_multi_item_queue() {
Ok(Ok(acp::RequestPermissionResponse {
outcome: acp::RequestPermissionOutcome::Selected(_),
..
})) => {} // OK
})) => {}
other => panic!(
"item {i} did not receive AllowOnce Selected response: {other:?} — \
drain skipped items beyond the first?",
@@ -970,7 +964,7 @@ fn set_yolo_mode_on_duplicate_dispatch_still_drains_queue() {
Ok(Ok(acp::RequestPermissionResponse {
outcome: acp::RequestPermissionOutcome::Selected(_),
..
})) => {} // OK
})) => {}
other => panic!(
"duplicate dispatch must auto-approve the newly queued permission, got {other:?}",
),
@@ -1377,7 +1371,8 @@ fn cycle_mode_pre_session_normal_to_plan_does_not_persist_permission_mode() {
/// mutation are all gated by the same `app.active_view` guard).
#[test]
fn set_yolo_mode_no_op_when_no_active_agent() {
let mut app = test_app(); // no agent, active_view = Welcome
// No agent, active_view = Welcome.
let mut app = test_app();
let default_yolo_before = app.default_yolo;
let perm_mode_before = app.current_ui.permission_mode.clone();
@@ -1441,7 +1436,6 @@ fn set_yolo_mode_refreshes_open_modal_snapshots() {
);
}
// ----------------------------------------------------------------
// Dispatch-layer integration tests for
// `Action::SetPermissionMode(kind)`.
//
@@ -1472,7 +1466,6 @@ fn set_yolo_mode_refreshes_open_modal_snapshots() {
// - `apply_setting_rollback("permission_mode", Enum("default"))`
// restores `current_ui.permission_mode = Some("default")`
// (preserves canonical; doesn't re-emit any Effect).
// ----------------------------------------------------------------
#[test]
fn set_permission_mode_default_overrides_canonical_to_default() {
@@ -2130,14 +2123,12 @@ fn set_theme_auto_enables_auto_mode_and_persists_auto() {
});
}
// ────────────────────────────────────────────────────────────────────
// set_plan_mode dispatch-level coverage.
//
// Mirrors the `coding_data_sharing` and `yolo` test
// patterns. These exercise the dispatch path directly (not the
// modal Enter path or the slash-command parser path), so they cover
// the same plumbing every entry point ultimately funnels through.
// ────────────────────────────────────────────────────────────────────
/// Idempotent ON: dispatcher sees `prev == new`,
/// toasts but emits NO Effect (saves a wasted ACP round-trip).
@@ -2,19 +2,15 @@
use super::*;
/// `ConfirmResetSetting
/// { Reset }` on `permission_mode` (the security-critical SHELL Enum)
/// dispatches `Action::SetPermissionMode(PermissionModeKind::Ask)`
/// (the typed Action, per the modal-commit ↔ typed-setter
/// rule) via recursive dispatch. Emits
/// `Effect::PersistPermissionMode` — verifies the recursive
/// dispatch reaches the YOLO pipeline through
/// `set_permission_mode` rather than the legacy `set_yolo_mode`.
/// Resetting `permission_mode` must dispatch the typed
/// `Action::SetPermissionMode(PermissionModeKind::Ask)` via recursive
/// dispatch, reaching the YOLO pipeline through `set_permission_mode`
/// rather than the legacy `set_yolo_mode`.
#[test]
fn dispatch_confirm_reset_setting_reset_dispatches_set_permission_mode_for_permission_mode() {
use crate::views::modal::ResetSettingsResult;
let mut app = test_app_with_agent();
// Flip yolo on first (default is OFF = "ask").
// Default is OFF ("ask"); flip yolo on first so the reset is a real transition.
let _ = dispatch(Action::SetYoloMode(true), &mut app);
assert!(app.agents[&AgentId(0)].session.is_yolo());
@@ -36,7 +32,6 @@ fn dispatch_confirm_reset_setting_reset_dispatches_set_permission_mode_for_permi
has_persist,
"Reset of permission_mode must emit PersistPermissionMode, got {effects:?}",
);
// Agent's yolo flag is reset to default (off).
assert!(
!app.agents[&AgentId(0)].session.is_yolo(),
"agent.session.yolo_mode must be reset to default (off)",
@@ -105,7 +100,6 @@ fn set_yolo_mode_on_drains_permission_queue_with_allow_once() {
let _ = dispatch(Action::SetYoloMode(true), &mut app);
// Queue is drained.
assert!(
app.agents[&AgentId(0)].permission_queue.is_empty(),
"YOLO ON must drain the permission_queue",
@@ -231,7 +225,6 @@ fn set_permission_mode_always_approve_blocked_by_policy_pin() {
assert_eq!(app.current_ui.permission_mode.as_deref(), Some("ask"));
}
/// SetPermissionMode(Auto) persists auto and does not enable yolo.
#[test]
fn set_permission_mode_auto_persists_without_yolo() {
use crate::app::actions::PermissionModeKind;
@@ -283,27 +276,16 @@ fn set_permission_mode_auto_degrades_to_ask_when_gated_off() {
);
}
/// Rollback with an unknown canonical: defensively defaults to
/// "ask" (the safe fallback — fewer prompts on a corrupt
/// rollback value is worse, more prompts is safer).
///
/// The previous docstring claimed "logs a
/// warning and defaults to 'ask'" — the warning log is fired via
/// `tracing::warn!` in `apply_setting_rollback`'s arm, but the
/// test doesn't capture/assert it. The fix is documentary: the
/// test pins the OBSERVABLE behaviour (state defaults to "ask")
/// and acknowledges that the warn-log is best-effort visibility
/// for developers, not a contract surface the test enforces.
/// `tracing_test::traced_test` capture would be more rigorous
/// but is not currently used in this crate.
/// Rollback with an unknown canonical defensively defaults to "ask" (the
/// safe fallback — fewer prompts on a corrupt rollback value is worse than
/// more prompts). `apply_setting_rollback` also fires `tracing::warn!` for
/// this case, but the test only pins the observable state, not the log.
#[test]
fn rollback_permission_mode_unknown_canonical_defaults_to_ask() {
use crate::settings::SettingValue;
let mut app = test_app_with_agent();
// Pre-set to true.
let _ = dispatch(Action::SetYoloMode(true), &mut app);
// Garbage canonical rolls back to "ask" (the safe default).
let _ = dispatch(
Action::TaskComplete(TaskResult::SettingPersistFailed {
key: "permission_mode",
@@ -318,31 +300,23 @@ fn rollback_permission_mode_unknown_canonical_defaults_to_ask() {
"unknown canonical → safe default (ask = no auto-approve)",
);
assert_eq!(app.current_ui.permission_mode.as_deref(), Some("ask"));
// The failure toast is the standard
// `✗ Could not save permission_mode: …` format. A future
// enhancement could differentiate "schema corruption" from
// "real disk failure" in the toast text, but currently the
// user sees the same wording; pinned here so a future
// divergence is intentional.
// The toast text does not currently distinguish schema corruption from
// a real disk failure; both produce the standard
// `✗ Could not save permission_mode: …` wording.
}
/// Rollback path refreshes open modal
/// snapshots in the same way the success path does. Mirror of
/// `set_yolo_mode_refreshes_open_modal_snapshots` for the
/// `apply_setting_rollback` entry into `set_yolo_mode_inner`.
/// Without this, a modal that's open when a disk write fails
/// shows a stale "always-approve" indicator after the state
/// has rolled back to "ask".
/// The rollback path (`apply_setting_rollback` into `set_yolo_mode_inner`)
/// must refresh open modal snapshots the same way the success path does.
/// Without this, a modal open when a disk write fails would keep showing a
/// stale "always-approve" indicator after the state rolls back to "ask".
#[test]
fn rollback_permission_mode_refreshes_open_modal_snapshots() {
use crate::settings::SettingValue;
use crate::views::modal::ActiveModal;
let mut app = test_app_with_agent();
// Pre-set yolo=true via the typed setter so the rollback
// captures real prior state.
let _ = dispatch(Action::SetYoloMode(true), &mut app);
// Open the modal AFTER the optimistic toggle so the open-time
// Open the modal after the optimistic toggle so the open-time
// snapshot reflects yolo=true.
let _ = dispatch(Action::OpenSettings, &mut app);
let agent = app.agents.get(&AgentId(0)).unwrap();
@@ -354,7 +328,6 @@ fn rollback_permission_mode_refreshes_open_modal_snapshots() {
"pre-rollback snapshot reflects optimistic state (yolo=true)",
);
// Simulate disk-write failure → rollback to "ask".
let _ = dispatch(
Action::TaskComplete(TaskResult::SettingPersistFailed {
key: "permission_mode",
@@ -364,7 +337,6 @@ fn rollback_permission_mode_refreshes_open_modal_snapshots() {
&mut app,
);
// The modal's snapshot MUST refresh to the rolled-back value.
let agent = app.agents.get(&AgentId(0)).unwrap();
let Some(ActiveModal::Settings { state }) = &agent.active_modal else {
panic!("modal must stay open after rollback");
@@ -395,9 +367,9 @@ fn set_permission_mode_ask_emits_brand_consistent_toast() {
assert!(!app.agents[&AgentId(0)].session.is_yolo());
assert_eq!(app.current_ui.permission_mode.as_deref(), Some("ask"));
// Toast brands as "Permission mode" not
// "Always-approve". Previously the Ask arm reused `yolo_toast(false)`
// which produced "✓ Always-approve: off" — a brand mismatch.
// Toast must brand as "Permission mode", not "Always-approve" — the Ask
// arm must not fall back to `yolo_toast(false)`, which produces the
// wrong brand.
let toast = app.agents[&AgentId(0)]
.toast
.as_ref()
@@ -426,14 +398,12 @@ fn set_permission_mode_ask_emits_brand_consistent_toast() {
}
}
/// Regression test. A `--yolo`
/// startup sets `agent.session.yolo_mode = true` but leaves
/// `app.current_ui.permission_mode` at `None`. Without the
/// LIVE-precedence capture, dispatching `SetPermissionMode(Default)`
/// would produce `WithRollback("ask")` — diverging the pager from
/// the shell on disk failure (the ACP suppress-on-failure gate
/// keeps the shell at YOLO, but the pager would roll back to
/// non-YOLO). This test pins the LIVE-precedence fix.
/// A `--yolo` startup sets `agent.session.yolo_mode = true` but leaves
/// `app.current_ui.permission_mode` at `None`. Without LIVE-precedence
/// capture, dispatching `SetPermissionMode(Default)` would produce
/// `WithRollback("ask")` — diverging the pager from the shell on disk
/// failure (the ACP suppress-on-failure gate keeps the shell at YOLO, but
/// the pager would roll back to non-YOLO).
#[test]
fn set_permission_mode_with_live_yolo_and_no_ui_mirror_rolls_back_to_always_approve() {
use crate::app::actions::PermissionModeKind;
@@ -450,8 +420,6 @@ fn set_permission_mode_with_live_yolo_and_no_ui_mirror_rolls_back_to_always_appr
&mut app,
);
// The dispatch flipped yolo off (Default projects onto
// bool=false) and set the canonical to "default".
assert!(!app.agents[&AgentId(0)].session.is_yolo());
assert_eq!(app.current_ui.permission_mode.as_deref(), Some("default"));
@@ -473,17 +441,13 @@ fn set_permission_mode_with_live_yolo_and_no_ui_mirror_rolls_back_to_always_appr
}
}
/// `apply_setting_rollback("permission_mode",
/// Enum("default"))` — the rollback arm that preserves the
/// "default" canonical through a failed-persist. The headline
/// architectural contract: rolling back to "default" must NOT
/// collapse onto "ask" via the inner's bool projection.
/// `apply_setting_rollback("permission_mode", Enum("default"))` must
/// preserve the "default" canonical through a failed-persist, not collapse
/// onto "ask" via the inner's bool projection.
#[test]
fn rollback_permission_mode_default_canonical_preserves_default() {
use crate::settings::SettingValue;
let mut app = test_app_with_agent();
// Pre-flip to YOLO so the rollback has somewhere to roll
// back FROM.
let _ = dispatch(Action::SetYoloMode(true), &mut app);
assert!(app.agents[&AgentId(0)].session.is_yolo());
assert_eq!(
@@ -491,8 +455,6 @@ fn rollback_permission_mode_default_canonical_preserves_default() {
Some("always-approve"),
);
// Simulate disk-write failure with `rollback_value =
// Enum("default")`.
let effects = dispatch(
Action::TaskComplete(TaskResult::SettingPersistFailed {
key: "permission_mode",
@@ -509,7 +471,6 @@ fn rollback_permission_mode_default_canonical_preserves_default() {
"rollback path must not re-emit Effects, got {effects:?}",
);
// Yolo flipped to false (Default projects onto bool=false).
assert!(
!app.agents[&AgentId(0)].session.is_yolo(),
"Default projects onto yolo=false; agent.session.yolo_mode must flip back",
@@ -526,7 +487,6 @@ fn rollback_permission_mode_default_canonical_preserves_default() {
);
}
/// Non-empty permission_queue → NeedsInput.
#[test]
fn classify_top_level_permission_queue_non_empty_is_needs_input() {
use crate::views::dashboard::{RowState, classify_top_level};
@@ -110,8 +110,6 @@ fn show_undo_tip_no_op_when_flag_off() {
assert!(!app.agents[&id].ephemeral_tip.is_active());
}
// ── Small-screen tip (`show_small_screen_tip` + its one-shot trigger) ──
/// `show_small_screen_tip` on a drawable agent shows the tip and increments
/// the per-session seen count in memory (nothing persisted — the fn returns
/// nothing, so it cannot raise effects).
@@ -557,9 +555,9 @@ fn chip_submit_while_enqueued_clears_follow_up_chips() {
// A chip click submitted while a turn is RUNNING *and* the local queue
// is non-empty takes the ENQUEUE path, not immediate-server-send:
// `immediate_server_send_eligible` is false whenever `pending_prompts`
// is non-empty. Before the fix, only the immediate-send branch cleared
// the chips, so this path left them on screen after the user had already
// acted on one. The clear now runs for every `SubmitFollowUp` path.
// is non-empty. The chip clear must run on this path too, not just the
// immediate-send branch, or the chips would linger on screen after the
// user had already acted on one.
let mut app = test_app_with_agent();
let id = AgentId(0);
{
@@ -586,7 +584,7 @@ fn chip_submit_while_enqueued_clears_follow_up_chips() {
.any(|e| matches!(e, Effect::SendPrompt { text, .. } if text == "Summarize")),
"chip must be enqueued, not immediate-sent, got {effects:?}"
);
// The chips are cleared on the enqueue path too (the bug fix).
// The chips are cleared on the enqueue path too.
assert!(
app.agents[&id].follow_ups.is_none(),
"enqueue chip path must clear chips"
@@ -597,7 +595,6 @@ fn chip_submit_while_enqueued_clears_follow_up_chips() {
fn send_prompt_while_running_queues_without_drain() {
let mut app = test_app_with_agent();
let id = AgentId(0);
// Simulate a running turn.
app.agents.get_mut(&id).unwrap().session.state = AgentState::TurnRunning;
let effects = dispatch(Action::SendPrompt("queued".into()), &mut app);
@@ -1689,8 +1686,6 @@ fn bash_while_idle_stays_on_local_path() {
assert!(app.agents[&id].bash_turn);
}
// ── Reconnect-pending dispatch guards ─────────────────────────────
#[test]
fn send_prompt_blocked_during_reconnect() {
let mut app = test_app_with_agent();
@@ -2049,7 +2044,8 @@ fn prompt_history_loaded_refreshes_open_history_search_with_current_query() {
fn agent_send_before_paste_probe_keeps_image() {
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let mut app = test_app_with_agent();
app.project_picker_shown = true; // don't intercept the send with the picker
// don't intercept the send with the picker
app.project_picker_shown = true;
let id = AgentId(0);
{
let agent = app.agents.get_mut(&id).unwrap();
@@ -2131,7 +2127,8 @@ fn interject_before_paste_probe_keeps_image() {
let id = AgentId(0);
{
let agent = app.agents.get_mut(&id).unwrap();
agent.session.state = AgentState::TurnRunning; // interject needs a live turn
// interject needs a live turn
agent.session.state = AgentState::TurnRunning;
agent.set_active_pane(ActivePane::Prompt, true);
agent.prompt.set_text("look at this");
}
@@ -2215,7 +2212,8 @@ fn interject_before_paste_probe_keeps_image() {
#[test]
fn agent_paste_completion_after_switch_does_not_send_to_other_agent() {
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
let mut app = test_app_with_agent(); // agent A = AgentId(0), active view
// agent A = AgentId(0), active view
let mut app = test_app_with_agent();
app.project_picker_shown = true;
let a = AgentId(0);
let b = AgentId(1);
@@ -2351,8 +2349,6 @@ fn slash_and_exit_input_does_not_trigger_project_picker() {
assert!(!input_can_trigger_project_picker(" "));
}
// ── Minimal-mode slash gate tests ───────────────────────────────────
/// Returns true if any system block in agent 0's scrollback contains
/// `needle`. Avoids `last_system_text`'s "last block must be System" panic
/// for the allowed-command control (which may leave no system block).
@@ -2409,8 +2405,6 @@ fn minimal_mode_allows_mode_agnostic_slash_command() {
);
}
// ── /queue (ShowQueue) dispatch tests ───────────────────────────────
#[test]
fn show_queue_empty_commits_empty_message() {
let mut app = test_app_with_agent();
@@ -2447,8 +2441,6 @@ fn show_queue_no_active_agent_is_noop() {
assert!(effects.is_empty(), "ShowQueue without an agent is a no-op");
}
// ── Send-now cancel marker suppression (PromptResponse rail) ────────
/// Count of "Turn cancelled by user …" marker blocks in the agent's scrollback.
fn count_cancelled_markers(app: &AppView, id: AgentId) -> usize {
let agent = &app.agents[&id];
@@ -437,7 +437,7 @@ fn slash_model_invalid_arg_produces_scrollback_error() {
/// `/model` + Enter (required args missing) must NOT error into scrollback —
/// it re-opens the prompt in the args phase so the existing dropdown lists
/// the catalog (= the connected providers' models). This is the documented
/// "Blocks" row of `is_command_complete`, which previously had no consumer.
/// "Blocks" row of `is_command_complete`.
#[test]
fn slash_model_no_args_reopens_the_model_picker() {
let mut app = test_app_with_agent();
@@ -1183,9 +1183,7 @@ fn find_agent_by_session_id_finds_inactive_agent() {
/// Cross-setting smoke test.
/// Verifies that the dispatcher routes each Action to the
/// correct setter (catches a copy-paste registration bug
/// where two setters were swapped). The original 5-setting
/// matrix shrank to 2 after the user-feedback drop of
/// `session_picker_grouped` / `load_envrc` / `use_leader`.
/// where two setters were swapped).
#[test]
fn pr13_each_setter_writes_to_its_own_mirror() {
let mut app = test_app_with_agent();
@@ -1201,9 +1199,8 @@ fn pr13_each_setter_writes_to_its_own_mirror() {
/// Three-way alignment pin: the PAGER registry default must agree
/// with `PagerLocalSnapshot::default()` (covered by
/// `defaults_match_pager_state` in `registry::tests`) AND with
/// `AgentView::new`'s runtime initializer. This is the third leg
/// of the triangle that was previously missing — the
/// registry test alone can't see `AgentView::new`'s constant.
/// `AgentView::new`'s runtime initializer — the registry test alone
/// can't see `AgentView::new`'s constant.
#[test]
fn pager_registry_default_matches_agent_view_new_initializer() {
use crate::settings::{SettingKind, SettingOwner, SettingsRegistry};
@@ -1267,9 +1264,9 @@ fn pager_registry_default_matches_agent_view_new_initializer() {
}
}
/// If the user picks the regular "Yes, proceed" option (NOT
/// enable-always-approve), the dispatcher must behave exactly as
/// before — no PersistPermissionMode effect, no YOLO flip. Pins
/// that the new code path is gated strictly on the id check.
/// enable-always-approve), the dispatcher must emit no
/// PersistPermissionMode effect and no YOLO flip: the
/// always-approve path is gated strictly on the id check.
#[test]
fn regular_allow_once_does_not_trigger_always_approve_persist() {
use std::sync::Arc;
@@ -1404,7 +1401,6 @@ fn show_tasks_no_active_agent_is_noop() {
let effects = dispatch(Action::ShowTasks, &mut app);
assert!(effects.is_empty(), "ShowTasks without an agent is a no-op");
}
/// classify_top_level decision matrix.
#[test]
fn classify_top_level_branches() {
use crate::views::dashboard::{RowState, classify_top_level};
@@ -1563,7 +1559,6 @@ fn peek_label_reflects_last_response_type() {
.push_block(RenderBlock::user_prompt("do it"));
assert_eq!(extract_last_response_type(agent), "Idle");
}
/// agent.question_view.is_some() → NeedsInput.
#[test]
fn classify_top_level_question_view_some_is_needs_input() {
use crate::views::dashboard::{RowState, classify_top_level};
@@ -1602,7 +1597,6 @@ fn top_level_label_strips_control_characters() {
);
assert!(top.label.contains("evil"));
}
/// Build a synthetic MouseEvent for tests.
fn mouse_event(
kind: crossterm::event::MouseEventKind,
col: u16,
@@ -1615,11 +1609,6 @@ fn mouse_event(
modifiers: crossterm::event::KeyModifiers::NONE,
}
}
/// Left-click on a row selects it (single click).
/// Single left-click on a row attaches the
/// conversation immediately (was: selects only, required
/// double-click to attach). The user explicitly reported the
/// previous click-to-select behaviour as unresponsive.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn mouse_left_click_attaches_immediately() {
@@ -1645,11 +1634,8 @@ fn mouse_left_click_attaches_immediately() {
}
assert_eq!(d.selected, Some(id));
}
/// Every left-click attaches, including
/// rapid repeated clicks. The previous design used a 500ms window
/// to distinguish single (select) from double (attach) click;
/// the new design makes every click attach so the user's mental
/// model "click = open" always holds.
/// There is no double-click window — every click attaches, so the
/// user's mental model "click = open" always holds.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn mouse_repeated_click_keeps_attaching() {
@@ -1682,9 +1668,8 @@ fn mouse_repeated_click_keeps_attaching() {
other => panic!("expected DashboardAttach on second click, got {other:?}"),
}
}
/// Clicks after the previous 500ms-double-click
/// window also attach (the previous test asserted single-click
/// behaviour for >500ms-apart clicks; now every click attaches).
/// Clicks more than 500ms apart also attach — there is no
/// double-click window.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn mouse_click_after_long_pause_still_attaches() {
@@ -1715,7 +1700,6 @@ fn mouse_click_after_long_pause_still_attaches() {
other => panic!("expected DashboardAttach, got {other:?}"),
}
}
/// Click on the peek close-button rect closes the peek.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn mouse_click_on_peek_close_rect_clears_peek() {
@@ -2,8 +2,6 @@
use super::*;
// ── Worktree session tests ───────────────────────────────────────
#[test]
fn open_new_worktree_dialog_sets_dialog_state() {
let mut app = test_app();
@@ -26,7 +24,6 @@ fn worktree_forked_sets_session_id_eagerly_and_emits_load() {
);
let id = AgentId(0);
// Before WorktreeForked: session_id is None, loading_replay is false.
assert!(app.agents[&id].session.session_id.is_none());
assert!(!app.agents[&id].session.loading_replay);
@@ -45,21 +42,17 @@ fn worktree_forked_sets_session_id_eagerly_and_emits_load() {
&mut app,
);
// session_id set eagerly.
assert_eq!(
app.agents[&id].session.session_id,
Some(acp::SessionId::new("forked-sess-1"))
);
// loading_replay enabled so UI suppresses redraws during replay.
assert!(app.agents[&id].session.loading_replay);
// CWD updated to worktree.
assert_eq!(app.agents[&id].session.cwd, session_cwd);
assert!(app.agents[&id].session.is_worktree);
// Emits LoadSession effect.
assert_eq!(effects.len(), 1);
assert!(matches!(&effects[0], Effect::LoadSession { session_id, .. }
if session_id == "forked-sess-1"));
// Scrollback has the "Worktree ready" message.
assert!(!app.agents[&id].scrollback.is_empty());
}
@@ -93,13 +86,11 @@ fn worktree_forked_with_restore_shows_summary_in_scrollback() {
&mut app,
);
// Should emit LoadSession.
assert_eq!(effects.len(), 1);
assert!(matches!(
&effects[0],
Effect::LoadSession { session_id, .. } if session_id == "forked-sess-2"
));
// Scrollback should contain the restore summary.
let has_restore_msg = app.agents[&id]
.scrollback
.entries_in_range(0..app.agents[&id].scrollback.len())
@@ -161,7 +152,6 @@ fn worktree_forked_with_restore_failure_shows_warning_banner() {
);
assert!(text.contains("restore aborted"));
assert!(text.contains("MERGE_HEAD present"));
// The success banner must NOT also appear.
let success_present = entries
.iter()
.any(|e| matches!(&e.block, RenderBlock::System(s) if s.text.contains("Code restored")));
@@ -412,7 +402,6 @@ fn dispatch_fork_no_flag_non_git_skips_modal_and_forks_without_worktree() {
Action::Fork(fork_args(None, Some("explore offline"))),
&mut app,
);
// Must skip the modal and emit ForkSession immediately.
assert!(
matches!(effects.as_slice(), [Effect::ForkSession { .. }]),
"non-git cwd must skip modal and emit ForkSession, got {effects:?}"
@@ -450,7 +439,6 @@ fn dispatch_fork_no_flag_always_opens_question_modal() {
}
other => panic!("expected Fork, got {other:?}"),
}
// The modal must offer four options: Yes / No / Always / Never.
assert_eq!(
qv.questions[0].options.len(),
4,
@@ -1846,7 +1846,6 @@ fn dashboard_stop_double_press_via_handle_key_closes_top_level() {
"second Ctrl+X via handle_input must close the target agent (Issue 300 regression)",
);
}
/// Top-level resolver round-trip via real AgentView.
#[test]
fn session_id_resolver_round_trip_top_level() {
use crate::views::dashboard::{DashboardRowId, PersistedRowId, SessionIdResolver};
@@ -1864,7 +1863,6 @@ fn session_id_resolver_round_trip_top_level() {
};
assert!(resolver.resolve(&absent).is_none());
}
/// Subagent resolver round-trip.
#[test]
fn session_id_resolver_round_trip_subagent() {
use crate::views::dashboard::{DashboardRowId, PersistedRowId, SessionIdResolver};
@@ -1606,7 +1606,7 @@ fn build_mode_query_arms_debounce_despite_title_hits_and_force_skips_it() {
);
}
/// Build mode: a sub-2-char query clears the content results AND
/// invalidates the previously armed debounce, so its late expiry can't
/// invalidates the debounce armed earlier, so its late expiry can't
/// resurrect the search.
#[test]
fn build_mode_short_query_clears_results_and_invalidates_armed_debounce() {
@@ -73,8 +73,6 @@ fn session_created_with_flag_but_modal_closed_clears_flag_no_fetches() {
assert!(!app.agents[&id].pending_extensions_fetch);
}
// ── /new dispatcher tests ─────────────────────────────────────────────
#[test]
fn dispatch_new_session_opens_question_modal_in_git_repo() {
let mut app = new_session_test_app();
@@ -125,8 +123,6 @@ fn dispatch_new_session_skips_modal_in_non_git_repo() {
);
}
// ── Session close (shared with dashboard) ─────────────────────────────
#[test]
fn close_inactive_agent_drops_it() {
let mut app = three_agent_app();
@@ -184,9 +184,9 @@ fn stashed_model_keeps_model_when_unsupported() {
#[test]
fn effort_max_rejected_when_model_offers_no_max() {
// Since the Xhigh/Max split, "max" is its own canonical level — a model
// whose menu has no max-valued option rejects it with the offered list
// (previously the parse alias silently rode it onto the xhigh option).
// "max" is its own canonical level, distinct from "xhigh": a model
// whose menu has no max-valued option rejects it with the offered
// list rather than falling back to the xhigh option.
let models = models_with_current(true);
let out = take_deferred_model_switch(None, &models, Some("max"));
assert_eq!(
@@ -126,10 +126,10 @@ fn toggle_vim_mode_propagates_to_open_subagent_views() {
);
}
/// `/vim-mode` must toggle vim from the DASHBOARD too (not just an
/// agent view) — previously it early-returned unless an agent was
/// active, so it was a silent no-op and the overview's j/k never
/// turned on. Turning vim ON also focuses the overview so j/k
/// navigate immediately; turning it OFF returns focus to the input.
/// agent view) — an early return without an active agent would make
/// this a silent no-op and the overview's j/k would never turn on.
/// Turning vim ON also focuses the overview so j/k navigate
/// immediately; turning it OFF returns focus to the input.
#[serial_test::serial(KIGI_AGENT_DASHBOARD)]
#[test]
fn toggle_vim_mode_works_on_dashboard_and_focuses_overview() {
@@ -776,11 +776,9 @@ fn refresh_open_settings_modals_updates_reset_confirm_settings_state() {
_ => panic!("expected ResetSettingsConfirm still active"),
}
}
/// The
/// dispatch-arm bail-out path is verified directly via
/// `#[should_panic]` in debug mode rather than the previous
/// `if cfg!(debug_assertions) { return; }` placebo which gave
/// debug-mode CI no coverage of the routing-bug bail-out.
/// The dispatch-arm bail-out path is verified directly via
/// `#[should_panic]` in debug mode, giving debug-mode CI real
/// coverage of the routing-bug bail-out.
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "OpenResetConfirm dispatched without an open Settings modal")]
@@ -42,7 +42,6 @@ fn send_while_idle_with_nonempty_shared_queue_routes_to_server() {
_ => None,
})
.unwrap_or_else(|| panic!("expected immediate SendPrompt for 'c', got {effects:?}"));
// Did NOT start a local turn or adopt "c" as the running prompt.
assert!(
!app.agents[&id].session.state.is_turn_running(),
"must not promote 'c' to a local running turn"
@@ -220,8 +219,6 @@ fn show_usage_on_welcome_screen_is_noop() {
);
}
// ── Minimal update-notice tests ──────────────────────────────────────
#[test]
fn minimal_update_notice_commits_a_system_block() {
let mut app = test_app_with_agent();
@@ -541,16 +541,12 @@ fn switch_model_complete_success_updates_model_and_pushes_message() {
&mut app,
);
// Pending flag cleared.
assert!(!app.agents[&id].session.model_switch_pending);
// Current model updated.
assert_eq!(
app.agents[&id].session.models.current,
Some(model_id.clone())
);
// Success message pushed to scrollback.
assert_eq!(app.agents[&id].scrollback.len(), initial_scrollback + 1);
// PersistPreferredModel effect emitted.
assert_eq!(effects.len(), 1);
assert!(matches!(
&effects[0],
@@ -632,7 +628,8 @@ fn switch_model_complete_persists_resolved_effort_from_catalog_meta() {
Action::TaskComplete(TaskResult::SwitchModelComplete {
agent_id: id,
model_id: model_id.clone(),
effort: None, // user typed `/model Blackbox 4.7` with no effort
// user typed `/model Blackbox 4.7` with no effort
effort: None,
result: Ok(()),
prev_model_id: None,
}),
@@ -752,9 +749,7 @@ fn switch_model_complete_failure_pushes_error_and_clears_pending() {
assert!(effects.is_empty());
// Pending flag cleared.
assert!(!app.agents[&id].session.model_switch_pending);
// Current model unchanged.
assert_eq!(app.agents[&id].session.models.current, old_current);
// Error message pushed to scrollback.
assert_eq!(app.agents[&id].scrollback.len(), initial_scrollback + 1);
}
@@ -794,16 +789,13 @@ fn switch_model_incompatible_agent_shows_question_modal() {
// No effects emitted (modal is synchronous state).
assert!(effects.is_empty());
// Pending flag cleared.
assert!(!app.agents[&id].session.model_switch_pending);
// Question modal is open.
assert!(app.agents[&id].question_view.is_some());
let qv = app.agents[&id].question_view.as_ref().unwrap();
assert!(matches!(
qv.local_kind,
Some(crate::views::question_view::LocalQuestionKind::AgentTypeMismatch { .. })
));
// No error message pushed to scrollback.
assert_eq!(app.agents[&id].scrollback.len(), initial_scrollback);
}
@@ -855,7 +847,6 @@ fn incompatible_agent_rollback_restores_previous_model() {
&mut app,
);
// models.current must be rolled back to the previous model.
assert_eq!(
app.agents[&id].session.models.current,
Some(prev_model),
@@ -901,12 +892,10 @@ fn incompatible_agent_closes_active_modal() {
&mut app,
);
// Active modal must be closed.
assert!(
app.agents[&id].active_modal.is_none(),
"active modal must be closed when IncompatibleAgent fires",
);
// Question modal must be open.
assert!(
app.agents[&id].question_view.is_some(),
"question modal must be open",
@@ -950,7 +939,6 @@ fn same_agent_type_switch_no_modal() {
// Model should be switched, no modal.
assert_eq!(app.agents[&id].session.models.current, Some(model_b));
assert!(app.agents[&id].question_view.is_none());
// Should emit PersistPreferredModel.
assert!(
effects
.iter()
@@ -965,10 +953,8 @@ fn switch_model_pending_lifecycle() {
let id = AgentId(0);
let model_id = acp::ModelId::new(std::sync::Arc::from("kigi-4.5"));
// Initially false.
assert!(!app.agents[&id].session.model_switch_pending);
// Action sets pending.
dispatch(
Action::SwitchModel {
model_id: model_id.clone(),
@@ -978,7 +964,6 @@ fn switch_model_pending_lifecycle() {
);
assert!(app.agents[&id].session.model_switch_pending);
// TaskResult clears pending.
dispatch(
Action::TaskComplete(TaskResult::SwitchModelComplete {
agent_id: id,
@@ -1012,7 +997,6 @@ fn no_deferred_switch_means_no_extra_effect() {
&mut app,
);
// No SwitchModel effect.
assert!(
!effects
.iter()
@@ -1178,8 +1162,6 @@ fn available_commands_refreshed_empty_is_noop() {
);
}
// -- Session deletion from the /resume picker -----------------------
#[test]
fn delete_session_complete_removes_only_matching_source_and_id() {
use crate::views::modal::ActiveModal;
@@ -1470,13 +1452,11 @@ fn rename_session_failed_keeps_local_display_name_and_pushes_system_block() {
&mut app,
);
// No rollback.
assert_eq!(
app.agents[&AgentId(0)].display_name.as_deref(),
Some("optimistic title"),
"display_name must NOT roll back on RenameSessionFailed"
);
// System block appended with the error.
let scrollback = &app.agents[&AgentId(0)].scrollback;
assert_eq!(
scrollback.len(),
@@ -1500,10 +1480,9 @@ fn rename_session_failed_keeps_local_display_name_and_pushes_system_block() {
fn rollback_known_key_reverts_cache_and_no_effect() {
use crate::settings::SettingValue;
let mut app = test_app_with_agent();
// Toggle to true first.
let _ = dispatch(Action::SetCompactMode(true), &mut app);
assert!(app.current_ui.compact_mode);
// Now simulate persist failure that rolls back to false.
// Simulate persist failure that rolls back to false.
let effects = dispatch(
Action::TaskComplete(TaskResult::SettingPersistFailed {
key: "compact_mode",
@@ -1516,7 +1495,6 @@ fn rollback_known_key_reverts_cache_and_no_effect() {
effects.is_empty(),
"rollback path must NOT emit any new Effects (would loop)",
);
// Cache is reverted.
assert!(!app.current_ui.compact_mode);
}
@@ -1689,8 +1667,6 @@ fn rollback_to_always_approve_blocked_by_policy_pin() {
assert!(!app.default_yolo);
}
// -- SessionListLoaded ------------------------------------------------
/// Canary: an empty list surfaces the generic "no sessions" toast.
#[test]
fn session_list_empty_shows_generic_toast() {
@@ -195,8 +195,8 @@ fn open_block_viewer_uses_markdown_viewer_for_agent_message_with_image_ref() {
assert!(effects.is_empty());
let agent = app.agents.get(&id).unwrap();
// Agent messages with image refs now open the normal markdown viewer
// (inline media rendering moved to the tool call block).
// Agent messages with image refs open the normal markdown viewer;
// inline media rendering happens in the tool call block.
assert!(agent.block_viewer.is_some());
}
@@ -230,15 +230,15 @@ fn open_block_viewer_opens_image_only_blocks_natively() {
let _guard = set_protocol_for_test(GraphicsProtocol::Kitty);
let effects = dispatch(Action::OpenBlockViewer, &mut app);
// Generated media now opens in the OS-native viewer (fire-and-forget),
// so neither the in-app block viewer nor image viewer is shown.
// Generated media opens in the OS-native viewer (fire-and-forget);
// neither the in-app block viewer nor image viewer is shown.
assert!(effects.is_empty());
let agent = app.agents.get(&id).unwrap();
assert!(agent.block_viewer.is_none());
assert!(agent.image_viewer.is_none());
}
// -- Plugins tab: group-collapse seeding on PluginsListLoaded --------------
// Plugins tab: group-collapse seeding on PluginsListLoaded
fn plugins_list_response() -> kigi_hooks_plugins_types::PluginsListResponse {
use crate::views::extensions_modal::test_plugin_info;
@@ -8,7 +8,7 @@ use super::*;
/// `Internal error: "session failed to respond"`. An `acp::Error` carries
/// no `promptId`, so before the Err-arm gate this error was misattributed
/// to the running turn and rendered as a spurious "Turn failed", detonating
/// an unrelated in-flight turn. The handler now gates the Err arm on the
/// an unrelated in-flight turn. The handler gates the Err arm on the
/// `prompt_id` the pager minted for that RPC: an error whose id is NOT the
/// running turn is discarded; the running turn is left untouched.
#[test]
@@ -1022,13 +1022,11 @@ fn bg_task_kill_failed_clears_pending_kill_on_inactive_agent() {
assert!(task.kill_requested_at.is_none());
}
/// build_rows handles many subagents (placeholder).
#[test]
fn build_rows_collapses_many_subagents() {
use crate::views::dashboard::build_rows;
let mut app = test_app_with_agent();
let agent = app.agents.get_mut(&AgentId(0)).unwrap();
// Insert 9 subagents.
for i in 0..9 {
let info = make_test_subagent(&format!("c{i}"), &format!("sa{i}"));
agent
@@ -1078,7 +1076,6 @@ fn build_rows_seven_subagents_no_placeholder() {
assert!(!rows.last().unwrap().is_more_placeholder);
}
/// At the threshold (exactly 8), no placeholder.
#[test]
fn build_rows_eight_subagents_no_placeholder() {
use crate::views::dashboard::build_rows;
@@ -1208,7 +1205,6 @@ fn subagent_label_strips_control_characters() {
"subagent label must not retain \\x1b: {:?}",
sub.label
);
// Visible characters survive.
assert!(
sub.label.contains("evil"),
"sanitised label should preserve printable characters, got {:?}",