Bundles the prior autonomous-session output that was sitting unstaged:
- Em-dash sweep across src/ + tests/ (en-dash/em-dash to hyphen, ~2280 instances)
- country-flag-icons rollout (CountryFlag component, replaces emoji glyphs that
never rendered on Windows; lazy-loads the 3x2 SVG index as a single chunk
after the per-subpath dynamic-import approach silently failed in webpack)
- Admin IA Phase 1+2: 7-domain regroup, 41 to 38 pages, /admin/berths index,
redirects (ocr to ai, reports to dashboard, invitations to users),
docs/admin-ia-proposal.md
- Per-template email tester (registry + endpoint + UI on Email admin page)
- Cancel-document mode picker (delete-from-Documenso vs keep-for-audit)
- Dashboard PDF report: 25 widgets, SVG charts, date-range picker, 11 resolvers
- Customize-widgets per-region sortables at xl+ (charts/rails/feed); single
flat sortable below xl when the layout stacks; per-viewport saved orders
- Audit doc updates capturing each shipped item
- Lint fixes: react-compiler immutability in DonutChart (reduce instead of
let-reassign), set-state-in-effect disables in CountryFlag and
UploadForSigning preview-bytes effect, unused 'confirm' destructures in
interest contract + reservation tabs, unescaped apostrophe in test-template
card copy
Closes the silence after the first signing invitation. Three real
improvements on top of the existing webhook plumbing, all aligned with
the Documenso v1.32 + v2 webhook payload shape (verified against the
official OpenAPI spec + Context7 docs):
1. Cascading "your turn" emails — when DOCUMENT_SIGNED / DOCUMENT_
RECIPIENT_COMPLETED / RECIPIENT_SIGNED fires for a recipient,
handleRecipientSigned now resolves the next pending signer in
signing order and sends them the branded sendSigningInvitation()
email with the embedded-host-wrapped URL. Stamps invitedAt so a
duplicate webhook retry doesn't re-send.
2. On-completion PDF distribution — handleDocumentCompleted now re-
reads the just-committed signedFileId, resolves all signers, and
fires sendSigningCompleted() to every recipient with the signed
PDF attached. resolveAttachments in lib/email already pulls bytes
through getStorageBackend() so this works under both the s3/minio
and filesystem backends without changes. Failures fall through to
logger.error rather than throwing — the document is already marked
completed and the admin can re-trigger manually.
3. Token-based recipient matching — Documenso v1 + v2 webhook recipients
carry a `token` field (per the OpenAPI spec); same token appears in
the document-create response. Captured at send time into the existing
document_signers.signing_token column (already in schema from Phase 1)
and used by handleRecipientSigned + handleDocumentOpened before
falling back to email match. Robust against the case where one email
serves multiple roles on a contract — which is the documented gap in
the legacy nocodb-based handler.
Supporting changes:
- New helper module lib/services/documenso-signers.ts with
extractSigningToken() (URL-tail fallback), DOC_TYPE_LABEL map, and
nextPendingSigner() picker. 11 unit tests cover the token-regex,
the helper picks the lowest pending signing-order, and rejects
declined/signed correctly.
- documenso-client normalizeDocument now reads `token` from both
`recipients[]` and the legacy capital-R `Recipient[]` array Documenso
v1.32 sometimes ships in webhooks.
- documents.service signer-update at send time prefers the explicit
token field, falling back to extractSigningToken(signingUrl) for any
v2 deployment whose distribute response omits it.
Out of scope for Phase 2 (per the build plan):
- Custom-doc upload-to-Documenso path (Phase 3)
- Recipient + field-placement UI (Phase 4)
- DNS-rebinding hardening + circuit-breaker (deferred-refactor list)
- Auto-reminder cron — manual "Send reminder" button + auto-reminder
toggle remain manual until Phase 6 polish
Tests: 1315/1315 vitest ✅ + 11 new tests for documenso-signers ✅;
tsc clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>