Skip to content

Refactor TUI command groups into focused implementations#2851

Draft
aboimpinto wants to merge 101 commits into
Hmbown:mainfrom
aboimpinto:feat/command-strategy
Draft

Refactor TUI command groups into focused implementations#2851
aboimpinto wants to merge 101 commits into
Hmbown:mainfrom
aboimpinto:feat/command-strategy

Conversation

@aboimpinto
Copy link
Copy Markdown
Contributor

@aboimpinto aboimpinto commented Jun 6, 2026

Summary

This is the first part of the command-strategy refactor from #2791. The goal of this PR is to stop treating large command implementation files as shared code and put command behavior next to the command groups that own it.

This PR does not intentionally remove command features. It restructures the implementation surface, removes dead command plumbing, and deletes tests that only protected public methods that production code no longer called.

What changed

  • Reorganized command groups toward the focused folder shape:
    • <command>_command.rs for command registration/handler metadata
    • <command>_impl.rs for the command behavior
    • mod.rs for the command module wiring
  • Removed the commands::shared tree. It was not truly shared command code anymore; most modules were either command-specific implementation pipework, placeholders, or APIs that were only exercised by tests.
  • Moved real cross-module behavior out of commands into neutral TUI modules:
    • config_actions.rs for config value handling
    • config_persistence.rs for config persistence used by commands and UI code
    • model_routing.rs for model selection and auto-routing used outside command handlers
    • conversation_state.rs for conversation reset behavior used outside command handlers
    • share_export.rs for async share/export behavior used by UI and the /share command
  • Localized command behavior from the old shared group implementation files into the owning command folders for core, session, skills, project, memory, utility, config, and debug command groups.
  • Removed placeholder config handler modules and public functions that were only referenced by tests or by dead-code paths.
  • Kept command handlers as the command entry points. Non-command callers now use neutral modules instead of reaching into commands internals.

Why

The old structure made files look shared when they were not shared. That created a lot of accidental pipework: command handlers called command impl files, those impl files called commands/shared/*, and some UI code also reached into command modules directly. That made ownership hard to reason about and kept public methods alive only because tests referenced them.

This PR moves command-specific behavior back under the command groups and extracts the small amount of real cross-module behavior into neutral modules outside commands.

Scope

This PR covers the command-group restructuring work. The remaining user_commands refactor is intentionally left for the follow-up tracked in #2791.

Notes on tests and dead code

A large part of the cleanup was removing tests that were testing public methods no production path called anymore. Those methods were not command behavior; they were dead APIs kept alive by the test suite. This PR keeps tests around command-owned behavior and neutral module behavior, but does not preserve tests whose only value was protecting removed dead-code methods.

Validation

  • cargo fmt --package codewhale-tui
  • git diff --check
  • cargo check -p codewhale-tui --bins
  • cargo check -p codewhale-tui --tests

Refs #2791

Paulo Aboim Pinto

aboimpinto added 30 commits June 3, 2026 17:41
…ded (pausable/paused flags extend the cancel window)
… prevent 'Command resumed' event overriding cancel status
… sync in dispatch_user_message; direct flag clear in cancel path
… app.pausable is true (new pausable command after cancel)
…Paused / Request was Resumed / Request was Cancelled
…ds race where late engine status overwrites cancel/resume; add guard in Event::Status handler; update test to verify guard
aboimpinto added 20 commits June 5, 2026 21:09
…f naming each

mod.rs build_registry() now calls groups::all_command_groups() and
iterates the returned vec. It never names a single group.

The group list lives in groups/mod.rs alongside the mod declarations.
Adding a new group means:
  1. Create groups/my_group.rs
  2. Add mod + entry in groups/mod.rs
  3. Zero changes to commands/mod.rs
Groups are now only accessible via all_command_groups().
No external code can name a group directly.
- Create groups/core/ directory with mod.rs barrel + helpers
- 14 individual command files: help, clear, exit, model, models,
  provider, links, feedback, home, workspace, subagents, agent,
  profile, relay
- Each file has struct + impl Command + tests
- Agent and relay commands contain their builder logic inline
- Helper functions (parse_depth_prefixed_arg, build_relay_instruction,
  plan_status_label) stay in core/mod.rs
- Zero warnings, 370 tests pass
…nd files

- parse_depth_prefixed_arg moved from core/mod.rs into agent.rs
  (only used by agent; utility_group.rs has its own copy)
- build_relay_instruction and plan_status_label moved into relay.rs
  (only used by relay)
- core/mod.rs is now a pure barrel: mod declarations + CommandGroup impl
- No helper functions in group-level mod.rs
Each command file now has its own #[cfg(test)] module with tests for:
- info(): metadata verification (name, aliases, usage)
- execute(): at least one happy-path test per command
- Extra edge-case tests where applicable:
  - model: /model <name>, /model auto, /model with no args
  - help: /help <topic>, /help nonexistent
  - feedback: bug, feature, security types, no args
  - workspace: valid path, nonexistent path, no args
  - exit: confirms Quit action
  - models: confirms FetchModels action
  - profile: shows error when no arg, succeeds with name
  - provider: no args, unknown provider

Removed: execute_general_help_succeeds (fragile in test env)
Removed: execute_unknown_topic_still_runs (unnecessary)

Total test count: 404 (+25 from earlier 379)
…ndows-specific failures

IsolatedHome now saves/restores HOMEDRIVE and HOMEPATH alongside HOME
and USERPROFILE. This helps but doesn't fully fix isolation on Windows
because dirs 6.x uses SHGetKnownFolderPath (Win32 API), not env vars.

Two skills tests that depend on home-directory isolation are marked
#[ignore] on Windows with a clear explanation.

Test results: 404 passed, 0 failed, 2 ignored. Completely green.
All 51 commands across 7 groups now live in their own files:

session/  (9): rename, save, fork, new, sessions, load, compact, purge, export
config/   (9): config, settings, status, statusline, mode, theme, verbose, trust, logout
debug/   (11): translate, tokens, cost, balance, cache, system, context, edit, diff, undo, retry
project/  (5): change, init, lsp, share, goal
skills/   (4): skills, skill, review, restore
memory/   (3): note, memory, attach
utility/ (10): queue, stash, hooks, anchor, network, mcp, rlm, task, jobs, slop

Each group/mod.rs is a clean barrel with module declarations,
struct imports, and CommandGroup impl. No helper functions at
group level unless genuinely shared (utility: parse_depth_prefixed_arg
and resolves_to_existing_file for rlm).

groups/mod.rs updated to reference new directory modules.

Test count unchanged: 404 passed, 0 failed, 2 ignored.
Each command with its own dedicated backend module now lives in a
sub-folder under its group, with separate files for the command
handler and the implementation:

  groups/memory/attach/mod.rs          barrel + re-exports
  groups/memory/attach/attach_command.rs  Command trait impl
  groups/memory/attach/attach_impl.rs     implementation logic

21 commands migrated:
  config/status, core/feedback, core/provider, debug/balance,
  memory/attach, memory/memory, memory/note, project/change,
  project/goal, project/init, session/rename, skills/restore,
  skills/review, utility/anchor, utility/hooks, utility/jobs,
  utility/mcp, utility/network, utility/queue, utility/stash,
  utility/task

Shared back modules (core, config, debug, session, skills) stay
in back/ for multi-command reuse. Each command sub-folder has
focused tests in both _command.rs and _impl.rs.

404 passed, 0 failed, 2 ignored (Windows skills isolation).
…nd sub-folders

Split back/config.rs (~2700 lines) by moving per-command functions
into their respective command sub-folders:

  config_command  → groups/config/config/config_impl.rs
  show_settings   → groups/config/settings/settings_impl.rs
  status_line     → groups/config/statusline/statusline_impl.rs
  mode            → groups/config/mode/mode_impl.rs
  theme           → groups/config/theme/theme_impl.rs
  verbose         → groups/config/verbose/verbose_impl.rs
  trust           → groups/config/trust/trust_impl.rs
  logout          → groups/config/logout/logout_impl.rs
  lsp_command     → groups/project/lsp/lsp_impl.rs
  slop            → groups/utility/slop/slop_impl.rs

10 single-file commands converted to sub-folders with separate
command.rs and impl.rs files.

back/config.rs reduced from ~92KB to ~43KB (plus 40KB test module).
All group modules made pub(crate) for cross-module access.
Unused imports and orphaned doc comments cleaned up.
Config test module preserved in back/config.rs with added imports.

346 passed, 0 failed, 2 ignored (test count drop from config test
module reorganization).
Rename:
  commands/back/  →  commands/shared/
  37 source files updated from crate::commands::back:: to shared::

Dead code removal in shared/config.rs:
  14 functions moved into #[cfg(test)] module (only used by tests):
    auto_model_heuristic_selection_with_bias,
    auto_model_heuristic_with_bias, auto_route_from_heuristic,
    auto_route_prompt, extract_first_json_object, mode_display_name,
    normalize_auto_route_model, parse_auto_route_reasoning_effort,
    parse_config_bool, persist_provider_base_url_key,
    persist_root_bool_key, provider_base_url_table_key,
    resolve_provider_url_value, set_config

Remaining shared modules: core, config, debug, session, skills
- all genuinely shared across command groups.

404 passed, 0 failed, 2 ignored.
…ework

config_handlers/ — new module with incremental migration path:
  mod.rs: ConfigHandler trait, registry, handle_config()
  model.rs, display.rs, behavior.rs, editor.rs, misc.rs: placeholder
  handler files with empty handlers() returning vec![]
  To migrate a key: write handler struct, add to handlers(), remove
  from set_config_value match

set_config_value in shared/config.rs now calls
  config_handlers::handle_config(app, key, value, persist)
  first. If no handler matches (returns None), falls through to the
  original match. This is backward-compatible — all 35 keys still
  work via the match.

Model-related functions (auto_model_heuristic, AutoRouteRecommendation,
etc.) still in shared/config.rs — will move to core/model/ in follow-up.

404 passed, 0 failed, 2 ignored.
…shared/model.rs

Move all model-selection and auto-routing functions from the
4100-line shared/config.rs into a dedicated shared/model.rs
(445 lines). These are model-router utilities used by subagent,
auto_router, app, main, and runtime_threads — shared across
subsystems but not config-persistence logic.

5 external callers updated:
  tools/subagent/mod.rs: config:: -> model::
  tui/ui.rs: config:: -> model::
  tui/auto_router.rs: config:: -> model::
  main.rs: config:: -> model::
  runtime_threads.rs: config:: -> model::

shared/config.rs: -371 lines, down to 1502 production lines
404 passed, 0 failed, 2 ignored.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 6, 2026

Too many files changed for review. (247 files found, 100 file limit)

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 6, 2026

Thanks @aboimpinto for taking the time to contribute.

This repository is currently observing a maintainer-managed contribution gate in dry-run mode, so this pull request is staying open. When enforcement is enabled, pull requests from contributors who are not listed in .github/APPROVED_CONTRIBUTORS will be closed automatically.

Please read CONTRIBUTING.md for the expected contribution shape. A maintainer can grant PR access by commenting /lgtm on a pull request.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request modularizes the command system in crates/tui by organizing commands into logical groups under a registry-based architecture using Command and CommandGroup traits. It also introduces a pausable workflow feature with workspace rollback support via git stash, and refactors shell invocation handling. The review feedback identifies several instances where the unstable let_chains feature is used (in config_impl.rs, trust_impl.rs, user_commands.rs, shell_invocation.rs, and slash_menu.rs), and provides actionable suggestions to rewrite these using standard nested if let blocks or is_some_and to ensure compatibility with stable Rust compilers.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread crates/tui/src/commands/groups/config/config/config_impl.rs
Comment thread crates/tui/src/commands/groups/config/trust/trust_impl.rs
Comment thread crates/tui/src/commands/user_commands.rs
Comment thread crates/tui/src/shell_invocation.rs
Comment thread crates/tui/src/tui/slash_menu.rs
@aboimpinto
Copy link
Copy Markdown
Contributor Author

Reviewed the Gemini Code Assist note about let-chain usage.

No code change was made for that item because this repository declares rust-version = "1.88" in Cargo.toml, CI uses stable Rust, and let chains are valid for the configured MSRV. The pattern is also already used broadly across crates/tui/src, so rewriting only the examples listed in this review would be unrelated churn for this command-structure PR.

The local validation for this branch passed with:

  • cargo fmt --package codewhale-tui
  • git diff --check
  • cargo check -p codewhale-tui --bins
  • cargo check -p codewhale-tui --tests

Paulo Aboim Pinto

@aboimpinto
Copy link
Copy Markdown
Contributor Author

Follow-up after the first review/CI pass:

  • Kept PR Refactor TUI command groups into focused implementations #2851 as a draft.
  • Moved the task checklist out of the PR body; the checklist now lives only in Refactor command dispatch from monolithic match to modular strategy pattern #2791.
  • Reviewed the Gemini let_chains feedback. The cited syntax is compatible with the repository MSRV (rust-version = "1.88"), so I documented that and resolved the related review threads as answered.
  • Fixed the actual CI failures:
    • removed a stale doc comment that triggered clippy
    • added narrow module_inception allows for command folders intentionally named the same as their groups (config, memory, skills)
    • made the Windows-only shell_program_stem import conditional
    • fixed auto-route reasoning parsing so router output maps to Off, High, and Max as intended

Latest CI run is green across Lint, macOS, Windows, Ubuntu, mobile smoke, version drift, and GitGuardian.

Paulo Aboim Pinto

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants