Examples menu

Background Upload via SharedWorker

Start a simulated or real upload, then open Browse or Settings while it runs. The progress bar follows you. A SharedWorker sends the chunks, extendedLifetime (Chrome 148+) keeps it alive between pages, and session-scoped signals carry the progress.

The core idea: in a classic MPA, navigating away tears down the page, kills any in-flight XHR, and resets all client state. A SharedWorker partially breaks that rule: it is shared across tabs and not tied to a single page lifecycle. But the browser is still allowed to terminate it the moment all ports disconnect, which happens briefly during every navigation.
extendedLifetime (Chrome 148+) closes the last gap: without it, the browser may terminate the SharedWorker during the brief moment between pages when the port count drops to zero. On Chrome 148+, a real upload continues chunking with zero user friction. On other browsers, that gap can kill the worker, hence the navigation guard.
Per-session signals are the server-side complement. They hold uploadStatus, uploadPct, uploadFileName, and byte counters for every tab of your session, in a custom upload: scope built from Scope::sessionScope(). Each write broadcasts that scope, and when the new page's SSE stream opens, the server pushes the current state (no polling, no local storage).
Real file mode shows the full picture: the worker slices the file into 512 KB chunks (File.slice()) and POSTs them one by one to the uploadChunk action. Between each await the worker may receive a connect message with fresh URLs from the new page, making mid-upload navigation transparent.
Graceful fallback: on browsers where the worker can be killed between pages, an in-page confirm dialog intercepts subnav clicks and a beforeunload handler covers other navigation. The guard is automatically suppressed on Chrome 148+ once the worker's workerBorn timestamp confirms it survived.

Account Settings

This is a placeholder page to demonstrate that uploads survive navigation. The progress bar above is still tracking your upload, driven by SESSION-scoped signals.

Profile
Upload preferences

Signals

uploadStatus string custom scope initial: "idle"

Upload lifecycle: idle | uploading | complete | cancelled.

uploadPct int custom scope initial: 0

Upload progress 0 to 100. Authoritative server value: navigation gaps self-heal on next chunk.

uploadFileName string custom scope initial: empty string

Filename displayed in the progress bar.

uploadTotalBytes int custom scope initial: 0

Total file size in bytes (virtual or real).

uploadedBytes int custom scope initial: 0

Bytes transferred so far.

uploadFileInfo string custom scope initial: empty string

Human-readable file metadata for real uploads, e.g. "3.2 MB · PDF". Empty for simulated uploads.

uploadMode string tab scope initial: "sim"

Which form is shown: sim (SharedWorker simulation) or real (multipart upload). TAB-scoped: each tab can differ.

Actions

startUpload

Initialises the upload signals (status=uploading, total, filename); the writes reach every tab of the session. Called by the worker before the chunk loop.

receiveChunk

Simulated mode only. Called by SharedWorker every 200 ms. Updates pct and uploadedBytes from the worker's authoritative value, which heals navigation gaps.

uploadChunk

Real file mode only. Receives a 512 KB slice from the SharedWorker chunk loop. Tracks offset and size, updates pct/uploadedBytes, sets status=complete and formats uploadFileInfo on the final chunk.

setMode

Switches uploadMode between sim and real, clears file errors, re-renders the form block.

cancelUpload

Resets all upload signals to idle. Works from any sub-page.

resetUpload

Clears completed/cancelled state so a new upload can begin.

Views

file-upload.html.twig

Mode toggle, sim form (filename + size/speed pickers), real file form. Real uploads use File.slice() chunks (512 KB each) sent by the SharedWorker via uploadChunk. Navigation guard shown on non-Chrome browsers.

file-upload-browse.html.twig

Fake file browser sub-page showing live upload row during transfer.

file-upload-settings.html.twig

Fake settings sub-page proving upload state survives any navigation.