1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! **A pending start draft replaces the composer** (bl-6ad8), driven through
//! the real window.
//!
//! §11's rule is one box and one Enter (S0). A ball start legitimately opens the
//! §8.1 goal draft — that is the rung's prefill, and bl-9acf left exactly this
//! case standing — but the draft was painted as one more bottom panel *above* a
//! composer that stayed live beneath it: two goal boxes, each with its own
//! greyed name prediction, and no way to tell which one Enter would fire.
//!
//! So the draft **takes the composer's seat** rather than stacking on it, and
//! Cancel/Escape gives it back. The composer's own draft is untouched
//! throughout: it lives in the per-target map (`actions::Drafts`, bl-a69a),
//! which the start pane never reaches into, so restoring the box restores what
//! was in it.
//!
//! The pending draft is seated directly — it is precisely what
//! `start_pane::run_prepare` leaves after ▶ Start / `s` on a ready ball, and the
//! rung that reaches it is proven from the gesture end in [`super::raise`].
use super::fixture::world;
use super::screen::{Screen, press};
use crate::start::Prepared;
/// The ball rung's prefill, headline-first as §3.3 composes it.
const BALL_GOAL: &str = "Ball bl-1234: fix the gate\n\nthe gate is stuck";
#[test]
fn a_pending_start_draft_replaces_the_composer_and_cancel_restores_it() {
let mut world = world();
let screen = Screen::new();
assert!(screen.idle(&mut world), "the cursor starts in the composer");
screen.frame(
&mut world,
vec![egui::Event::Text("ship the goal".to_owned())],
);
let before = screen.text(&mut world);
assert!(
before.contains("New prompt"),
"the composer is up:\n{before}"
);
assert!(
before.contains("ship the goal"),
"holding its own draft:\n{before}"
);
assert!(
!before.contains("Start goal →"),
"and nothing is pending yet:\n{before}"
);
// What ▶ Start on a ready ball leaves: a prefilled goal awaiting Send.
let ws = world.ws.clone();
world.state.start.pending = Some(Prepared {
workspace: crate::naming::leaf(&ws),
binding: None,
goal: BALL_GOAL.to_owned(),
origin: crate::opslog::Origin::Balls,
});
let pending = screen.text(&mut world);
assert!(
pending.contains("Start goal →") && pending.contains("Send (detached prompt)"),
"the start draft is up:\n{pending}"
);
assert!(
!pending.contains("New prompt"),
"and it REPLACED the composer — one box, one Enter:\n{pending}"
);
assert_eq!(
pending.matches("will be named ").count(),
1,
"so exactly one name prediction is on screen:\n{pending}"
);
assert!(
!pending.contains("ship the goal"),
"the composer's draft is off screen, not a second live box:\n{pending}"
);
// Escape — `KeyAction::Cancel` (§11). It reaches the plane directly here
// because the vanished composer took the keyboard with it and the goal box
// does not grab one; an operator mid-type spends a first press surrendering
// that focus, the §11 idiom the keymap's own tests pin.
screen.frame(
&mut world,
vec![press(egui::Key::Escape, egui::Modifiers::NONE)],
);
assert!(
world.state.start.pending.is_none(),
"Escape cancels the pending start"
);
let after = screen.text(&mut world);
assert!(
after.contains("New prompt"),
"and the composer comes back:\n{after}"
);
assert!(
after.contains("ship the goal"),
"with its own draft intact — the start pane never held it:\n{after}"
);
assert!(
!after.contains("Start goal →"),
"and the draft box is gone:\n{after}"
);
}