{"record":{"id":"1a6867e0ec4186ac","repo":"onetimesecret/onetimesecret","slug":"incoming-validation-form-errors","errorCode":null,"errorMessage":"incoming.validation_form_errors","messagePattern":"incoming\\.validation_form_errors","errorType":"exception","errorClass":"ApplicationError","httpStatus":null,"severity":"warning","filePath":"src/shared/composables/useIncomingSecret.ts","lineNumber":151,"sourceCode":"   * Creates API payload from form data\n   */\n  const createPayload = (): IncomingSecretPayload => ({\n    memo: form.value.memo.trim() || '', // Empty string if no memo provided\n    secret: form.value.secret,\n    recipient: form.value.recipientId,\n  });\n\n  /**\n   * Handles form submission\n   */\n  const submit = () =>\n    wrapSubmit(async () => {\n      if (!isFeatureEnabled.value) {\n        throw createError(t('incoming.validation_feature_disabled'), 'human');\n      }\n\n      if (!validateForm()) {\n        throw createError(t('incoming.validation_form_errors'), 'human');\n      }\n\n      const payload = createPayload();\n      const response = await incomingStore.createIncomingSecret(payload);\n\n      if (options?.onSuccess) {\n        await options.onSuccess(response);\n      } else if (response.success && response.record?.receipt?.key) {\n        // Default navigation to success view\n        await router.push({\n          name: 'IncomingSuccess',\n          params: { receiptKey: response.record.receipt.key },\n        });\n      }\n\n      return response;\n    });\n","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/onetimesecret/onetimesecret/blob/f81295e41bc3e808a3dd2cbea03ef65401a662e4/src/shared/composables/useIncomingSecret.ts#L133-L169","documentation":"After the feature check, submit() runs validateForm(): memo is optional but must not exceed memoMaxLength (from config, default 50), secret must be non-empty after trim, and recipientId must be chosen. If any check fails, this aggregate human error is thrown while the per-field messages land in the errors ref (incoming.validation_secret_required, incoming.validation_recipient_required, incoming.validation_memo_too_long) for inline display.","triggerScenarios":"Empty or whitespace-only secret; empty recipientId; memo longer than memo_max_length from /api/incoming/config (default 50 characters).","commonSituations":"User hits submit before selecting a recipient; long pasted memo because the input lacks a maxlength binding; programmatic submit without touching the form fields.","solutions":["Read the errors ref after the failure - it names the exact offending field(s).","Disable the submit button while !isFormValid (the composable already exposes it) so this throw never fires.","Bind :maxlength=\"memoMaxLength\" on the memo input to make the length failure impossible.","Require the recipient select before enabling submit."],"exampleFix":"// before\n<button @click=\"submit\">Send</button>\n\n// after\n<button :disabled=\"!isFormValid || isSubmitting\" @click=\"submit\">Send</button>\n<input v-model=\"form.memo\" :maxlength=\"memoMaxLength\" />","handlingStrategy":"validation","validationCode":"const canSubmit = computed(\n  () =>\n    !!form.value.secret.trim() &&\n    !!form.value.recipientId &&\n    form.value.memo.length <= memoMaxLength.value\n)\nif (!canSubmit.value) {\n  // highlight offending fields; do not call submit()\n}","typeGuard":null,"tryCatchPattern":"submit() toasts the aggregate message via wrap; rely on the errors ref for per-field display and isFormValid to prevent the call entirely. No try/catch is needed.","preventionTips":["Bind the submit button's disabled state to isFormValid","Bind :maxlength on text inputs matched to server limits","Validate on blur for early feedback","Never prefill recipientId with an unvalidated value"],"tags":["form-validation","incoming-secrets","client-side-validation","vue"],"backgroundTag":"form-validation-failed","analyzedSha":"f81295e41bc3e808a3dd2cbea03ef65401a662e4","analyzedAt":"2026-08-23T21:13:10.635Z","schemaVersion":2},"datasetVersion":"2026-08-24T02:17:37.098Z"}