Web UI: resumable uploads via TUS with progress + resume (v0.4 #21) #39

Merged
Cordy merged 1 commit from feat/browser-tus into main 2026-07-26 14:33:11 +00:00
Owner

The web UI's upload was a single POST /api/v1/files — a dropped connection at 90% of a 2 GB file meant starting over, even though the server has had TUS since v0.2.0. This wires the browser to it (vanilla JS, no library):

  • Files ≥ 16 MiB upload through /api/v1/tus/ in 8 MiB chunks: create with Upload-Metadata: path <b64>, offset-checked PATCHes, progress bar (<progress id="upprog">) + percent in the status line. Small files keep the direct POST.
  • Retry: up to 5 attempts per stall with exponential backoff, re-syncing the offset via HEAD each time (handles a chunk that landed but whose response was lost).
  • Resume across reloads: the upload URL is remembered in localStorage keyed by (dest, size, lastModified); reselecting the same file after a crash/reload HEADs the old upload and continues from the server's offset. Cleared on success; also cleared on fatal statuses (404/410 expired, 507 quota, 413 too large) which surface as proper errors instead of retries.

Verified: script extracted and node --check passes; markers + single onchange handler asserted (runner workflow, since the UI has no CI coverage).

Needs a human browser smoke-test before sign-off (throttle network, upload >16 MiB, kill the connection mid-way, reselect the file → should continue from the server offset).

The web UI's upload was a single `POST /api/v1/files` — a dropped connection at 90% of a 2 GB file meant starting over, even though the server has had TUS since v0.2.0. This wires the browser to it (vanilla JS, no library): - Files **≥ 16 MiB** upload through `/api/v1/tus/` in 8 MiB chunks: create with `Upload-Metadata: path <b64>`, offset-checked PATCHes, progress bar (`<progress id="upprog">`) + percent in the status line. Small files keep the direct POST. - **Retry**: up to 5 attempts per stall with exponential backoff, re-syncing the offset via HEAD each time (handles a chunk that landed but whose response was lost). - **Resume across reloads**: the upload URL is remembered in localStorage keyed by `(dest, size, lastModified)`; reselecting the same file after a crash/reload HEADs the old upload and continues from the server's offset. Cleared on success; also cleared on fatal statuses (404/410 expired, 507 quota, 413 too large) which surface as proper errors instead of retries. Verified: script extracted and `node --check` passes; markers + single onchange handler asserted (runner workflow, since the UI has no CI coverage). **Needs a human browser smoke-test before sign-off** (throttle network, upload >16 MiB, kill the connection mid-way, reselect the file → should continue from the server offset).
web: resumable TUS uploads for large files with progress + resume (#21)
All checks were successful
ci / test-and-build (pull_request) Successful in 18s
14bb22097f
Cordy merged commit 47007f7685 into main 2026-07-26 14:33:11 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#39
No description provided.