agentmux_srv\reducer/
block.rs1use agentmux_common::ipc::{ErrorCode, Event};
5
6use crate::state::State;
7
8
9use crate::state::BlockRecord;
10
11pub(super) fn handle_create_block(
19 state: &mut State,
20 tab_id: String,
21 meta: serde_json::Value,
22) -> Vec<Event> {
23 if !state.tabs.contains_key(&tab_id) {
24 let v = state.bump_version();
25 return vec![Event::Error {
26 code: ErrorCode::InvalidCommand,
27 message: format!("CreateBlock: tab not found: {}", tab_id),
28 fatal: false,
29 version: v,
30 }];
31 }
32 let block_id = uuid::Uuid::new_v4().to_string();
33 state.blocks.insert(
34 block_id.clone(),
35 BlockRecord {
36 block_id: block_id.clone(),
37 tab_id: tab_id.clone(),
38 },
39 );
40 let tab = state.tabs.get_mut(&tab_id).expect("checked");
41 tab.block_ids.push(block_id.clone());
42 let v = state.bump_version();
43 vec![Event::BlockCreated {
44 tab_id,
45 block_id,
46 meta,
47 version: v,
48 }]
49}
50
51pub(super) fn handle_delete_block(state: &mut State, tab_id: String, block_id: String) -> Vec<Event> {
54 let Some(tab) = state.tabs.get_mut(&tab_id) else {
55 return Vec::new();
56 };
57 let Some(pos) = tab.block_ids.iter().position(|b| b == &block_id) else {
58 return Vec::new();
59 };
60 tab.block_ids.remove(pos);
61 state.blocks.remove(&block_id);
62 let v = state.bump_version();
63 vec![Event::BlockDeleted {
64 tab_id,
65 block_id,
66 version: v,
67 }]
68}
69
70pub(super) fn handle_move_block(
78 state: &mut State,
79 block_id: String,
80 src_tab_id: String,
81 dst_tab_id: String,
82 dst_index: u32,
83) -> Vec<Event> {
84 let validation_error: Option<String> = {
85 if !state.tabs.contains_key(&src_tab_id) {
86 Some(format!("MoveBlock: src tab not found: {}", src_tab_id))
87 } else if !state.tabs.contains_key(&dst_tab_id) {
88 Some(format!("MoveBlock: dst tab not found: {}", dst_tab_id))
89 } else {
90 match state.blocks.get(&block_id) {
91 None => Some(format!("MoveBlock: block not found: {}", block_id)),
92 Some(block) if block.tab_id != src_tab_id => Some(format!(
93 "MoveBlock: block {} belongs to tab {}, not {}",
94 block_id, block.tab_id, src_tab_id
95 )),
96 _ => None,
97 }
98 }
99 };
100 if let Some(message) = validation_error {
101 let v = state.bump_version();
102 return vec![Event::Error {
103 code: ErrorCode::InvalidCommand,
104 message,
105 fatal: false,
106 version: v,
107 }];
108 }
109
110 let final_dst_index: u32 = if src_tab_id == dst_tab_id {
115 let tab = state.tabs.get_mut(&src_tab_id).expect("checked");
116 tab.block_ids.retain(|id| id != &block_id);
117 let clamped = (dst_index as usize).min(tab.block_ids.len());
118 tab.block_ids.insert(clamped, block_id.clone());
119 clamped as u32
120 } else {
121 state
123 .tabs
124 .get_mut(&src_tab_id)
125 .expect("checked")
126 .block_ids
127 .retain(|id| id != &block_id);
128 let dst = state.tabs.get_mut(&dst_tab_id).expect("checked");
130 let clamped = (dst_index as usize).min(dst.block_ids.len());
131 dst.block_ids.insert(clamped, block_id.clone());
132 state.blocks.get_mut(&block_id).expect("checked").tab_id = dst_tab_id.clone();
134 clamped as u32
135 };
136
137 let v = state.bump_version();
138 vec![Event::BlockMoved {
139 block_id,
140 src_tab_id,
141 dst_tab_id,
142 dst_index: final_dst_index,
143 version: v,
144 }]
145}
146
147pub(super) fn handle_update_block_meta(
149 state: &mut State,
150 block_id: String,
151 meta_patch: serde_json::Value,
152) -> Vec<Event> {
153 if !state.blocks.contains_key(&block_id) {
154 let v = state.bump_version();
155 return vec![Event::Error {
156 code: ErrorCode::InvalidCommand,
157 message: format!("UpdateBlockMeta: block not found: {}", block_id),
158 fatal: false,
159 version: v,
160 }];
161 }
162 let v = state.bump_version();
163 vec![Event::BlockMetaUpdated {
164 block_id,
165 meta_patch,
166 version: v,
167 }]
168}