{"record":{"id":"9376b066f3a64228","repo":"koala73/worldmonitor","slug":"name-is-required-9376b0","errorCode":null,"errorMessage":"Name is required","messagePattern":"Name is required","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/leads/v1/submit-contact.ts","lineNumber":162,"sourceCode":"    logPrefix: '[contact]',\n  });\n  if (!turnstileOk) {\n    throw new ApiError(403, 'Bot verification failed', '');\n  }\n\n  const { email, name, organization, phone, message, source } = req;\n\n  if (!email || !EMAIL_RE.test(email)) {\n    throw new ValidationError([{ field: 'email', description: 'Invalid email' }]);\n  }\n\n  const emailDomain = email.split('@')[1]?.toLowerCase();\n  if (emailDomain && FREE_EMAIL_DOMAINS.has(emailDomain)) {\n    throw new ApiError(422, 'Please use your work email address', '');\n  }\n\n  if (!name || name.trim().length === 0) {\n    throw new ValidationError([{ field: 'name', description: 'Name is required' }]);\n  }\n  if (!organization || organization.trim().length === 0) {\n    throw new ValidationError([{ field: 'organization', description: 'Company is required' }]);\n  }\n  if (!phone || !PHONE_RE.test(phone.trim())) {\n    throw new ValidationError([{ field: 'phone', description: 'Valid phone number is required' }]);\n  }\n\n  const safeName = name.slice(0, MAX_FIELD);\n  const safeOrg = organization.slice(0, MAX_FIELD);\n  const safePhone = phone.trim().slice(0, 30);\n  const safeMsg = message ? message.slice(0, MAX_MESSAGE) : undefined;\n  const safeSource = source ? source.slice(0, 100) : 'enterprise-contact';\n\n  const convexUrl = process.env.CONVEX_URL;\n  if (!convexUrl) {\n    throw new ApiError(503, 'Service unavailable', '');\n  }","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/koala73/worldmonitor/blob/a96956387a927b8cd7aa34b0c41fca357e746be9/server/worldmonitor/leads/v1/submit-contact.ts#L144-L180","documentation":"Required-field validation on the contact form (submit-contact.ts:161): name must be present AND name.trim().length > 0. Empty string, undefined, or whitespace-only fails with a ValidationError on field 'name'. Values are truncated to MAX_FIELD=500 only AFTER this check, so any non-blank value 1-500+ chars passes.","triggerScenarios":"Submitting the form without typing a name; a whitespace-only value (user hit space); the name input not bound to state so undefined ships; a test payload omitting the field entirely.","commonSituations":"Forms where the name field is optional in the UI but required server-side; state initialization bugs leaving name undefined; paste of an invisible/zero-width character passes trim() only if non-whitespace — plain spaces are the usual culprit.","solutions":["Mark the name input required and validate non-blank after trim client-side","Initialize form state with '' and check name.trim() before enabling submit","On the server error, map violations[].field === 'name' back to the input for display"],"exampleFix":"// before\nawait submitContact({ name: '   ', ... }); // whitespace-only -> ValidationError\n\n// after\nconst name = rawName.trim();\nif (!name) throw new Error('name required');\nawait submitContact({ name, ... });","handlingStrategy":"validation","validationCode":"const name = rawName.trim();\nif (!name) { showFieldError('name', 'Name is required'); return; }\nawait submitContact({ ...form, name });","typeGuard":"function isNonBlankString(v: unknown): v is string {\n  return typeof v === 'string' && v.trim().length > 0;\n}","tryCatchPattern":"try { await submitContact(form); }\ncatch (e) {\n  if (isValidationError(e)) {\n    for (const v of e.violations ?? []) if (v.field === 'name') showFieldError('name', v.description);\n    return;\n  }\n  throw e;\n}","preventionTips":["Validate all required fields client-side after trim() before enabling submit","Run the full field battery at once so users fix everything in one pass","Bind the input to initialized ('') state so undefined never ships"],"tags":["validation","required-field","form-input"],"backgroundTag":"missing-required-argument","analyzedSha":"a96956387a927b8cd7aa34b0c41fca357e746be9","analyzedAt":"2026-08-21T16:51:25.751Z","contentChangedAt":"2026-08-21T16:51:25.751Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}