Cost an unknown icon name the icon, not the whole dialog - #289
Merged
Conversation
`F.CreateIcon` ends in a bare `throw` for a name FD does not have, and the names come out of the blueprint. `UIContainer.createEditor` has no `try` above it on purpose, so an unguarded call cost the user the whole editor: clicking the entity did nothing at all. Six sites, not the one #286 names. Four are icon slots and take `F.SafeIcon`, which #285 added for exactly this: - `Recipe.ts:38`, the site #286 names - `DisplayPanelIcon.ts:39` - the branch #285 did not reach. `DisplayPanel Editor` returns early when the panel is connected, and both display panel specs set `connect_to_logistic_network: true`, so an ordinary unconnected panel with a planet icon still lost its whole dialog - `Modules.ts:40` and `:68` - `Filters.ts:242` and `:251`, the amounts-on and amounts-off arms The other two are not icon slots and are why guarding `Recipe.ts` alone leaves the machine editor still refusing to open: - `Preview.generatePreview` called the *static* `OverlayContainer.createEntityInfo` bare, where `OverlayContainer`'s own instance method wraps the identical call. `Preview` is built by `Editor`'s base constructor, so that throw cost *every* editor its dialog. - `EntityInfoPanel.updateVisualization` calls `getModule`, which throws by design (#55). It runs from `pointerOverEventHandler`, so an unknown module threw on hover, before any click. Those two warn with `console.warn` rather than `G.logger`, which is the toast: both repeat on every hover or redraw, and the icon slots already raise one toast when the dialog opens. `tests/editor-icon-guards.spec.ts` covers all six, one case each, mutation checked - every guard reverted in turn fails at least one test, and the two `Filters` arms need both the chest and splitter cases. `tests/dialog-registry-leak.spec.ts` borrowed the machine editor's throw as the fixture for its registry half, and said so. That fixture is gone, and replacing it with another live bug would mean a test that argues against fixing it. It now uses a `ThrowingDialog` behind a `__fbe_test` hook - constructed, never added, so registration on the `added` event leaves nothing behind. Re-measured: reverting the registry fix still fails that test at the keybind, 0 against 1. Closes #286. Closes #288. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016ZkWutgXP9XeVoh2RaAX3S
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.
F.CreateIconends in a barethrowfor a name FD does not have, and thosenames come out of the blueprint.
UIContainer.createEditorhas notryaboveit on purpose, so an unguarded call cost the user the whole editor: clicking
the entity did nothing at all.
#286 names one site. Measured, there are six.
The four icon slots
F.SafeIconat each, which #285 added for exactly this - it warns naming theicon and returns an empty container.
Recipe.ts:38MachineEditor,TempEditorrecipeDisplayPanelIcon.ts:39DisplayPanelEditoriconModules.ts:40,:68Editor.addModulesitemsFilters.ts:242,:251ChestEditor,SplitterEditor,InserterEditorrequest_filters,filterThe display panel one is the half #285 did not reach. That editor has two
branches:
if (connected)draws the read-only conditions list and holds the twosites #285 guarded, then
returns. An unconnected panel - the ordinary one -falls past it to
new DisplayPanelIcon(entity), which was still bare. Bothdisplay panel specs on record set
connect_to_logistic_network: true, so bothtake the guarded branch and neither could see it.
The two that are not icon slots
These are why guarding
Recipe.tsalone does not make the machine editor open,and neither is findable by grepping
CreateIcon.Preview.generatePreviewcalled the staticOverlayContainer.createEntityInfowith nothing around it, whileOverlayContainer's own instance method wraps the identical call atOverlayContainer.ts:493.Previewis built byEditor's base constructor, sothis one throw cost every editor its dialog, not one entity type.
EntityInfoPanel.updateVisualizationcallsgetModule, which throws bydesign (
factorioData.ts:255, added in #55 to tell an absent item from a presentnon-module one). It runs from
pointerOverEventHandler, so an entity holding anunknown module threw on hover, before any click, with no dialog involved.
Both warn with
console.warnrather thanG.logger, which is the toast: theysit on paths that repeat - every hover, every preview redraw - so a toast each
would be a stream of them. The icon slots raise the toast instead, once, when the
dialog opens.
Coverage
tests/editor-icon-guards.spec.ts, one case per site. Every assertion carriesweight: the dialog count is the bug, the empty page-error list tells a guard from
a throw swallowed higher up, and the toast stops a "fix" that drops the icon
silently.
Mutation checked, each guard reverted in turn against the spec:
Recipe.tsDisplayPanelIcon.tsModules.ts(both)Filters.ts(both)Preview.tstry/catchEntityInfoPanel.readModuleNo guard is invisible, and the two
Filtersarms need both the chest case(
CreateIconWithAmount, amounts on) and the splitter case (CreateIcon, amountsoff) - either alone leaves the other arm unmeasured.
The registry spec's fixture
tests/dialog-registry-leak.spec.tsborrowed the machine editor's throw as thefixture for its registry half, and its header said so: "that editor is still
broken and this spec does not fix it; it is the fixture precisely because it is
broken."
This removes that fixture. Replacing it with another live bug would only move the
wall one file over - a test that needs a bug to stay unfixed is a test that
argues against fixing it. So the fixture is written down instead:
throwingDialogAttempt()constructs aThrowingDialog, aDialogsubclass whoseconstructor throws after
super()and does nothing else, and answers whether itthrew. It is never added to the display tree, which is the point - registration
hangs off the
addedevent.Re-measured rather than assumed: reverting the registry fix still fails that test
at the keybind assertion, 0 against 1, while the other two pass. The mutation
record's stale entries are corrected in place, including one whose reading no
longer applies at all.
Dialogis now exported from the editor package for that hook, alongside the fourclasses already exported only for specs.
Verification
vp check- 0 warnings, lint errors or type errors across 190 filesvp test- 221 passednpx playwright test- 233 passed, full suiteCloses #286.
Closes #288.
🤖 Generated with Claude Code
https://claude.ai/code/session_016ZkWutgXP9XeVoh2RaAX3S