diff --git a/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx b/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx
index c52859ac..b9f327a4 100644
--- a/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx
+++ b/src/app/(dashboard)/[portSlug]/admin/documenso/page.tsx
@@ -7,6 +7,7 @@ import { TemplateSyncButton } from '@/components/admin/documenso/template-sync-b
import { WebhookHealthCard } from '@/components/admin/documenso/webhook-health-card';
import { PageHeader } from '@/components/shared/page-header';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { WarningCallout } from '@/components/ui/warning-callout';
// All field arrays removed - every Documenso setting now flows through
// `RegistryDrivenForm`, which surfaces the env-fallback / port / global
@@ -22,6 +23,35 @@ export default function DocumensoSettingsPage() {
description="API credentials, signer identities, templates, and signing behaviour for every document the CRM puts out for signature (EOI, reservation, contract, custom uploads). Use the test-connection button to verify a saved configuration before relying on it."
/>
+
+
+ The CRM's signing features are built for Documenso 2.x (v2). Set the API version
+ below to v1 only if this port still points at a Documenso 1.13.x server.
+ Be aware these CRM functions do not work (or run degraded) on v1:
+
+
+
+ Editing an envelope after it is created (title, subject, redirect URL):
+ hard-fails, because v1 has no /envelope/update endpoint.
+
+
+ Upload-and-send contracts / reservations fall back to v1's
+ per-field placement: page size is assumed to be A4, and rich field metadata (required
+ flags, NUMBER min/max, CHECKBOX / DROPDOWN / RADIO option lists) is dropped.
+
+
+ One-call send with per-recipient signing links,{' '}
+ sequential signing enforcement, and the{' '}
+ v2 webhook events (recipient viewed / signed, declined, reminder sent)
+ are unavailable or ignored on v1.
+
+
+
+ Recommended: upgrade the Documenso server to 2.x, then set the API version to v2 and run
+ the test-connection button to confirm.
+
+
+
diff --git a/src/components/files/file-preview-dialog.tsx b/src/components/files/file-preview-dialog.tsx
index 007b2ba1..d2d335ac 100644
--- a/src/components/files/file-preview-dialog.tsx
+++ b/src/components/files/file-preview-dialog.tsx
@@ -104,7 +104,7 @@ export function FilePreviewDialog({
// useQuery replaces the prior useEffect(fetch+setState) pattern. The
// request is gated on the dialog being open and a fileId being set.
- const previewQuery = useQuery<{ data: { url: string } }>({
+ const previewQuery = useQuery<{ data: { url: string; mimeType?: string } }>({
queryKey: ['file-preview', fileId],
queryFn: () => apiFetch(`/api/v1/files/${fileId}/preview`),
enabled: open && !!fileId,
@@ -113,7 +113,13 @@ export function FilePreviewDialog({
const loading = previewQuery.isLoading;
const error = previewQuery.error ? 'Failed to load preview' : null;
- const kind = previewKindFor(mimeType, fileName);
+ // Prefer the caller-supplied mime, but fall back to the server's resolved
+ // mime (getPreviewUrl returns it). Without this, callers that pass only a
+ // display name (e.g. the EOI tab passing "EOI - ") or files whose
+ // stored name lacks a `.pdf` extension (migration-backfilled EOIs) fall
+ // through to the "unknown" surface even though the server knows it's a PDF.
+ const resolvedMime = mimeType ?? previewQuery.data?.data.mimeType;
+ const kind = previewKindFor(resolvedMime, fileName);
return (