Conversation
Adds a template picker to the Contact Support dialog so customers
submit structured first messages instead of vague ones, reducing
the back-and-forth needed to triage. Selected template prefills the
textarea (without clobbering user edits), drives the Plain thread
title, and is captured in PostHog as `template_id` for adoption
tracking. Default behavior ("Something else") is unchanged.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
||
| const handleTemplateChange = useCallback( | ||
| (nextId: SupportTemplateId) => { | ||
| const nextPrefill = getSupportTemplate(nextId).prefill | ||
| const current = form.getValues('description') | ||
| if (current === '' || current === lastPrefillRef.current) { | ||
| form.setValue('description', nextPrefill, { | ||
| shouldValidate: true, | ||
| shouldDirty: nextPrefill.length > 0, | ||
| }) | ||
| } | ||
| lastPrefillRef.current = nextPrefill | ||
| form.setValue('templateId', nextId, { shouldValidate: true }) | ||
| }, | ||
| [form] |
There was a problem hiding this comment.
🟡 When a user picks a non-default template (so the prefill is applied) and then switches back to Something else, handleTemplateChange runs form.setValue('description', '', { shouldValidate: true }). The empty string immediately fails the description.min(1) rule and FormMessage renders Please describe how we can help under the textarea before the user has typed anything. Consider passing shouldValidate: false (or skipping the setValue call) when transitioning to the empty default — the field will still be invalid, so the submit button stays disabled, but the error message won't appear until the user has actually had a chance to type.
Extended reasoning...
What happens
In report-issue-dialog.tsx:149-163, handleTemplateChange runs:
if (current === '' || current === lastPrefillRef.current) {
form.setValue('description', nextPrefill, {
shouldValidate: true,
shouldDirty: nextPrefill.length > 0,
})
}When nextPrefill is the empty string (only something_else has prefill: '') and the second branch (current === lastPrefillRef.current) is hit, the description gets cleared with shouldValidate: true. RHF runs the resolver synchronously, the zod description.min(1, 'Please describe how we can help') rule fails, and formState.errors.description is populated.
Why the message renders
The shared FormMessage (src/ui/primitives/form.tsx) renders any error in fieldState unconditionally — it does not gate on touched or dirty. So the moment shouldValidate: true produces an error, the textarea grows a Please describe how we can help line under it, even though the user has not typed anything in this category and just used the picker.
Step-by-step proof
- Open dialog:
description = '',templateId = 'something_else',lastPrefillRef = ''. No error (RHF default mode isonSubmit, nothing has triggered validation). - Pick Issue with a sandbox:
current === ''→ setValue with the sandbox prefill.shouldValidate: truevalidates, but the prefill is non-empty somin(1)passes.lastPrefillRef.currentbecomes the sandbox prefill. - Pick Something else:
nextPrefill = '',current === lastPrefillRef.currentis true, soform.setValue('description', '', { shouldValidate: true })runs.min(1)fails →formState.errors.descriptionis set →FormMessagerenders the error under the still-empty textarea.
Addressing the counter-argument
It is true that (a) submission relies on form.formState.isValid, so the field must validate, and (b) eager inline validation is not by itself an anti-pattern. The issue is specifically the timing: the error surfaces in response to a category change, not in response to user input on that field. The user has not had any opportunity to enter content for the new category before being told it is missing. That distinguishes it from the typical eager-validation cases (validate on blur, on submit, or after first keystroke).
Fix
Either pass shouldValidate: false when applying an empty prefill, or skip the setValue entirely when nextPrefill === '' (and clear by other means if needed). The form will remain invalid (the existing !form.formState.isValid guard already disables the Send button regardless of whether the resolver has run), but the visible error won't appear until the user actually engages with the textarea. Marking as nit — minor visible UX papercut, no functional/correctness impact.
Summary
report-issue-dialog.tsx). Defaults to "Something else" (current free-text behavior). Selecting a template prefills the textarea without clobbering user edits.src/core/modules/support/templates.tsexportingSUPPORT_TEMPLATESas the single source of truth for client and server (sandbox issue, snapshot/pause/resume, performance, cancel, refund, delete account, change owner, limit increase, volumes, EU cluster, enterprise, self-hosting, startup program, something else).contactSupportaccepts an optionaltemplateIdand the repository sets the Plain thread title to<Template title> [Team Name]when a non-default template is chosen; otherwise unchanged.support_request_submittednow includestemplate_idfor per-category adoption tracking.Test plan
template_idappears in the PostHog event.Support Request [Team Name]and free-text behavior unchanged.e2b_…in prefilled+edited content.