☰ MenuUX research — client flow

UX research — client flow (landing → footage delivered)

Scope. The client journey only: //order → Stripe → /order/success/jobs/[id]/upload/jobs / /jobs/[id]/jobs/[id]/review. Depth on the upload screen (B6), the make-or-break step. Every recommendation maps to a real screen/component in docs/design/PAGE-SPECS.md (referenced as B1–B8, C1–C2) and respects our architecture: the three ingest tiers (INGEST-SPEC §1), the footage-readiness gate (job can't leave NEW until an original lands and no ingest is mid-flight), authorize-now/capture-on-approval, and the unlisted posture (no SEO furniture, invited audience).

Tags. Each recommendation is tagged: [adopt now] cheap copy/layout, no new backend · [design change] needs Claude Design to redraw a component · [Phase 6+] needs backend not yet built (email, capture wiring, transcoder) · [reject] considered and declined, with reason.

Grounding note. Our uploader (uploader.tsx) uses Uppy + AwsS3 multipart to R2, not the tus protocol. This matters for several findings below: multipart resume works within a live session but does not survive a tab close/reload today — which the current copy honestly admits ("only in this session").


Theme 1 — Large-file upload UX (the B6 screen)

What the best products do

What we do today (B6, uploader.tsx)

Recommendations

  1. Make the "keep this tab open" constraint load-bearing, not a footnote. [design change] Promote it to a small persistent banner inside the Uppy re-skin (brief §7 allows CSS re-skin only), with an icon and one line: "Keep this tab open until uploads finish — they'll resume after a hiccup, but only while this tab is open." This is the single most important honesty on the page for our persona (mobile, patchy marina Wi-Fi, DESIGN-BRIEF §2). Pair it with the opposite promise on the ingest panel (already present: "Safe to close this tab") so the two paths are visibly, deliberately different.

  2. Add an aggregate progress + time-remaining line above the Dashboard. [design change] The stock per-file bars satisfy NN/G's percent-done rule per file, but a client dropping 40 GB across 12 clips has no single "how far am I" signal. Design one summary line ("4 of 12 files · 31 GB of 44 GB · ~18 min left") driven by Uppy's upload-progress/progress events. Uncertain ETAs should round generously and never count up — NN/G says variance argues for showing the reassurance sooner, not a precise-looking number that then slips.

  3. Evaluate cross-session resume for browser uploads. [Phase 6+] The real gap vs Frame.io is that our multipart session dies on tab close. Two options, in cost order: (a) persist the R2 uploadId + completed parts in localStorage and rehydrate the Uppy AwsS3 upload on return (Uppy exposes the multipart callbacks we already implement — listParts is the resume hinge); (b) longer-term, weigh a tus-backed path. Until then, do not weaken the "only in this session" copy — it's currently true and honest. Flag for a Phase-6 spike; not required for the Phase-2b gate.

  4. Surface resumability as a promise, not just recovery. [adopt now] Frame.io sells "resume with no loss of progress" as a feature up front. Our / home page (MARKETING-PAGES §1 step 2) and the B6 heading can say "large uploads survive a dropped connection" — this is trust built before the hours- long wait, which is when the client is deciding whether to believe us.

  5. Keep failure messages concrete and calm at the file level. [design change] The Uppy re-skin must keep Uppy's own per-file error affordance legible in-theme (PAGE-SPECS B6 already requires this). Add a single retry affordance per errored file rather than an all-or-nothing restart — the tus research shows silent/one-click retries keep users from perceiving the network as broken.


Theme 2 — Alternative-source ingest & choice architecture (B6)

What the best products do

What we do today (B6)

Recommendations

  1. Rank the three paths; don't present them as equals. [design change] Apply Hick's Law: drag-drop is the primary path for a client who has the files on the device in front of them; give it the most weight. "Connect a service" is inside the Dashboard already (good — it reads as a secondary tab, not a third top-level choice). Demote the paste-link panel to a quieter, collapsible "Footage somewhere else? Paste a link" disclosure rather than a full-weight <h2> section — it's the fallback path, and most clients won't need it. This keeps the visible choice count at ~1 primary + 2 progressive.

  2. Keep the manual fallback framed as service, surfaced late — never as an error. [adopt now] The queued_manual chip copy is already right ("our team will handle this", PAGE-SPECS A2b, brief §4). Reinforce this at the point of paste too, not only after failure: the sub-copy "if we can't fetch it automatically, our team will" (already in ingest-panel.tsx) is the exact MASV-style "complexity is our problem" reassurance — keep it verbatim. The manual path should never be a button the client chooses; it's an automatic degradation the server decides (INGEST-SPEC §1 Tier 3). Confirmed correct.

  3. Never show the raw pasted URL back. [adopt now] Already enforced (source_display = redacted host · filename; source_url column-revoked, PAGE-SPECS B6). This is both a privacy requirement (share links are credentials, INGEST-SPEC §4) and, incidentally, a cleaner UI. No change — flag only that Claude Design must not "helpfully" echo the link in any hover/tooltip.

  4. Distinguish the two "safe to close" postures explicitly and symmetrically. [design change] This is load-bearing (brief §4) and easy to get subtly wrong. Drag-drop = "keep tab open"; paste-link = "safe to close." Give each a matching small inline status icon + one line so a non-technical client reads the difference at a glance, rather than inferring it. (See rec 1.)


Theme 3 — Status & anticipation UX for a multi-day service (B4, B5, /jobs)

What the best products do

What we do today

Recommendations

  1. Ship the client label map (A1) as the anti-anxiety spine. [adopt now] This is the Domino's move: a small, calm set of named states, deliberately coarse. The A1 mapping is already designed to hide internal churn — QC_REJECTED and IN_REVIEW both read "Quality check" so rework is invisible (brief §4). Adopt exactly as specced; it's the single highest-leverage calm change on the pull surfaces.

  2. Render status as a horizontal stepper, not just a chip, on /jobs/[id]. [design change] Domino's power is the sense of a journey with a known end. Map the client-label states to a 4-step visual: Order received → In production → Quality check → Ready for review → Delivered (collapse ASSIGNED/IN_PROGRESS into "In production", per A1). The chip says where; the stepper says how far and how much is left. Keep it hairline-quiet (brief §6) — the chip stays the loudest element. Do not expose per-editor micro- stages (labor illusion tempts over-sharing, but identity privacy and calm win here — brief §2).

  3. Add milestone push via email at 3 moments only. [Phase 6+] Uber's lesson is push-at-milestones, pull-for-the-rest. Once Resend lands (Phase 7), notify the client on exactly: (a) footage received / production started, (b) "Ready for your review" (the one status that asks them to act — A1 marks it accent/action), (c) Approved & finals ready. Nothing in between — over-notifying a multi-day wait manufactures anxiety. Everything else stays pull (/jobs). This respects the unlisted/low-ceremony posture.

  4. Keep the footage-readiness banner concrete, and consider echoing it on the upload page. [design change] "Footage incoming — {n} import(s) pending" is exactly the operational-transparency the labor-illusion research rewards: it shows the machine working. PAGE-SPECS B6 already suggests surfacing the same getFootageReadiness() line on the upload page — do it, so the client sees "we'll start once everything lands" while their imports run, closing the loop between "I sent it" and "they have it."

  5. A stalled ingest should self-explain, Uber-style. [adopt now] A failed chip today shows an ⓘ tooltip with the error. For a non-technical client that's thin. When an ingest fails and degrades to queued_manual, the row should read as reassurance the platform noticed: "We couldn't fetch this automatically — our team will import it for you." (This is a copy change to the failure→manual transition surface, not a new state.)


Theme 4 — Premium-service checkout: authorize-now, charge-on-approval (B2, B3, C2)

What the best products do

What we do today

Recommendations

  1. Move the "authorized, not charged" promise upstream to the /order button context. [adopt now] Put a single quiet line directly under the primary button: "You won't be charged today — we authorize your card now and only charge when you approve the final edit." The industry research is explicit that the reassurance belongs on the payment page, before commitment, not only on the receipt. This is pure copy; no backend change. Reuse the exact B3 wording for consistency.

  2. Show the amount the hold is for, and that rush is included in it. [adopt now] PAGE-SPECS B2 already wants the rush surcharge shown as a line item before commit. Frame the total as "authorization hold: £X (incl. rush)" so the number the client later sees as "pending" on their statement matches what we told them — the hold-amount mismatch is a top confusion driver in the research.

  3. Make Approve (C2) the confident capture moment, with hold→charge stated plainly. [Phase 6+] When the client approves, the confirm should close the loop opened at /order: "Approving releases your payment (the hold you authorized becomes a charge) and unlocks your final files." PAGE-SPECS C2 already specs a deliberate accent-led confirm; this is the copy that connects it back to the authorize step. Note the current permission matrix makes CLIENT_REVIEW→APPROVED admin-only until the Phase-6 capture wiring — don't ship the client Approve before that.

  4. On /order/success when the webhook is still landing, keep it calm. [adopt now] The "Setting up your job… refresh your jobs →" branch (B3) must never read as a payment failure — it's expected webhook latency. Confirmed correct in spec; flag only that the copy must not say "pending" or "problem."


Theme 5 — Mobile-first brief form (B2)

What the best products do

What we do today (B2, order-form.tsx)

Recommendations

  1. Group the brief into labelled sections; keep it one page but chunked. [design change] Don't force a wizard (the brief is short-to-medium and a multi-step flow adds ceremony the persona doesn't want), but apply Baymard's "collapse future sections" idea lightly: visually group Package → Your brief → Rush & billing with hairline dividers and section headers so the phone user perceives progress and structure instead of an endless column. This is a layout change to order-form.tsx, no data change.

  2. Autosave the in-progress brief to localStorage. [design change] The highest-value mobile-form fix for our persona: persist briefData + selected package to localStorage on change, rehydrate on return, clear on successful submit. A charter manager filling a brief on marina Wi-Fi will get interrupted; losing a half-written creative brief is a silent conversion killer. No backend — it's client-state only. (Distinct from Theme 1's upload resume, but the same "survive the interruption" principle.)

  3. Right keyboard per field type; required markers not colour-only. [adopt now] type=date → native date picker, type=url on the transfer-link input (already), email inputs inputmode. PAGE-SPECS A5 already requires accessible required markers (not the crimson * alone) — confirm Claude Design ships an (required) text/aria affordance, per WCAG AA (brief §7).

  4. Validation: render data.details as a fixable checklist, preserve typed input. [adopt now] Already specced (B2). Add: never clear the form on a validation or network error — same "don't lose the client's work" rule as C2's note field. Scroll to the first error on mobile.


Cross-cutting: the unlisted, premium, engine-neutral posture


Top-10 prioritized recommendations

Ranked by impact ÷ cost, upload-screen-weighted (B6 is the make-or-break step).

# Recommendation Screen Tag Why it ranks here
1 Ship the client status label map (A1) everywhere B4, B5 adopt now Removes raw enums; the calm spine of the whole flow. Cheap, already specced.
2 Promote "keep this tab open" to a real in-Dashboard banner; mirror "safe to close" on paste-link B6 design change The one honesty that saves a marina-Wi-Fi upload. Load-bearing (brief §4).
3 Move "authorized, not charged" promise upstream to the /order button B2 adopt now Research says the hold reassurance belongs before commit; pure copy.
4 Autosave the in-progress brief to localStorage B2 design change Silent conversion killer removed; no backend.
5 Aggregate progress + honest time-remaining line above the Dashboard B6 design change NN/G: feedback makes hours-long waits feel 11–15% faster.
6 Rank the three ingest paths (drag-drop primary, paste-link collapsible) B6 design change Hick's Law; MASV "one surface" model; cuts choice paralysis.
7 Status stepper (journey view) on /jobs/[id] B5 design change Domino's effect: shows distance-to-done, cuts anxiety + support pings.
8 Milestone email push at 3 moments only (footage in, ready-for-review, approved) B4/B5 Phase 6+ Uber's push-at-milestones; needs Resend (Phase 7).
9 Cross-session resume for browser uploads (persist multipart state) B6 Phase 6+ Closes the real gap vs Frame.io; don't weaken honest copy until shipped.
10 Group the brief into hairline-divided sections B2 design change Baymard: perceived progress on a long mobile brief; layout-only.

Confirmed-correct (no change needed): redacted ingest source display (B6); queued_manual framed as service not error (A2b); /order/success authorized-not-charged as the visual focus (B3); single-column mobile brief (A5); manual fallback as automatic server degradation, never a client-chosen button.