Skip to content

Fix "Install Update" silently failing to pick a file on Windows/Chrome - #518

Closed
jaysuk wants to merge 1 commit into
Duet3D:v3.7-devfrom
jaysuk:fix-firmware-install-file-picker
Closed

jaysuk wants to merge 1 commit into
Duet3D:v3.7-devfrom
jaysuk:fix-firmware-install-file-picker

Conversation

@jaysuk

@jaysuk jaysuk commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

The Settings > Infrastructure Install Update button reused a single, persistent hidden <input type="file"> element across every click. On Windows/Chrome this specific long-lived element could stop handing back a file selection entirely: the native picker opens, the user picks a file, change fires, but input.files comes back empty - with no error anywhere in the app, since nothing downstream of an empty FileList had any reason to complain. The button appeared to do nothing at all after the file dialog closed.

Root cause and how it was isolated

This was tracked down through live testing against a real board (Windows/Chrome):

  • Removing the accept attribute entirely made no difference.
  • Triggering the raw DOM .click() directly (bypassing the Vuetify button) made no difference.
  • A freshly-created <input type="file"> element, with attributes byte-for-byte identical to the persistent one (type, multiple, accept, hidden), picked up the same file correctly on the first try - every time, on both the Settings and Explorer pages.

That isolates the fault to the one specific, long-lived DOM node itself, independent of its attributes, how it's triggered, or the page it's on. The fix: pickFirmwareFiles() now creates a single-use <input> on every click and discards it afterwards, instead of reusing one persistent element - matching the pattern proven reliable by testing. The static <input> was removed from the template.

Additional hardening found along the way

Two related silent-failure paths surfaced while chasing this down and are fixed here too, independent of the root cause above:

  • matchesBoardBinary() built a RegExp directly from a board-reported firmwareFileName. A name containing characters that don't form a valid pattern would throw and silently abort classification for every other picked file, not just the one board. Now caught and treated as "no match" for that board.
  • Any exception from planFiles() other than the two already-handled types (PluginBundleDetectedError, ZipExtractionError) was swallowed by a bare console.warn with zero UI feedback. Now surfaces a proper error notification.
  • Added a notification for the case where a file uploads successfully but doesn't match any connected board/module/config file - previously this silently uploaded the file and did nothing further, with no indication that nothing was actually installed.

Testing

  • vue-tsc --noEmit and npm run build both pass.
  • Verified against a real Duet board in standalone mode (Windows/Chrome): confirmed the original bug, isolated it via extensive live DevTools testing (see above), and confirmed Install Update now works correctly with the fix applied.

🤖 Generated with Claude Code

The Settings > Infrastructure "Install Update" button reused a single,
persistent hidden <input type="file"> across every click. On Windows
Chrome this specific long-lived element could stop handing back a
selection entirely: the native picker opens, the user picks a file,
`change` fires, but `input.files` comes back empty - with no error
anywhere, since nothing downstream of an empty FileList had a reason
to complain. A freshly-created input with byte-for-byte identical
attributes picked up the same file correctly every time, which is
what led to the fix: pickFirmwareFiles() now creates a single-use
input on each click and discards it afterwards, instead of reusing one
long-lived element.

Also hardens two related silent-failure paths surfaced while chasing
this down, both real bugs independent of the root cause above:
- matchesBoardBinary() built a RegExp straight from a board-reported
  firmwareFileName; a name containing characters that don't form a
  valid pattern would throw and silently abort classification for
  every other picked file too. Now caught and treated as "no match".
- Any exception from planFiles() other than the two already-handled
  types was swallowed by a bare console.warn with no UI feedback.
  Now surfaces a proper error notification. Also added a notification
  for the case where a file uploads successfully but doesn't match any
  connected board/module/config file, so nothing was actually
  installed - previously silent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
chrishamm added a commit that referenced this pull request Aug 19, 2026
…518)

The change handler cleared the input before reading the picked files, so Chromium returned an empty FileList and the upload never started
@jaysuk jaysuk closed this Aug 19, 2026
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant