Files
1jehuang--jcode/docs/TUISTATE_TRAIT_DECOMPOSITION.md
wehub-resource-sync a789495a98
CI / Quality Guardrails (push) Waiting to run
CI / Build & Test (macos-latest) (push) Waiting to run
CI / Build & Test (ubuntu-latest) (push) Waiting to run
CI / Build & Test (windows-latest) (push) Waiting to run
CI / Format (push) Waiting to run
CI / PowerShell Syntax (push) Waiting to run
CI / Windows Cross-Target Check (Linux) (push) Waiting to run
FreeBSD Smoke / FreeBSD Smoke (x86_64) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:34 +08:00

6.4 KiB

TuiState Trait Decomposition Plan

Status: Analysis + proposed plan

This document audits the TuiState trait (crates/jcode-tui/src/tui/mod.rs) and proposes a safe, incremental decomposition. It is the Phase 1.5 follow-on to the App god-object decomposition (see CLIENT_CORE_PRESENTATION_SPLIT_PLAN.md).

Current state

  • pub trait TuiState exposes 114 methods.
  • Implementors: 2 (App in tui/app/tui_state.rs, and TestState in tui/ui_tests/mod.rs).
  • Consumers: ~95 usages across 29 files, almost all as &dyn TuiState (50 render-function signatures take app: &dyn TuiState).

It is the presentation-layer counterpart to the App god-object: a single wide interface that couples every render module to the entire client surface.

Why a naive sub-trait split has limited value

Two structural facts constrain the refactor:

  1. App implements the whole surface regardless. Splitting TuiState into TuiTranscriptState + TuiInputState + ... does not reduce what App must implement, and (because the trait is presentation-only data access) it does not change crate-level compile coupling. The win is intent/navigability, not decoupling of App.

  2. &dyn TuiState does not compose. Render functions take trait objects. Rust has no stable &dyn (A + B), so any consumer that needs methods from more than one domain must take a supertrait that re-aggregates them. The two central renderers (ui.rs, ui_viewport.rs) use methods from nearly every domain, so they would keep the full supertrait bound.

Measured: of the ~28 &dyn TuiState render modules, only 2 are multi-category (ui.rs, ui_viewport.rs); the other ~26 each use a single domain. So a sub-trait split does narrow the declared surface for the majority of render modules, but the headline god-interface (driven by the 2 central renderers) stays wide via the supertrait.

Conclusion: the split is worthwhile for readability and for narrowing leaf render-module bounds, but it is not a compile-coupling win and should be done incrementally to avoid a high-conflict big-bang across 29 files.

Proposed target shape

trait TuiState:
    TuiTranscriptState + TuiInputState + TuiScrollState + TuiStreamStatusState
    + TuiProviderState + TuiSessionServerState + TuiWorkspaceState
    + TuiDiagramPaneState + TuiDiffPaneState + TuiSidePanelState
    + TuiInlineState + TuiOverlayState + TuiCopySelectionState
    + TuiOnboardingState + TuiMiscState
{}

App and TestState keep a single impl per sub-trait (mechanical move). The 2 central renderers take &dyn TuiState (the supertrait). Each leaf render module narrows to the one sub-trait it needs.

Method categorization (all 114)

TuiTranscriptState

display_messages, display_user_message_count, compacted_hidden_user_prompts, has_display_edit_tool_messages, side_pane_images, display_messages_version, render_streaming_markdown

TuiInputState

input, cursor_pos, queued_messages, interleave_message, pending_soft_interrupts, has_stashed_input, command_suggestions, command_suggestion_selected, suggestion_prompts, queue_mode, next_prompt_new_session_armed, dictation_key_label

TuiScrollState

scroll_offset, auto_scroll_paused, chat_overscroll_active, copy_selection_edge_autoscroll_active, chat_native_scrollbar, has_pending_mouse_scroll_animation

TuiStreamStatusState

streaming_text, is_processing, streaming_tokens, streaming_cache_tokens, output_tps, streaming_tool_calls, elapsed, status, active_skill, subagent_status, batch_progress, time_since_activity, stream_message_ended, status_notice, status_detail, rate_limit_remaining, animation_elapsed

TuiProviderState

provider_name, provider_model, upstream_provider, connection_type, mcp_servers, available_skills, auth_status, update_cost, total_session_tokens, session_compaction_count, context_info, context_snapshot, context_limit, cache_ttl_status

TuiSessionServerState

is_remote_mode, is_canary, is_replay, current_session_id, session_display_name, server_display_name, server_display_icon, server_sessions, connected_clients, remote_startup_phase_active, client_update_available, server_update_available, info_widget_data, active_experimental_feature_notice

TuiWorkspaceState

workspace_mode_enabled, workspace_map_rows, workspace_animation_tick

TuiDiagramPaneState

diagram_mode, diagram_focus, diagram_index, diagram_scroll, diagram_pane_ratio, diagram_pane_ratio_user_adjusted, diagram_pane_animating, diagram_pane_enabled, diagram_pane_position, diagram_zoom

TuiDiffPaneState

diff_mode, diff_pane_scroll, diff_pane_scroll_x, diff_pane_focus, diff_line_wrap

TuiSidePanelState

side_panel, side_panel_image_zoom_percent, side_panel_native_scrollbar, pin_images, pinned_images_auto_hide_remaining_secs

TuiInlineState

inline_interactive_state, inline_view_state, inline_ui_state

TuiOverlayState

changelog_scroll, help_scroll, model_status_overlay, session_picker_overlay, login_picker_overlay, account_picker_overlay, usage_overlay

TuiCopySelectionState

copy_badge_ui, copy_selection_mode, copy_selection_range, copy_selection_status

TuiOnboardingState

onboarding_preview_mode, onboarding_welcome_active, onboarding_welcome_kind

TuiMiscState

working_dir, now_millis, has_notification, centered_mode

Incremental, low-conflict migration

Do not split all 15 sub-traits at once across 29 files. Recommended order:

  1. Land the documented section headers in the trait definition (done; pure comments, single file). Gives the categorization a canonical home.
  2. Extract one leaf sub-trait with a single-file consumer as a proof of pattern (e.g. TuiCopySelectionState or TuiDiagramPaneState). Verify with cargo check -p jcode-tui.
  3. Extract remaining leaf sub-traits one per commit, narrowing the corresponding leaf render module's bound in the same commit.
  4. Keep ui.rs and ui_viewport.rs on the TuiState supertrait throughout.

Each step is behavior-preserving (data accessors only) and compiles independently, so it can be merged between other agents' work without a big-bang conflict.

Verification

  • cargo check -p jcode-tui after each sub-trait extraction (TMPDIR must point at real disk, not the RAM-backed tmpfs, or ring/aws-lc-sys build scripts fail with "Disk quota exceeded").
  • cargo test -p jcode-tui --lib once at the end. Note: the lib test suite has pre-existing flaky parallel-order failures unrelated to this trait (verify any failing test in isolation with --test-threads=1).