From ef407cb0b4643b9c0f9ec68a742dd25aab3eca32 Mon Sep 17 00:00:00 2001 From: Catalin Pit Date: Fri, 2 Jan 2026 04:16:45 +0200 Subject: [PATCH 1/9] refactor: simplify form validation and enhance recipient handling (#2317) --- .../direct-template-configure-form.tsx | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx b/apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx index 47e983003..33be2c927 100644 --- a/apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx +++ b/apps/remix/app/components/general/direct-template/direct-template-configure-form.tsx @@ -1,6 +1,4 @@ import { zodResolver } from '@hookform/resolvers/zod'; -import { msg } from '@lingui/core/macro'; -import { useLingui } from '@lingui/react'; import { Trans } from '@lingui/react/macro'; import type { Recipient } from '@prisma/client'; import type { Field } from '@prisma/client'; @@ -57,8 +55,6 @@ export const DirectTemplateConfigureForm = ({ initialEmail, onSubmit, }: DirectTemplateConfigureFormProps) => { - const { _ } = useLingui(); - const { sessionData } = useOptionalSession(); const user = sessionData?.user; @@ -77,17 +73,7 @@ export const DirectTemplateConfigureForm = ({ }); const form = useForm({ - resolver: zodResolver( - ZDirectTemplateConfigureFormSchema.superRefine((items, ctx) => { - if (template.recipients.map((recipient) => recipient.email).includes(items.email)) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: _(msg`Email cannot already exist in the template`), - path: ['email'], - }); - } - }), - ), + resolver: zodResolver(ZDirectTemplateConfigureFormSchema), defaultValues: { email: initialEmail || '', }, @@ -138,7 +124,7 @@ export const DirectTemplateConfigureForm = ({ {!fieldState.error && ( -

+

Enter your email address to receive the completed document.

)} From 27eb2d65d4378fbf7e726a2ddca3227f5213a2a1 Mon Sep 17 00:00:00 2001 From: Ted Liang Date: Sat, 3 Jan 2026 11:31:56 +1100 Subject: [PATCH 2/9] feat: upgrade alpine and support chromium path (#2353) Upgrade alpine to 3.22 Support chromium executable path --- docker/Dockerfile | 2 +- packages/lib/server-only/htmltopdf/get-audit-logs-pdf.ts | 4 +++- packages/lib/server-only/htmltopdf/get-certificate-pdf.ts | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index c8864e40a..47304ba8d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,7 +1,7 @@ ########################### # BASE CONTAINER # ########################### -FROM node:22-alpine3.20 AS base +FROM node:22-alpine3.22 AS base RUN apk add --no-cache openssl RUN apk add --no-cache font-freefont diff --git a/packages/lib/server-only/htmltopdf/get-audit-logs-pdf.ts b/packages/lib/server-only/htmltopdf/get-audit-logs-pdf.ts index d77d334f8..b5ef967ff 100644 --- a/packages/lib/server-only/htmltopdf/get-audit-logs-pdf.ts +++ b/packages/lib/server-only/htmltopdf/get-audit-logs-pdf.ts @@ -33,7 +33,9 @@ export const getAuditLogsPdf = async ({ documentId, language }: GetAuditLogsPdfO // !: Previously we would have to keep the playwright version in sync with the browserless version to avoid errors. browser = await chromium.connectOverCDP(browserlessUrl); } else { - browser = await chromium.launch(); + browser = await chromium.launch({ + executablePath: env('PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH') || undefined, + }); } if (!browser) { diff --git a/packages/lib/server-only/htmltopdf/get-certificate-pdf.ts b/packages/lib/server-only/htmltopdf/get-certificate-pdf.ts index 0fa07538f..fdc186deb 100644 --- a/packages/lib/server-only/htmltopdf/get-certificate-pdf.ts +++ b/packages/lib/server-only/htmltopdf/get-certificate-pdf.ts @@ -33,7 +33,9 @@ export const getCertificatePdf = async ({ documentId, language }: GetCertificate // !: Previously we would have to keep the playwright version in sync with the browserless version to avoid errors. browser = await chromium.connectOverCDP(browserlessUrl); } else { - browser = await chromium.launch(); + browser = await chromium.launch({ + executablePath: env('PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH') || undefined, + }); } if (!browser) { From c1217c5a58f40ad68e4fd0eb3f83cc0ea05ce06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20B=C3=A9cue?= Date: Sat, 3 Jan 2026 01:43:55 +0100 Subject: [PATCH 3/9] docs: ensure cert directory exists before generating PKCS12 (#2354) --- apps/documentation/pages/developers/self-hosting/how-to.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/documentation/pages/developers/self-hosting/how-to.mdx b/apps/documentation/pages/developers/self-hosting/how-to.mdx index 936c99bd6..0d199b6b5 100644 --- a/apps/documentation/pages/developers/self-hosting/how-to.mdx +++ b/apps/documentation/pages/developers/self-hosting/how-to.mdx @@ -148,6 +148,7 @@ This method avoids file permission issues by creating the certificate directly i # Generate certificate inside container using environment variable docker exec -e CERT_PASS="$CERT_PASS" -it documenso-production-documenso-1 bash -c " + mkdir -p /app/certs && \ openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /tmp/private.key \ -out /tmp/certificate.crt \ From c2ea5e5859427e817835aa13fde1f4d421135a7f Mon Sep 17 00:00:00 2001 From: David Nguyen Date: Tue, 6 Jan 2026 14:26:19 +1100 Subject: [PATCH 4/9] fix: migrate certificate generation (#2251) Generate certificates and audit logs using Konva instead of browserless. This should: - Reduce the changes of generations failing - Improve sealing speed --- apps/remix/app/components/forms/token.tsx | 8 +- .../document-signing-mobile-widget.tsx | 18 +- .../document-page-view-recent-activity.tsx | 44 +- .../envelope-editor-settings-dialog.tsx | 16 +- .../components/tables/document-logs-table.tsx | 2 +- .../tables/internal-audit-log-table.tsx | 22 +- .../_authenticated+/o.$orgUrl._index.tsx | 26 +- .../o.$orgUrl.settings.branding.tsx | 2 +- .../_internal+/[__htmltopdf]+/audit-log.tsx | 4 +- .../_recipient+/sign.$token+/_index.tsx | 12 +- .../_recipient+/sign.$token+/rejected.tsx | 8 +- .../_recipient+/sign.$token+/waiting.tsx | 4 +- .../visual-regression/blank-certificate.png | Bin 147496 -> 166551 bytes .../email/templates/confirm-team-email.tsx | 2 +- packages/email/templates/reset-password.tsx | 2 +- packages/lib/constants/app.ts | 9 + packages/lib/constants/pdf.ts | 5 + .../internal/seal-document.handler.ts | 160 ++-- .../htmltopdf/get-audit-logs-pdf.ts | 3 + .../htmltopdf/get-certificate-pdf.ts | 3 + .../server-only/pdf/generate-audit-log-pdf.ts | 60 ++ .../pdf/generate-certificate-pdf.ts | 160 ++++ .../lib/server-only/pdf/render-audit-logs.ts | 721 +++++++++++++++ .../lib/server-only/pdf/render-certificate.ts | 819 ++++++++++++++++++ packages/lib/types/document-audit-logs.ts | 2 + packages/lib/utils/document-audit-logs.ts | 17 +- packages/prisma/seed/documents.ts | 17 +- .../trpc/server/envelope-router/router.ts | 2 +- .../ui/primitives/data-table-pagination.tsx | 2 +- packages/ui/primitives/dialog.tsx | 8 +- .../primitives/document-flow/add-subject.tsx | 12 +- .../document-flow/document-flow-root.tsx | 23 +- .../document-flow/field-content.tsx | 10 +- packages/ui/primitives/sheet.tsx | 8 +- .../signature-pad/signature-pad-draw.tsx | 6 +- .../template-flow/add-template-settings.tsx | 24 +- 36 files changed, 2008 insertions(+), 233 deletions(-) create mode 100644 packages/lib/server-only/pdf/generate-audit-log-pdf.ts create mode 100644 packages/lib/server-only/pdf/generate-certificate-pdf.ts create mode 100644 packages/lib/server-only/pdf/render-audit-logs.ts create mode 100644 packages/lib/server-only/pdf/render-certificate.ts diff --git a/apps/remix/app/components/forms/token.tsx b/apps/remix/app/components/forms/token.tsx index 7311bfce4..95b1a8830 100644 --- a/apps/remix/app/components/forms/token.tsx +++ b/apps/remix/app/components/forms/token.tsx @@ -212,12 +212,12 @@ export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => { />
- + Never expire
@@ -254,14 +254,14 @@ export const ApiTokenForm = ({ className, tokens }: ApiTokenFormProps) => { > -

+

Your token was created successfully! Make sure to copy it because you won't be able to see it again!

-

+

{newlyCreatedToken.token}

diff --git a/apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx b/apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx index 6c765b2a1..40eb9dc92 100644 --- a/apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx +++ b/apps/remix/app/components/general/document-signing/document-signing-mobile-widget.tsx @@ -35,7 +35,7 @@ export const DocumentSigningMobileWidget = () => { return (
-
+
{/* Main Header Bar */}
@@ -48,15 +48,15 @@ export const DocumentSigningMobileWidget = () => { aria-label={isExpanded ? 'Collapse' : 'Expand'} > {isExpanded ? ( - + ) : ( - + )} )}
-

+

{match(recipient.role) .with(RecipientRole.VIEWER, () => View Document) .with(RecipientRole.SIGNER, () => Sign Document) @@ -65,7 +65,7 @@ export const DocumentSigningMobileWidget = () => { .otherwise(() => null)}

-

+

{recipientFieldsRemaining.length === 0 ? ( match(recipient.role) .with(RecipientRole.VIEWER, () => ( @@ -102,11 +102,11 @@ export const DocumentSigningMobileWidget = () => { {recipient.role !== RecipientRole.VIEWER && recipient.role !== RecipientRole.ASSISTANT && (

-
+
{ {/* Expandable Content */} {isExpanded && ( -
+
{!hidePoweredBy && ( -
+
Powered by
diff --git a/apps/remix/app/components/general/document/document-page-view-recent-activity.tsx b/apps/remix/app/components/general/document/document-page-view-recent-activity.tsx index abeeacbc4..12febc4cb 100644 --- a/apps/remix/app/components/general/document/document-page-view-recent-activity.tsx +++ b/apps/remix/app/components/general/document/document-page-view-recent-activity.tsx @@ -22,7 +22,7 @@ export const DocumentPageViewRecentActivity = ({ documentId, userId, }: DocumentPageViewRecentActivityProps) => { - const { _ } = useLingui(); + const { _, i18n } = useLingui(); const { data, @@ -48,9 +48,9 @@ export const DocumentPageViewRecentActivity = ({ const documentAuditLogs = useMemo(() => (data?.pages ?? []).flatMap((page) => page.data), [data]); return ( -
+
-

+

Recent activity

@@ -59,18 +59,18 @@ export const DocumentPageViewRecentActivity = ({ {isLoading && (
- +
)} {isLoadingError && (
-

+

Unable to load document history

@@ -83,16 +83,16 @@ export const DocumentPageViewRecentActivity = ({ {hasNextPage && (
  • -
    +
    -
    -
    +
    +
    @@ -101,7 +101,7 @@ export const DocumentPageViewRecentActivity = ({ {documentAuditLogs.length === 0 && (
    -

    +

    No recent activity

    @@ -115,44 +115,44 @@ export const DocumentPageViewRecentActivity = ({ 'absolute left-0 top-0 flex w-6 justify-center', )} > -
    +
    -
    +
    {match(auditLog.type) .with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_COMPLETED, () => ( -
    +
    )) .with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_COMPLETED, () => ( -
    +
    )) .with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_RECIPIENT_REJECTED, () => ( -
    +
    )) .with(DOCUMENT_AUDIT_LOG_TYPE.DOCUMENT_OPENED, () => ( -
    +
    )) .otherwise(() => ( -
    +
    ))}

    - {formatDocumentAuditLogAction(_, auditLog, userId).description} + {formatDocumentAuditLogAction(i18n, auditLog, userId).description}

    -
  • diff --git a/apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx b/apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx index 5ee92f661..475dc5b62 100644 --- a/apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx +++ b/apps/remix/app/components/general/envelope-editor/envelope-editor-settings-dialog.tsx @@ -339,7 +339,7 @@ export const EnvelopeEditorSettingsDialog = ({ {/* Sidebar. */} -
    +
    Document Settings @@ -390,7 +390,7 @@ export const EnvelopeEditorSettingsDialog = ({ - + Controls the language for the document, including the language to be used for email notifications, and the final certificate @@ -441,7 +441,7 @@ export const EnvelopeEditorSettingsDialog = ({ }))} selectedValues={field.value} onChange={field.onChange} - className="bg-background w-full" + className="w-full bg-background" emptySelectionPlaceholder="Select signature types" /> @@ -518,7 +518,7 @@ export const EnvelopeEditorSettingsDialog = ({ - + Add an external ID to the document. This can be used to identify the document in external systems. @@ -548,7 +548,7 @@ export const EnvelopeEditorSettingsDialog = ({ - + Add a URL to redirect the user to once the document is signed @@ -576,7 +576,7 @@ export const EnvelopeEditorSettingsDialog = ({ - +

    Document Distribution Method @@ -735,14 +735,14 @@ export const EnvelopeEditorSettingsDialog = ({ - + -