Pin the focus hand-back's non-last-dialog case, and fix three comments that overstate the code - #284
Merged
Merged
Conversation
Four test-side follow-ups from #242. The case `Dialog.close()`'s comment argues hardest for had no test: the hand-back is deliberately not also conditional on this being the last dialog, and every test that existed passed under `!Dialog.anyOpen() && document.activeElement === document.body` too. The new test opens Import, opens Export, waits for Export's field to take the focus, closes Export through its own toggle, and expects one dialog still open with the canvas focused. Mutation-checked three ways against both spec files (17 tests): adding the `anyOpen()` half fails only the new test, an unconditional `focus()` fails the live-field test here and the settings-pane treatment, and deleting the hand-back fails the two hand-back tests here and neither settings-pane test. The table is in the file header. `settings-pane-book-index.spec.ts` used `blueprint_book` where the schema pins `blueprint-book`, so every run took the "loaded anyway" validation path and raised a warning toast. The hyphen alone does not clear it - `definitions/blueprint` also requires `icons`, which the fixture omitted - so the entries carry icons now. Confirmed by reading the console and the toast text: no validation line, and only the success toast. Restored the `toBeFocused()` barrier that used to sit between opening ExportDialog and clicking Import's field. Export's focus is deferred to a ticker callback, so without it a click that won the race would fail the final assertion on a 60s timeout and read as a broken guard. Corrected three comments. The barrier's stated reason (the canvas losing focus) was wrong - a fresh page has the focus on `<body>` already, and what the barrier really does is make the close orphan something. A test title still said "last dialog". And the settings-pane header claimed `setValue` starts an async load that the second arrow could race; `Editor.loadBlueprint` has no `await` in its body and `initBP()` does not yield, so the whole load runs inside the keydown handler and there is no such gap. The sync point stays for the reason that does hold. Closes #281 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L7dF9eT5yMLqdJZ2rwytCz
The comment opened with "only when this close is what orphaned it", and the merged #242 subject says the same. The check cannot see that. It reads whether anything holds the focus right now, which is a different question with a third answer: nothing held it to begin with. Two reachable cases land there. Dragging the BP Book Index *slider* rather than typing in its box leaves <body> focused, since dat.gui blurs on mousedown and its track is a plain <div>; and an InventoryDialog owns no <input> at all, so closing one can never orphan anything. Both reach a true condition and both get a focused canvas, which is the right answer for them - the guard is fine, the sentence describing it was not. Also stops quoting the clipboard listeners' condition verbatim. Issue #279 gives it a second half, so the copy here would have gone stale with nobody opening this file. It says what the listeners require instead. Comment only - the executable line is untouched.
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 #281.
Four follow-ups from #242, grouped because they are one commit's tail. Only the
first adds coverage; the rest are a wrong fixture and wording that says more
than the code does.
1. The untested case, which is the point of this PR
Dialog.close()'s comment spends a paragraph on why the hand-back isdeliberately not also conditional on this being the last dialog. Nothing
pinned that. Measured, every existing test passes under
!Dialog.anyOpen() && document.activeElement === document.bodyas well, sore-adding that half would go green everywhere a person would look for it.
New test: open Import, open Export, wait for Export's field to take focus, close
Export through its own toggle, then assert one dialog is still open and the
canvas is focused.
Mutation table, measured across
quick-actions.spec.ts+settings-pane-book-index.spec.ts(17 tests), changing only
Dialog.ts:126:activeElement === document.body!Dialog.anyOpen() && activeElement === document.bodyfocus()Read down the columns: each mutation is caught by a different test and A by
exactly one, which is the gap. The table is now in the spec's file header, which
is where someone looks before deleting a test that seems to always pass.
2. The fixture was wrong, and one character was not enough
settings-pane-book-index.spec.tshaditem: 'blueprint_book'where the schemapins
blueprint-book. Fixing that left the spec still warning, becausedefinitions/blueprintalso requiresiconsand the fixture had none:blueprint_book, no icons/blueprint_book/item must be equal to constantblueprint-book, no icons.../blueprint must have required property 'icons'blueprint-bookwith iconsConfirmed against the committed fixture with a throwaway probe reading both the
console and
.toasts-toast, then removed. This is repo-wide, not local tothis spec -
quick-actions.spec.ts's ownONE_CHESTandbook-serialize.spec.ts's book warn for the same missing-iconsreason. Leftalone here as outside the issue, but worth its own pass.
3. A dropped barrier
The test at
quick-actions.spec.ts:299opened ExportDialog and went straight toclicking Import's field. Export's focus is deferred through
G.app.ticker.addOnce, so nothing forced the ordering. If the click ever won,the toggle would destroy a focused field and the final assertion would fail
after the 60s expect timeout - reading as "the guard is broken". Barrier
restored.
4. Three comments that say something untrue
focus first. On a fresh page the focus is already on
<body>-waitForEditoronly navigates and
loadBlueprintruns throughpage.evaluate, so nothingever clicked. It is load-bearing for a different reason: the field has to hold
the focus so that destroying it orphans it.
longer about last.
setValuestarts an async load with a gapthe second arrow could race into. Checked:
Editor.loadBlueprinthas noawaitin its body andinitBP()is synchronous, so the whole load and itsDialog.closeAll()run inside the keydown handler and no such gap exists.Dialog.close()'s own comment opened with "only when this close is whatorphaned it", and the merged Add a ToolsPanel with entity-info toggle/import string/export string/export image/undo/redo UI actions #242 subject says the same. The check cannot see
that - it reads whether anything holds the focus right now, which has a third
answer: nothing held it to begin with. Dragging the BP Book Index slider
leaves
<body>focused (dat.gui blurs on mousedown, its track is a<div>),and an
InventoryDialogowns no<input>so closing one can never orphananything. Both reach a true condition and both get a focused canvas, which is
right for them. The guard is fine; the sentence was not.
That comment also quoted the clipboard listeners' condition verbatim. #279 gives
it a second half, so the copy would have gone stale with nobody opening this
file - it now says what the listeners require rather than how they spell it.
Comment only; the executable line is untouched.
Not done
The issue's optional item - widening
=== document.bodyto also acceptnulland
documentElement- is deliberately skipped. The e2e job is Chromium only,where those arms are unreachable, so they would be untestable speculation. The
assumption and its failure direction (closed and silent, no worse than before
#242) are worth documenting if anyone adds a second browser.
Test plan
vp check .- 0 warnings, lint or type errors across 187 filesvp test- 221 passedquick-actions.spec.ts+settings-pane-book-index.spec.ts- 17 passedDialog.tsrestored and verified byte-identical afterwards🤖 Generated with Claude Code
https://claude.ai/code/session_01L7dF9eT5yMLqdJZ2rwytCz