feat: support parallel multi-file uploads

The browser upload flow was built around one selected file and one global
upload state. That made the existing chunk pool useful for a single file, but
users could not start several selected files at the same time.

Refactor the browser state into per-file upload items. Each selected file now
has its own upload record, completed-chunk set, abort controller, retry state,
progress row, and saved IndexedDB resume record. The picker accepts multiple
files, `Start all` and `Resume all` use a bounded file-level pool, and each file
keeps the existing bounded chunk pool. This keeps parallel uploads useful
without letting one large selection create unbounded request fan-out.

Keep the server API unchanged. Each file still receives a separate server upload
id, and server-side progress remains authoritative before any missing chunks are
scheduled. Terminal conflicts still stop the affected file without overwriting
completed data.

Update the user-facing markup, styles, project docs, and test checklist for the
multi-file scheduler. Add a server regression test that interleaves two uploads
and verifies the completed files contain exactly their own bytes.

Test Plan:
- just check
- git diff --check
This commit is contained in:
2026-05-30 18:32:29 +02:00
parent a7b3abd54a
commit 1923ff2a6f
8 changed files with 635 additions and 192 deletions
+2 -1
View File
@@ -22,7 +22,8 @@ async fn serves_index_page() -> Result<(), Box<dyn std::error::Error>> {
let body = String::from_utf8(body.to_vec())?;
assert!(body.contains("<title>upl</title>"));
assert!(body.contains("Choose file"));
assert!(body.contains("Choose files"));
assert!(body.contains("Selected uploads"));
assert!(body.contains("Saved upload progress"));
assert!(!body.contains("Server online"));