Skip to content

RFC: Extract DisplayState from IndicatifRenderer for testability #220

Description

@evansenter

Summary

Extract a DisplayState abstraction from IndicatifRenderer to separate state tracking from terminal rendering, enabling unit testing of display logic.

Problem

Currently, IndicatifRenderer mixes:

  1. State tracking - subagent depth, current phase, tool execution status
  2. Terminal rendering - indicatif progress bars, spinners, colored output

This makes unit testing difficult since we can't test state transitions without mocking terminal output.

Proposed Solution

Create a DisplayState struct that tracks:

  • Current subagent nesting depth
  • Active phase (decomposition, execution, synthesis)
  • Tool execution status
  • Token usage

The Renderer trait would consume DisplayState for actual rendering.

/// Terminal-agnostic state machine for display logic
pub struct DisplayState {
    pub subagent_depth: usize,
    pub current_phase: Option<String>,
    pub active_tools: Vec<ToolStatus>,
    pub token_usage: Option<TokenUsage>,
}

impl DisplayState {
    pub fn on_event(&mut self, update: &AgentUpdate) -> StateChange {
        match update.event_type.as_str() {
            "subagent_started" => {
                self.subagent_depth += 1;
                StateChange::SubagentEntered(...)
            }
            "subagent_completed" => {
                self.subagent_depth = self.subagent_depth.saturating_sub(1);
                StateChange::SubagentExited(...)
            }
            // ...
        }
    }
}

Benefits

  1. Testable state logic - Can test DisplayState::on_event() without terminal
  2. Swappable renderers - Same state, different backends (indicatif, ratatui, plain)
  3. Clearer separation - State machine vs presentation

Test Cases to Enable

#[test]
fn test_subagent_depth_tracking() {
    let mut state = DisplayState::new();
    state.on_event(&subagent_started_event("deep_research"));
    assert_eq!(state.subagent_depth, 1);
    
    state.on_event(&subagent_completed_event("deep_research"));
    assert_eq!(state.subagent_depth, 0);
}

#[test]
fn test_nested_subagents() {
    let mut state = DisplayState::new();
    state.on_event(&subagent_started_event("developer"));
    state.on_event(&subagent_started_event("deep_research"));
    assert_eq!(state.subagent_depth, 2);
}

Scope

  • Extract state tracking from gemicro-cli/src/display/indicatif.rs
  • Create gemicro-cli/src/display/state.rs
  • Update IndicatifRenderer to use DisplayState
  • Add unit tests for state transitions

Context

Identified during PR #219 (Phase 2 - Subagent Access) when evaluating test coverage gaps. The subagent rendering logic is currently untestable.


🤖 Generated with Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions