Skip to content

Commit f34bda3

Browse files
committed
♻️ Refactor Studio into modular submodule architecture
Split monolithic app.rs, reducer.rs, state.rs, and modals.rs files into focused submodules for improved maintainability and code organization. Key structural changes: - app.rs → app/mod.rs + app/agent_tasks.rs Extracts all agent task spawning (chat, review, PR, changelog, etc.) into a dedicated module - reducer.rs → reducer/mod.rs + reducer/agent.rs + reducer/modal.rs + reducer/navigation.rs Separates concerns: agent event handling, modal lifecycle, and navigation/scroll logic into cohesive units - state.rs → state/mod.rs + state/chat.rs + state/modes.rs Isolates chat state management and mode-specific state structs (ExploreState, CommitState, ReviewState, etc.) - modals.rs → modals/mod.rs + individual modal renderers Creates dedicated modules for each modal type: chat_modal, confirm, emoji_selector, help, instructions, preset_selector, ref_selector, search, and settings No functional changes - pure code organization refactor. Each new module maintains the same public API through re-exports in the parent mod.rs.
1 parent 625d5f9 commit f34bda3

File tree

20 files changed

+2949
-1989
lines changed

20 files changed

+2949
-1989
lines changed

src/studio/app/agent_tasks.rs

Lines changed: 808 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 4 additions & 758 deletions
Large diffs are not rendered by default.

src/studio/reducer/agent.rs

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
//! Agent-related reducer functions
2+
//!
3+
//! Handles: `AgentStarted`, `AgentProgress`, `AgentComplete`, `AgentError`, `StreamingChunk`, `StreamingComplete`
4+
5+
use super::super::events::{AgentResult, ContentType, SideEffect, StudioEvent, TaskType};
6+
use super::super::history::{ChatRole, ContentData, History};
7+
use super::super::state::{Mode, Notification, StudioState};
8+
9+
/// Reduce agent-related events
10+
pub fn reduce(
11+
state: &mut StudioState,
12+
event: StudioEvent,
13+
history: &mut History,
14+
) -> Vec<SideEffect> {
15+
let effects = Vec::new();
16+
17+
match event {
18+
StudioEvent::AgentStarted { task_type } => {
19+
state.set_iris_thinking(format!("Working on {}...", task_type));
20+
history.record_agent_start(task_type);
21+
}
22+
23+
StudioEvent::AgentProgress {
24+
task_type: _,
25+
tool_name,
26+
message,
27+
} => {
28+
state.set_iris_thinking(format!("{}: {}", tool_name, message));
29+
}
30+
31+
StudioEvent::AgentComplete { task_type, result } => {
32+
reduce_agent_complete(state, history, task_type, result);
33+
state.mark_dirty();
34+
}
35+
36+
StudioEvent::AgentError { task_type, error } => {
37+
reduce_agent_error(state, history, task_type, error);
38+
}
39+
40+
StudioEvent::StreamingChunk {
41+
task_type,
42+
chunk: _,
43+
aggregated,
44+
} => {
45+
reduce_streaming_chunk(state, task_type, aggregated);
46+
state.mark_dirty();
47+
}
48+
49+
StudioEvent::StreamingComplete { task_type } => {
50+
reduce_streaming_complete(state, task_type);
51+
state.mark_dirty();
52+
}
53+
54+
_ => {}
55+
}
56+
57+
effects
58+
}
59+
60+
fn reduce_agent_complete(
61+
state: &mut StudioState,
62+
history: &mut History,
63+
task_type: TaskType,
64+
result: AgentResult,
65+
) {
66+
state.set_iris_idle();
67+
history.record_agent_complete(task_type, true);
68+
69+
match result {
70+
AgentResult::CommitMessages(messages) => {
71+
state.modes.commit.messages.clone_from(&messages);
72+
state.modes.commit.current_index = 0;
73+
state.modes.commit.generating = false;
74+
state
75+
.modes
76+
.commit
77+
.message_editor
78+
.set_messages(messages.clone());
79+
80+
if let Some(msg) = messages.first() {
81+
history.record_content(
82+
Mode::Commit,
83+
ContentType::CommitMessage,
84+
&ContentData::Commit(msg.clone()),
85+
super::super::events::EventSource::Agent,
86+
"generation_complete",
87+
);
88+
}
89+
}
90+
91+
AgentResult::ReviewContent(content) => {
92+
state.modes.review.review_content.clone_from(&content);
93+
state.modes.review.generating = false;
94+
95+
history.record_content(
96+
Mode::Review,
97+
ContentType::CodeReview,
98+
&ContentData::Markdown(content),
99+
super::super::events::EventSource::Agent,
100+
"generation_complete",
101+
);
102+
}
103+
104+
AgentResult::PRContent(content) => {
105+
state.modes.pr.pr_content.clone_from(&content);
106+
state.modes.pr.generating = false;
107+
108+
history.record_content(
109+
Mode::PR,
110+
ContentType::PRDescription,
111+
&ContentData::Markdown(content),
112+
super::super::events::EventSource::Agent,
113+
"generation_complete",
114+
);
115+
}
116+
117+
AgentResult::ChangelogContent(content) => {
118+
state.modes.changelog.changelog_content.clone_from(&content);
119+
state.modes.changelog.generating = false;
120+
121+
history.record_content(
122+
Mode::Changelog,
123+
ContentType::Changelog,
124+
&ContentData::Markdown(content),
125+
super::super::events::EventSource::Agent,
126+
"generation_complete",
127+
);
128+
}
129+
130+
AgentResult::ReleaseNotesContent(content) => {
131+
state
132+
.modes
133+
.release_notes
134+
.release_notes_content
135+
.clone_from(&content);
136+
state.modes.release_notes.generating = false;
137+
138+
history.record_content(
139+
Mode::ReleaseNotes,
140+
ContentType::ReleaseNotes,
141+
&ContentData::Markdown(content),
142+
super::super::events::EventSource::Agent,
143+
"generation_complete",
144+
);
145+
}
146+
147+
AgentResult::ChatResponse(response) => {
148+
history.add_chat_message(ChatRole::Iris, &response);
149+
state.chat_state.add_iris_response(&response);
150+
}
151+
152+
AgentResult::SemanticBlame(result) => {
153+
state.modes.explore.semantic_blame = Some(result);
154+
state.modes.explore.blame_loading = false;
155+
state.notify(Notification::success("Blame analysis complete"));
156+
}
157+
}
158+
}
159+
160+
fn reduce_agent_error(
161+
state: &mut StudioState,
162+
history: &mut History,
163+
task_type: TaskType,
164+
error: String,
165+
) {
166+
state.set_iris_error(&error);
167+
history.record_agent_complete(task_type.clone(), false);
168+
169+
match task_type {
170+
TaskType::Commit => state.modes.commit.generating = false,
171+
TaskType::Review => state.modes.review.generating = false,
172+
TaskType::PR => state.modes.pr.generating = false,
173+
TaskType::Changelog => state.modes.changelog.generating = false,
174+
TaskType::ReleaseNotes => state.modes.release_notes.generating = false,
175+
TaskType::Chat => {
176+
state.chat_state.is_responding = false;
177+
}
178+
TaskType::SemanticBlame => {
179+
state.modes.explore.blame_loading = false;
180+
}
181+
}
182+
183+
state.notify(Notification::error(format!(
184+
"{} failed: {}",
185+
task_type, error
186+
)));
187+
}
188+
189+
fn reduce_streaming_chunk(state: &mut StudioState, task_type: TaskType, aggregated: String) {
190+
match task_type {
191+
TaskType::Review => {
192+
state.modes.review.streaming_content = Some(aggregated);
193+
}
194+
TaskType::PR => {
195+
state.modes.pr.streaming_content = Some(aggregated);
196+
}
197+
TaskType::Changelog => {
198+
state.modes.changelog.streaming_content = Some(aggregated);
199+
}
200+
TaskType::ReleaseNotes => {
201+
state.modes.release_notes.streaming_content = Some(aggregated);
202+
}
203+
TaskType::Chat => {
204+
state.chat_state.streaming_response = Some(aggregated);
205+
}
206+
TaskType::SemanticBlame => {
207+
state.modes.explore.streaming_blame = Some(aggregated);
208+
}
209+
TaskType::Commit => {}
210+
}
211+
}
212+
213+
fn reduce_streaming_complete(state: &mut StudioState, task_type: TaskType) {
214+
match task_type {
215+
TaskType::Review => {
216+
state.modes.review.streaming_content = None;
217+
}
218+
TaskType::PR => {
219+
state.modes.pr.streaming_content = None;
220+
}
221+
TaskType::Changelog => {
222+
state.modes.changelog.streaming_content = None;
223+
}
224+
TaskType::ReleaseNotes => {
225+
state.modes.release_notes.streaming_content = None;
226+
}
227+
TaskType::Chat => {
228+
state.chat_state.streaming_response = None;
229+
if let Some(tool) = state.chat_state.current_tool.take() {
230+
state.chat_state.add_tool_to_history(tool);
231+
}
232+
}
233+
TaskType::SemanticBlame => {
234+
state.modes.explore.streaming_blame = None;
235+
}
236+
TaskType::Commit => {}
237+
}
238+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
//!
88
//! Side effects are returned for the app to execute after state update.
99
10+
mod agent;
11+
mod modal;
12+
mod navigation;
13+
1014
use crossterm::event::MouseEventKind;
1115

1216
use super::events::{

0 commit comments

Comments
 (0)