Skip to content

feat: allow sending a document back to a signer for correction - #145

Merged
reeseherber merged 1 commit into
mainfrom
build-143
Jul 28, 2026
Merged

feat: allow sending a document back to a signer for correction#145
reeseherber merged 1 commit into
mainfrom
build-143

Conversation

@reeseherber

@reeseherber reeseherber commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

Problem

Freshservice ticket FS-162755: a recipient partway through signing a document has no way to fix a mistake made by an earlier recipient (or the sender) without rejecting the whole envelope. Reject immediately seals the document as terminal REJECTED (packages/lib/server-only/document/reject-document-with-token.ts, packages/lib/jobs/definitions/internal/seal-document.handler.ts), which throws away every other recipient's work and forces starting over.

Approach

Added a "send back for correction" action, modeled closely on the existing reject flow but non-terminal: the document stays PENDING, and only the targeted recipient is reset.

  • Who can act: a recipient (via signing token, mirroring rejectDocumentWithToken) or the document owner (via an authenticated, team-scoped mutation, mirroring resendDocument).
  • Who can be targeted: "an earlier recipient" is defined as a SIGNER/APPROVER/ASSISTANT recipient who has already completed their part (signingStatus === SIGNED) — that's the concrete, checkable meaning of "earlier" given the schema has no linear recipient state machine. A recipient can also target "the sender" (the document owner), represented as a null target — that path is notification-only since the owner isn't a Recipient row.
  • What gets reset: only the target recipient's signingStatus/signedAt and their own Field/Signature rows are cleared. No schema migration was needed — SigningStatus.NOT_SIGNED already exists, and the correction reason is recorded on the audit log entry rather than a new column. Every other recipient's data is left untouched, satisfying the "already-collected signatures are preserved" acceptance criterion by construction rather than by extra bookkeeping.
  • Notifications: a new job (send.document.sent-back-for-correction.emails) emails whoever must act (the target recipient, or the owner if sent back to the sender), and separately notifies the document owner as an FYI when a recipient (not the owner) initiated the send-back.
  • Audit log: new DOCUMENT_RECIPIENT_SENT_BACK_FOR_CORRECTION entry, following the same createDocumentAuditLogData pattern as DOCUMENT_RECIPIENT_REJECTED.

Note on environment: this worktree's packages/prisma/generated/ directory was missing when I started (not caused by this change) — regenerated it with npx prisma generate per the worktree's documented exception for that exact failure mode, since it was blocking typecheck.

Rebuild note (validation attempt 3)

Validation attempt 2 found the owner-side "Send back for correction" icon button inert: the trigger passed to DocumentSendBackForCorrectionDialog was a <TooltipProvider> tree, and Radix's <DialogTrigger asChild> clones its click handlers onto its immediate child — a bare context provider that renders no DOM node — so they never reached the <button>.

Fixed by moving the tooltip outside the dialog component: TooltipTrigger asChild now clones onto a <span> wrapper, and the dialog's own DialogTrigger asChild clones onto the <Button> directly. This matches the pattern already used a few lines below in the same file for the "Copy Signing Links" button, and matches every other trigger-prop consumer in the repo, which all pass a single DOM-forwarding element. Single file changed: apps/remix/app/components/general/document/document-page-view-recipients.tsx.

The recipient-initiated flow, the DB reset logic and the audit-log entry were all verified working in validation attempt 2 and are unchanged.

Changes

  • packages/lib/server-only/document/send-document-back-for-correction.ts — core logic: sendDocumentBackForCorrectionWithToken (recipient-initiated) and sendDocumentBackForCorrection (owner-initiated), sharing a resetRecipientForCorrection transaction helper.
  • packages/lib/types/document-audit-logs.ts, packages/lib/utils/document-audit-logs.ts — new audit log type, Zod event schema, and human-readable formatting.
  • packages/lib/jobs/definitions/emails/send-sent-back-for-correction-emails.{ts,handler.ts}, packages/lib/jobs/client.ts — new background job that sends the correction/notification emails.
  • packages/email/templates/document-sent-back-for-correction.tsx, packages/email/template-components/template-document-sent-back-for-correction.tsx — new email template (shared by the "please correct this" and "FYI" copy variants).
  • packages/trpc/server/recipient-router/{schema,router}.tssendDocumentBackForCorrectionWithToken mutation (token auth, like rejectDocumentWithToken).
  • packages/trpc/server/envelope-router/send-back-for-correction.{ts,types.ts}, packages/trpc/server/envelope-router/router.tssendBackForCorrection mutation (authenticated, owner-initiated), registered as envelope.sendBackForCorrection.
  • apps/remix/app/components/general/document-signing/document-signing-send-back-dialog.tsx, document-signing-page-view-v2.tsx — recipient-side "Send Back for Correction" dialog in the signing page's Quick Actions, with a select for which earlier recipient (or the sender) to target.
  • apps/remix/app/components/general/document/document-send-back-for-correction-dialog.tsx, document-page-view-recipients.tsx — owner-side action: a small icon button next to a "Signed" recipient's badge that opens the same kind of dialog scoped to that recipient. This build fixed the icon button's dead trigger prop (see Rebuild note above).

Test plan

  • npx tsc --noEmit via apps/remix's npm run typecheck — passes for every file touched by this change, with output identical before/after the fix (confirmed via git stash/stash pop diff of the error set). The run surfaces ~20 pre-existing, unrelated errors from duplicate @lingui/core package versions (workspace hoisting), a missing googleapis type declaration, and a couple of React RefObject<T | null> generics — none in files this PR touches.
  • Verified end-to-end in validation attempt 2 against the shared dev Postgres:
    • Recipient-initiated flow at /sign/<token> → Actions → Send Back for Correction: dialog opens, "earlier recipients" dropdown populates, submit produces a "Document sent back" toast.
    • DB effects: target recipient signingStatusNOT_SIGNED, signedAt cleared, their Field.inserted/customText cleared, their Signature row deleted, other recipients untouched.
    • Audit log row DOCUMENT_RECIPIENT_SENT_BACK_FOR_CORRECTION written with correct actor, target and reason.
    • Docker image builds clean.
  • Not yet verified in this build: the owner-side icon button opening the dialog (this is exactly the regression this rebuild fixes) and email delivery. To verify manually: open a PENDING document you own with at least one recipient showing a Signed badge, click the small circular-arrow icon next to that badge, confirm the dialog now opens, submit a reason, confirm the toast and DB effects above, and confirm the recipient-side flow still works as a regression check.

Closes #143

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@reeseherber reeseherber added needs-validation PR awaiting AI validator review validating Validator polecat is reviewing changes-requested Validator rejected - builder should iterate and removed needs-validation PR awaiting AI validator review validating Validator polecat is reviewing labels Jul 28, 2026
@reeseherber

Copy link
Copy Markdown
Collaborator Author

🔁 PR #145 · changes-requested

Build: ❌ · Playwright E2E:

worker exited without writing a verdict

Screenshots

validation screenshot

validation screenshot

validation screenshot

validation screenshot

@reeseherber reeseherber added needs-validation PR awaiting AI validator review validating Validator polecat is reviewing changes-requested Validator rejected - builder should iterate and removed changes-requested Validator rejected - builder should iterate needs-validation PR awaiting AI validator review validating Validator polecat is reviewing labels Jul 28, 2026
@reeseherber

Copy link
Copy Markdown
Collaborator Author

🔁 PR #145 · changes-requested

Build: ❌ · Playwright E2E:

worker exited without writing a verdict

Screenshots

validation screenshot

validation screenshot

validation screenshot

validation screenshot

@reeseherber reeseherber added needs-validation PR awaiting AI validator review validating Validator polecat is reviewing changes-requested Validator rejected - builder should iterate and removed changes-requested Validator rejected - builder should iterate needs-validation PR awaiting AI validator review validating Validator polecat is reviewing labels Jul 28, 2026
@reeseherber

Copy link
Copy Markdown
Collaborator Author

🔁 PR #145 · changes-requested

Build: ❌ · Playwright E2E:

worker exited without writing a verdict

@reeseherber reeseherber added needs-validation PR awaiting AI validator review validating Validator polecat is reviewing changes-requested Validator rejected - builder should iterate and removed changes-requested Validator rejected - builder should iterate needs-validation PR awaiting AI validator review validating Validator polecat is reviewing labels Jul 28, 2026
@reeseherber

Copy link
Copy Markdown
Collaborator Author

🔁 PR #145 · changes-requested

Build: ✅ · Playwright E2E:

Owner-side Send back for correction button is inert: DialogTrigger asChild clones props onto TooltipProvider (a non-forwarding context provider) instead of the Button, so the dialog never opens for document owners. Recipient-initiated flow works correctly end-to-end and DB-level reset/audit logic verified sound.

What I tested

I built PR #145 (build-143) into a throwaway image and ran it as a separate documenso-test container on the shared dev DB, alongside the untouched live documenso service. Using Playwright against http://localhost:3000 (tunneled from the dev server), I tested both entry points to the new "send document back for correction" feature: (1) the recipient-initiated flow, from a pending signer's /sign/<token> page via Actions → Send Back for Correction, and (2) the document-owner flow, from a document's recipients list via the small circular-arrow icon next to a signed recipient.

The recipient-initiated flow worked correctly end-to-end: the dialog opened, the "earlier recipients" dropdown was populated, submitting with a target recipient and reason produced a "Document sent back" toast, and I confirmed in Postgres that the target recipient's signingStatus was reset to NOT_SIGNED, signedAt cleared, their field's inserted/customText cleared, their Signature row deleted, and a DOCUMENT_RECIPIENT_SENT_BACK_FOR_CORRECTION audit log entry was written with the correct actor/target/reason.

The document-owner flow is broken: clicking the "Send back for correction" icon button on the recipients list does nothing — the dialog never opens. In document-page-view-recipients.tsx, the trigger passed to DocumentSendBackForCorrectionDialog is <TooltipProvider><Tooltip><TooltipTrigger asChild><Button .../></TooltipTrigger></Tooltip></TooltipProvider>, and this whole tree is wrapped by <DialogTrigger asChild>. Radix's asChild clones its click/pointer handlers onto its immediate child — here that's TooltipProvider, a plain context provider that does not forward unknown props to a DOM node — so the handlers never reach the actual <button>. I confirmed this with three independent click methods (Playwright .click(), a raw mouse click at the button's coordinates, and element.click() via evaluate()); none opened the dialog, showed a toast, or logged a console error. The backend logic this button would trigger (sendDocumentBackForCorrection / tRPC envelope.sendBackForCorrection) is otherwise sound — it shares the same reset helper exercised successfully by the recipient-side test — but it's currently unreachable from the document-owner UI.

Screenshots in evidence/: 01 owner document page, 02 post-click state showing the dialog did not open, 05-09 the full recipient-initiated flow through to the success toast.

Screenshots

validation screenshot

validation screenshot

validation screenshot

validation screenshot

@reeseherber reeseherber removed the changes-requested Validator rejected - builder should iterate label Jul 28, 2026
Recipients and document owners can now route a pending envelope back to
an earlier recipient (or the sender) to fix a mistake, instead of
rejecting and restarting the whole document. The target recipient's
signing status and fields are reset so they can redo their part; every
other recipient's data is left untouched. Affected parties are notified
by email and the action is recorded in the audit log.

Closes #143
@reeseherber reeseherber added needs-validation PR awaiting AI validator review validating Validator polecat is reviewing validated AI validator approved and removed needs-validation PR awaiting AI validator review validating Validator polecat is reviewing labels Jul 28, 2026
@reeseherber

Copy link
Copy Markdown
Collaborator Author

No description provided.

@reeseherber

Copy link
Copy Markdown
Collaborator Author

✅ PR #145 · validated

Build: ✅ · Playwright E2E:

Owner-side Send Back for Correction dialog exercised end-to-end (login, click, fill, submit); DB verified: target recipient reset to NOT_SIGNED, field cleared, signature deleted, audit log written correctly. Email job failed on SMTP relay allowlisting (known environmental issue, not a PR defect).

What I tested

I validated PR #145 ("allow sending a pending document back to a signer for correction") by building commit 6768ee3b0 (matching the PR head) into a standalone documenso-test container on the dev server, and seeding a PENDING document owned by pipeline@psd401.net with two SIGNER recipients: Alice (already SIGNED, with an inserted signature field) and Bob (still pending). Logged in as the pipeline bot via the JSON credentials bypass, I navigated to the document detail page at /t/personal_ezdvtanuarhsuhcr/documents/66066364, clicked the new circular-arrow "Send back for correction" button next to Alice's row (only shown for SIGNED, non-CC/VIEWER recipients on a PENDING document), filled in a reason ("Please re-sign, wrong signature was used."), and submitted the DocumentSendBackForCorrectionDialog.

The dialog closed and a "Document sent back" toast appeared. I then verified directly in Postgres that the mutation did exactly what the PR describes: Alice's signingStatus was reset to NOT_SIGNED (signedAt cleared), her Field.inserted was reset to false and customText cleared, her Signature row was deleted, and a DOCUMENT_RECIPIENT_SENT_BACK_FOR_CORRECTION audit log entry was written with the correct reason/target data. Bob's recipient and fields were untouched, as expected.

Caveat: the send.document.sent-back-for-correction.emails background job exhausted its retries because the test container's IP isn't allowlisted on the Google Workspace SMTP relay — this is the same environmental limitation seen in prior PR validations on this dev server, not a defect in this PR (the DB-side effects and audit trail are correct regardless of email delivery). I did not additionally exercise the recipient-facing DocumentSigningSendBackDialog (the second entry point on the signer's own signing page), since it shares the same backend mutation (sendDocumentBackForCorrectionWithToken calling the same resetRecipientForCorrection) already verified above, and time was better spent confirming the DB-level correctness of the shared core logic.

Screenshots

validation screenshot

validation screenshot

validation screenshot

validation screenshot

@reeseherber reeseherber added deploying and removed validated AI validator approved labels Jul 28, 2026
@reeseherber
reeseherber marked this pull request as ready for review July 28, 2026 19:44
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@reeseherber
reeseherber merged commit 6955e9b into main Jul 28, 2026
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow sending a document back to a signer for correction

1 participant