Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
4014 commits
Select commit Hold shift + click to select a range
0d65693
fix: highlight rejected certificate text (#2478)
dguyen Feb 12, 2026
066e6bc
fix: envelope editor flush race condition (#2482)
dguyen Feb 12, 2026
9bcb240
fix: revert canceled individual subscriptions to free claim (#2483)
dguyen Feb 12, 2026
d66c330
fix: match cert and audit log page dimensions to source document (#2473)
ephraimduncan Feb 12, 2026
2e3d22c
fix: use instance-specific emails for service accounts (#2502)
Mythie Feb 16, 2026
f158305
fix(i18n): mark paragraph strings for translation (#2498)
mKoonrad Feb 16, 2026
51916cd
fix(i18n): mark DialogTitle string for translation (#2497)
mKoonrad Feb 16, 2026
34f14ba
fix(i18n): mark tabs trigger strings for translation (#2496)
mKoonrad Feb 16, 2026
435d61e
fix(i18n): mark badge string for translation (#2495)
mKoonrad Feb 16, 2026
ca199e7
fix(i18n): mark span strings for translation (#2494)
mKoonrad Feb 16, 2026
3f7c4df
fix: strip diacritics from team URL slug generation (#2489)
Jahangirbabar2000 Feb 16, 2026
cf8e21b
fix: create full sentences for document-signing-auth files (#2451)
McMek590 Feb 16, 2026
f35c19d
chore: extract translations (#2458)
github-actions[bot] Feb 16, 2026
a60c6a9
chore: add translations (#2504)
Mythie Feb 16, 2026
ff9e6ac
fix(ui): clarify email settings labels (#2448)
LouisLau-art Feb 16, 2026
6fcf0a6
chore: add translations (#2507)
Mythie Feb 17, 2026
5de2527
fix: v2 embed direct templates not reading email/lockEmail from hash …
Mythie Feb 18, 2026
29622d3
fix(i18n): mark strings inside div for translation (#2514)
mKoonrad Feb 18, 2026
9a66d0e
fix: simplify openapi field schemas to fix SDK generation (#2503)
Mythie Feb 18, 2026
f3ec8dd
v2.6.1
Mythie Feb 18, 2026
006b1d0
feat: per-recipient envelope expiration (#2519)
Mythie Feb 20, 2026
653ab36
feat: better ratelimiting (#2520)
Mythie Feb 20, 2026
6fb88fe
chore: upgrade libpdf (#2522)
Mythie Feb 21, 2026
36eef79
fix: omit fieldId from embed create endpoints (#2523)
Mythie Feb 21, 2026
3ad3216
fix: update button width to fit content in public profile page (#2506)
KarloDerEchte Feb 23, 2026
bc72d9c
chore: extract translations (#2505)
github-actions[bot] Feb 24, 2026
c112392
feat: add admin email domain management and sync job (#2538)
Mythie Feb 25, 2026
6f5014a
feat: support optional read replicas (#2540)
Mythie Feb 25, 2026
484e1c2
chore: add translations (#2533)
Mythie Feb 26, 2026
92d82c0
fix(i18n): mark supported languages for translation (#2377)
mKoonrad Feb 26, 2026
0d3bd59
fix: handle cjk and special chars when slugifying (#2544)
Mythie Feb 26, 2026
194660d
fix: return all documents from V1 API regardless of folder (#2471)
ephraimduncan Feb 26, 2026
f8ac782
deps: 2026-02-26 upgrades (#2545)
Mythie Feb 26, 2026
b92c53d
feat: docs v2 (#2460)
Mythie Feb 27, 2026
24a5c85
fix(docs): rewrite mermaid component to avoid async client component …
Mythie Feb 28, 2026
454f73f
chore: remove old docs (#2550)
Mythie Mar 2, 2026
a05251d
v2.7.0
Mythie Mar 3, 2026
f132367
fix: use default field meta for embedding template fields (#2556)
catalinpit Mar 3, 2026
7d3a56a
feat: add admin ability to move subscription between orgs (#2558)
Mythie Mar 4, 2026
7f5f2b2
feat: add seal-document sweep job and admin unsealed documents page (…
Mythie Mar 5, 2026
03e2e4f
docs: clarify placeholder support is envelope.* only (#2560)
ephraimduncan Mar 5, 2026
8b02318
chore: extract translations (#2539)
github-actions[bot] Mar 5, 2026
dfbf68e
fix(i18n): mark editor field number form placeholder for translation …
mKoonrad Mar 5, 2026
d21b998
fix(i18n): add pluralization to expiration period picker (#2535)
mKoonrad Mar 5, 2026
525dd92
fix(i18n): mark SUBSCRIPTION_STATUS_MAP for translation (#2515)
mKoonrad Mar 5, 2026
1f985e2
fix: invalid po translations (#2567)
dguyen Mar 5, 2026
ef0a5b5
fix: verify before re-registering in email sync (#2568)
Mythie Mar 5, 2026
db1087d
v2.7.1
Mythie Mar 5, 2026
bff360b
fix: upgrade @libpdf/core (#2569)
Mythie Mar 5, 2026
406e77e
chore: add translations (#2570)
Mythie Mar 5, 2026
7f27137
fix: upgrade @libpdf/core (#2572)
Mythie Mar 5, 2026
0ce909a
refactor: find envelopes (#2557)
Mythie Mar 6, 2026
6faa01d
feat: add pdf image renderer (#2554)
dguyen Mar 6, 2026
c63b4ca
fix(i18n): mark dropdown and radio placeholder for translation (#2537)
mKoonrad Mar 6, 2026
7e2cbe4
fix: show current month data and add caching (#2573)
ephraimduncan Mar 6, 2026
7ea6642
feat: add embedded envelopes (#2564)
dguyen Mar 6, 2026
1ff8680
chore: extract translations (#2566)
github-actions[bot] Mar 6, 2026
abd031b
chore: add translations (#2575)
Mythie Mar 6, 2026
6c8726b
fix: performance improvements (#2581)
dguyen Mar 8, 2026
c475455
feat: implement template search functionality (#2376)
catalinpit Mar 8, 2026
15399cb
feat: auto-disable telemetry when license key is configured (#2562)
ephraimduncan Mar 9, 2026
7cfe876
chore: extract translations (#2577)
github-actions[bot] Mar 9, 2026
da7b5d1
fix: make signing page left-hand sidebar collapsible (#2541)
catalinpit Mar 9, 2026
58f0c98
chore: add embed envelope docs (#2576)
dguyen Mar 9, 2026
eaaf8f9
chore: add translations (#2582)
Mythie Mar 9, 2026
ef57c84
fix: dropdown fields (#2584)
dguyen Mar 9, 2026
0e20d36
fix: opt findDocumentsInternal query out of batch fetching (#2585)
Mythie Mar 9, 2026
f82bf97
fix: only use embed hash name/email as fallback when recipient values…
Mythie Mar 9, 2026
cc71c7d
fix: add cmaps (#2588)
Mythie Mar 9, 2026
11eb4dd
fix: security CVE-2026-29045 (#2589)
tedliang Mar 9, 2026
4daec44
fix: move window.__ENV__ script before client bundle to prevent stale…
Mythie Mar 10, 2026
ab69ee6
fix: include extra recipient info in missing fields error msg (#2590)
catalinpit Mar 10, 2026
af346b1
feat: add recipient role editing and audit log PDF download in admin …
Mythie Mar 10, 2026
5ea4060
v2.8.0
Mythie Mar 10, 2026
03ca397
perf: upgrade @libpdf/core to 0.3.3 and deduplicate font registration…
Mythie Mar 11, 2026
1d2c781
docs: add organisation ownership transfer guide (#2601)
ephraimduncan Mar 12, 2026
a5fd814
fix: handle invalid qr share tokens without 500 (#2597)
vivek-tiwari-vt Mar 12, 2026
4751c9c
fix: template description overflow (#2605)
joshuafsharp Mar 12, 2026
364537e
chore: update hipaa status in docs (#2599)
ElTimuro Mar 13, 2026
e67e193
fix: add hipaa flag (#2603)
dguyen Mar 13, 2026
8d97f1d
fix: resolve error flash on page refresh (#2606)
dguyen Mar 13, 2026
2f2b5dd
feat: allow creating embeds in folder (#2612)
dguyen Mar 13, 2026
76d96d2
chore: extract translations (#2583)
github-actions[bot] Mar 13, 2026
9f680c7
perf: set global prisma transaction timeouts and reduce transaction s…
Mythie Mar 13, 2026
6b1b1d0
fix: improve webhook execution (#2608)
Mythie Mar 13, 2026
1ee6ec8
chore: add translations (#2614)
Mythie Mar 13, 2026
83fbc70
refactor: avoid recipient color duplication (#2355)
tedliang Mar 13, 2026
32c54e1
fix: hide name/email in embed signing when provided via prop (#2600)
catalinpit Mar 13, 2026
3106fd7
fix: exclude native modules from Vite dependency optimization (#2615)
tedliang Mar 14, 2026
66e357c
feat: add email domain restriction for signups (#2266)
ephraimduncan Mar 14, 2026
70fb834
feat: add more webhook events (#2125)
ephraimduncan Mar 15, 2026
ac09a48
perf: parallelize independent async operations in createEnvelope (#2618)
ephraimduncan Mar 16, 2026
6ef501c
perf: parallelize getTeamSettings and getEditorEnvelopeById (#2617)
ephraimduncan Mar 16, 2026
943a0b5
perf: parallelize async operations in duplicateEnvelope (#2619)
ephraimduncan Mar 16, 2026
36bbd97
feat: add organisation template type (#2611)
ephraimduncan Mar 16, 2026
de134af
v2.8.1
Mythie Mar 16, 2026
647dc5f
fix(i18n): mark billing messages for translation (#2525)
mKoonrad Mar 17, 2026
455fef7
fix: folder view all page nested navigation and search filtering (#2450)
catalinpit Mar 17, 2026
f48aa84
fix(recipient): filter invalid emails in suggestions (#2510)
abdulalim110 Mar 18, 2026
5dcdac7
feat: support language in embedding (#2364)
tedliang Mar 18, 2026
0b605d6
feat: add envelope pdf replacement (#2602)
dguyen Mar 18, 2026
48fb066
feat: allow editing pending envelope titles (#2604)
dguyen Mar 19, 2026
108d422
chore: extract translations (#2613)
github-actions[bot] Mar 19, 2026
9cf8ed1
fix: resolve envelope editor settings ccer logic (#2628)
dguyen Mar 19, 2026
4f8132b
fix(ui): add scroll to date format dropdown (#2626)
nielskaspers Mar 19, 2026
e87aa29
feat: add page title translations (#2629)
dguyen Mar 19, 2026
bef3ea4
chore: add translations (#2630)
Mythie Mar 19, 2026
dd1b6d7
chore: add translations (#2632)
Mythie Mar 19, 2026
b2d395e
fix: stale envelope editor query (#2633)
dguyen Mar 19, 2026
ace472c
fix: prevent managers from deleting admin invitations (#2636)
dguyen Mar 20, 2026
5be71cc
feat: add option to disable Document created from template (#2609)
catalinpit Mar 23, 2026
53b6078
fix: missing embed direct template email validation (#2635)
dguyen Mar 23, 2026
0434bdf
fix: require billing address on checkout (#2647)
Mythie Mar 25, 2026
814f6e6
fix: replace z.string().email() with RFC 5322 compliant ZEmail/zEmail…
Mythie Mar 26, 2026
2346de8
fix: replace z.string().email() with RFC 5322 compliant zEmail() (#2656)
Mythie Mar 26, 2026
f5b3bab
feat: display the field id in dev mode (#2658)
catalinpit Mar 26, 2026
a71c445
feat: admin panel org improvements (#2548)
catalinpit Mar 27, 2026
025a27d
docs: add user-facing documentation for recipient expiration (#2659)
Mythie Mar 30, 2026
ad559f7
feat: add BullMQ background job provider with Bull Board dashboard (#…
Mythie Apr 1, 2026
1c82595
feat: webhook allow private hosts (#2654)
jpsimonsen Apr 1, 2026
74d79dc
chore: update labeler.yml (#2653)
ElTimuro Apr 1, 2026
e3b7a9e
feat: add ability to save documents as template (#2661)
dguyen Apr 1, 2026
b13ec89
fix: resolve incorrect recipient comparision check (#2646)
dguyen Apr 1, 2026
3cca8cd
fix: labeler typo (#2670)
dguyen Apr 2, 2026
0b9a23c
fix: handle malformed pdf cropbox/mediabox entries (#2668)
Mythie Apr 2, 2026
6f650e1
feat: add document rename feature (#2542) (#2595)
swalihkolakkadan Apr 2, 2026
229cd2f
fix: validate Resend API key before creating mail transport (#2672)
lawrence3699 Apr 7, 2026
9bfaa08
fix: documents table team email recipient lookup (#2578)
ephraimduncan Apr 7, 2026
14b0b48
feat: auto insert email and date fields (#2639)
dguyen Apr 8, 2026
4c69cb9
chore: extract translations (#2631)
github-actions[bot] Apr 8, 2026
7cb64c3
fix: allow nullable document audit logs (#2682)
Mythie Apr 8, 2026
1af83ea
chore: add translations (#2683)
Mythie Apr 9, 2026
2833349
fix: update team member invitation ux (#2687)
dguyen Apr 9, 2026
6d7bd21
fix: clean up duplicate dialogs (#2686)
dguyen Apr 9, 2026
4935f38
feat: signing reminders (#1749)
ephraimduncan Apr 14, 2026
bc82b2e
fix: admin org sorting (#2694)
dguyen Apr 14, 2026
5082226
fix: brand logo caching (#2699)
dguyen Apr 14, 2026
f54a8ed
feat: add turnstile captcha to auth flow (#2703)
Mythie Apr 16, 2026
2f1aaa2
fix: prevent TooltipTrigger from submitting parent forms (fixes #2684…
armorbreak001 Apr 16, 2026
198dafc
v2.9.0
Mythie Apr 18, 2026
34b31c0
chore: deps upgrades (#2712)
Mythie Apr 21, 2026
3249f85
fix: show captcha on challenge for sign in (#2713)
Mythie Apr 22, 2026
d38d703
fix: error message (update title) (#2691)
catalinpit Apr 22, 2026
e5da5bc
fix: unwrap webhook payload before test and resend (#2710)
ephraimduncan Apr 22, 2026
dc575f5
fix: don't block organisation member removal on billing checks (#2706)
Mythie Apr 22, 2026
e063af6
feat: allow admins to remove organisation and team members (#2705)
catalinpit Apr 22, 2026
8f3e189
v2.9.1
Mythie Apr 23, 2026
135b676
chore: add translations (#2689)
Mythie Apr 27, 2026
19c2f7b
docs: add signing reminders guide (#2716)
Mythie Apr 27, 2026
8bad62c
chore: extract translations (#2734)
github-actions[bot] Apr 27, 2026
2c7a1be
feat: add envelope ids to certs (#2733)
dguyen Apr 28, 2026
61338af
chore: add translations (#2735)
Mythie Apr 28, 2026
2f4c389
fix: remove envelope title cropping (#2739)
dguyen Apr 28, 2026
ae49709
fix: security improvements (#2593)
catalinpit Apr 30, 2026
5d92aaf
feat: render signatures on pending envelopes (#2743)
dguyen Apr 30, 2026
84fc866
fix: improve signature rendering quality with high-resolution caching…
dguyen Apr 30, 2026
c428170
fix: allow users to download templates (#2746)
dguyen Apr 30, 2026
e19b1d0
fix: improve embed error messages (#2752)
dguyen May 1, 2026
aebb5e2
fix: assistant signing auth (#2753)
Mythie May 1, 2026
a697832
v2.10.0
Mythie May 1, 2026
6243a51
fix: csp frame-ancestors on signing routes
Mythie May 1, 2026
690491c
fix: prevent 2fa users from being flagged as bots (#2748)
Mythie May 4, 2026
6a6ef8d
feat: allow add myself feature for embeds (#2754)
dguyen May 4, 2026
f10d328
feat: remove default personal orgs from custom sso (#2741)
dguyen May 5, 2026
8c0e029
feat: add pending signed PDF downloads (#2730)
ephraimduncan May 5, 2026
36c10d1
v2.10.1
Mythie May 5, 2026
b79b4bd
feat: add DD-MM-YYYY date format variants (#2767)
catalinpit May 6, 2026
bc3aa9c
chore: extract translations (#2737)
github-actions[bot] May 7, 2026
f667516
fix: paginate and search member/group pickers (#2768)
Mythie May 7, 2026
4877d19
chore: add translations (#2771)
Mythie May 7, 2026
207135d
feat: add new field overflow methods (#2715)
dguyen May 8, 2026
edbf659
fix: replace linter with biome (#2645)
dguyen May 8, 2026
8671f26
fix: lint project (#2693)
dguyen May 8, 2026
22122f5
chore: extract translations (#2772)
github-actions[bot] May 8, 2026
ec8728b
chore: add translations (#2774)
Mythie May 8, 2026
a197bf1
feat: add granular signup disable flags (#2765)
ephraimduncan May 9, 2026
0b86ece
feat: add custom branding for signing pages (#2785)
Mythie May 11, 2026
f15d6f0
perf: dynamically import posthog (#2622)
ephraimduncan May 11, 2026
0a7794b
feat: protect signing URLs from indexing, caching, and embedding (#2469)
ephraimduncan May 11, 2026
87315ad
chore: extract translations (#2786)
github-actions[bot] May 11, 2026
d794ceb
chore: add translations (#2788)
Mythie May 12, 2026
684fab1
chore: add section on personal organisations for SSO users (#2793)
dguyen May 12, 2026
57eb40d
chore: update French translations (#2717)
abdhenni May 12, 2026
be3e454
chore: update fair use policy (#2798)
ElTimuro May 12, 2026
73a7335
refactor: remove unnecessary DateRange type assertion (#2790)
anish1204 May 13, 2026
8dfd548
chore: remove github action caches (#2802)
dguyen May 13, 2026
bc184d4
feat: support DOCX uploads via Gotenberg (#2801)
Mythie May 13, 2026
a8efb6f
fix: remove translation tag from css textarea placeholder (#2803)
Mythie May 13, 2026
8b171c9
chore: update docs to use editor instead of authoring (#2800)
dguyen May 13, 2026
9a45b35
chore: extract translations (#2796)
github-actions[bot] May 13, 2026
cfaad6e
feat: add admin org deletion (#2795)
dguyen May 13, 2026
d6dd2b3
perf: compress signing-celebration.png from 20MB to 4MB (#2781)
ggoswami777 May 13, 2026
abbca79
chore: extract translations (#2804)
github-actions[bot] May 13, 2026
4078c6b
chore: add translations (#2805)
Mythie May 13, 2026
1911709
v2.11.0
Mythie May 13, 2026
b8df027
fix: convert DOCX template uploads to PDF (#2807)
ephraimduncan May 14, 2026
194b213
docs: remove leftover Next.js commands and update to Remix-compatible…
mirtafon May 14, 2026
9999420
chore: update docs for self hosters (#2816)
dguyen May 14, 2026
8f5bdef
docs: require English for PRs and issues (#2819)
ephraimduncan May 15, 2026
8f6be47
fix: improve api logging (#2820)
dguyen May 15, 2026
9e0b567
chore: deps upgrade (#2831)
Mythie May 18, 2026
247a015
refactor(ui): replace hardcoded colors with semantic tokens (#2749)
ephraimduncan May 19, 2026
bc3acba
fix: use captcha imperatively (#2832)
Mythie May 19, 2026
d9b5f01
chore: add translations (#2833)
Mythie May 19, 2026
2cb4cc2
feat: allow admins to create users (#2082)
ephraimduncan May 19, 2026
c0ea4c6
fix(docs): correct API example URLs from /documents to /document (#2836)
roshboi May 20, 2026
f93a98e
chore: updated certification status (#2850)
roshboi May 21, 2026
d0f9f68
fix: correct reversed comparison in admin organisations table paginat…
Abzaek May 22, 2026
f1b2358
fix: remove duplicate loadingSpinnerGroup.destroy() in DROPDOWN sign …
Abzaek May 22, 2026
fec5d55
fix: move document complete email to a job (#2835)
dguyen May 22, 2026
0a6b045
fix: handleInitialsFieldClick now returns initialsToInsert instead of…
Abzaek May 22, 2026
43fe558
fix: prevent crash when removing last dropdown option in removeValue …
Abzaek May 22, 2026
6a20fef
chore: extract translations (#2806)
github-actions[bot] May 22, 2026
6ac67e6
fix: always show captcha (#2860)
Mythie May 25, 2026
fa2c53b
fix: prevent React state mutation by spreading envelope.recipients be…
Abzaek May 26, 2026
5421b0d
fix: prevent prop array mutation by spreading allRecipients before so…
Abzaek May 26, 2026
eedf483
fix(prisma): stop large-team-seed running on import (#2852)
ephraimduncan May 26, 2026
6bb0496
fix: prevent division by zero in progress bar when requiredRecipientF…
durgeshhhhhhh May 26, 2026
7c00316
docs: implement global error handling and troubleshooting matrix (#2784)
redouanegrib May 26, 2026
0fe697c
fix: handle duplicate organization URL update errors gracefully (#2808)
anish1204 May 26, 2026
b1b82b7
fix: add missing doc page ref (#2865)
dguyen May 26, 2026
886c40a
fix: add constraint on name schema (#2866)
dguyen May 26, 2026
3cef238
chore: add translations (#2854)
Mythie May 26, 2026
807d094
fix: email dictated direct template signer (#2810)
zahedshareef May 27, 2026
993df7d
fix(docs): correct broken internal docs links (#2869)
nikhil-shukl May 27, 2026
9da2db2
feat(storage): add native Azure Blob transport (#2871)
kengru-digito May 27, 2026
d304d87
fix: add temp email rate limit (#2879)
dguyen May 28, 2026
7e8da85
feat: block disposable email signups (#2883)
Mythie May 28, 2026
a84da2f
chore: disabled account enforcement (#2882)
Mythie May 28, 2026
22ceff4
feat: admin-configurable email blocklist (#2884)
Mythie May 28, 2026
61138cd
fix: add dynamic rate limits (#2892)
dguyen May 30, 2026
44c4826
fix: track monthly usage for unlimited quotas (#2894)
dguyen May 31, 2026
536142b
feat: add admin org stats (#2904)
dguyen Jun 1, 2026
a7713f7
chore: add translations (#2885)
Mythie Jun 1, 2026
4bda501
feat: add stripe sync (#2877)
dguyen Jun 1, 2026
c50a01d
fix: improve field signing (#2830)
dguyen Jun 1, 2026
d2f60b1
fix: correctly log cc emails (#2913)
dguyen Jun 2, 2026
c3135a3
fix: filter ccers from delete email (#2914)
dguyen Jun 2, 2026
50006ca
fix: prevent sending emails for free organisation claims (#2917)
roshboi Jun 3, 2026
8b8e7e9
chore: extract translations (#2867)
github-actions[bot] Jun 3, 2026
ce96238
fix(ui): direct signing templates window layout (#2909)
mKoonrad Jun 3, 2026
743d316
fix(i18n): mark Bio string for translation (#2910)
mKoonrad Jun 3, 2026
993a494
fix: add email reporting (#2918)
dguyen Jun 3, 2026
9583e79
chore: extract translations (#2919)
github-actions[bot] Jun 3, 2026
240bef1
fix: add org insight document complete stat (#2920)
dguyen Jun 3, 2026
0a21598
chore: add translations (#2922)
Mythie Jun 3, 2026
7f796ed
fix: update org stats table ui (#2924)
dguyen Jun 3, 2026
c41e387
docs(embedding): document iframe URL fragment options (#2915)
catalinpit Jun 4, 2026
0ecde7a
feat: add header metadata to emails (#2927)
dguyen Jun 4, 2026
fc6b787
chore: sync upstream documenso/documenso @ 0ecde7a
Mudit-Lal Jun 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
138 changes: 138 additions & 0 deletions .agents/plans/happy-teal-tree-platform-signing-page-branding.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
---
date: 2026-05-06
title: Platform Signing Page Branding
---

## What

Platform-plan organisations (and their teams) can customise the **non-embed
signing pages** (`/sign/:token`, `/d/:token`, and the sibling
complete/expired/rejected/waiting pages) with:

- Six brand colour tokens (background, foreground, primary, primary-foreground,
border, ring) plus a border-radius length.
- A free-text custom CSS block (up to 256 KB).

Settings live on `OrganisationGlobalSettings` and `TeamGlobalSettings`. Teams
inherit from the org via the existing `brandingEnabled === null` mechanism.

## Why

- Embed customers already have white-label CSS; Platform customers want the
same coverage on direct signing URLs that they iframe or link to.
- Persisting on org/team (not per envelope) means it's set-and-forget.
- Sanitising **on save** lets us inline the verbatim string at SSR — no
per-render parsing cost, no `<style>.innerHTML` injection on the client.
- Reusing the existing `embedSigningWhiteLabel` claim flag keeps "if you can
white-label an embed, you can white-label this" as one decision.

## How

### Storage (`packages/prisma/schema.prisma`)

Two new fields on each settings model. No new tables.

| Field | Org type | Team type |
| ---------------- | ------------------ | ------------------ |
| `brandingColors` | `Json?` (nullable) | `Json?` (nullable) |
| `brandingCss` | `String @default("")` | `String?` |

Colours are validated against `ZCssVarsSchema`. The team's `null` means
"inherit"; an empty colour object is collapsed to `null` server-side so a
team toggling `brandingEnabled = true` without filling in colours doesn't
silently override the org's defaults with nothing.

### Sanitiser (`packages/lib/utils/sanitize-branding-css.ts`)

PostCSS + `postcss-selector-parser`. Runs on save only.

- Drops selectors containing `::before`/`::after`/`::backdrop`/`::marker` or
the universal `*`.
- Drops integrity-breaking properties (`display`, `position`, `transform`,
layout-affecting dimensions, text-hiding properties).
- Drops declaration values containing `url(`, `expression(`, `@import`,
`javascript:`.
- Strips `!important`.
- Allows `@media` only; drops other at-rules.
- **Does not** rewrite selectors. Scoping happens at render time via native
CSS nesting under `.documenso-branded { ... }`.
- Final-pass tripwire: if a literal `</style` somehow survives serialization,
reject the entire output. PostCSS already escapes `<` to `\3c` whenever it
would form `</...`; the explicit check is belt-and-braces in case a future
serializer regresses.
- Returns `{ css, warnings[] }`. Warnings are surfaced in the UI.

Border-radius is the only token interpolated raw into a `<style>` block; it
is regex-validated (`CSS_LENGTH_REGEX`) at both the Zod schema and the
runtime `toNativeCssVars` call. Belt-and-braces against schema drift.

### Render (`apps/remix/app/components/general/recipient-branding.tsx`)

Each recipient loader calls `loadRecipientBrandingByTeamId` and threads the
payload through to `<RecipientBranding>`, which emits a single
nonce-attributed `<style>`:

```
.documenso-branded {
--background: ...; ...
<user css>
}
```

Native CSS nesting expands user rules under the wrapper. The body class is
applied unconditionally to recipient routes in `root.tsx` via `useMatches()`
so portaled Radix content (dialogs, popovers, tooltips, dropdowns) inherits
the scope.

CSP for recipient routes already supports `<style nonce>`; no policy
changes needed.

### Plan gate

`organisationClaim.flags.embedSigningWhiteLabel || !IS_BILLING_ENABLED()`.
Self-hosted instances always allow. The outer paywall for logo/URL/details
stays on `allowCustomBranding` (Team plan and up); only the new
colour/CSS section is Platform-only.

### UI (`apps/remix/app/components/forms/branding-preferences-form.tsx`)

Extends the existing branding form. Six `<ColorPicker showHex>` (rewritten
to use the native `<input type="color">` instead of `react-colorful`, which
was removed) in a 2-col grid, plus a free-text radius input and an
`<Accordion>` revealing a mono `<Textarea>`. Defaults come from
`packages/lib/constants/theme.ts` (light-mode hex mirror of `theme.css`).

Warnings from the sanitiser are surfaced in an `<Alert variant="warning">`
after save, and the `brandingCss` textarea is re-synced from the persisted
value so the user sees exactly what was stored. Other fields are
deliberately NOT reset on settings refetch — that would clobber in-flight
edits.

### TRPC

`update-organisation-settings` and `update-team-settings` accept the new
fields, run them through `sanitizeBrandingCss` + `normalizeBrandingColors`,
and return any sanitiser warnings to the client. The team route treats
`null` as "inherit"; an empty post-sanitisation string is collapsed to
`null` (team) so an empty override doesn't mask the org's CSS.

## Known accepted limitations

- The sanitiser does not prevent hostile-but-syntactically-valid CSS
(`color: transparent`, low-contrast values, etc.). The customer is
branding **their own** signing pages — we focus on integrity (no
overlay/hide/exfiltrate), not aesthetic policing.
- User rules targeting `body`/`html`/`:root` no-op once nested under the
wrapper class. Documented for users.
- CSS nesting baseline is Chrome 120+ / Firefox 117+ / Safari 16.5+.
Acceptable for the Platform-tier audience.
- No automated `theme.css` ↔ `theme.ts` sync check; fat comment in
`theme.ts` reminds devs to update both.
- Per-section team inherit is coarse — `brandingEnabled = null` inherits
everything from the org. Per-field inherit toggles are deferred.

## Out of scope

Live preview, embed-route sanitiser unification, email/PDF certificate
branding, custom font upload, the full ~30 colour tokens in the picker UI,
wiring `hidePoweredBy` through to the actual footer.
94 changes: 94 additions & 0 deletions .agents/plans/smooth-ivory-sky-partial-signed-pdf-download.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
date: 2026-04-22
title: Partial Signed Pdf Download
---

## Summary

Let team members fetch a PDF with all currently-inserted fields burned in while the envelope is still in `PENDING` status. Today the only available bytes for a pending envelope are the original (no fields) - the sealed PDF only materialises after the last recipient signs and the `seal-document` job runs.

Exposed in two places:

- v2 API: `GET /api/v2/envelope/item/{envelopeItemId}/download?version=pending` (API-token auth)
- UI: a `Partial` button in the existing `EnvelopeDownloadDialog`, alongside `Original`. Replaces the `Signed` slot when the envelope is `PENDING`. Backed by the existing session-authed file route `GET /api/files/envelope/{envelopeId}/envelopeItem/{id}/download/pending`.

## Scope

- v2 API only (no v1).
- `internalVersion === 2` envelopes only. Legacy v1 returns 400 `ENVELOPE_LEGACY`.
- Team-side / owner only. No recipient-token download path - recipients have the in-app overlay viewer for verification, and a downloadable half-signed PDF is a leak vector for partially-executed contracts. Enforced both at the server (the recipient-token file route does not accept `pending`) and at the UI (the dialog hides the Partial button when a recipient token is set).
- No PKI signature, no certificate page, no audit log appendix - the response is explicitly not a final executed document.
- No watermark or banner text. The filename suffix (`_pending.pdf`), the `Cache-Control: no-store, private` header, and the absence of a PKI signature are sufficient to signal draft status.

## Behaviour

API response matrix (both `/api/v2/envelope/item/{id}/download?version=pending` and the UI-facing `/api/files/envelope/{envelopeId}/envelopeItem/{id}/download/pending`):

| Envelope status | Response |
|---|---|
| `PENDING` (v2) | 200, PDF with currently-inserted fields burned in |
| `PENDING` (v1) | 400 `ENVELOPE_LEGACY` |
| `DRAFT` | 400 `ENVELOPE_DRAFT` |
| `COMPLETED` | 400 `ENVELOPE_COMPLETED` |
| `REJECTED` | 400 `ENVELOPE_REJECTED` |

All v1-vs-v2 / status-mismatch errors are 4xx so callers can cleanly separate them from real server failures (5xx). Specifically v1 PENDING returns 400 not 501: 5xx is reserved for actual server problems, while "this envelope can't satisfy this request shape" is a client-addressable condition.

Filename: `{title}_pending.pdf`.

ETag is content-addressed over `sha256(envelope.status + sorted((field.id, field.customText, field.signature?.id, field.signature?.created) for inserted===true fields))`. Returns 304 on `If-None-Match` match.

No persistent caching. Generated on-demand per request when ETag misses.

Error response shape (envelope item v2 download route and the team-side file route): preserves the existing `{ error: <message> }` field for backwards compatibility and adds `code: <APP_ERROR_CODE>` as a new field for callers that want to branch on it. The document download route (`/document/{documentId}/download`) is untouched.

## UI

`apps/remix/app/components/dialogs/envelope-download-dialog.tsx`:

- The dialog shows `Original` plus one of:
- `Signed` when status is `COMPLETED` (existing behaviour)
- `Partial` when status is `PENDING`, there is no recipient token, and the envelope is not legacy (`!isLegacy`)
- nothing otherwise
- New optional prop `isLegacy?: boolean`. Only consulted to gate the `Partial` button, so callers whose status can never be `PENDING` (DRAFT/COMPLETED/REJECTED hardcoded, or `isComplete: true` matchers) and callers that always set a recipient token can omit it. Three call sites pass it (`isLegacy={envelope.internalVersion === 1}`): `documents-table-action-dropdown.tsx`, `envelope-editor.tsx`, `document-page-view-dropdown.tsx`. The other eight callers were left alone.

Trade-off: a future team-side dialog usage where status could be PENDING but the dev forgets `isLegacy` will silently not render the Partial button. The status gate prevents an actively broken click; missing button is discoverable in testing. Required-prop alternative was rejected because eight of eleven call sites would carry a meaningless value.

## Files

Server:

- `apps/remix/server/api/download/download.types.ts` - added `'pending'` to the `version` enum; split the validator into `param` (envelopeItemId) + `query` (version). The original wiring as a path-param validator was a pre-existing bug: requests like `?version=original` were silently returning the signed PDF since `version` actually arrives as a query string. Fixed as a side effect.
- `packages/trpc/server/envelope-router/download-envelope-item.types.ts` - mirrored the enum change in the OpenAPI schema.
- `apps/remix/server/api/download/download.ts` - the envelope item v2 route now fetches envelope recipients alongside the envelope, branches on `version` when calling the helper, and emits AppError responses as `{ error, code }` consistently across all status codes.
- `apps/remix/server/api/files/files.types.ts` - added `'pending'` to the team-side download schema only. The recipient-token download schema is untouched, so `/api/files/token/.../download/pending` is rejected by the schema validator.
- `apps/remix/server/api/files/files.ts` - the team-side download handler fetches envelope recipients and dispatches the `pending` branch through the same `handleEnvelopeItemFileRequest` helper. Wrapped in a try/catch that returns `{ error, code }` for AppErrors.
- `apps/remix/server/api/files/files.helpers.ts` - `handleEnvelopeItemFileRequest` is now a single entry point taking a discriminated-union options type. The static-file flow (`signed`/`original`) and the on-demand pending flow are private helpers in the same module.
- `packages/lib/server-only/pdf/generate-partial-signed-pdf.ts` (new) - small orchestrator that loads the original PDF, groups inserted fields by page, calls the existing `insertFieldInPDFV2` overlay helper for each page, flattens, and saves.
- `packages/lib/errors/app-error.ts` - added `ENVELOPE_DRAFT`, `ENVELOPE_COMPLETED`, `ENVELOPE_REJECTED`, `ENVELOPE_LEGACY` codes, all mapped to 400. The legacy-envelope case deliberately returns 4xx rather than 501 to keep "this resource can't satisfy this operation" distinct from real 5xx server failures in caller logs/metrics.

Client:

- `packages/lib/utils/envelope-download.ts` - `EnvelopeItemPdfUrlOptions` download variant now allows `'pending'` as a version. The recipient-token URL builder will produce a URL the server rejects, but the dialog gates on no-token at the call site.
- `packages/lib/client-only/download-pdf.ts` - `DocumentVersion` extended; filename suffix logic moved into a small switch (`_signed.pdf`, `_pending.pdf`, `.pdf`).
- `apps/remix/app/components/dialogs/envelope-download-dialog.tsx` - secondary download derivation with the new `Partial` branch, optional `isLegacy` prop.
- `apps/remix/app/components/tables/documents-table-action-dropdown.tsx`, `apps/remix/app/components/general/envelope-editor/envelope-editor.tsx`, `apps/remix/app/components/general/document/document-page-view-dropdown.tsx` - pass `isLegacy={envelope.internalVersion === 1}` (or `row.internalVersion === 1`) to the dialog.

## Verification

1. E2E (`packages/app-tests/e2e/api/v2/partial-signed-pdf-download.spec.ts`):
- Pending envelope, recipient 1 signs, API token download with `?version=pending` returns 200 + PDF; subsequent call with `If-None-Match: <etag>` returns 304; after recipient 2 completes the envelope flips to `COMPLETED` and the same call returns 400 `ENVELOPE_COMPLETED`; `?version=signed` then succeeds.
- Draft envelope returns 400 `ENVELOPE_DRAFT`.
- `internalVersion === 1` pending envelope returns 400 `ENVELOPE_LEGACY`.

2. `npx tsc --noEmit -p apps/remix/tsconfig.json` and `npm run lint`.

3. Manual: open the Documents table or envelope editor on a PENDING envelope (v2), open the download dialog, confirm `Partial` appears alongside `Original` and produces a `_pending.pdf` with current fields burned in. Same dialog on a COMPLETED envelope shows `Signed`. Same dialog on a v1 PENDING envelope shows neither (status gate would show Partial, but the `isLegacy` flag filters it out).

## Out of Scope / Follow-ups

- Recipient-token download path (API and UI) - decided against. Revisit if there is concrete demand and a story for limiting the leak vector.
- v1 API parity / v1 partial rendering - not building. Implementing partial for v1 would require porting `legacy_insertFieldInPDF` / `insertFieldInPDFV1` into a partial-only flow, which is code with no long-term home as v1 is being phased out.
- Document download route (`/document/{documentId}/download`) - untouched. Same error shape and validator wiring as before. Consider normalising to the same `{ error, code }` shape in a follow-up if any caller wants to branch on `code` from that route.
- Persistent caching layer / job-queue generation - revisit if p95 latency on large PDFs becomes an issue.
- Specific toast for `ENVELOPE_LEGACY` in the dialog - currently the catch-all "Something went wrong" handles it. Worth a polish if v1 PENDING envelopes are common in your data and we see complaints. (Note: with the `isLegacy` gate at the UI, the error is unreachable from the dialog itself; the API can still surface it for direct callers.)
24 changes: 23 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,16 @@ NEXT_PRIVATE_REDIS_PREFIX="documenso"
NEXT_PUBLIC_POSTHOG_KEY=""
# OPTIONAL: Leave blank to disable billing.
NEXT_PUBLIC_FEATURE_BILLING_ENABLED=
# OPTIONAL: Leave blank to allow users to signup through /signup page.
# OPTIONAL: Set to "true" to disable all signup methods (email, Google, Microsoft, OIDC, including the organisation OIDC portal).
NEXT_PUBLIC_DISABLE_SIGNUP=
# OPTIONAL: Set to "true" to disable email/password signup only.
NEXT_PUBLIC_DISABLE_EMAIL_PASSWORD_SIGNUP=
# OPTIONAL: Set to "true" to block new-account creation through Google. Existing linked users can still sign in.
NEXT_PUBLIC_DISABLE_GOOGLE_SIGNUP=
# OPTIONAL: Set to "true" to block new-account creation through Microsoft. Existing linked users can still sign in.
NEXT_PUBLIC_DISABLE_MICROSOFT_SIGNUP=
# OPTIONAL: Set to "true" to block new-account creation through OIDC (including the organisation portal).
NEXT_PUBLIC_DISABLE_OIDC_SIGNUP=
# OPTIONAL: Comma-separated list of email domains allowed to sign up (e.g., example.com,acme.org).
NEXT_PRIVATE_ALLOWED_SIGNUP_DOMAINS=
# OPTIONAL: Set to true to use internal webapp url in browserless requests.
Expand Down Expand Up @@ -203,3 +211,17 @@ NEXT_PRIVATE_LOGGER_FILE_PATH=

# [[PLAIN SUPPORT]]
NEXT_PRIVATE_PLAIN_API_KEY=

# [[DOCUMENT CONVERSION]]
# OPTIONAL: Base URL of a Gotenberg-compatible service used to convert uploaded
# DOCX files to PDF on the server. When unset, DOCX uploads are disabled and
# only PDF is accepted. The dev docker compose exposes Gotenberg on port 3005.
# NEXT_PRIVATE_DOCUMENT_CONVERSION_URL="http://localhost:3005"
# OPTIONAL: Per-request timeout in milliseconds for the conversion service.
# Defaults to 30000 (30s) if unset.
# NEXT_PRIVATE_DOCUMENT_CONVERSION_TIMEOUT_MS=30000
# OPTIONAL: HTTP Basic auth credentials for the conversion service. Set both
# when the service is started with `--api-enable-basic-auth` (the dev compose
# does this; the matching values there are `documenso` / `password`).
# NEXT_PRIVATE_DOCUMENT_CONVERSION_USERNAME=documenso
# NEXT_PRIVATE_DOCUMENT_CONVERSION_PASSWORD=password
20 changes: 1 addition & 19 deletions .github/actions/node-install/action.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: 'Setup node and cache node_modules'
name: 'Setup node'
inputs:
node_version:
required: false
Expand All @@ -16,25 +16,7 @@ runs:
shell: bash
run: corepack enable npm

- name: Cache npm
uses: actions/cache@v3
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-

- name: Cache node_modules
uses: actions/cache@v3
id: cache-node-modules
with:
path: |
node_modules
packages/*/node_modules
apps/*/node_modules
key: modules-${{ hashFiles('package-lock.json') }}

- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
shell: bash
run: |
npm ci --no-audit
Expand Down
13 changes: 1 addition & 12 deletions .github/actions/playwright-install/action.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
name: Install playwright binaries
description: 'Install playwright, cache and restore if necessary'
description: 'Install playwright'
runs:
using: 'composite'
steps:
- name: Cache playwright
id: cache-playwright
uses: actions/cache@v3
with:
path: |
~/.cache/ms-playwright
${{ github.workspace }}/node_modules/playwright
key: playwright-${{ hashFiles('**/package-lock.json') }}
restore-keys: playwright-

- name: Install playwright
if: steps.cache-playwright.outputs.cache-hit != 'true'
run: npx playwright install --with-deps
shell: bash
18 changes: 0 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,28 +41,10 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-

- name: Build Docker Image
uses: docker/build-push-action@v5
with:
push: false
context: .
file: ./docker/Dockerfile
tags: documenso-${{ github.sha }}
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

- # Temp fix
# https://github.com/docker/build-push-action/issues/252
# https://github.com/moby/buildkit/issues/1896
name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
1 change: 0 additions & 1 deletion .github/workflows/issue-assignee-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: '18'
cache: npm

- name: Install Octokit
run: npm install @octokit/rest@18
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr-labeler.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: 'PR Labeler'

on:
- pull_request_target
- pull_request

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand Down
Loading
Loading