1use sim_kernel::{Expr, Symbol};
4use sim_lib_scene::{data_map, node, sym};
5use sim_value::build::uint;
6
7pub const CHANGE_CAPSULE_LENS: &str = "view:agent-change-capsule";
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct ChangeCapsuleViewState {
13 pub id: Symbol,
15 pub diffs: Vec<CapsuleDiff>,
17 pub logs: Vec<CapsuleLog>,
19 pub generated_docs: Vec<GeneratedDocsSummary>,
21 pub pin_plan: Vec<PinPlanView>,
23 pub fairness: CapsuleFairnessFacet,
25 pub replay: CapsuleReplaySummary,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct CapsuleDiff {
32 pub repo: String,
34 pub path: String,
36 pub summary: String,
38}
39
40#[derive(Clone, Debug, PartialEq, Eq)]
42pub struct CapsuleLog {
43 pub kind: String,
45 pub label: String,
47 pub outcome: String,
49 pub log_path: String,
51}
52
53#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct GeneratedDocsSummary {
56 pub repo: String,
58 pub path: String,
60 pub generator: String,
62 pub regenerated: bool,
64}
65
66#[derive(Clone, Debug, PartialEq, Eq)]
68pub struct PinPlanView {
69 pub repo: String,
71 pub current_commit: String,
73 pub new_commit: String,
75 pub pushed_commit_exists: bool,
77}
78
79#[derive(Clone, Debug, PartialEq, Eq)]
81pub struct CapsuleFairnessFacet {
82 pub label: String,
84 pub evidence: String,
86 pub confidence: String,
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
92pub struct CapsuleReplaySummary {
93 pub content_hash: String,
95 pub replay_content_hash: String,
97 pub events: Vec<CapsuleReplayEvent>,
99}
100
101#[derive(Clone, Debug, PartialEq, Eq)]
103pub struct CapsuleReplayEvent {
104 pub sequence: u64,
106 pub kind: String,
108 pub summary: String,
110}
111
112pub fn change_capsule_view(state: &ChangeCapsuleViewState) -> Expr {
114 node(
115 "stack",
116 vec![
117 ("role", sym("change-capsule")),
118 ("capsule", Expr::Symbol(state.id.clone())),
119 ("dir", sym("column")),
120 (
121 "children",
122 Expr::List(vec![
123 capsule_summary(state),
124 diff_panel(&state.diffs),
125 logs_panel(&state.logs),
126 generated_docs_panel(&state.generated_docs),
127 pin_plan_panel(&state.pin_plan),
128 fairness_panel(&state.fairness),
129 replay_panel(&state.replay),
130 ]),
131 ),
132 ],
133 )
134}
135
136pub fn change_capsule_replay_frames(state: &ChangeCapsuleViewState) -> Vec<Expr> {
138 (0..=state.replay.events.len())
139 .map(|count| {
140 let mut frame = state.clone();
141 frame.replay.events.truncate(count);
142 change_capsule_view(&frame)
143 })
144 .collect()
145}
146
147pub fn fake_change_capsule_state() -> ChangeCapsuleViewState {
149 ChangeCapsuleViewState {
150 id: Symbol::qualified("atelier/capsule", "fixture"),
151 diffs: vec![
152 CapsuleDiff {
153 repo: "sim-agent-net".to_owned(),
154 path: "crates/sim-lib-agent/src/atelier/capsule.rs".to_owned(),
155 summary: "agent-side capsule review model".to_owned(),
156 },
157 CapsuleDiff {
158 repo: "sim-tooling".to_owned(),
159 path: "src/atelier/capsule.rs".to_owned(),
160 summary: "generated capsule cache".to_owned(),
161 },
162 ],
163 logs: vec![
164 CapsuleLog {
165 kind: "validation".to_owned(),
166 label: "agent-capsule-tests".to_owned(),
167 outcome: "passed".to_owned(),
168 log_path: ".sim/atelier/logs/agent-capsule-tests.log".to_owned(),
169 },
170 CapsuleLog {
171 kind: "docs".to_owned(),
172 label: "simdoc-check".to_owned(),
173 outcome: "passed".to_owned(),
174 log_path: ".sim/atelier/logs/simdoc-check.log".to_owned(),
175 },
176 ],
177 generated_docs: vec![GeneratedDocsSummary {
178 repo: "repo-docs".to_owned(),
179 path: "docs/site/repos.md".to_owned(),
180 generator: "simctl site".to_owned(),
181 regenerated: true,
182 }],
183 pin_plan: vec![PinPlanView {
184 repo: "sim-agent-net".to_owned(),
185 current_commit: "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_owned(),
186 new_commit: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_owned(),
187 pushed_commit_exists: true,
188 }],
189 fairness: CapsuleFairnessFacet {
190 label: "F6 trade-off".to_owned(),
191 evidence: "diff, validation, docs, pin, replay, risk, and rollback evidence".to_owned(),
192 confidence: "0.92".to_owned(),
193 },
194 replay: CapsuleReplaySummary {
195 content_hash: "fnv1a64:1111111111111111".to_owned(),
196 replay_content_hash: "fnv1a64:1111111111111111".to_owned(),
197 events: vec![
198 CapsuleReplayEvent {
199 sequence: 0,
200 kind: "edit".to_owned(),
201 summary: "Patch capsule model".to_owned(),
202 },
203 CapsuleReplayEvent {
204 sequence: 1,
205 kind: "validate".to_owned(),
206 summary: "Validation passed".to_owned(),
207 },
208 CapsuleReplayEvent {
209 sequence: 2,
210 kind: "pin".to_owned(),
211 summary: "Pushed commit exists before pin".to_owned(),
212 },
213 ],
214 },
215 }
216}
217
218fn capsule_summary(state: &ChangeCapsuleViewState) -> Expr {
219 node(
220 "box",
221 vec![
222 ("role", sym("capsule-summary")),
223 (
224 "children",
225 Expr::List(vec![
226 text(format!("diffs: {}", state.diffs.len())),
227 text(format!("logs: {}", state.logs.len())),
228 text(format!("pin updates: {}", state.pin_plan.len())),
229 text(format!("replay hash: {}", state.replay.replay_content_hash)),
230 ]),
231 ),
232 ],
233 )
234}
235
236fn diff_panel(diffs: &[CapsuleDiff]) -> Expr {
237 panel(
238 "capsule-diff",
239 diffs
240 .iter()
241 .map(|diff| format!("{}:{} {}", diff.repo, diff.path, diff.summary)),
242 )
243}
244
245fn logs_panel(logs: &[CapsuleLog]) -> Expr {
246 panel(
247 "capsule-logs",
248 logs.iter().map(|log| {
249 format!(
250 "{} {} {} {}",
251 log.kind, log.label, log.outcome, log.log_path
252 )
253 }),
254 )
255}
256
257fn generated_docs_panel(docs: &[GeneratedDocsSummary]) -> Expr {
258 panel(
259 "generated-docs",
260 docs.iter().map(|doc| {
261 format!(
262 "{}:{} {} regenerated={}",
263 doc.repo, doc.path, doc.generator, doc.regenerated
264 )
265 }),
266 )
267}
268
269fn pin_plan_panel(pins: &[PinPlanView]) -> Expr {
270 let rows = pins
271 .iter()
272 .map(|pin| {
273 data_map(vec![
274 ("repo", Expr::String(pin.repo.clone())),
275 ("current", Expr::String(pin.current_commit.clone())),
276 ("new", Expr::String(pin.new_commit.clone())),
277 ("pushed", Expr::Bool(pin.pushed_commit_exists)),
278 ])
279 })
280 .collect();
281 node(
282 "box",
283 vec![
284 ("role", sym("pin-plan")),
285 (
286 "children",
287 Expr::List(vec![node("table", vec![("rows", Expr::List(rows))])]),
288 ),
289 ],
290 )
291}
292
293fn fairness_panel(facet: &CapsuleFairnessFacet) -> Expr {
294 node(
295 "box",
296 vec![
297 ("role", sym("fairness-facet")),
298 (
299 "children",
300 Expr::List(vec![
301 text(facet.label.clone()),
302 text(facet.evidence.clone()),
303 text(format!("confidence: {}", facet.confidence)),
304 ]),
305 ),
306 ],
307 )
308}
309
310fn replay_panel(replay: &CapsuleReplaySummary) -> Expr {
311 let events = replay
312 .events
313 .iter()
314 .map(|event| {
315 data_map(vec![
316 ("at", uint(event.sequence)),
317 (
318 "event",
319 Expr::Symbol(Symbol::qualified("ide/event", event.kind.as_str())),
320 ),
321 ("label", Expr::String(event.summary.clone())),
322 ])
323 })
324 .collect();
325 node(
326 "box",
327 vec![
328 ("role", sym("replay-cassette")),
329 (
330 "children",
331 Expr::List(vec![
332 text(format!("content hash: {}", replay.content_hash)),
333 text(format!("replay hash: {}", replay.replay_content_hash)),
334 node(
335 "timeline",
336 vec![
337 ("lane", sym("dev-cassette")),
338 ("events", Expr::List(events)),
339 ],
340 ),
341 node(
342 "slider",
343 vec![
344 ("target", sym("replay-cassette")),
345 ("value", uint(replay.events.len() as u64)),
346 ("max", uint(replay.events.len() as u64)),
347 ],
348 ),
349 ]),
350 ),
351 ],
352 )
353}
354
355fn panel(role: &str, rows: impl Iterator<Item = String>) -> Expr {
356 node(
357 "box",
358 vec![
359 ("role", sym(role)),
360 ("children", Expr::List(rows.map(text).collect())),
361 ],
362 )
363}
364
365fn text(value: String) -> Expr {
366 node("text", vec![("text", Expr::String(value))])
367}