Skip to content

Commit 047124a

Browse files
committed
Add comprehensive refactoring analysis for agent-browser codebase
Identifies 24 refactoring points across 4 priority levels, covering architecture issues (god objects, missing abstractions, error handling), file-level duplication (500+ repeated patterns), and cross-cutting concerns. https://claude.ai/code/session_01ULpFE5rXUwL8fbk4NoifP8
1 parent f806b66 commit 047124a

File tree

1 file changed

+330
-0
lines changed

1 file changed

+330
-0
lines changed

REFACTORING_ANALYSIS.md

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
# Refactoring Analysis Report
2+
3+
> Automated analysis of `agent-browser` codebase identifying key refactoring opportunities.
4+
5+
---
6+
7+
## Executive Summary
8+
9+
The `agent-browser` CLI is a well-engineered Rust project (~37K LOC) with solid fundamentals. However, rapid feature growth has introduced technical debt in several areas. This analysis identifies **4 critical**, **8 high-priority**, and **12 medium-priority** refactoring points across the codebase.
10+
11+
**Top 3 impact areas:**
12+
1. **God module decomposition**`actions.rs` (7,389 LOC) and `commands.rs` (4,174 LOC) handle too many responsibilities
13+
2. **Error handling system** — Pervasive `Result<T, String>` prevents error categorization and context chaining
14+
3. **Code duplication** — 500+ instances of repeated patterns (browser checks, parameter extraction, selector parsing)
15+
16+
---
17+
18+
## 1. Architecture-Level Issues
19+
20+
### 1.1 God Object: `DaemonState` (CRITICAL)
21+
22+
**File:** `cli/src/native/actions.rs:158-199`
23+
24+
`DaemonState` contains 28 fields spanning every aspect of the daemon — browser management, event handling, network interception, recording/tracing, policy/auth, and stream server integration. Nearly every module receives `&mut DaemonState`, creating implicit coupling across all subsystems.
25+
26+
**Impact:** Any modification risks cascading failures. No separation between transport layer (CDP/WebDriver), domain logic (navigation, interaction), and cross-cutting concerns (policy, tracing, recording).
27+
28+
**Recommendation:** Decompose into focused manager structs:
29+
```
30+
DaemonState
31+
├── BrowserContext (browser, pages, sessions)
32+
├── NetworkManager (routes, fetch handler, origin headers, domain filter)
33+
├── RecordingManager (recording state, tracing state, HAR entries)
34+
├── PolicyManager (policy, confirmations)
35+
└── StreamContext (stream client, stream server)
36+
```
37+
38+
### 1.2 No Error Type System (CRITICAL)
39+
40+
**Affected:** All 35+ modules
41+
42+
Every module uses `Result<T, String>` with no error categorization. Transient network errors look identical to config errors. Error context is lost during propagation.
43+
44+
**Specific problems:**
45+
- `connection.rs:492-508` — Brittle string matching (`error.contains("os error 35")`) for error classification
46+
- No context chain — when errors propagate, intermediate information is lost
47+
- Inconsistent messages — some use "Failed to...", others expose raw technical details
48+
- `actions.rs:405` — Malformed CDP events silently discarded with `.ok()`
49+
50+
**Recommendation:** Introduce a proper error enum:
51+
```rust
52+
enum AgentBrowserError {
53+
ConnectionError { source: io::Error, context: String },
54+
CdpError { code: i64, message: String },
55+
ValidationError { field: String, reason: String },
56+
BrowserNotLaunched,
57+
TimeoutError { operation: String, duration: Duration },
58+
// ...
59+
}
60+
```
61+
62+
### 1.3 Missing Trait Abstractions (HIGH)
63+
64+
Only one trait exists in the codebase (`BrowserBackend` in `webdriver/backend.rs`), yet the architecture has several natural polymorphism boundaries:
65+
66+
| Missing Trait | Benefit | Affected Modules |
67+
|--------------|---------|-----------------|
68+
| `DomAccessor` | Abstracts element queries from CDP specifics | element.rs, interaction.rs, snapshot.rs |
69+
| `EventSubscriber` | Unified event pub/sub lifecycle | recording.rs, tracing.rs, network.rs, stream.rs |
70+
| `LaunchStrategy` | Eliminates engine-specific branching | browser.rs (Chrome, Lightpanda, WebDriver) |
71+
| `CommandRouter` | Replaces 1,271-line match statement | actions.rs |
72+
73+
### 1.4 Flag Propagation Ceremony (MEDIUM)
74+
75+
Adding a new CLI flag requires changes in 5 files:
76+
1. `flags.rs` — Parsing logic
77+
2. `connection.rs::DaemonOptions` — Struct field
78+
3. `connection.rs::apply_daemon_env()` — Env var mapping
79+
4. `native::daemon.rs` — Environment reading
80+
5. `native::actions.rs` — Runtime behavior
81+
82+
**Recommendation:** Single source of truth config struct with derive macros for CLI parsing and env var binding.
83+
84+
---
85+
86+
## 2. File-Level Refactoring Points
87+
88+
### 2.1 `actions.rs` — 7,389 LOC (CRITICAL)
89+
90+
#### 2.1.1 Repeated Browser Manager Check (~120 occurrences)
91+
92+
```rust
93+
let mgr = state.browser.as_ref().ok_or("Browser not launched")?;
94+
```
95+
96+
This exact pattern appears 120+ times throughout handler functions.
97+
98+
**Fix:** Extract helper:
99+
```rust
100+
fn get_browser(state: &DaemonState) -> Result<&BrowserManager, String> {
101+
state.browser.as_ref().ok_or_else(|| "Browser not launched".to_string())
102+
}
103+
```
104+
105+
#### 2.1.2 Repeated Parameter Extraction (~400+ occurrences)
106+
107+
```rust
108+
let selector = cmd.get("selector").and_then(|v| v.as_str()).ok_or("Missing 'selector'")?;
109+
```
110+
111+
**Fix:** Extract typed getters:
112+
```rust
113+
fn get_str_param<'a>(cmd: &'a Value, key: &str) -> Result<&'a str, String> {
114+
cmd.get(key).and_then(|v| v.as_str())
115+
.ok_or_else(|| format!("Missing '{}' parameter", key))
116+
}
117+
118+
fn get_bool_param(cmd: &Value, key: &str) -> bool {
119+
cmd.get(key).and_then(|v| v.as_bool()).unwrap_or(false)
120+
}
121+
```
122+
123+
#### 2.1.3 Massive Functions
124+
125+
| Function | Lines | LOC | Issue |
126+
|----------|-------|-----|-------|
127+
| `parse_key_chord` | 2107-4380 | 2,273 | Should be split into key category handlers |
128+
| `browser_metadata_from_version` | 5508-6448 | 940 | Deep nesting, should be table-driven |
129+
| `build_role_selector` | 4381-5203 | 822 | Complex role mapping, extract to data structure |
130+
| `error_response` | 6735-7389 | 654 | Mixes response builders with test code |
131+
| `launch_options_from_env` | 1175-1621 | 446 | Repetitive env var parsing |
132+
| `open_url_in_browser` | 1647-2106 | 459 | Platform-specific branching |
133+
134+
#### 2.1.4 Repeated Env Var Parsing
135+
136+
Lines 1176-1212 show repetitive patterns:
137+
```rust
138+
.map(|v| v == "1" || v == "true").unwrap_or(false) // 10+ times
139+
v.split([',', '\n']).map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect() // 5+ times
140+
```
141+
142+
**Fix:** Helper functions `parse_bool_env()` and `parse_csv_env()`.
143+
144+
#### 2.1.5 Similar Handler Functions
145+
146+
Mouse event handlers (`handle_mousemove`, `handle_mousedown`, `handle_mouseup`) and check/uncheck handlers share nearly identical boilerplate. Could use a macro or generic handler.
147+
148+
### 2.2 `commands.rs` — 4,174 LOC (HIGH)
149+
150+
#### 2.2.1 Monolithic `parse_command` Function (1,271 lines)
151+
152+
Lines 74-1344 contain a single match statement handling 40+ commands. While helper functions exist (`parse_get`, `parse_set`, `parse_auth`), the main function remains enormous.
153+
154+
**Recommendation:** Command registry pattern — each command implements a `Command` trait with `parse()` and `name()` methods.
155+
156+
#### 2.2.2 Selector-Based Command Duplication
157+
158+
Commands `dblclick`, `hover`, `focus`, `check`, `uncheck` (lines 139-202) all follow:
159+
```rust
160+
let sel = rest.first().ok_or_else(|| ParseError::MissingArguments { ... })?;
161+
Ok(json!({ "id": id, "action": "command", "selector": sel }))
162+
```
163+
164+
**Fix:** `simple_selector_action(rest, id, action, usage)` helper.
165+
166+
#### 2.2.3 Flag-to-Value Extraction Pattern (10+ times)
167+
168+
```rust
169+
let filter_idx = rest.iter().position(|&s| s == "--filter");
170+
let filter = filter_idx.and_then(|i| rest.get(i + 1).copied());
171+
```
172+
173+
**Fix:** `extract_flag_value(rest, "--filter")` helper.
174+
175+
#### 2.2.4 Duplicate Diff Parsing (~250 lines)
176+
177+
Lines 1349-1603: Snapshot diff and screenshot diff parse nearly identical flag combinations (`--baseline`, `--selector`, `--output`, `--depth`). Should be consolidated into shared diff option parser.
178+
179+
#### 2.2.5 Identical Start/Stop Subcommand Patterns
180+
181+
Trace, profiler, and record commands (lines 1018-1142) have identical structures:
182+
```rust
183+
const VALID: &[&str] = &["start", "stop"];
184+
match sub { "start" => {...}, "stop" => {...}, _ => error }
185+
```
186+
187+
**Fix:** Generic `start_stop_command(name, rest, id)` helper.
188+
189+
#### 2.2.6 Dialog Accept/Dismiss Duplication
190+
191+
Lines 992-1004: Only difference is the `"response"` field value. Should be a single parameterized arm.
192+
193+
### 2.3 `browser.rs` — 1,733 LOC (HIGH)
194+
195+
#### 2.3.1 Target Create + Attach Duplication (3 locations)
196+
197+
Identical CDP call sequence at lines 357-378, 675-696, and 746-767:
198+
```rust
199+
let result = self.client.send_command_typed("Target.createTarget", ...).await?;
200+
let attach = self.client.send_command_typed("Target.attachToTarget", ...).await?;
201+
```
202+
203+
**Fix:** Extract `create_and_attach_target(&self) -> Result<(String, String), String>`.
204+
205+
#### 2.3.2 PageInfo Construction Duplication (4 locations)
206+
207+
Lines 380-386, 403-409, 698-704, 772-778 all construct identical `PageInfo` structs.
208+
209+
**Fix:** `PageInfo::new_blank(target_id, session_id)` constructor.
210+
211+
#### 2.3.3 `launch()` Method — 113 Lines
212+
213+
Lines 199-312 mix engine validation, browser launching, manager initialization, and feature configuration. Extract `configure_browser_features()` for HTTPS errors, user agent, color scheme, and downloads configuration.
214+
215+
#### 2.3.4 Validation Logic Overlap
216+
217+
`validate_launch_options()` (lines 20-58) and `validate_lightpanda_options()` (lines 61-88) share duplicate checks for extensions, profile, and storage_state incompatibilities.
218+
219+
#### 2.3.5 Magic Values
220+
221+
| Value | Lines | Meaning |
222+
|-------|-------|---------|
223+
| `25_000` | 259 | Default timeout (ms) |
224+
| `10_000` | 323 | Connection timeout (ms) |
225+
| `600` | 1143 | Network poll timeout (ms) |
226+
| `500` | 1190 | Idle detection threshold (ms) |
227+
| `"about:blank"` | 362, 680, 701, 744 | Default page URL |
228+
| `"page"` | 99, 144, 385, 408, 703 | Target type constant |
229+
230+
All should be named constants.
231+
232+
### 2.4 `output.rs` — Very Large (MEDIUM)
233+
234+
Extremely large file handling all output formatting and help text. Should be split into:
235+
- `output/format.rs` — Response formatting logic
236+
- `output/help.rs` — Help text content (which is mostly static data)
237+
238+
### 2.5 `connection.rs` (MEDIUM)
239+
240+
#### 2.5.1 Brittle Error Detection
241+
242+
Lines 492-508: Transient error detection via string matching:
243+
```rust
244+
fn is_transient_error(error: &str) -> bool {
245+
error.contains("os error 35") // EAGAIN macOS
246+
|| error.contains("os error 11") // EAGAIN Linux
247+
// ... 11 more patterns
248+
}
249+
```
250+
251+
This breaks if error message formats change. Should use `io::ErrorKind` matching.
252+
253+
---
254+
255+
## 3. Cross-Cutting Patterns
256+
257+
### 3.1 JSON Path Repetition (~100+ occurrences)
258+
259+
Deep JSON access chains scattered everywhere:
260+
```rust
261+
tree_result.get("frameTree").and_then(|t| t.get("frame")).and_then(|f| f.get("id"))...
262+
```
263+
264+
**Recommendation:** Consider a `json_path!()` macro or typed CDP response structs.
265+
266+
### 3.2 Inconsistent Color Usage
267+
268+
`color.rs` provides a clean API, but some modules use it while others use raw strings or skip coloring entirely.
269+
270+
### 3.3 Scattered Validation
271+
272+
Validation logic is spread across 5+ files (`validation.rs`, `browser.rs`, `policy.rs`, `flags.rs`, `commands.rs`) with inconsistent error types and return conventions.
273+
274+
---
275+
276+
## 4. Priority Matrix
277+
278+
### Critical (address first)
279+
| # | Issue | Files | Est. Reduction |
280+
|---|-------|-------|---------------|
281+
| 1 | Introduce error type enum | All modules | Better debugging, no LOC reduction |
282+
| 2 | Decompose `DaemonState` god object | actions.rs | Reduces coupling across 35+ modules |
283+
| 3 | Extract parameter/browser helpers in actions.rs | actions.rs | ~500+ duplicate lines eliminated |
284+
| 4 | Break down 2,273-line `parse_key_chord` | actions.rs | Better maintainability |
285+
286+
### High Priority
287+
| # | Issue | Files | Est. Reduction |
288+
|---|-------|-------|---------------|
289+
| 5 | Consolidate selector commands in commands.rs | commands.rs | ~100 lines |
290+
| 6 | Unify diff option parsing | commands.rs | ~250 lines |
291+
| 7 | Extract target create+attach helper | browser.rs | ~60 lines |
292+
| 8 | Extract flag parsing helper in commands.rs | commands.rs | ~100 lines |
293+
| 9 | Generic start/stop command handler | commands.rs | ~80 lines |
294+
| 10 | Extract `configure_browser_features()` | browser.rs | Better separation |
295+
| 11 | Split output.rs into format + help | output.rs | Better organization |
296+
| 12 | Define trait abstractions (DomAccessor, EventSubscriber) | native/ | Decoupling |
297+
298+
### Medium Priority
299+
| # | Issue | Files | Est. Reduction |
300+
|---|-------|-------|---------------|
301+
| 13 | Env var parsing helpers | actions.rs | ~50 lines |
302+
| 14 | Constants for magic values | browser.rs, actions.rs | Better readability |
303+
| 15 | Consolidate validation patterns | 5+ files | Consistency |
304+
| 16 | Typed CDP response structs | native/ | Type safety |
305+
| 17 | JSON path macro or helper | multiple | ~200 lines |
306+
| 18 | PageInfo constructor | browser.rs | ~40 lines |
307+
| 19 | Dialog accept/dismiss consolidation | commands.rs | ~15 lines |
308+
| 20 | Config struct with derive macros | flags.rs, connection.rs | Eliminates flag ceremony |
309+
| 21 | Color usage consistency | multiple | UX consistency |
310+
| 22 | Unified storage manager trait | state.rs, cookies.rs, storage.rs | Consistency |
311+
| 23 | Mouse event handler macro | actions.rs | ~60 lines |
312+
| 24 | Connection error kind matching | connection.rs | Robustness |
313+
314+
---
315+
316+
## 5. Estimated Impact
317+
318+
| Metric | Current | After Refactoring |
319+
|--------|---------|-------------------|
320+
| `actions.rs` LOC | 7,389 | ~5,500 (-25%) |
321+
| `commands.rs` LOC | 4,174 | ~3,300 (-21%) |
322+
| `browser.rs` LOC | 1,733 | ~1,500 (-13%) |
323+
| Duplicate patterns | 500+ instances | ~100 instances |
324+
| Error types | 1 (`String`) | 8-10 categorized variants |
325+
| Trait abstractions | 1 | 5-6 |
326+
| Max function length | 2,273 lines | <200 lines |
327+
328+
---
329+
330+
*Analysis generated for `agent-browser` v0.22.0*

0 commit comments

Comments
 (0)