Ask before a delete that takes other nodes with it - #30
Merged
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #21.
Delsits 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 afterwardssaying 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:
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 thereflexive Enter the confirmation exists to prevent. The
^Qprompt gives the rule away:Save, Discard, Cancelrenders its default brackets onCancel. Cancel now goes last, and the buttonarray sits directly beside the index that proceeds, because the two only mean anything together.
The drill cannot press the button. MessageBox's
Dialogexposes noSubViewsin 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
Escappears towork on a dialog while
Taband the arrows do nothing. So the drill answers withEscandasserts 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
--drillon all five fixtures: 388 / 392 / 379 / 366 / 371, all PASS. Confirmed in a realterminal, which is where the default-button inversion was caught.
Del on a node with children asks first (modal depth was -1)+ the three cancel checksdeleting a leaf (@xmlns) does not askCancel is the delete prompt's LAST button…,…and the index that proceeds is the Delete buttonThe 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 nowguarded, 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 runhadalready 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 saysDelasks first.🤖 Generated with Claude Code
https://claude.ai/code/session_016X4pgrsMBcsXjqWDo2gBBt