The file drop zone
What happens the moment a file crosses the threshold.
Dragging a file into the composer is one of the most satisfying gestures in software. It's also one of the easiest to ruin. The classic mistakes: a single spinner for the whole batch, vague success states, no indication of what "parsed" actually means. The difference between a dropped file feeling magical and feeling fragile is entirely in the status layer.
"The drop is the easy part. The parse is the product."
Per-file status, never a global spinner.
When multiple files are dropped, each one gets its own row. Each row has a name, size, and status — queued, parsing, ready. Status moves forward as each file lands. One file finishing does not wait on another file parsing.
The zone itself is a permanent part of the composer at rest, not a modal. Hover reveals a subtle border highlight. No bouncing arrows, no animated clouds.
Batches die on their slowest member.
The moment you wrap a batch of files behind a single progress bar, the user experiences the batch as if it were one file — pegged to whichever one is slowest. Per-file rows let the fast files become usable immediately. The psychological effect is not small.
Small details that compound.
- Pre-parse preview. As soon as the file hits the queue, show page count or row count. The user learns the file's shape before it's fully read.
- Parse feedback. Not just "parsing." Say "extracting tables" or "running OCR." The verb is a trust signal.
- Usable partials. A file still parsing should still be addressable in the composer. Let the prompt queue behind the parse, don't block the compose.
The global spinner is a lie about batches.
A single spinner over five dropped files tells the user nothing real. When it finally ends, the user has no way to verify that every file was handled, or that any failure was surfaced. Trust is built by legible, individual transitions, not by generic motion.
What this pattern gets wrong when it gets wrong.
- Silent truncate
- The response ran out of room or tokens and the product didn't tell the user where it stopped.
- Leaky context
- Content from another source, session, or user surfacing in a place it shouldn't.
- Ambiguous state
- Running, done, errored, paused all look the same. The user has to infer from context.
Three shipping variants worth copying.
- A 1px brass border that fades in over 200ms
- Rejected files trigger a single-line inline message, not a toast
- The zone becomes the whole page, not just the composer