Skip to content

Commit 6c26152

Browse files
donislawdevclaude
andcommitted
guard: a refused preview leaves the screen usable
The busy state a preview needs brought an edge with it. Only the worker hands the screen back, and a refusal never starts one - so a preview that reached the busy state before knowing it was refused would freeze the form for the rest of the session. The refusal returns first today, and that ordering was the whole of the correctness with nothing watching it. Nothing else was. The guards for refusals check that the right box is marked and the right sentence appears, and every one of them would still pass with the form dead underneath. Three cases rather than one, including the legal degenerate: a value that is not a size, a size below every format minimum, and nothing to produce at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent d261ab3 commit 6c26152

1 file changed

Lines changed: 49 additions & 0 deletions

File tree

‎internal/guard/runstate_test.go‎

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,3 +205,52 @@ func TestAPreviewGivesTheScreenBackWhenItIsDone(t *testing.T) {
205205
t.Error("Cancel is still on the screen after a preview, offering a way out of something that is not happening")
206206
}
207207
}
208+
209+
// What this defends. A preview that is turned down leaves the screen exactly as
210+
// usable as it found it.
211+
//
212+
// This is the edge the busy state introduced. A preview marks the screen
213+
// occupied and hands the disk work to a worker, and the worker is the only
214+
// thing that hands the screen back - so a refusal that reached the busy state
215+
// without starting a worker would freeze the form for the rest of the session.
216+
// Today the refusal returns first and nothing is frozen, and that ordering is
217+
// the whole of the correctness here.
218+
//
219+
// Nothing else was watching it. The guards for refusals check that the right
220+
// box is marked and the right sentence appears, and every one of them would
221+
// still pass with the form dead underneath.
222+
func TestARefusedPreviewLeavesTheScreenUsable(t *testing.T) {
223+
for _, c := range []struct{ name, field, value string }{
224+
// Refused while settling, which is where a bad value is caught.
225+
{"a size that is not a size", text.FieldSize, "abc"},
226+
// Refused deeper, by the engine, for a size no format can make.
227+
{"a size below every minimum", text.FieldSize, "1"},
228+
// Legal and degenerate: nothing to do at all.
229+
{"nothing to produce", text.FieldCount, "0"},
230+
} {
231+
t.Run(c.name, func(t *testing.T) {
232+
_, content := screen(t)
233+
fill(t, content, text.FieldOutputDir, t.TempDir())
234+
fill(t, content, c.field, c.value)
235+
press(t, content, text.ButtonPreview)
236+
237+
// No worker was started, so there is nothing to join - and that is
238+
// exactly why this can freeze: the thing that gives the screen back
239+
// never runs.
240+
box := entryUnder(t, content, text.FieldSize)
241+
if box == nil {
242+
t.Fatal("there is no size box, so this guard read the wrong tree")
243+
}
244+
if box.Disabled() {
245+
t.Error("the preview was refused and the form is frozen.\n" +
246+
"Reason: the screen is marked busy before the refusal is known, and only a worker hands it back - so a refused press leaves the form dead for the rest of the session.\n" +
247+
"What to do: settle and plan before marking the screen busy.")
248+
}
249+
for _, name := range []string{text.ButtonPreview, text.ButtonGenerate} {
250+
if b := buttonNamed(content, name); b == nil || b.Disabled() {
251+
t.Errorf("%q is disabled after a refused preview, so nothing can be tried again", name)
252+
}
253+
}
254+
})
255+
}
256+
}

0 commit comments

Comments
 (0)