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 TuiStateexposes 114 methods.- Implementors: 2 (
Appintui/app/tui_state.rs, andTestStateintui/ui_tests/mod.rs). - Consumers: ~95 usages across 29 files, almost all as
&dyn TuiState(50 render-function signatures takeapp: &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:
-
Appimplements the whole surface regardless. SplittingTuiStateintoTuiTranscriptState + TuiInputState + ...does not reduce whatAppmust 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 ofApp. -
&dyn TuiStatedoes 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:
- Land the documented section headers in the trait definition (done; pure comments, single file). Gives the categorization a canonical home.
- Extract one leaf sub-trait with a single-file consumer as a proof of pattern
(e.g.
TuiCopySelectionStateorTuiDiagramPaneState). Verify withcargo check -p jcode-tui. - Extract remaining leaf sub-traits one per commit, narrowing the corresponding leaf render module's bound in the same commit.
- Keep
ui.rsandui_viewport.rson theTuiStatesupertrait 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-tuiafter 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 --libonce 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).