Skip to content

fix(server): restore multi-file (bulk) upload in the Add Asset modal - #3049

Merged
vpetersson merged 5 commits into
masterfrom
fix/multi-file-upload-3045
Jun 10, 2026
Merged

fix(server): restore multi-file (bulk) upload in the Add Asset modal#3049
vpetersson merged 5 commits into
masterfrom
fix/multi-file-upload-3045

Conversation

@vpetersson

Copy link
Copy Markdown
Contributor

Issues Fixed

Fixes #3045

Description

Multi-file (bulk) upload was added for the React UI in #2778 but silently lost in the #2818 React→Alpine/HTMX rewrite — the Add Asset file picker accepted a single file only. The assets_upload endpoint already accepts one file per POST, so this is a client-only fix.

  • Added multiple to the #add-file input.
  • The file tab is no longer htmx-managed (htmx's single-form submit would only ever send the first file). A new uploadFiles() in home.ts iterates the selected files and POSTs them sequentially — one XHR per file — against the existing single-file endpoint, surfacing an "X of N" progress like the old React version did. Server toasts are replayed from the HX-Trigger header by hand since the request isn't htmx-managed.
  • Single-file uploads still flow through the same path with unchanged UX.

Added an integration test (test_add_multiple_uploads_at_once) that selects two files in one go and asserts both persist; it also implicitly guards the multiple attribute (Playwright rejects a multi-file set_input_files on a non-multiple input).

Forum: https://forums.screenly.io/t/6579

Checklist

  • I have performed a self-review of my own code.
  • New and existing unit tests pass locally and on CI with my changes.
  • I have done an end-to-end test for Raspberry Pi devices.
  • I have tested my changes for x86 devices.
  • I added a documentation for the changes I have made (when necessary).

🤖 Generated with Claude Code

Multi-file upload (added in #2778 for the React UI) was lost in the
#2818 React→Alpine/HTMX rewrite: the file picker accepted a single
file only. The assets_upload endpoint already takes one file per
POST, so this is a client-only fix.

- Add `multiple` to the #add-file input.
- Drive the upload from uploadFiles() in home.ts: iterate the selected
  files and POST them sequentially (one XHR per file) against the
  existing single-file endpoint, with "X of N" progress. htmx's
  single-form submit would only ever send the first file, so the file
  tab is no longer htmx-managed; toasts are replayed from the server's
  HX-Trigger header by hand.
- Single-file uploads still flow through the same path unchanged.

Adds an integration test (test_add_multiple_uploads_at_once) that
selects two files in one go and asserts both persist.

Fixes #3045

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@vpetersson
vpetersson requested a review from a team as a code owner June 9, 2026 15:58
@vpetersson vpetersson self-assigned this Jun 9, 2026
@vpetersson
vpetersson requested a review from Copilot June 9, 2026 15:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Restores multi-file (bulk) upload support in the Add Asset modal after the React → Alpine/HTMX rewrite regressed the file picker to single-file selection, while keeping the existing single-file assets_upload endpoint (one file per POST) unchanged.

Changes:

  • Re-enabled multi-select in the Add Asset upload input and switched the upload flow from HTMX form submit to a JS-driven sequential uploader (one XHR per file) with “File X of N” progress.
  • Replays server toasts from the HX-Trigger response header for the non-HTMX XHR path and triggers an explicit refresh-assets after successful uploads.
  • Added a Playwright integration test to guard multi-file selection and persistence.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
tests/test_app.py Adds an integration test ensuring multi-file selection uploads and persists all files.
src/anthias_server/app/templates/_asset_modal.html Adds multiple to the upload input and routes change handling to the new JS uploader (removing HTMX management for the file tab).
src/anthias_server/app/static/src/home.ts Implements sequential multi-file upload via XHR, progress/index tracking, and manual toast replay from HX-Trigger.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/anthias_server/app/static/src/home.ts Outdated
Hiding the modal during an in-flight 'sending' upload cleared
uploadState, which disarmed uploadFiles()'s re-entry guard and let a
reopened modal start a second batch racing the first over the shared
progress/index fields. Only reset upload state when nothing is in
flight.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread src/anthias_server/app/static/src/home.ts
assets_upload refused invalid/empty uploads with messages.error +
HTTP 200, which the HTMX/XHR path drops silently (the partial carries
no toast header) — so the operator saw nothing and the batch uploader
counted the rejection as a success. Pass the rejection through
_asset_table_response(toast=('error', …)) so it rides the HX-Trigger
header on every transport.

Client side, uploadOne() now distinguishes 'ok' / 'rejected' (2xx +
error toast) / 'error' (transport): a rejected file surfaces its
server toast and the batch skips it and keeps going, while a true
transport failure aborts. Addresses Copilot review feedback.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread src/anthias_server/app/static/src/home.ts Outdated
The doc comment still described the old boolean return; it now
documents the ok / rejected / error tri-state. Comment-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread src/anthias_server/app/templates/_asset_modal.html Outdated
Comment thread src/anthias_server/app/static/src/home.ts Outdated
The comments said htmx "would only ever send the first file", but
htmx includes every selected file in the multipart body — the real
single-file constraint is assets_upload reading request.FILES.get.
Reword both comments. Comment-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@sonarqubecloud

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@vpetersson
vpetersson merged commit bb9da44 into master Jun 10, 2026
11 checks passed
vpetersson added a commit that referenced this pull request Jun 11, 2026
* fix(server): polish home bulk-action & upload UI/UX

Follow-up UI/UX pass over the recently shipped bulk asset management
(#3048), multi-file upload (#3049), and ffmpeg/HandBrake rejection
hints (#3040).

- Reserve bottom space (.has-bulk-selection) while a selection is
  active so the fixed bulk-action bar never floats over the last rows
  or their action buttons — exactly the assets a bulk selection
  targets.
- Cap .modal-card to the viewport and scroll inside it, with sticky
  header/footer, so a tall bulk-edit form (or the Edit modal with the
  failure alert + Advanced open) keeps its title and Apply/Save buttons
  reachable instead of pushing them below the fold.
- Wire real drag-and-drop on the upload dropzone (dropFiles() feeds the
  same sequential uploadFiles() batch path); the dashed zone already
  read as a drop target but silently ignored drops.
- Lift the selection checkbox contrast on the dark Enabled surface so
  the select-all (checked/indeterminate) and per-row boxes are legible
  against the purple gradient; scoped to .asset-select so the activity
  switch keeps its track styling.
- Anchor the bulk bar's clear (x) to the top-right on phones so it no
  longer wraps beside the destructive Delete (mis-tap risk).
- Stack the ffmpeg recipe's copy button under the command at narrow
  widths; make the empty-state CTA a <button> (action, not nav); drop
  the bulk duration field's placeholder that collided with its
  floating label.

Presentational only — no API/model changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(server): stop dropzone highlight flicker on child drag-over

Copilot review on #3066: dragleave bubbles from the dropzone's child
icon/paragraphs, toggling dragActive off while the cursor is still over
the label and flickering the highlight. Add the .self modifier so the
handler only runs when leaving the label itself.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

* fix(server): clear dropzone drag highlight when the modal closes

Second Copilot pass on #3066: dragActive could stay true if the user
drags into the dropzone then closes the Add modal (Esc/backdrop/Cancel)
before dragleave fires, so the dropzone re-opened still highlighted.
Reset dragActive in closeModal() alongside the other modal state.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Multi-file (bulk) upload regressed in the React→Alpine rewrite (#2818); single-file only

2 participants