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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! The composer's draft is per **target**, not one global buffer that re-labels
//! its verb (§11, §5.3, bl-a69a): a goal typed for a new conversation must not
//! follow the selection into `→ message <name>` and be sent to an unrelated
//! agent. Driven through the real keyboard, on the real window, because the bug
//! was invisible to every per-widget test — the buffer was correct, its *key*
//! was missing.
use super::fixture::{MINTED_FIRST, world};
use super::screen::{Screen, press};
use crate::actions::DraftKey;
/// Type into one target, switch to another: the second box is its own, and
/// switching back restores the first. The industry-normal rule (every chat app
/// keeps a draft per conversation), asserted at the paint layer.
#[test]
fn a_draft_belongs_to_the_target_it_was_typed_for() {
let mut world = world();
let ws = world.ws.clone();
let screen = Screen::new();
// Launch puts the cursor in the composer (§11), and nothing is selected —
// so this is the new-conversation box, whose Enter would fire a start.
assert!(screen.idle(&mut world), "the cursor starts in the composer");
screen.frame(&mut world, vec![egui::Event::Text("ship the goal".into())]);
let goal = screen.text(&mut world);
assert!(
goal.contains("ship the goal"),
"the start goal is in the box:\n{goal}"
);
// Select an existing conversation. The composer retargets — and the goal
// must NOT come with it: Enter here would deposit a fresh start's text as a
// message to an agent that never asked for it.
world.model.focus_agent(&ws, "c-1");
let message = screen.text(&mut world);
assert!(
message.contains("→ message hello"),
"the composer retargets to the selection:\n{message}"
);
assert!(
!message.contains("ship the goal"),
"the start goal must not follow the selection:\n{message}"
);
// This target's own draft. A one-word needle is safe again (bl-cba6): the
// new-conversation seat below paints a §3.3 name preview, and the fixture's
// mint seed is pinned, so that word is the known `MINTED_FIRST` rather than
// a fresh draw from entropy that a needle could collide with by chance.
screen.frame(&mut world, vec![egui::Event::Text("steer".into())]);
let typed = screen.text(&mut world);
assert!(
typed.contains("steer"),
"the message draft is its own:\n{typed}"
);
// Back to the new conversation: the goal is still there, and the message
// stayed behind with the agent it was written to.
world.model.focus_workspace(&crate::naming::leaf(&ws));
let back = screen.text(&mut world);
assert!(
back.contains("ship the goal"),
"switching back restores the target's own draft:\n{back}"
);
assert!(
back.contains(&format!("will be named {MINTED_FIRST}")),
"the pinned §3.3 preview is the word beside this needle:\n{back}"
);
assert!(
!back.contains("steer"),
"the message draft stayed with its agent:\n{back}"
);
}
/// The composer's key contract (§11, bl-4515): **Shift+Enter inserts a newline
/// at the cursor without sending; Enter alone sends.** Driven through the real
/// keyboard because the seam is egui's own — the multiline box's return key is
/// Shift+Enter, and the plain press is read back out as the send.
#[test]
fn shift_enter_newlines_the_draft_and_enter_alone_sends() {
let mut world = world();
let ws = world.ws.clone();
let screen = Screen::new();
assert!(screen.idle(&mut world), "the cursor starts in the composer");
world.model.focus_agent(&ws, "c-1");
let key = DraftKey::Message("c-1".to_owned());
// A two-line draft: text, Shift+Enter, text. Nothing may fire on the combo
// — the Screen's lernie is absent, so a send would banner in ichor.
screen.frame(&mut world, vec![egui::Event::Text("first line".into())]);
screen.frame(
&mut world,
vec![press(egui::Key::Enter, egui::Modifiers::SHIFT)],
);
screen.frame(&mut world, vec![egui::Event::Text("second line".into())]);
assert_eq!(
world.state.actions.drafts.text(&key),
"first line\nsecond line",
"Shift+Enter put a newline at the cursor, and only that"
);
world.converge();
assert!(
world
.model
.last_failure(crate::opslog::Origin::Conversation)
.is_none(),
"and it dispatched nothing"
);
let quiet = screen.text(&mut world);
assert!(
quiet.contains("first line") && quiet.contains("second line"),
"the multi-line draft reaches the paint layer on both lines:\n{quiet}"
);
// Enter alone is the send. The absent lernie refuses the spawn, which is
// the proof the verb fired — and a refused send keeps the draft (§5.3)
// exactly as typed: no third line, because the plain press never reaches
// the box as a newline.
screen.frame(
&mut world,
vec![press(egui::Key::Enter, egui::Modifiers::NONE)],
);
world.converge();
assert_eq!(
world.state.actions.drafts.text(&key),
"first line\nsecond line",
"Enter was spent as the send, never as a newline"
);
assert!(
world
.model
.last_failure(crate::opslog::Origin::Conversation)
.is_some(),
"the send really dispatched (the absent binary leaves its ops row)"
);
}
/// The box's **third** Enter (§11, bl-a33d): **Ctrl+Enter is send-and-interrupt,
/// and its first act is the stop.** Driven through the real keyboard beside its
/// two siblings, because the whole family is the box's own and the only way to
/// tell the three apart is which verb reached the substrate. The absent lernie
/// is the witness: the attempted argv is what a refused spawn records, so the
/// leading verb says which gesture fired.
#[test]
fn ctrl_enter_interrupts_where_plain_enter_only_deposits() {
let mut world = world();
let ws = world.ws.clone();
let screen = Screen::new();
assert!(screen.idle(&mut world), "the cursor starts in the composer");
world.model.focus_agent(&ws, "c-1");
// Plain Enter first, for the contrast: the deposit, and nothing ahead of it.
screen.frame(&mut world, vec![egui::Event::Text("carry on".into())]);
screen.frame(
&mut world,
vec![press(egui::Key::Enter, egui::Modifiers::NONE)],
);
world.converge();
let sent = world
.model
.last_failure(crate::opslog::Origin::Conversation)
.expect("Enter reached the deposit");
assert!(
sent.argv.contains(" message "),
"a bare Enter is the deposit alone: {}",
sent.argv
);
// Now the combo, on the same box and the same target. The stop goes first —
// and because the absent binary refuses that spawn, the stop is also the
// last thing the trail saw, which is exactly what distinguishes this
// gesture from the one above.
screen.frame(&mut world, vec![egui::Event::Text("no, do this".into())]);
screen.frame(
&mut world,
vec![press(egui::Key::Enter, egui::Modifiers::COMMAND)],
);
world.converge();
let cut = world
.model
.last_failure(crate::opslog::Origin::Conversation)
.expect("Ctrl+Enter reached the substrate");
assert!(
cut.argv.contains(" stop "),
"Ctrl+Enter interrupts before it deposits: {}",
cut.argv
);
}