Skip to content

Ask before a delete that takes other nodes with it - #30

Merged
MarcelInTO merged 1 commit into
mainfrom
delete-confirmation
Aug 22, 2026
Merged

MarcelInTO merged 1 commit into
mainfrom
delete-confirmation

Conversation

@MarcelInTO

Copy link
Copy Markdown
Owner

Closes #21.

Del sits beside the navigation keys, takes no modifier, and the cursor is already in the tree.
The tree gives a row to every element and every attribute, so one keypress on a collapsed
<division> can take 1899 rows of a book with it — no prompt, and nothing on screen afterwards
saying how much went. That the delete is undoable was the original reason not to ask, and it is
not wrong; the case for asking is that undo only helps someone who notices.

It now asks when something goes with the node, and says how much:

        ┏┥Delete┝━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
        ┃                                           ┃
        ┃Delete <Employee> and the 14 rows under it?┃
        ┃                                           ┃
        ┃         ⟦ Delete ⟧   ⟦► Cancel ◄⟧         ┃
        ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

A leaf — an attribute, an empty element, an element whose only content is its own text — does
not ask. It is the commonest delete there is and the most trivially restored, and a modal in
front of it would only train people to dismiss modals. That is the issue's own recommendation of
the three options it listed; say the word if you'd rather it asked every time.

Two things had to be measured rather than assumed

MessageBox makes the LAST label the default button, not the first. This prompt was written
Cancel-first, following the issue's mock-up, and rendered ⟦► Delete ◄⟧ — arming precisely the
reflexive Enter the confirmation exists to prevent. The ^Q prompt gives the rule away: Save, Discard, Cancel renders its default brackets on Cancel. Cancel now goes last, and the button
array sits directly beside the index that proceeds, because the two only mean anything together.

The drill cannot press the button. MessageBox's Dialog exposes no SubViews in 2.4.17 —
its adornments are lightweight settings objects, not views, so the buttons are not reachable
there either — and injected keys reach only app-scope bindings, which is why Esc appears to
work on a dialog while Tab and the arrows do nothing. So the drill answers with Esc and
asserts the button order, the confirming index, the leaf rule and the prompt text from the
constants. What is not covered is the press itself, and the comments say so rather than
implying more coverage than exists.

Verification

--drill on all five fixtures: 388 / 392 / 379 / 366 / 371, all PASS. Confirmed in a real
terminal, which is where the default-button inversion was caught.

mutation goes red on
no confirmation at all Del on a node with children asks first (modal depth was -1) + the three cancel checks
ask on every delete, leaves included deleting a leaf (@xmlns) does not ask
put Cancel first again Cancel is the delete prompt's LAST button…, …and the index that proceeds is the Delete button

The first mutation also ended the run at 120 of 388 checks instead of failing it — deleting
an already-gone node takes a null container into a NullReferenceException. That path is now
guarded, because a crash truncates the report and hides every check after it.

Drive-by

The drill runner sat waiting its full 900s deadline whenever the build failed: dotnet run had
already printed the errors and exited, and the wait read exactly like a hung drill. That cost
three 600s waits in this session before anyone read the actual output. It now builds first and
reports the errors.

readme.md's key table says Del asks first.

🤖 Generated with Claude Code

https://claude.ai/code/session_016X4pgrsMBcsXjqWDo2gBBt

Del sits beside the navigation keys, takes no modifier, and the cursor is
already in the tree. The tree gives a row to every element *and every
attribute*, so one keypress on a collapsed <division> can take 1899 rows of a
book with it — no prompt, and nothing on screen afterwards saying how much
went. That the delete is undoable was the original reason not to ask, and it
is not wrong; the case for asking is that undo only helps someone who notices,
and a stray Del leaves nothing to notice (#21).

It asks only when something goes with the node, and says how much:

    Delete <Employee> and the 14 rows under it?
             ⟦ Delete ⟧   ⟦► Cancel ◄⟧

A leaf — an attribute, an empty element, an element whose only content is its
own text — is the commonest delete there is and the most trivially restored,
and a modal in front of it would only train people to dismiss modals. Naming
the count is the point: "Delete this node?" teaches exactly that reflex.

Two things had to be measured rather than assumed.

MessageBox makes the LAST label the default button, not the first. This prompt
was written Cancel-first, on the assumption in the issue, and rendered
⟦► Delete ◄⟧ — arming precisely the reflexive Enter the confirmation exists to
prevent. The ^Q prompt gives the rule away: "Save, Discard, Cancel" renders its
default brackets on Cancel. Cancel now goes last, and the button array sits
beside the index that proceeds, because the two only mean anything together.

The drill cannot press the button. MessageBox's Dialog exposes no SubViews in
2.4.17 — its adornments are lightweight settings objects, not views, so the
buttons are not reachable there either — and injected keys reach only app-scope
bindings, which is why Esc appears to work on a dialog while Tab and the arrows
do nothing at all. So the drill answers the prompt with Esc and asserts the
button order, the confirming index, the leaf rule and the prompt text from the
constants. What is NOT covered is the press itself; the comments say so rather
than implying more coverage than exists.

Drilled on all five fixtures (388/392/379/366/371, green) and mutation-tested:
removing the confirmation, asking on leaves too, and putting Cancel back first
each turn red on the checks that name them. The first of those also ended the
run at 120 of 388 checks rather than failing it — a delete of an already-gone
node takes a null container into a NullReferenceException — so that path is now
guarded, because a crash truncates the report and hides everything after it.

Also fixes the drill runner, which sat waiting the full 900s deadline whenever
the build failed: `dotnet run` had already printed the errors and exited, and
the wait read exactly like a hung drill. It cost three 600s waits in one
session before anyone read the actual output. It now builds first and reports
the errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016X4pgrsMBcsXjqWDo2gBBt
@MarcelInTO
MarcelInTO merged commit 3738819 into main Aug 22, 2026
5 checks passed
@MarcelInTO
MarcelInTO deleted the delete-confirmation branch August 22, 2026 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Confirm before deleting a node that takes others with it

1 participant