From 775e85804246db26cee160a7d5aab8cbaa5107bd Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Sun, 19 Jul 2026 23:24:11 +0000 Subject: [PATCH 1/2] docs(uploads): note that marked fields are multipart-only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lvt-upload-with is documented as a contract without naming the transport it holds on. It holds on the multipart path — always for Proxied, and for Volume/Direct only when the socket is down. Over the chunked WebSocket transport the fields never arrive. Say so, and point at the alternative (read context from controller state rather than the upload's form) so the section describes what to do rather than only what not to expect. Refs livetemplate/livetemplate#508 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Ui2cwpeGkrUfRt8rh2FgGG --- content/reference/uploads.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/content/reference/uploads.md b/content/reference/uploads.md index 38b4d76..5fa03e2 100644 --- a/content/reference/uploads.md +++ b/content/reference/uploads.md @@ -106,6 +106,22 @@ When one selection carries several files, each is POSTed as its own request and the marked fields ride along with every one, so each reaches `OnUpload` self-describing. +#### Marked fields are multipart-only + +Marked fields travel on the **multipart** upload path. A Proxied upload always +uses it, so an `OnUpload` handler always receives them. + +Volume and Direct uploads go over the WebSocket in chunks while the socket is up, +and that transport carries no form fields — so a marked field will not reach the +`upload__complete` action handler on those modes. (They do arrive if the +socket is down, since the client falls back to a multipart POST.) The client logs +a console warning when it sends a chunked upload from a form that marks fields, so +this shows up as a diagnosable message rather than a silently empty value. + +If a Volume or Direct handler needs context, read it from the state your controller +already holds rather than from the upload's form. Tracked as +[livetemplate#508](https://github.com/livetemplate/livetemplate/issues/508). + The reader enforces `MaxFileSize` mid-stream, returning `ErrUploadTooLarge` (a distinct sentinel, not `io.EOF`) so a truncated stream aborts instead of committing a partial object. Because nothing stages to disk, a pure-Proxied app From da095411e8fcab2219ad4c69203959cf5908e2d9 Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Mon, 20 Jul 2026 00:17:06 +0000 Subject: [PATCH 2/2] docs(uploads): separate Direct from Volume in the transport boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review caught that the first pass lumped the two together. Volume goes chunked while the socket is up and falls back to multipart when it is down, so marked fields arrive intermittently. Direct routes to uploadExternal — a presigned PUT plus a metadata-only completion — and so never carries them on any path. The old text told readers marked fields "do arrive if the socket is down", which for Direct is a workaround that does not work. Replaced with a per-mode table and an explicit note about the asymmetry, since a handler seeing fields intermittently is its own trap. Refs livetemplate/livetemplate#508 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Ui2cwpeGkrUfRt8rh2FgGG --- content/reference/uploads.md | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/content/reference/uploads.md b/content/reference/uploads.md index 5fa03e2..990e200 100644 --- a/content/reference/uploads.md +++ b/content/reference/uploads.md @@ -108,18 +108,28 @@ self-describing. #### Marked fields are multipart-only -Marked fields travel on the **multipart** upload path. A Proxied upload always -uses it, so an `OnUpload` handler always receives them. - -Volume and Direct uploads go over the WebSocket in chunks while the socket is up, -and that transport carries no form fields — so a marked field will not reach the -`upload__complete` action handler on those modes. (They do arrive if the -socket is down, since the client falls back to a multipart POST.) The client logs -a console warning when it sends a chunked upload from a form that marks fields, so -this shows up as a diagnosable message rather than a silently empty value. - -If a Volume or Direct handler needs context, read it from the state your controller -already holds rather than from the upload's form. Tracked as +Marked fields travel on the **multipart** upload path, and only there. Which mode +you use decides whether that path is taken: + +| Mode | Transport | Marked fields arrive? | +| --- | --- | --- | +| **Proxied** | always multipart | **Yes**, always — `OnUpload` included | +| **Volume** | chunked over the WebSocket; multipart when the socket is down | Only on the multipart fallback | +| **Direct** | presigned PUT to storage, then a metadata-only completion | **No**, on any path | + +The chunked WebSocket transport sends bytes and entry ids, and Direct completes +with a metadata-only message — neither carries form fields, so the server builds +the `upload__complete` action's context from an empty map. + +The client logs a console warning when an upload leaves by one of those +transports from a form that marks fields, so this surfaces as a diagnosable +message rather than a silently empty value. + +Note the asymmetry between the two: a Volume upload *does* deliver marked fields +when it falls back to multipart, so a handler can see them intermittently +depending on socket state — don't depend on that. Direct never delivers them at +all. For either, read the context from the state your controller already holds +rather than from the upload's form. Tracked as [livetemplate#508](https://github.com/livetemplate/livetemplate/issues/508). The reader enforces `MaxFileSize` mid-stream, returning `ErrUploadTooLarge` (a