diff --git a/crates/op-editor-core/src/agent_indicators.rs b/crates/op-editor-core/src/agent_indicators.rs index 85cf24e89..e069e23aa 100644 --- a/crates/op-editor-core/src/agent_indicators.rs +++ b/crates/op-editor-core/src/agent_indicators.rs @@ -17,19 +17,20 @@ use std::collections::{HashMap, HashSet}; use std::sync::{LazyLock, Mutex}; /// Duration of the short generated-node entrance animation. -pub const REVEAL_DURATION_MS: u64 = 1_360; +pub const REVEAL_DURATION_MS: u64 = 1_520; /// Delay between the first generated nodes in one applied batch. -pub const REVEAL_STAGGER_MS: u64 = 24; -/// Extra delay for nested generated nodes. Keeps child content trailing -/// its parent without making the stream feel sluggish. -pub const REVEAL_DEPTH_STAGGER_MS: u64 = 6; +pub const REVEAL_STAGGER_MS: u64 = 18; +/// Extra delay for nested generated nodes. Visual traversal order +/// already places children after parents; keeping this at zero avoids +/// depth changes compressing adjacent stream slots into the same frame. +pub const REVEAL_DEPTH_STAGGER_MS: u64 = 0; /// Parent reveals suppress child transforms only during their opening /// beat. Once the parent has begun settling, delayed children animate /// independently so streamed content does not pop in abruptly. -pub const REVEAL_CHILD_SUPPRESS_FRACTION: f32 = 0.11; -const REVEAL_FULL_STAGGER_SIBLINGS: u64 = 24; -const REVEAL_MID_STAGGER_SIBLINGS: u64 = 48; -const REVEAL_COMPRESSED_STAGGER_MS: u64 = 20; +pub const REVEAL_CHILD_SUPPRESS_FRACTION: f32 = 0.04; +const REVEAL_FULL_STAGGER_SIBLINGS: u64 = 36; +const REVEAL_MID_STAGGER_SIBLINGS: u64 = 72; +const REVEAL_COMPRESSED_STAGGER_MS: u64 = REVEAL_FRAME_MS; const REVEAL_TAIL_STAGGER_MS: u64 = REVEAL_FRAME_MS; const CLOCK_REBASE_THRESHOLD_MS: u64 = 60_000; const REVEAL_FRAME_MS: u64 = 16; @@ -384,7 +385,11 @@ mod tests { assert!(is_active(), "reveal should keep the paint loop active"); assert_eq!(snapshot_at(1_250).reveals.get("n7"), Some(&1_000)); assert!( - snapshot_at(2_400).reveals.is_empty(), + snapshot_at(2_400).reveals.get("n7").is_some(), + "reveal should stay active long enough to read as a smooth entrance" + ); + assert!( + snapshot_at(2_600).reveals.is_empty(), "expired reveal should be pruned" ); assert!(!is_active(), "expired reveal should not keep animating"); @@ -416,8 +421,8 @@ mod tests { let snap = snapshot_at(1_040); - assert_eq!(snap.reveals.get("external-a"), Some(&1_104)); - assert_eq!(snap.reveals.get("external-b"), Some(&1_128)); + assert_eq!(snap.reveals.get("external-a"), Some(&1_098)); + assert_eq!(snap.reveals.get("external-b"), Some(&1_116)); clear(); } @@ -440,6 +445,10 @@ mod tests { offsets[1] - offsets[0] <= 32, "the first visible nodes should start quickly" ); + assert!( + offsets[20] - offsets[0] <= 380, + "the first screenful should not wait behind a slow reveal queue" + ); assert!( offsets .windows(2) diff --git a/crates/op-editor-ui/src/widgets/canvas_agent_overlay.rs b/crates/op-editor-ui/src/widgets/canvas_agent_overlay.rs index 4a6ba921f..035aa9d0f 100644 --- a/crates/op-editor-ui/src/widgets/canvas_agent_overlay.rs +++ b/crates/op-editor-ui/src/widgets/canvas_agent_overlay.rs @@ -390,7 +390,7 @@ mod tests { &roots, Point2D::new(100.0, 50.0), 2.0, - 2_400, + 2_600, ); assert!(expired_backend.round_fills.is_empty()); assert!(expired_backend.round_strokes.is_empty()); @@ -437,7 +437,7 @@ mod tests { } #[test] - fn parent_reveal_suppresses_nested_child_sweep() { + fn opening_parent_reveal_suppresses_nested_child_sweep() { let _guard = crate::agent_indicator_test_support::lock(); let epoch = op_editor_core::agent_indicators::begin(); op_editor_core::agent_indicators::add_reveal(epoch, "parent", 1_000); @@ -453,12 +453,12 @@ mod tests { let mut cx = PaintCx { backend: &mut backend, }; - paint_agent_frame_indicators(&mut cx, &roots, Point2D::new(100.0, 50.0), 2.0, 1_120); + paint_agent_frame_indicators(&mut cx, &roots, Point2D::new(100.0, 50.0), 2.0, 1_040); assert_eq!( backend.round_strokes.len(), 1, - "nested child reveal should not add another sweep while parent is active" + "nested child reveal should not add another sweep during the parent's opening beat" ); op_editor_core::agent_indicators::end_if_epoch(epoch); } diff --git a/crates/op-editor-ui/src/widgets/canvas_viewport_reveal_tests.rs b/crates/op-editor-ui/src/widgets/canvas_viewport_reveal_tests.rs index cbf5ae0ec..c5f15480b 100644 --- a/crates/op-editor-ui/src/widgets/canvas_viewport_reveal_tests.rs +++ b/crates/op-editor-ui/src/widgets/canvas_viewport_reveal_tests.rs @@ -119,15 +119,15 @@ fn active_reveal_wraps_node_paint_in_transform() { } #[test] -fn active_parent_reveal_prevents_nested_child_transform() { +fn opening_parent_reveal_prevents_nested_child_transform() { let frame = frame_with_child(); let reveals = HashMap::from([("f".to_string(), 1_000), ("c".to_string(), 1_040)]); - let backend = paint_with_reveals(&frame, &reveals, 1_120); + let backend = paint_with_reveals(&frame, &reveals, 1_040); assert_eq!( backend.scales, 1, - "child reveal should not stack another transform while its parent is easing" + "child reveal should not stack another transform during the parent's opening beat" ); assert!( backend.ops.contains(&"fill(10,10)".to_string()), @@ -135,6 +135,19 @@ fn active_parent_reveal_prevents_nested_child_transform() { ); } +#[test] +fn child_reveal_gets_own_transform_after_parent_opening_beat() { + let frame = frame_with_child(); + let reveals = HashMap::from([("f".to_string(), 1_000), ("c".to_string(), 1_080)]); + + let backend = paint_with_reveals(&frame, &reveals, 1_120); + + assert_eq!( + backend.scales, 2, + "parent reveal should not swallow the child's own streamed entrance after the opening beat" + ); +} + #[test] fn delayed_child_reveal_keeps_its_own_transform_after_parent_settles() { let frame = frame_with_child(); diff --git a/crates/op-orchestrator/src/subagent_reveal_tests.rs b/crates/op-orchestrator/src/subagent_reveal_tests.rs index ab0474f39..533ab8540 100644 --- a/crates/op-orchestrator/src/subagent_reveal_tests.rs +++ b/crates/op-orchestrator/src/subagent_reveal_tests.rs @@ -40,11 +40,11 @@ fn reveal_schedule_streams_large_subtrees_without_long_tail() { let section = *snapshot.reveals.get("section").expect("section reveal"); let last_label = *snapshot.reveals.get("label-19").expect("last label reveal"); assert!( - last_label - section >= 420, + last_label - section >= 300, "large nested subtrees should stay visibly streamed instead of arriving in one burst" ); assert!( - last_label - section <= 650, + last_label - section <= 430, "large nested subtrees should avoid a slow reveal queue" ); assert!(