Skip to content

feat: add prebuilt templates to support form#316

Open
beran-t wants to merge 1 commit intomainfrom
support-templates
Open

feat: add prebuilt templates to support form#316
beran-t wants to merge 1 commit intomainfrom
support-templates

Conversation

@beran-t
Copy link
Copy Markdown
Contributor

@beran-t beran-t commented May 5, 2026

Summary

  • New template picker in the Contact Support dialog (report-issue-dialog.tsx). Defaults to "Something else" (current free-text behavior). Selecting a template prefills the textarea without clobbering user edits.
  • New src/core/modules/support/templates.ts exporting SUPPORT_TEMPLATES as 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).
  • tRPC contactSupport accepts an optional templateId and the repository sets the Plain thread title to <Template title> [Team Name] when a non-default template is chosen; otherwise unchanged.
  • PostHog support_request_submitted now includes template_id for per-category adoption tracking.

Test plan

  • Open Contact Support dialog: picker is visible below the message field, defaults to "Something else".
  • Select "Issue with a sandbox" with empty textarea → textarea fills with the labeled prefill.
  • Edit the prefilled text, switch templates → user edits are preserved.
  • Switch templates without editing → textarea swaps to the new prefill.
  • Submit with a non-default template → Plain thread title uses the template title; template_id appears in the PostHog event.
  • Submit with "Something else" → existing title Support Request [Team Name] and free-text behavior unchanged.
  • API key regex still blocks submissions containing e2b_… in prefilled+edited content.

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.
@beran-t beran-t requested a review from ben-fornefeld as a code owner May 5, 2026 14:17
@vercel
Copy link
Copy Markdown

vercel Bot commented May 5, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web Ready Ready Preview, Comment May 5, 2026 2:18pm
web-juliett Ready Ready Preview, Comment May 5, 2026 2:18pm

Request Review

Comment on lines 149 to +163

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]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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

  1. Open dialog: description = '', templateId = 'something_else', lastPrefillRef = ''. No error (RHF default mode is onSubmit, nothing has triggered validation).
  2. Pick Issue with a sandbox: current === '' → setValue with the sandbox prefill. shouldValidate: true validates, but the prefill is non-empty so min(1) passes. lastPrefillRef.current becomes the sandbox prefill.
  3. Pick Something else: nextPrefill = '', current === lastPrefillRef.current is true, so form.setValue('description', '', { shouldValidate: true }) runs. min(1) fails → formState.errors.description is set → FormMessage renders 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant