diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-21 21:17:34 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-21 21:17:34 +0200 |
| commit | c72d96d6428f99f06c04868a70cb09c4a9180f98 (patch) | |
| tree | f44b8e53ca192b5c0ecfe5600293cd465cc89c3b /assets/icons | |
| parent | 84e3205ddcba3263e6c07fa314437318309d4b76 (diff) | |
| download | qtmaildir-c72d96d6428f99f06c04868a70cb09c4a9180f98.tar.gz qtmaildir-c72d96d6428f99f06c04868a70cb09c4a9180f98.zip | |
feat(compose): the send popup and its undo window, item 123
Three rows in every state so nothing reflows and the window never jumps. The
bar changes MODE rather than place: determinate while the countdown drains,
because a countdown has measurable progress, and indeterminate once the
command starts, because a send does not. That is the pairing item 134's
widget was extracted to serve.
The delay is where cancelling is safe and it is the only place it is.
Nothing has reached a server during the countdown, so Undo means genuinely
nothing happened; killing send_command once it runs leaves an UNKNOWN send,
which is worse than either clean outcome. Undo therefore disables itself the
moment the command starts, and stays visible while disabled: a control that
vanishes re-lays out the popup mid-operation, and a greyed Undo says why
cancelling is no longer possible where an absent one looks like it was never
offered.
The test for this asserts the NEGATIVE property, that committed() never
fires after Undo, including after the original countdown would have elapsed.
Asserting only that undone() fired would pass against a design that ran the
command and threw the result away, which is the whole failure the delay
exists to prevent.
Removing the close BUTTON is not the same as closing the code path, and the
first draft did only the former while its comments claimed otherwise. Escape
still reached QDialog::reject(), and close() during the countdown hid the
window while leaving the timer running, so the send committed with nothing
on screen and the only cancel control destroyed: measured, committed=1 on a
dialog the user had dismissed. A never-shown dialog did the same, since
close() returns early without reaching done(). That is CLAUDE.md's done(int)
trap in the one place it costs mail rather than state.
Dismissal is REFUSED before commit rather than treated as an implicit Undo,
at the user's decision: a close that silently means cancel overloads one
gesture with two meanings, while a refusal leaves Undo as the only way out,
which is what the popup's single control already says. done(int) refuses
pre-commit and forces Accepted after, closeEvent covers the never-shown
route done() cannot see, and Undo passes through both. Task 12 needs no
special entry point, since it closes after the send finishes and that is
post-commit by definition.
A refusal must not read as a hang, so the label says how to leave. Making
the hint silent was a mutation that SURVIVED, because the text was written
in two places and neutering one was masked by the other; extracting it to
one function exposed a real defect behind the wrong green, in that the next
tick overwrote the hint 100ms later and the refusal was effectively silent
anyway. It is held for 1500ms now, with a test that it survives a tick and
still releases.
setStage is public and Task 12 passes values into it, so it refuses to wind
back to CountingDown after commit rather than trusting its caller with an
invariant this class documents as inviolable; the label read "Sending in
0..." and the bar returned to determinate. Both m_committed guards carry
tests: removing them left the suite green, so two deliberate safety
additions rested on reasoning alone.
Every route out is asserted, per the rule that a test using close() while
the user uses Cancel covers one route of three: close() shown, close()
never-shown, Escape bare and with Shift and Ctrl, reject() direct, and Undo,
which must still work or the popup is a trap.
The status label is sized to the longest string it can hold in the current
language rather than to its content: Italian 'Rimozione della bozza...' is
longer than 'Removing draft...', and a label sized to content resizes the
popup between stages.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FXF741wz4SY7j5dqvAxMU5
Diffstat (limited to 'assets/icons')
0 files changed, 0 insertions, 0 deletions
