From 4012022f5562f0e133a43765fa3300546b5bf459 Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Tue, 26 Aug 2025 11:08:43 +1000 Subject: [PATCH] fix: element visible race condition (#1996) On larger documents we could accidentally start trying to render fields while not all pages of the PDF have loaded due to us checking for a single page existing. This would cause an error to be thrown, hard locking those documents. This change resolves this by grabbing the highest page number from the given fields and using it for the visibility check instead. --- .../components/embed/authoring/configure-fields-view.tsx | 6 +++++- .../components/embed/embed-direct-template-client-page.tsx | 6 +++++- apps/remix/app/components/embed/embed-document-fields.tsx | 4 +++- .../app/components/embed/embed-document-signing-page.tsx | 6 +++++- .../embed/multisign/multi-sign-document-signing-view.tsx | 6 +++++- .../direct-template/direct-template-signing-form.tsx | 6 +++++- .../general/document-signing/document-signing-page-view.tsx | 6 +++++- .../_authenticated+/t.$teamUrl+/documents.$id._index.tsx | 1 + .../ui/components/document/document-read-only-fields.tsx | 4 +++- 9 files changed, 37 insertions(+), 8 deletions(-) diff --git a/apps/remix/app/components/embed/authoring/configure-fields-view.tsx b/apps/remix/app/components/embed/authoring/configure-fields-view.tsx index 60b732a76..21faee750 100644 --- a/apps/remix/app/components/embed/authoring/configure-fields-view.tsx +++ b/apps/remix/app/components/embed/authoring/configure-fields-view.tsx @@ -172,6 +172,8 @@ export const ConfigureFieldsView = ({ name: 'fields', }); + const highestPageNumber = Math.max(...localFields.map((field) => field.pageNumber)); + const onFieldCopy = useCallback( (event?: KeyboardEvent | null, options?: { duplicate?: boolean; duplicateAll?: boolean }) => { const { duplicate = false, duplicateAll = false } = options ?? {}; @@ -540,7 +542,9 @@ export const ConfigureFieldsView = ({
- + {localFields.map((field, index) => { const recipientIndex = recipients.findIndex( (r) => r.id === field.recipientId, diff --git a/apps/remix/app/components/embed/embed-direct-template-client-page.tsx b/apps/remix/app/components/embed/embed-direct-template-client-page.tsx index 09f6a91d2..8b883a36b 100644 --- a/apps/remix/app/components/embed/embed-direct-template-client-page.tsx +++ b/apps/remix/app/components/embed/embed-direct-template-client-page.tsx @@ -91,6 +91,8 @@ export const EmbedDirectTemplateClientPage = ({ localFields.filter((field) => field.inserted), ]; + const highestPendingPageNumber = Math.max(...pendingFields.map((field) => field.page)); + const hasSignatureField = localFields.some((field) => field.type === FieldType.SIGNATURE); const { mutateAsync: createDocumentFromDirectTemplate, isPending: isSubmitting } = @@ -442,7 +444,9 @@ export const EmbedDirectTemplateClientPage = ({
- + {showPendingFieldTooltip && pendingFields.length > 0 && ( Click to insert field diff --git a/apps/remix/app/components/embed/embed-document-fields.tsx b/apps/remix/app/components/embed/embed-document-fields.tsx index 561fdf4cb..ea14b3f1f 100644 --- a/apps/remix/app/components/embed/embed-document-fields.tsx +++ b/apps/remix/app/components/embed/embed-document-fields.tsx @@ -50,8 +50,10 @@ export const EmbedDocumentFields = ({ onSignField, onUnsignField, }: EmbedDocumentFieldsProps) => { + const highestPageNumber = Math.max(...fields.map((field) => field.page)); + return ( - + {fields.map((field) => match(field.type) .with(FieldType.SIGNATURE, () => ( diff --git a/apps/remix/app/components/embed/embed-document-signing-page.tsx b/apps/remix/app/components/embed/embed-document-signing-page.tsx index ef2eedc1c..d7d8a0713 100644 --- a/apps/remix/app/components/embed/embed-document-signing-page.tsx +++ b/apps/remix/app/components/embed/embed-document-signing-page.tsx @@ -106,6 +106,8 @@ export const EmbedSignDocumentClientPage = ({ fields.filter((field) => field.inserted), ]; + const highestPendingPageNumber = Math.max(...pendingFields.map((field) => field.page)); + const { mutateAsync: completeDocumentWithToken, isPending: isSubmitting } = trpc.recipient.completeDocumentWithToken.useMutation(); @@ -465,7 +467,9 @@ export const EmbedSignDocumentClientPage = ({ - + {showPendingFieldTooltip && pendingFields.length > 0 && ( Click to insert field diff --git a/apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx b/apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx index 37abdcce3..5867a1a2a 100644 --- a/apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx +++ b/apps/remix/app/components/embed/multisign/multi-sign-document-signing-view.tsx @@ -92,6 +92,8 @@ export const MultiSignDocumentSigningView = ({ [], ]; + const highestPendingPageNumber = Math.max(...pendingFields.map((field) => field.page)); + const uninsertedFields = document?.fields.filter((field) => !field.inserted) ?? []; const onSignField = async (payload: TSignFieldWithTokenMutationSchema) => { @@ -357,7 +359,9 @@ export const MultiSignDocumentSigningView = ({ {hasDocumentLoaded && ( - + {showPendingFieldTooltip && pendingFields.length > 0 && ( field.page)); + const fieldsRequiringValidation = useMemo(() => { return localFields.filter((field) => isFieldUnsignedAndRequired(field)); }, [localFields]); @@ -221,7 +223,9 @@ export const DirectTemplateSigningForm = ({ - + {validateUninsertedFields && uninsertedFields[0] && ( Click to insert field diff --git a/apps/remix/app/components/general/document-signing/document-signing-page-view.tsx b/apps/remix/app/components/general/document-signing/document-signing-page-view.tsx index a1bf3d24e..cbbdc7926 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-page-view.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-page-view.tsx @@ -78,6 +78,8 @@ export const DocumentSigningPageView = ({ const targetSigner = recipient.role === RecipientRole.ASSISTANT && selectedSigner ? selectedSigner : null; + const highestPageNumber = Math.max(...fields.map((field) => field.page)); + return (
@@ -224,7 +226,9 @@ export const DocumentSigningPageView = ({ )} - + {fields .filter( (field) => diff --git a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx index 75592696c..84c9c6883 100644 --- a/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx +++ b/apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents.$id._index.tsx @@ -67,6 +67,7 @@ export async function loader({ params, request }: Route.LoaderArgs) { const documentVisibility = document?.visibility; const currentTeamMemberRole = team.currentTeamRole; const isRecipient = document?.recipients.find((recipient) => recipient.email === user.email); + let canAccessDocument = true; if (!isRecipient && document?.userId !== user.id) { diff --git a/packages/ui/components/document/document-read-only-fields.tsx b/packages/ui/components/document/document-read-only-fields.tsx index 0786357d1..d1c96f8a9 100644 --- a/packages/ui/components/document/document-read-only-fields.tsx +++ b/packages/ui/components/document/document-read-only-fields.tsx @@ -95,8 +95,10 @@ export const DocumentReadOnlyFields = ({ setHiddenFieldIds((prev) => ({ ...prev, [fieldId]: true })); }; + const highestPageNumber = Math.max(...fields.map((field) => field.page)); + return ( - + {fields.map( (field) => !hiddenFieldIds[field.secondaryId] && (