From 1e5f55c1d6e46c8d293c891507cdf036edb2ba06 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 27 Jan 2026 20:07:57 +0200 Subject: [PATCH 01/29] fix Point is invalid --- app/javascript/draw.js | 2 +- app/javascript/elements/signature_form.js | 2 +- app/javascript/submission_form/signature_step.vue | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/draw.js b/app/javascript/draw.js index 2a0a917c..3b95b5ea 100644 --- a/app/javascript/draw.js +++ b/app/javascript/draw.js @@ -89,7 +89,7 @@ window.customElements.define('draw-signature', class extends HTMLElement { } redrawCanvas (oldWidth, oldHeight) { - if (this.pad && !this.pad.isEmpty()) { + if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) { const sx = this.canvas.width / oldWidth const sy = this.canvas.height / oldHeight diff --git a/app/javascript/elements/signature_form.js b/app/javascript/elements/signature_form.js index b7ce43e5..5fc9af48 100644 --- a/app/javascript/elements/signature_form.js +++ b/app/javascript/elements/signature_form.js @@ -80,7 +80,7 @@ export default targetable(class extends HTMLElement { } redrawCanvas (oldWidth, oldHeight) { - if (this.pad && !this.pad.isEmpty()) { + if (this.pad && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) { const sx = this.canvas.width / oldWidth const sy = this.canvas.height / oldHeight diff --git a/app/javascript/submission_form/signature_step.vue b/app/javascript/submission_form/signature_step.vue index d7cb86fb..19e9ab2b 100644 --- a/app/javascript/submission_form/signature_step.vue +++ b/app/javascript/submission_form/signature_step.vue @@ -514,7 +514,7 @@ export default { redrawCanvas (oldWidth, oldHeight) { const canvas = this.$refs.canvas - if (this.pad && !this.isTextSignature && !this.pad.isEmpty()) { + if (this.pad && !this.isTextSignature && !this.pad.isEmpty() && oldWidth > 0 && oldHeight > 0) { const sx = canvas.width / oldWidth const sy = canvas.height / oldHeight From 5081e387d01de4e2a589f23350827dfe60812e28 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Tue, 27 Jan 2026 20:09:50 +0200 Subject: [PATCH 02/29] fix undefined --- app/javascript/template_builder/contenteditable.vue | 7 +++++-- app/javascript/template_builder/document.vue | 10 +++++++++- app/javascript/template_builder/upload.vue | 6 +++++- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/javascript/template_builder/contenteditable.vue b/app/javascript/template_builder/contenteditable.vue index 1ee20ddb..c17724fb 100644 --- a/app/javascript/template_builder/contenteditable.vue +++ b/app/javascript/template_builder/contenteditable.vue @@ -179,8 +179,11 @@ export default { }, onBlur (e) { setTimeout(() => { - this.value = this.$refs.contenteditable.innerText.trim() || this.modelValue - this.$emit('update:model-value', this.value) + if (this.$refs.contenteditable) { + this.value = this.$refs.contenteditable.innerText.trim() || this.modelValue + this.$emit('update:model-value', this.value) + } + this.$emit('blur', e) this.isEditable = false diff --git a/app/javascript/template_builder/document.vue b/app/javascript/template_builder/document.vue index 040f6edb..fea0ed05 100644 --- a/app/javascript/template_builder/document.vue +++ b/app/javascript/template_builder/document.vue @@ -180,7 +180,15 @@ export default { methods: { scrollToArea (area) { this.$nextTick(() => { - this.pageRefs[area.page].areaRefs.find((e) => e.area === area).$el.scrollIntoView({ behavior: 'smooth', block: 'center' }) + const pageRef = this.pageRefs[area.page] + + if (pageRef && pageRef.areaRefs) { + const areaRef = pageRef.areaRefs.find((e) => e.area === area) + + if (areaRef && areaRef.$el) { + areaRef.$el.scrollIntoView({ behavior: 'smooth', block: 'center' }) + } + } }) }, setPageRefs (el) { diff --git a/app/javascript/template_builder/upload.vue b/app/javascript/template_builder/upload.vue index 5f4faa57..813ce12e 100644 --- a/app/javascript/template_builder/upload.vue +++ b/app/javascript/template_builder/upload.vue @@ -290,7 +290,11 @@ export default { if (resp.ok) { resp.json().then((data) => { this.$emit('success', data) - this.$refs.input.value = '' + + if (this.$refs.input) { + this.$refs.input.value = '' + } + this.isLoading = false }) } else if (resp.status === 422) { From 43f1b598a72e01ecc9ce2aa737d25403da0c10b2 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 28 Jan 2026 08:27:25 +0200 Subject: [PATCH 03/29] adjust smtp noverify --- lib/action_mailer_configs_interceptor.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/action_mailer_configs_interceptor.rb b/lib/action_mailer_configs_interceptor.rb index 3f09abfa..5b2ad19b 100644 --- a/lib/action_mailer_configs_interceptor.rb +++ b/lib/action_mailer_configs_interceptor.rb @@ -47,6 +47,9 @@ module ActionMailerConfigsInterceptor is_tls = value['security'] == 'tls' || (value['security'].blank? && value['port'].to_s == '465') is_ssl = value['security'] == 'ssl' + is_noverify = value['security'] == 'noverify' + + enable_starttls = is_noverify ? :enable_starttls_auto : :enable_starttls { user_name: value['username'], @@ -54,9 +57,9 @@ module ActionMailerConfigsInterceptor address: value['host'], port: value['port'], domain: value['domain'], - openssl_verify_mode: value['security'] == 'noverify' ? OpenSSL::SSL::VERIFY_NONE : nil, + openssl_verify_mode: is_noverify ? OpenSSL::SSL::VERIFY_NONE : nil, authentication: value['password'].present? ? value.fetch('authentication', 'plain') : nil, - enable_starttls: !is_tls && !is_ssl, + enable_starttls => !is_tls && !is_ssl, open_timeout: OPEN_TIMEOUT, read_timeout: READ_TIMEOUT, ssl: is_ssl, From c82c8b22727b2b90f75f3e4d9d9595f2882f8bb0 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 28 Jan 2026 08:50:44 +0200 Subject: [PATCH 04/29] fix race condition --- lib/submissions/ensure_result_generated.rb | 2 ++ lib/submissions/generate_result_attachments.rb | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/submissions/ensure_result_generated.rb b/lib/submissions/ensure_result_generated.rb index 39f4ef2f..66d3e9e4 100644 --- a/lib/submissions/ensure_result_generated.rb +++ b/lib/submissions/ensure_result_generated.rb @@ -32,6 +32,8 @@ module Submissions LockEvent.create!(key:, event_name: :complete) + submitter.documents.reset + documents end rescue ActiveRecord::RecordNotUnique diff --git a/lib/submissions/generate_result_attachments.rb b/lib/submissions/generate_result_attachments.rb index 47293728..dd5a231a 100644 --- a/lib/submissions/generate_result_attachments.rb +++ b/lib/submissions/generate_result_attachments.rb @@ -790,10 +790,10 @@ module Submissions def build_pdfs_index(submission, submitter: nil, flatten: true) latest_submitter = find_last_submitter(submission, submitter:) - Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter + documents = Submissions::EnsureResultGenerated.call(latest_submitter) if latest_submitter + documents ||= submission.schema_documents - documents = latest_submitter&.documents&.preload(:blob).to_a.presence - documents ||= submission.schema_documents.preload(:blob) + ActiveRecord::Associations::Preloader.new(records: documents, associations: [:blob]).call attachment_uuids = Submissions.filtered_conditions_schema(submission).pluck('attachment_uuid') attachments_index = documents.index_by { |a| a.metadata['original_uuid'] || a.uuid } From b5e57e1f2fd7998072859691571f3f7ab2c5727e Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 28 Jan 2026 11:49:35 +0200 Subject: [PATCH 05/29] add include fields --- lib/submissions/serialize_for_api.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/submissions/serialize_for_api.rb b/lib/submissions/serialize_for_api.rb index 0366ebc5..4569e9bb 100644 --- a/lib/submissions/serialize_for_api.rb +++ b/lib/submissions/serialize_for_api.rb @@ -14,6 +14,7 @@ module Submissions module_function + # rubocop:disable Metrics def call(submission, submitters = nil, params = {}, with_events: true, with_documents: true, with_values: true, expires_at: Accounts.link_expires_at(Account.new(id: submission.account_id))) submitters ||= submission.submitters.preload(documents_attachments: :blob, attachments_attachments: :blob) @@ -32,6 +33,10 @@ module Submissions json['submission_events'] = Submitters::SerializeForApi.serialize_events(submission.submission_events) end + if params[:include].to_s.include?('fields') + json['fields'] = submission.template_fields || submission.template&.fields + end + if submitters.all?(&:completed_at?) last_submitter = submitters.max_by(&:completed_at) @@ -57,6 +62,7 @@ module Submissions json end + # rubocop:enable Metrics def build_status(submission, submitters) if submitters.any?(&:declined_at?) From d2c9bbf6ab7ae8a0231853ed6919c9997fe78d12 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 28 Jan 2026 13:24:05 +0200 Subject: [PATCH 06/29] optimize conditions --- app/javascript/submission_form/form.vue | 38 +++++++++++++++++-------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/app/javascript/submission_form/form.vue b/app/javascript/submission_form/form.vue index fe264ddb..3f78624b 100644 --- a/app/javascript/submission_form/form.vue +++ b/app/javascript/submission_form/form.vue @@ -926,10 +926,12 @@ export default { }, {}) }, attachmentConditionsIndex () { + const cache = {} + return this.schema.reduce((acc, item) => { if (item.conditions?.length) { if (item.conditions.every((c) => this.fieldsUuidIndex[c.field_uuid])) { - acc[item.attachment_uuid] = this.checkFieldConditions(item) + acc[item.attachment_uuid] = this.checkFieldConditions(item, cache) } else { acc[item.attachment_uuid] = true } @@ -1023,7 +1025,9 @@ export default { return this.readonlyFields.filter((f) => f.conditions?.length) }, readonlyFields () { - return this.fields.filter((f) => f.readonly && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) + const cache = {} + + return this.fields.filter((f) => f.readonly && this.checkFieldConditions(f, cache) && this.checkFieldDocumentsConditions(f)) }, stepFields () { const verificationFields = [] @@ -1078,10 +1082,12 @@ export default { sortedFields.push(verificationFields.pop()) } + const cache = {} + return sortedFields.reduce((acc, f) => { const prevStep = acc[acc.length - 1] - if (this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) { + if (this.checkFieldConditions(f, cache) && this.checkFieldDocumentsConditions(f)) { if (f.type === 'checkbox' && Array.isArray(prevStep) && prevStep[0].type === 'checkbox' && !f.description) { prevStep.push(f) } else { @@ -1093,7 +1099,9 @@ export default { }, []) }, formulaFields () { - return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment' && this.checkFieldConditions(f) && this.checkFieldDocumentsConditions(f)) + const cache = {} + + return this.fields.filter((f) => f.preferences?.formula && f.type !== 'payment' && this.checkFieldConditions(f, cache) && this.checkFieldDocumentsConditions(f)) }, attachmentsIndex () { return this.attachments.reduce((acc, a) => { @@ -1223,27 +1231,33 @@ export default { return true } }, - checkFieldConditions (field) { + checkFieldConditions (field, cache = {}) { + if (cache[field.uuid] !== undefined) { + return cache[field.uuid] + } + + cache[field.uuid] = true + if (field.conditions?.length) { const result = field.conditions.reduce((acc, cond) => { if (cond.operation === 'or') { - acc.push(acc.pop() || this.checkFieldCondition(cond)) + acc.push(acc.pop() || this.checkFieldCondition(cond, cache)) } else { - acc.push(this.checkFieldCondition(cond)) + acc.push(this.checkFieldCondition(cond, cache)) } return acc }, []) - return !result.includes(false) - } else { - return true + cache[field.uuid] = !result.includes(false) } + + return cache[field.uuid] }, - checkFieldCondition (condition) { + checkFieldCondition (condition, cache = {}) { const field = this.fieldsUuidIndex[condition.field_uuid] - if (['not_empty', 'checked', 'equal', 'contains'].includes(condition.action) && field && !this.checkFieldConditions(field)) { + if (['not_empty', 'checked', 'equal', 'contains'].includes(condition.action) && field && !this.checkFieldConditions(field, cache)) { return false } From 4f5acbec9daa3dfe666d3ec97c29ed545a64dbb6 Mon Sep 17 00:00:00 2001 From: Pete Matsyburka Date: Wed, 28 Jan 2026 21:36:48 +0200 Subject: [PATCH 07/29] readonly radio --- app/javascript/template_builder/context_menu.vue | 2 +- app/javascript/template_builder/field_settings.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/javascript/template_builder/context_menu.vue b/app/javascript/template_builder/context_menu.vue index 4ad6972b..8e1bb19d 100644 --- a/app/javascript/template_builder/context_menu.vue +++ b/app/javascript/template_builder/context_menu.vue @@ -399,7 +399,7 @@ export default { showReadOnly () { if (!this.field) return false - return ['text', 'number'].includes(this.field.type) + return ['text', 'number', 'radio', 'multiple', 'select'].includes(this.field.type) }, isRequired () { return this.field?.required || false diff --git a/app/javascript/template_builder/field_settings.vue b/app/javascript/template_builder/field_settings.vue index 032f6a62..d807c2ff 100644 --- a/app/javascript/template_builder/field_settings.vue +++ b/app/javascript/template_builder/field_settings.vue @@ -437,7 +437,7 @@
  • -
    - - -
    -
  • +