Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
26f0c4c
chore: deprecate endpoints (#3022)
dguyen Jul 23, 2026
54befb5
fix: update stripe team member billing (#2991)
dguyen Jul 23, 2026
c02dfab
feat: rework command search (#3109)
dguyen Jul 23, 2026
e4897fa
fix: sort CC recipients last (#2930)
catalinpit Jul 24, 2026
a457e1e
feat: add copy button for license key in admin panel (#3123)
ephraimduncan Jul 27, 2026
6ec67d1
feat: rejected and expired recipient filters (#2889)
ephraimduncan Jul 30, 2026
29020bc
docs(webhooks): correct retry policy, timeout and payload reference (…
dguyen Aug 3, 2026
b3c609a
feat: bulk download documents (#2711)
ephraimduncan Aug 3, 2026
9c27ce6
feat: replace document status tabs with filter pills (#3145)
Mythie Aug 3, 2026
8bfcec8
fix: add logging for errors on sign or complete (#3149)
Mythie Aug 4, 2026
f0ab7c1
fix: add more logging for errors on sign or complete (#3151)
Mythie Aug 5, 2026
d6cf3fe
feat: unify settings (#3128)
dguyen Aug 9, 2026
fc95ee9
fix: handle completion when already signed (#3159)
Mythie Aug 9, 2026
797f5c0
fix: improve unified settings (#3160)
dguyen Aug 10, 2026
820b319
fix: restore overriden translations (#3161)
dguyen Aug 10, 2026
962cffc
chore: extract translations (#3163)
github-actions[bot] Aug 10, 2026
1bd0948
feat(remix): support serving app under a sub-path via NEXT_PUBLIC_BAS…
GMCfourX4 Aug 12, 2026
617f8cc
feat: show feature gated setting pages (#3167)
dguyen Aug 12, 2026
a5e37af
chore: update Polish translations (#3210)
mKoonrad Aug 14, 2026
688ef2f
fix: docker healtcheck (#3176)
catalinpit Aug 14, 2026
283c6d2
fix: use documenso fork of skia-canvas for rendering (#3214)
Mythie Aug 17, 2026
779de01
feat: migrate to react 19 (#3107)
Mythie Aug 17, 2026
6a8fe6f
chore: remove planning skills (#3177)
dguyen Aug 18, 2026
3e0c1c4
chore: deps 2026-08-17 (#3225)
Mythie Aug 18, 2026
9bab1cd
chore: add github action timeouts (#3228)
dguyen Aug 18, 2026
871c2a6
fix: surface actionable errors when completing documents (#3229)
Mythie Aug 18, 2026
0099dd6
feat(ui): redesign recipient field hover card (#3070)
ephraimduncan Aug 19, 2026
05f646b
docs(api): document rate limit headers and 429 variants (#3133)
ephraimduncan Aug 19, 2026
914e325
docs(trpc): add openapi descriptions to envelope cancel, delete and u…
ephraimduncan Aug 19, 2026
d42254f
docs(api): document cancel endpoint and fix get-many body shape (#3135)
ephraimduncan Aug 19, 2026
7533016
v2.17.0
Mythie Aug 19, 2026
ead3104
fix: add type button to multiselect remove and clear buttons (#3256)
misinierijon4-debug Aug 24, 2026
5beb57d
fix(i18n): add Plural support for bulk download limit alert (#3209) (…
Rodrigoue9 Aug 24, 2026
7f11c16
fix(i18n): add plural forms for command menu results and item counts …
Rodrigoue9 Aug 24, 2026
1de57a9
fix(emails): remove trailing period from document pending email subje…
waterWang Aug 24, 2026
73d58bd
feat: allow admin recipient search (#3277)
dguyen Aug 25, 2026
10f2b80
feat: last used column for api tokens (#3230)
catalinpit Aug 25, 2026
dd08da7
refactor(trpc): use ts-pattern for envelope document-data resolution …
ephraimduncan Aug 25, 2026
e532843
chore: extract translations (#3171)
github-actions[bot] Aug 25, 2026
9dc83bd
fix: allow zooming and dragging uploaded signatures (#3173)
dguyen Aug 26, 2026
ee83605
fix: render adjacent email template variables correctly (#3153)
Arunendra21 Aug 26, 2026
ae4b56d
feat: optimise posthog usage and add error tracking (#3301)
Mythie Aug 27, 2026
4285c88
chore: add provider request template (#3311)
Mythie Aug 27, 2026
5082b47
chore: update Polish translation (#3316)
mKoonrad Aug 28, 2026
eba71d9
fix: prevent dialogs from closing on blur (#3314)
dguyen Sep 1, 2026
937e937
fix: enlarge envelope field resize handle hit area (#3315)
catalinpit Sep 1, 2026
a2745ed
docs: clarify team API token and certificate behavior (#3317)
catalinpit Sep 1, 2026
bf406aa
chore: upgrade libpdf (#3323)
Mythie Sep 1, 2026
3ec877a
fix: add patches back to dockerfile (#3324)
Mythie Sep 1, 2026
cbb1cf7
fix: default unset signing transport to local (#3309)
catalinpit Sep 3, 2026
dabb7b7
fix: increase signature placeholder size when a timestamp authority i…
theredcat Sep 4, 2026
30a6b19
chore: upgrade to node 24 lts and clean up docker image (#3332)
Mythie Sep 4, 2026
4aa3583
fix: temporary fix for British to US spelling (#3329)
catalinpit Sep 4, 2026
2cac63a
fix: block SSRF via IPv4-mapped IPv6 webhook URLs (#2901) (#3166)
martindglaser Sep 4, 2026
6a8bb4b
fix: improve invalid bulk template upload error handling (#3326)
dguyen Sep 8, 2026
5e8a434
fix: use react router middleware (#3351)
Mythie Sep 9, 2026
3b20c2f
chore(ui): remove unused text fitting component (#3337)
ephraimduncan Sep 9, 2026
f5ab8a8
fix: resolve build errors (#3352)
Mythie Sep 9, 2026
c5acba5
chore(auth): remove commented account creation code (#3344)
ephraimduncan Sep 9, 2026
389390c
v2.18.0
Mythie Sep 9, 2026
b97c22a
fix: pass csp nonce to @hello-pangea/dnd style elements (#3354)
catalinpit Sep 10, 2026
8291816
fix(i18n): add missing plural in bulk template upload error handling …
mKoonrad Sep 10, 2026
e1ad4a2
docs: add externalId to send-for-signature workflow example (#3325)
catalinpit Sep 10, 2026
6a99b40
fix: improve action auth flow for passwordless users (#3358)
Mythie Sep 11, 2026
20ae849
fix: use nuqs prevent infinite re-render loop (#2868)
durgeshhhhhhh Sep 11, 2026
5603a9e
fix: preserve base path in email asset URLs (#3327)
GMCfourX4 Sep 11, 2026
b29fb9d
Merge remote-tracking branch 'upstream/main' into chore/sync-upstream…
Sep 11, 2026
aeab9c0
feat: add skia-canvas dependency to package.json and package-lock.json
Sep 12, 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
  •  
  •  
  •  
146 changes: 146 additions & 0 deletions .agents/plans/wild-indigo-wave-rejected-expired-recipient-filters.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
---
date: 2026-05-28
title: Rejected Expired Recipient Filters
---

## Context

Customers need to find (a) envelopes/documents in the `REJECTED` state and (b) envelopes
with at least one recipient whose signing link has **expired**. Today the UI only exposes
`INBOX / PENDING / COMPLETED / DRAFT / ALL` tabs, and the public API has no way to filter by
expired recipient links — forcing a fetch-all-`PENDING`-then-inspect-each-recipient workaround.

Two key facts from exploration shaped this plan:

- **`REJECTED` is already fully wired in the backend** — the where-clause (`find-documents.ts`),
stats counts (`get-stats.ts`), tRPC response schema, `ExtendedDocumentStatus` enum, and the
`FRIENDLY_STATUS_MAP` display all handle it. It is simply absent from the UI tab array.
- **Renewing expired links already works.** `resendDocument` refreshes `expiresAt` and clears
`expirationNotifiedAt` for unsigned, non-CC recipients (`resend-document.ts:98-121`), exposed
publicly via `POST /api/v2/document/redistribute` and `/api/v2/envelope/redistribute` and via the
resend/redistribute UI dialogs. No new renew mechanism is needed — only documentation/wording.

Expiration is a per-recipient condition (not an envelope status). The approved design models it
in the UI as an `EXPIRED` **pseudo-status tab** (reusing the existing tab machinery, mirroring how
`REJECTED` works) and in the public API as an orthogonal boolean `hasExpiredRecipients`. Both share
one EXISTS predicate.

Definition of "expired recipient" (matches `isRecipientExpired`, `packages/lib/utils/recipients.ts:118`):
a `Recipient` with `expiresAt IS NOT NULL AND expiresAt <= now() AND signingStatus = NOT_SIGNED AND role != CC`.

## Approach

### A. Shared EXISTS predicate (reused 4x, justified)
Add a local `hasExpiredRecipient(eb)` helper — modeled on the existing per-file `recipientExists` /
`senderEmailIs` helpers — to `find-documents.ts`, `get-stats.ts`, and `find-envelopes.ts`. It is the
single source of truth for the expired condition above (using `new Date()` for `now`, matching the
`period` filter's `.toJSDate()` style).

### B. REJECTED tab (UI only — backend already done)
- `apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents._index.tsx`: add
`ExtendedDocumentStatus.REJECTED` to the tab array (lines 149-155). Count badge, highlight, and
`?status=REJECTED` filtering already work via existing machinery.

### C. EXPIRED pseudo-status (UI + internal stats)
1. `packages/prisma/types/extended-document-status.ts`: add `EXPIRED: 'EXPIRED'`. Internal-only —
the public `DocumentStatus` enum is unaffected. This intentionally surfaces TS errors at the three
exhaustive/`Record<ExtendedDocumentStatus>` sites below, forcing them to be handled.
2. `packages/lib/server-only/document/find-documents.ts`:
- Add `.with(ExtendedDocumentStatus.EXPIRED, ...)` to **both** `applyPersonalFilters` and
`applyTeamFilters`, mirroring the `COMPLETED` branch's access control (deleted + visibility +
owner/recipient access) with `hasExpiredRecipient(eb)` AND-ed in. Do **not** constrain
`Envelope.status` — the EXISTS already restricts to unsigned recipients.
3. `packages/lib/server-only/document/get-stats.ts`:
- Add an `expiredQuery` mirroring `pendingQuery`'s access control + `hasExpiredRecipient(eb)`.
- Add it to the `Promise.all`, add `[ExtendedDocumentStatus.EXPIRED]: expired` to the `stats`
record. **Do not** add `expired` to the `all` sum (it overlaps `PENDING`).
4. `packages/trpc/server/document-router/find-documents-internal.types.ts`: add
`[ExtendedDocumentStatus.EXPIRED]: z.number()` to the `stats` response object. (`status` already
accepts the extended enum via `z.nativeEnum(ExtendedDocumentStatus)`.)
5. `apps/remix/app/components/general/document/document-status.tsx`: add an `EXPIRED` entry to
`FRIENDLY_STATUS_MAP` — `label: msg` Expired, an icon (e.g. lucide `TimerOff`, matching the
`/sign/$token/expired` page), and a distinct color (e.g. `text-orange-500`) to differentiate from
`REJECTED` (red).
6. `documents._index.tsx`: add `[ExtendedDocumentStatus.EXPIRED]: 0` to the `stats` `useState`
initializer and `ExtendedDocumentStatus.EXPIRED` to the tab array. Final order:
`INBOX, PENDING, COMPLETED, DRAFT, REJECTED, EXPIRED, ALL`.
7. (Optional, recommended) `apps/remix/app/components/tables/documents-table-empty-state.tsx`: add
tailored `EXPIRED` and `REJECTED` empty-state copy (currently both fall through to `.otherwise()`).

### D. Public API boolean `hasExpiredRecipients` (document + envelope, v2)
1. `packages/lib/server-only/document/find-documents.ts`: add `hasExpiredRecipients?: boolean` to
`FindDocumentsOptions`; when true, apply `.where((eb) => hasExpiredRecipient(eb))` inside
`buildBaseQuery` (orthogonal/additive to any `status`).
2. `packages/trpc/server/document-router/find-documents.types.ts`: add a query-safe boolean
`hasExpiredRecipients` to `ZFindDocumentsRequestSchema` with a `.describe(...)`. Mirror the
existing boolean-query-param handling in `find-document-audit-logs.types.ts`
(`filterForRecentActivity`) — avoid raw `z.coerce.boolean()` (the "false" -> true footgun); use a
string transform if needed. Pass it through in `find-documents.ts` (public handler).
3. `packages/lib/server-only/envelope/find-envelopes.ts`: add `hasExpiredRecipients?: boolean` to
`FindEnvelopesOptions` + the `hasExpiredRecipient(eb)` helper + the additive `.where`.
4. `packages/trpc/server/envelope-router/find-envelopes.types.ts`: add the same param to
`ZFindEnvelopesRequestSchema`; pass it through in the envelope-router find handler.
The param auto-appears in the generated `/api/v2/openapi.json`.

Note: REST v1 `GET /api/v1/documents` is deprecated and lacks status filtering — left unchanged.
`REJECTED` is already a valid public `status` value (`DocumentStatus.REJECTED`), so no API change is
needed for rejected filtering.

### E. Renew expired links — documentation only
No functional change. Document that resending renews expired links:
- Update the `.description` in `packages/trpc/server/document-router/redistribute-document.types.ts`
and `packages/trpc/server/envelope-router/redistribute-envelope.types.ts` to state that
redistributing refreshes the signing-link expiration for unsigned recipients.
- Optionally adjust resend/redistribute dialog copy
(`apps/remix/app/components/dialogs/document-resend-dialog.tsx`,
`envelope-redistribute-dialog.tsx`) to mention it renews expired links.

## Files To Modify (summary)

| Area | File |
|------|------|
| Enum | `packages/prisma/types/extended-document-status.ts` |
| Where-clause + API option | `packages/lib/server-only/document/find-documents.ts` |
| Stats counts | `packages/lib/server-only/document/get-stats.ts` |
| Envelope find (API) | `packages/lib/server-only/envelope/find-envelopes.ts` |
| Internal tRPC stats schema | `packages/trpc/server/document-router/find-documents-internal.types.ts` |
| Public doc API schema + handler | `packages/trpc/server/document-router/find-documents.types.ts`, `find-documents.ts` |
| Public envelope API schema + handler | `packages/trpc/server/envelope-router/find-envelopes.types.ts`, `find-envelopes.ts` |
| Status display | `apps/remix/app/components/general/document/document-status.tsx` |
| Tabs + stats init | `apps/remix/app/routes/_authenticated+/t.$teamUrl+/documents._index.tsx` |
| Empty state (optional) | `apps/remix/app/components/tables/documents-table-empty-state.tsx` |
| Renew docs | `redistribute-document.types.ts`, `redistribute-envelope.types.ts` (+ resend dialogs, optional) |

## Reused Utilities / Patterns
- `recipientExists` / `senderEmailIs` (per-file Kysely EXISTS helpers) — the template for the new
`hasExpiredRecipient` helper.
- `REJECTED` branches in `find-documents.ts` (lines 279, 416) and `rejectedQuery` in `get-stats.ts`
(line 227) — the template for the `EXPIRED` branches / `expiredQuery`.
- `isRecipientExpired` (`packages/lib/utils/recipients.ts:118`) — defines the `expiresAt <= now`
semantics to match.
- Existing tab machinery in `documents._index.tsx` (`getTabHref`, count badge, personal-org `.filter`)
— works unchanged for the new tabs.
- `resendDocument` / `trpc.document.redistribute` / `trpc.envelope.redistribute` — existing renew path.

## Verification
1. **Typecheck** (the enum change forces all exhaustive/Record sites): `npm run typecheck -w @documenso/remix`.
2. **Seed + UI** (dev server already running): seed a team via `seedTeam`, send a document, then:
- Reject one as a recipient -> it appears under the new **Rejected** tab with a count.
- Force expiry (set a recipient `expiresAt` in the past, e.g. via Prisma Studio or a short
`envelopeExpirationPeriod`) -> the doc appears under the new **Expired** tab with a count, and the
count excludes signed/CC recipients.
3. **Public API**: `GET /api/v2/document?hasExpiredRecipients=true` and
`GET /api/v2/envelope?hasExpiredRecipients=true` (Bearer API token) return only envelopes with >=1
expired unsigned recipient; confirm `GET /api/v2/document?status=REJECTED` works. Verify the param
appears in `/api/v2/openapi.json`.
4. **Renew**: on an expired doc, run resend/redistribute (UI dialog or
`POST /api/v2/document/redistribute`) -> recipient `expiresAt` is refreshed, the doc leaves the
Expired tab, and the signing link no longer redirects to `/sign/$token/expired`.
5. **E2E** (optional): extend `packages/app-tests/e2e/envelopes/envelope-expiration-send.spec.ts`
with an Expired-tab assertion.
6. Do **not** modify/commit `packages/lib/translations/*.po`; run `npm run translate` only if needed
for new `msg`/`Trans` strings, and keep generated `.po` files out of the branch.

## Open Questions
- Exact icon/color for the `EXPIRED` tab (proposed: `TimerOff`, `text-orange-500`).
- Whether to add the optional tailored empty-state copy now or defer.
56 changes: 0 additions & 56 deletions .agents/skills/create-justification/SKILL.md

This file was deleted.

56 changes: 0 additions & 56 deletions .agents/skills/create-plan/SKILL.md

This file was deleted.

56 changes: 0 additions & 56 deletions .agents/skills/create-scratch/SKILL.md

This file was deleted.

61 changes: 61 additions & 0 deletions .github/ISSUE_TEMPLATE/deploy-provider-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: 'One-Click Deploy Provider Request'
description: Request a new one-click deployment provider (Railway, Render, etc.) to be added to our README
title: 'One-Click Deploy Provider Request: [Provider Name]'
labels: ['deploy-provider-request']
body:
- type: markdown
attributes:
value: |
Thanks for your interest in adding a one-click deploy option for Documenso!

Each provider we list requires us to create, test, and maintain a deployment template, which is ongoing work on top of everything else. To keep this manageable, we ask that providers (or users) **open an issue instead of a PR** so the community can signal interest.

**How this works:**

- 👍 this issue if you'd like to see Documenso deployable on this provider.
- If community interest is high enough, we'll consider adding it to the README.
- Opening an issue is not a guarantee of inclusion. PRs adding badges without a prior issue and demonstrated interest will be closed.
- type: input
attributes:
label: Provider Name
placeholder: e.g. Railway
validations:
required: true
- type: input
attributes:
label: Provider Website
placeholder: e.g. https://railway.com
validations:
required: true
- type: input
attributes:
label: Deploy/Template URL
description: A link to an existing deployment template or deploy button URL, if one exists.
- type: dropdown
attributes:
label: Who creates and maintains the deployment template?
options:
- The provider
- Me / the community
- Nobody yet
validations:
required: true
- type: textarea
attributes:
label: Testing & Maintenance
description: Has the template been tested against the current Documenso release? How are updates handled when Documenso ships breaking changes (env vars, migrations, Docker changes)?
validations:
required: true
- type: textarea
attributes:
label: Why this provider?
description: Tell us why Documenso users would benefit — existing user base, region coverage, free tier, etc.
validations:
required: true
- type: checkboxes
attributes:
label: Please check the boxes that apply to this request.
options:
- label: I have searched existing issues to make sure this provider has not already been requested.
- label: I understand that inclusion depends on community interest and is not guaranteed.
- label: I understand that PRs adding deploy badges without a prior issue will be closed.
2 changes: 1 addition & 1 deletion .github/actions/node-install/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: 'Setup node'
inputs:
node_version:
required: false
default: v22.x
default: v24.x

runs:
using: 'composite'
Expand Down
Loading
Loading