Skip to content

Cost an unknown icon name the icon, not the whole dialog - #289

Merged
wormeyman merged 1 commit into
wormeyman-space-age-supportfrom
fix/286
Aug 31, 2026
Merged

Cost an unknown icon name the icon, not the whole dialog#289
wormeyman merged 1 commit into
wormeyman-space-age-supportfrom
fix/286

Conversation

@wormeyman

Copy link
Copy Markdown
Collaborator

F.CreateIcon ends in a bare throw for a name FD does not have, and those
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.

#286 names one site. Measured, there are six.

The four icon slots

F.SafeIcon at each, which #285 added for exactly this - it warns naming the
icon and returns an empty container.

site reached by blueprint field
Recipe.ts:38 MachineEditor, TempEditor recipe
DisplayPanelIcon.ts:39 DisplayPanelEditor icon
Modules.ts:40, :68 Editor.addModules items
Filters.ts:242, :251 ChestEditor, SplitterEditor, InserterEditor request_filters, filter

The 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 two
sites #285 guarded, then returns. An unconnected panel - the ordinary one -
falls past it to new DisplayPanelIcon(entity), which was still bare. Both
display panel specs on record set connect_to_logistic_network: true, so both
take the guarded branch and neither could see it.

The two that are not icon slots

These are why guarding Recipe.ts alone does not make the machine editor open,
and neither is findable by grepping CreateIcon.

Preview.generatePreview called the static
OverlayContainer.createEntityInfo with nothing around it, while
OverlayContainer's own instance method wraps the identical call at
OverlayContainer.ts:493. Preview is built by Editor's base constructor, so
this one throw cost every editor its dialog, not one entity type.

EntityInfoPanel.updateVisualization calls getModule, which throws by
design (factorioData.ts:255, added in #55 to tell an absent item from a present
non-module one). It runs from pointerOverEventHandler, so an entity holding an
unknown module threw on hover, before any click, with no dialog involved.

Both warn with console.warn rather than G.logger, which is the toast: they
sit 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 carries
weight: 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:

reverted tests failing
Recipe.ts 1 - the recipe test
DisplayPanelIcon.ts 1 - the display panel test
Modules.ts (both) 1 - the module test
Filters.ts (both) 2 - the chest and splitter tests
Preview.ts try/catch 3 - recipe, module and chest
EntityInfoPanel.readModule 1 - the module test

No guard is invisible, and the two Filters arms need both the chest case
(CreateIconWithAmount, amounts on) and the splitter case (CreateIcon, amounts
off) - either alone leaves the other arm unmeasured.

The registry spec's fixture

tests/dialog-registry-leak.spec.ts borrowed the machine editor's throw as the
fixture 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 a ThrowingDialog, a Dialog subclass whose
constructor throws after super() and does nothing else, and answers whether it
threw. It is never added to the display tree, which is the point - registration
hangs off the added event.

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.

Dialog is now exported from the editor package for that hook, alongside the four
classes already exported only for specs.

Verification

  • vp check - 0 warnings, lint errors or type errors across 190 files
  • vp test - 221 passed
  • npx playwright test - 233 passed, full suite

Closes #286.
Closes #288.

🤖 Generated with Claude Code

https://claude.ai/code/session_016ZkWutgXP9XeVoh2RaAX3S

`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
@wormeyman
wormeyman merged commit ea8b825 into wormeyman-space-age-support Aug 31, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant