| 1 | # RFC: File Decomposition for v0.9.0 |
| 2 | |
| 3 | **Status (2026-07-12): still valuable, needs redesign.** The original |
| 4 | `config.rs` plan was overtaken by events: `config.rs` shrank to ~7.0k lines via |
| 5 | a different module split (`crates/tui/src/config/`), and the provider-churn |
| 6 | problem it targeted was mostly solved by the Models.dev catalog + ProviderLake |
| 7 | (#4184–#4188) rather than by file moves. Meanwhile the render/CLI monoliths |
| 8 | grew — `ui.rs` 9.4k → 13.6k, `main.rs` 8.0k → 12.1k — so any revival of this |
| 9 | RFC should re-scope around `ui.rs` and `main.rs`. The migration method |
| 10 | (§"Migration strategy": git mv + re-exports, no functional changes) remains |
| 11 | the right approach. |
| 12 | |
| 13 | ## Problem |
| 14 | |
| 15 | Six files exceed 5,000 lines. The worst offenders accumulate provider-specific |
| 16 | logic, test code, and UI rendering in single translation units. This makes |
| 17 | provider additions touch 15+ files and makes code review fragile. |
| 18 | |
| 19 | ### Current state (lines, v0.8.6x era — see status note for v0.8.68 numbers) |
| 20 | |
| 21 | | File | Lines | Contents | |
| 22 | |------|-------|----------| |
| 23 | | `crates/tui/src/config.rs` | 10,046 | Provider resolution, env handling, model aliases, capability matrix, 2,000+ lines of tests | |
| 24 | | `crates/tui/src/tui/ui.rs` | 9,400 | TUI render loop, input handling, command dispatch, /logout clearing | |
| 25 | | `crates/tui/src/tui/ui/tests.rs` | 8,360 | Tests for ui.rs | |
| 26 | | `crates/tui/src/main.rs` | 7,998 | CLI arg parsing, mode selection, startup | |
| 27 | | `crates/tui/src/tui/app.rs` | 7,256 | Application state struct and lifecycle | |
| 28 | | `crates/tui/src/runtime_threads.rs` | 5,454 | Async runtime orchestration | |
| 29 | |
| 30 | ## Proposed decomposition |
| 31 | |
| 32 | ### 1. `config.rs` → provider module tree |
| 33 | |
| 34 | Split `crates/tui/src/config.rs` into: |
| 35 | |
| 36 | ``` |
| 37 | crates/tui/src/config/ |
| 38 | ├── mod.rs # Re-exports, Config struct, load/save |
| 39 | ├── provider.rs # ApiProvider enum, parse/as_str/display_name/all |
| 40 | ├── capability.rs # ProviderCapability, provider_capability() |
| 41 | ├── model_resolution.rs # wire_model_for_provider, normalize_model_name_for_provider |
| 42 | ├── env.rs # EnvGuard, env var precedence, per-provider env handling |
| 43 | ├── constants.rs # All DEFAULT_*_MODEL and DEFAULT_*_BASE_URL constants |
| 44 | └── tests/ # Test module |
| 45 | ├── mod.rs |
| 46 | ├── provider.rs |
| 47 | ├── capability.rs |
| 48 | ├── model_resolution.rs |
| 49 | └── env.rs |
| 50 | ``` |
| 51 | |
| 52 | **Why:** Every new provider currently requires edits to ~20 match arms scattered |
| 53 | across one 10K-line file. With constants in their own module and resolution |
| 54 | logic isolated, adding a provider becomes: add constants, add enum variant, add |
| 55 | one match arm per function. The drift check script can validate each sub-module |
| 56 | independently. |
| 57 | |
| 58 | ### 2. `ui.rs` → view modules |
| 59 | |
| 60 | Split `crates/tui/src/tui/ui.rs` into: |
| 61 | |
| 62 | ``` |
| 63 | crates/tui/src/tui/ |
| 64 | ├── ui.rs # Core render loop, frame dispatch (keep under 2,000 lines) |
| 65 | ├── input.rs # Keyboard/mouse input handling |
| 66 | ├── command_dispatch.rs # /command routing, /logout, /config |
| 67 | └── status_bar.rs # Status bar rendering |
| 68 | ``` |
| 69 | |
| 70 | **Why:** The /logout clearing logic, command dispatch, and render loop are |
| 71 | independent concerns. `ui.rs` currently has a 6,200-line function body for |
| 72 | `execute_command_input` that mixes input parsing, command routing, and state |
| 73 | mutation. |
| 74 | |
| 75 | ### 3. `main.rs` → CLI module |
| 76 | |
| 77 | Split `crates/tui/src/main.rs` into: |
| 78 | |
| 79 | ``` |
| 80 | crates/tui/src/cli/ |
| 81 | ├── mod.rs # Cli struct, arg parsing |
| 82 | ├── args.rs # Argument definitions |
| 83 | └── startup.rs # Mode selection, config loading, resume logic |
| 84 | ``` |
| 85 | |
| 86 | **Why:** `main.rs` at 8K lines suggests the CLI definition has outgrown a |
| 87 | single file. Separating arg definitions from startup logic makes the entry |
| 88 | point readable. |
| 89 | |
| 90 | ### 4. Provider additions should be data-driven |
| 91 | |
| 92 | The current provider pattern requires touching: |
| 93 | - `config.rs`: 20+ match arms |
| 94 | - `cli/src/lib.rs`: 4+ match arms |
| 95 | - `agent/src/lib.rs`: static registry |
| 96 | - `tui/provider_picker.rs`: picker list |
| 97 | - `docs/PROVIDERS.md`: registry table |
| 98 | - `config.example.toml`: example section |
| 99 | - `README.md`: env vars table |
| 100 | - `scripts/check-provider-registry.py`: drift check |
| 101 | |
| 102 | A data-driven approach would define each provider as a struct with its |
| 103 | constants, env vars, capability metadata, and display name — then derive the |
| 104 | match arms from the data. This is a larger refactor but would reduce provider |
| 105 | additions to a single file change. |
| 106 | |
| 107 | ## Priority |
| 108 | |
| 109 | 1. **config.rs decomposition** — highest impact, most provider churn happens here |
| 110 | 2. **ui.rs decomposition** — second highest, /logout and command dispatch are independent |
| 111 | 3. **Data-driven providers** — aspirational for v0.9.0, requires trait design |
| 112 | |
| 113 | ## Migration strategy |
| 114 | |
| 115 | Each decomposition should be a standalone PR that: |
| 116 | 1. Creates the new module tree |
| 117 | 2. Moves code with `git mv` (preserves history) |
| 118 | 3. Adds `pub use` re-exports in the old file location (zero API change) |
| 119 | 4. Runs the full test suite |
| 120 | 5. Removes the re-exports in a follow-up PR once consumers are updated |
| 121 | |
| 122 | No functional changes in decomposition PRs. Keep them boring. |
| 123 |