Commit Graph

6 Commits

Author SHA1 Message Date
cae5d39607 feat(documenso): rejection reason + poll fallback + rollback hardening + recipient UX
Documenso reliability + signer-UX bundle from the 2026-05-26 live UAT.
Each piece detailed in docs/superpowers/audits/active-uat.md with full
file:line + root cause + alternatives.

Webhook + poll convergence
- DocumensoRecipient (webhook payload type) gains rejectionReason +
  declineReason. The DOCUMENT_REJECTED / DOCUMENT_DECLINED handler
  coalesces them at the boundary so downstream code sees one stable
  field. Empty/whitespace normalised to null.
- DocumensoDocument.recipients[] (normalized client output) gains
  rejectionReason. normalizeDocument coalesces v2 + v1 field names the
  same way so poller consumers see identical shape.
- handleDocumentRejected signature gains rejectionReason. Stored on
  document_events.eventData, persisted in audit_logs metadata, quoted
  inline in the in-CRM rep notification (truncated 120 chars; full
  reason still on the audit row). New 'transfer' AuditAction added
  alongside.
- signature-poll job now handles REJECTED / DECLINED. Previously only
  SIGNED / COMPLETED / EXPIRED were reconciled, so a missed rejection
  webhook (stale tunnel URL is the typical dev cause) left documents
  stuck in 'sent' forever. The 5-min poll cycle now closes that gap —
  webhook becomes an optimisation, not a correctness requirement.

placeFields rollback gap
- custom-document-upload.service moved the synchronous field-placement
  map() INSIDE the same try/catch that wraps placeFields(). Previously
  the map's throw bubbled past the catch-and-rollback block, leaving
  Documenso with a live envelope + recipients but no fields, and the
  CRM document row stuck in 'sent' with no signing UI for the signers.
  Logger captures looked-up email + map keys on miss for diagnosis.
- Comment documents Documenso's by-email dedupe semantic so future
  readers don't reintroduce the per-recipient-row map assumption.

UploadForSigningDialog recipient UX
- New RECIPIENT_ROLE_META + RecipientRoleBadge helpers. Placement-step
  sidebar list rebuilt as a two-line layout (name + role badge / email
  on its own line) so duplicate-named recipients are visually
  distinguishable. FieldSidePanel dropdown SelectItem mirrors the same
  stacked shape.
- "Recipient" label renamed to "Assign this field to" with an explainer
  paragraph below.

SigningProgress copy-link parity
- Copy-link button now always renders for pending signers (disabled +
  explainer tooltip when signingUrl not yet issued). Reps can copy
  even when the URL hasn't been distributed via email yet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 20:06:12 +02:00
5bd0e1ad9a feat(documents): universal upload-with-fields UI wiring (B3 #11)
Backend foundations were already in place ('generic' CustomDocumentType,
storage-path routing). This wires the UI surface across Documents Hub +
entity file tabs.

- UploadForSigningDialog: interestId now string | null; new entity?,
  folderId?, onCreated? props. Generic path POSTs to new endpoint
  /api/v1/upload-for-signing; interest-scoped paths unchanged.
- uploadDocumentForSigning service: interestId nullable; skips interest
  lookup, pipeline-stage advance, doc-status flip on the generic path.
  Routes file FK + auto-filed folder via either interest.clientId or the
  caller-supplied entity. Validation enforces the matching invariant
  (generic must be interestId=null, type-specific must carry one).
- New menu item in NewDocumentMenu ("Upload & send for signature") on
  Documents Hub root + folder views.
- Upload & send-for-signature button on ClientFilesTab + CompanyFilesTab,
  gated by documents.send_for_signing.

Existing unit tests for the service still pass (validation paths unchanged).
2026-05-23 01:01:52 +02:00
221ae5784e chore(autonomous-session): consolidate uncommitted work from prior session
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
2026-05-23 00:52:59 +02:00
6b28459c45 feat(pipeline): 9→7 stage refactor + v1.1 hardening wave
Replaces the legacy 9-stage pipeline with 7 canonical stages
(enquiry → qualified → eoi → reservation → deposit_paid → contract →
nurturing) plus three doc sub-status columns (eoi_doc_status,
reservation_doc_status, contract_doc_status) that track sent/signed
within a single stage instead of branching it.

Schema (migration 0062):
- interests gains assigned_to, deposit_expected_amount/currency,
  three doc-status columns, two documenso-id columns, and
  date_reservation_signed.
- New tables: qualification_criteria (per-port admin-configurable),
  interest_qualifications (per-interest state), payments (deposit /
  balance / refund records keyed to interest + client).
- Default qualification criteria seeded for every existing port.
- Dummy-data UPDATEs collapse Sent/Signed pairs and 'completed' into
  the new stage + doc-status + outcome shape.

Migration 0063 adds interest_contact_log.voice_transcript and
template_used columns for v1.1-A/B (quick-template buttons + voice
transcription via Web Speech API).

v1.1 phase work bundled here:
- A/B: Quick-template buttons (Call / Visit / Email) + mic toggle on
       the contact-log compose dialog (useVoiceTranscription hook).
- C:   berth-rules-engine wraps state writes in pg_advisory_xact_lock
       with an idempotent re-read; emits rule_evaluated audit traces.
- D:   Documenso webhook: reservation/contract sub-status stamping
       moved out of the PDF-download try-block so a download failure
       no longer swallows the stamp. New integration test coverage.
- E:   /admin/qualification-criteria CRUD page + admin component.
- F:   default_new_interest_owner exposed in System Settings.
- G:   recentActivityCount + active_engagement deal-pulse signal
       surfaced as a chip on interests + hot-deals card.
- H:   interest_assigned notification on assignedTo change (skips
       self-assign, uses a dedupe key).

Plus the supporting components: AssignedToChip, DealPulseChip,
PaymentsSection, QualificationChecklist, MultiEoiChip,
SkipAheadBanner, WonStatusPanel, InterestBerthStatusBanner,
SupplementalInfoRequestButton, UserPicker.

Tests: 1370/1370 vitest pass (added deal-health unit suite +
expanded constants/validators/pipeline-transitions coverage). tsc
clean, eslint clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 03:39:21 +02:00
adebd5f91d feat(documenso-phase-6): activity badges + per-document invitation message
Two of the six Phase 6 polish items shipped in one commit because they
share the data + plumbing path (per-doc message uses the signing-
progress UI's existing layout).

1) Signing-progress activity badges
   - Surfaces `invitedAt`, `openedAt`, `lastReminderSentAt` (all
     populated by Phase 1+2 webhook handlers) per signer in the
     existing progress widget. Each badge renders as
     "Invited 2 hours ago / Opened yesterday / Reminded 3 days ago"
     via Intl.RelativeTimeFormat.
   - Resend button: was silent on success/failure; now uses
     useMutation + toast so the rep sees whether the reminder fired
     or fell into a cadence cooldown. Honours the existing
     sendReminderIfAllowed return shape (`{sent, reason}`).
   - Title-tooltips on each badge show the exact ISO timestamp.

2) Per-document custom invitation message
   - New `documents.invitation_message` column (migration 0060;
     applied via psql per the dev-flow note in CLAUDE.md).
   - Textarea in UploadForSigningDialog step 2 (recipient configurator),
     1000-char cap, placeholder text shows the expected tone.
   - custom-document-upload.service accepts `invitationMessage`,
     trims + stores on the documents row.
   - sendCascadingInviteForNextSigner now reads
     doc.invitationMessage and passes as customMessage so every
     cascaded recipient (developer / approver / witness) sees the
     same note — not just the first signer.
   - send-invitation route (manual resend path) reads the same
     column → customMessage so manual reminders match.
   - The email template's existing customMessage rendering does
     the XSS escape; no other plumbing needed.

Phase 6 items still deferred (each ~2-3h, mostly independent):
- Auto-send delay (`eoi_send_delay_minutes` setting + scheduled
  BullMQ job — needs a scheduler hook).
- Document expiration (`documents.expires_at` + Documenso
  `expiresAt` passthrough — needs Documenso v2 endpoint shape
  verification).
- Failed-webhook recovery admin UI (the BullMQ DLQ exists; needs
  an admin page with Replay button).

Tests: 1340 → 1350 ; tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 14:17:39 +02:00
33d0426911 feat(documenso-phase-3): custom document upload-to-Documenso
Backend foundation for the Contract + Reservation signing flows. The
existing tab placeholders point at a "send for signing" CTA that had
no code behind it; this commit lands the service + endpoint that the
Phase 4 drag-drop UI will POST to.

Files added:
- lib/services/custom-document-upload.service.ts — orchestrates the
  full PDF → Documenso → local-state-update flow:
    1. Magic-byte verifies the PDF (defense vs. mislabelled bytes —
       same posture as berth-pdf + brochures).
    2. Stores the source PDF via getStorageBackend(), works on s3 +
       filesystem backends. Auto-files into the client's entity folder
       when resolvable.
    3. Inserts the documents row (status=draft → sent), with the file
       FK + interest link + clientId snapshot.
    4. Documenso round-trip via createDocument → sendDocument →
       placeFields. Per-port apiVersion drives v1 vs v2 (existing
       client handles both — v1: /api/v1/documents; v2: envelope/create
       multipart). meta.signingOrder + redirectUrl flow through.
    5. Captures recipient signingUrl + token into document_signers so
       the Phase 2 cascade picks them up.
    6. Auto-send first invitation when port.eoi_send_mode === 'auto';
       stamps invitedAt to suppress duplicate cascades.
    7. Advances pipeline stage to contract_sent.

- app/api/v1/interests/[id]/upload-for-signing/route.ts — multipart
  POST endpoint. Zod-validates recipients (≤20), fields (≤200), PDF
  size (≤50MB), all 11 Documenso field types. Permission-gated by
  documents.send_for_signing + interests.edit (matches the
  external-eoi precedent — the auto-advance side-effect is
  interest-mutating).

Files modified: none — keeps the existing tab placeholders as the
entry point; Phase 4 builds the drag-drop UI on top.

Validation contract pinned by 8 unit tests covering: empty recipient
list, empty field list, empty/oversized PDF, non-PDF magic bytes,
out-of-range + negative recipientIndex, duplicate signingOrder.

The heavy paths (storage put, Documenso HTTP, signer update) are
exercised by the existing realapi Playwright project — no new
realapi specs added because the contract-upload UI doesn't exist yet
to drive them.

Verified against Documenso API spec (v1 OpenAPI + v2 docs via
Context7): recipients[].token is on the Recipient model in both
versions; webhook payloads echo the same shape so the Phase 2 token-
match handler works against custom-uploaded docs without changes.

Tests: 1326 → 1334 ; tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 13:52:21 +02:00