Commit Graph

3 Commits

Author SHA1 Message Date
203f543e60 feat(uat-batch-5): UI polish — dialog width, chart centering, recommender pill, audit link, inbox reorder
Six surgical Wave-2-3 wins:

- UploadForSigningDialog: dialog widened to max-w-[1400px] w-[95vw] so
  the place-fields step actually has room; recipient row converts from
  fixed grid to flex (name flex-1, email flex-[2] for the longer
  string, role w-40, delete shrink-0); invitation-message textarea
  rows 3 → 6.
- ChartCard becomes flex-col with flex-1 + items-center on CardContent
  so charts vertically center when neighbouring cards make the row
  taller (e.g. Pipeline Value's full breakdown).
- Berth recommender pill: drops the "Tier {letter} · " prefix; shows
  just the plain-English label ("Open" / "Fall-through" / "Active
  interest" / "Late stage") as a Popover trigger that explains the
  4-state ladder. HelpCircle icon makes the tooltip discoverable.
- Activity feed gains a "See all" link in the header pointing at
  /<port>/admin/audit, permission-gated by `admin.view_audit_log`.
- Inbox section order swaps to Reminders above Alerts (rep-noted
  priority); PageHeader title flips to "Reminders & Alerts". Section
  ids, deep-link hashes, and localStorage open-state keys untouched.
- Inbox ReminderList (embedded mode only): "New Reminder" button now
  shares the filter row (right-aligned via ml-auto) instead of
  occupying its own dedicated row above the filters.

tsc clean. 1419/1419 vitest pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:28:20 +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
7bf587de90 feat(documenso-phase-4): recipient configurator + field placement UI
Phase 4 lands the visual half of the Documenso build — the upload-
for-signing dialog the Contract + Reservation tabs hand off to. Four
files of new code; the existing tab placeholders point at it.

Files added:
- lib/services/document-field-detector.ts — Phase 4c auto-detect
  scanner. Uses pdfjs-dist to extract per-page text + positions, then
  matches anchor patterns (Signature, Date, Initials, Email, Name,
  underscore-runs) and produces percent-coordinate DetectedField
  rows. Recipient label inference walks ±100pt of each match for
  Buyer/Seller/Client/Witness/Notary keywords. Returns [] when the
  PDF is image-only; UI falls back to manual placement without an
  error. 6 unit tests pin the matching + coordinate math.

- app/api/v1/documents/auto-detect-fields/route.ts — multipart POST
  endpoint that delegates to detectFields(). Permission-gated by
  documents.send_for_signing.

- app/api/v1/documents/signing-defaults/route.ts — GET endpoint that
  surfaces just the per-port developer + approver display name/email
  + sendMode flag. No secrets exposed; lets the dialog prefill the
  recipient configurator without an admin-scoped settings read.

- components/documents/upload-for-signing-dialog.tsx — the Phase 4
  UI. Three-step state machine inside a single Dialog:
    1. select-file:  drop/click PDF picker + title input
    2. configure-recipients: client + developer + approver prefilled,
       rep can add/remove/reorder + change role (SIGNER/APPROVER/CC)
    3. place-fields: react-pdf renders the source PDF; auto-detect
       runs in the background on file load and seeds the overlay;
       rep places, drags, resizes, deletes, reassigns fields via the
       palette + side panel. Native DOM drag (no dnd-kit dependency
       added — the coordinate math stays obvious).
  Send fires POST /api/v1/interests/[id]/upload-for-signing (Phase 3
  service); success toast reflects port sendMode (auto fires the
  invite immediately, manual leaves it for the rep).

Files modified:
- components/interests/interest-contract-tab.tsx + reservation-tab.tsx:
  swap the ComingSoonDialog placeholder for the real
  UploadForSigningDialog with the matching documentType prop. The
  placeholder ComingSoonDialog helper is deleted from both.

- scripts/tsc-staged.mjs: pull src/types/**/*.d.ts into the temp
  staged-only tsconfig so side-effect CSS imports (e.g.
  react-pdf/dist/Page/AnnotationLayer.css) resolve via the existing
  declare-module shim. Without this fix the staged compile reports
  TS2882 even though the full tsc --noEmit pass passes.

Design choices noted in code comments:
- Native drag over dnd-kit: the field overlay's percent-based
  coordinate math is short enough that adding a drag library adds
  complexity without saving lines.
- Auto-detect on file-load (not on demand): runs immediately so the
  rep doesn't have to click a second button — empty result drops
  back to manual placement silently.
- Per-recipient color swatches indexed by signingOrder.
- Recipient seed via useMemo + user-event handler instead of
  useEffect → setRecipients (Wave 3 set-state-in-effect avoidance).

Server-side, Phase 3 plumbing handles the rest: tenant guard, magic-
byte verify, Documenso round-trip with per-port v1/v2 routing,
recipient signingToken capture for Phase 2 webhook cascade, auto-
send when port.sendMode === 'auto'.

Tests: 1334 → 1340  (6 new for the detector); tsc clean.

Deferred polish (Phase 6):
- Per-field metadata side panel for DROPDOWN/RADIO option lists
- Pinch-zoom + zoom-out controls on the field-placement canvas
- Recipient drag-reorder via dnd-kit
- Required toggle per field

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