From 0fc58580b5df9e2e87a52e80a0ef7c86e10297db Mon Sep 17 00:00:00 2001 From: Adnaan Badr Date: Sun, 19 Jul 2026 20:34:40 +0000 Subject: [PATCH] docs(uploads): document lvt-upload-with opt-in field marking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Form fields now travel with a Proxied upload only when marked lvt-upload-with (client b88d492). Document the contract in the uploads reference, add the attribute to the client-attributes table, and mark the record id in the upload-modes example. The example edit is load-bearing, not polish: it relied on the old serialize-everything default to deliver record_id, so without the marker the proxied demo silently routes every upload to "unfiled" — verified by watching upload-modes_test.go fail that way before the marker was added back. Refs livetemplate/livetemplate#452 Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Ui2cwpeGkrUfRt8rh2FgGG --- content/reference/client-attributes.md | 1 + content/reference/uploads.md | 43 ++++++++++++++++++++++--- examples/upload-modes/upload-modes.tmpl | 7 ++-- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/content/reference/client-attributes.md b/content/reference/client-attributes.md index afcab42..d7f1a7f 100644 --- a/content/reference/client-attributes.md +++ b/content/reference/client-attributes.md @@ -1051,6 +1051,7 @@ Directives use CSS custom properties for configuration: `--lvt-scroll-behavior`, | Attribute | Description | Example | |-----------|-------------|---------| | `lvt-upload` | File upload identifier | `lvt-upload="avatar"` | +| `lvt-upload-with` | Send this field along with an upload fired from the same form. Opt-in — unmarked fields never reach the upload endpoint | `` | ### Preservation Attributes diff --git a/content/reference/uploads.md b/content/reference/uploads.md index eb0f02d..38b4d76 100644 --- a/content/reference/uploads.md +++ b/content/reference/uploads.md @@ -67,11 +67,44 @@ func (c *Controller) OnUpload(part *livetemplate.UploadPart, ctx *livetemplate.C ``` Inside `OnUpload`, `ctx` carries the request identity (`ctx.UserID()`, -`ctx.GroupID()`) **and** the form fields parsed *before* this file part, read via -`ctx.GetString(...)`. Because parts stream in body order, a field is visible only -if its input precedes the file input — so order any field you need mid-stream -(e.g. the record id to route the bytes to the right destination) ahead of the -file input. Fields after the file part reach only the follow-on action. +`ctx.GroupID()`) **and** the form fields the upload was told to carry, read via +`ctx.GetString(...)`. + +#### Sending form fields with an upload + +A Proxied upload auto-fires the moment a file is selected — there is no submit +for the user to review — so nothing from the enclosing form travels unless you +mark it with `lvt-upload-with`: + +```html +
+ + + +
+``` + +Here `id` reaches `OnUpload` and `csrf` does not. Mark only what the handler +needs: unmarked fields — session tokens, co-located credentials, anything else +that happens to share the form — never reach the upload endpoint. Forgetting to +mark a field you *do* need surfaces as a missing value in `OnUpload`, which is +why the default is off. + +Three rules apply on top of the marking: + +- **Order matters.** Parts stream in body order, so a marked field is readable + mid-stream only if its input precedes the file input. Fields after the file + part reach only the follow-on action. +- **Marking is by name, and form-scoped.** A marked field travels with every + upload fired from its form, and marking any one member of a radio or checkbox + group opts in the whole group. Which values actually send is still the + browser's usual successful-control behaviour — an unchecked box sends nothing. +- **The file wins a name collision.** If a marked field shares the upload + field's name, the file part replaces it. + +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. The reader enforces `MaxFileSize` mid-stream, returning `ErrUploadTooLarge` (a distinct sentinel, not `io.EOF`) so a truncated stream aborts instead of diff --git a/examples/upload-modes/upload-modes.tmpl b/examples/upload-modes/upload-modes.tmpl index 85ea4a6..b7a4b2b 100644 --- a/examples/upload-modes/upload-modes.tmpl +++ b/examples/upload-modes/upload-modes.tmpl @@ -30,10 +30,11 @@

Proxied

Bytes stream through the server to storage with zero local-disk staging. The - record id below rides along in the same multipart POST, ahead of the file - part, so OnUpload reads it mid-stream and routes the bytes per record.

+ record id below is marked lvt-upload-with, so it rides along in the + same multipart POST, ahead of the file part — OnUpload reads it + mid-stream and routes the bytes per record. Unmarked fields stay put.

- +
{{if .ProxiedRef}}
stored: {{.ProxiedRef}}