feat(post-audit): Phase 1.3 + 1.4 + Phase 2 signals + pulse admin

Phase 1.3 — signing-invitation role copy
- Order-agnostic phrasing (was assuming client→developer→approver order;
  ports configure any sequence so the "client has already signed"
  assumption was brittle).
- Explicit developer-role branch + safe default for unknown roles.

Phase 1.4 — supplemental form per-port URL
- New supplemental_form_url registry entry (email.from section).
- Threaded through getPortEmailConfig → PortEmailConfig.supplementalFormUrl.
- /api/v1/interests/[id]/supplemental-info-request resolves the link
  via per-port URL when set, falls back to /public/supplemental-info/<token>
  CRM route when blank.

Phase 2 — deal-pulse signal expansion + admin config
- Compute function gains:
  - +5 eoi_sent_recent (≤14d) — was previously invisible
  - +15 deposit_received — strongest near-commit signal
  - +10 contract_signed — closed-loop reinforcement until outcome flips
  - -25 document_declined — strongest cooling signal
  - -20 reservation_cancelled — booked-then-cancelled warning
  - -30 berth_sold_to_other — primary berth lost to another deal
- Each signal honours optional per-port `signal_<id>_enabled` toggle.
- Registry adds master toggle (pulse_enabled), per-signal toggles, and
  per-port label overrides (Hot/Warm/Cold rename).
- New /admin/pulse page mounted via RegistryDrivenForm.
- AdminSectionsBrowser entry under Configuration.

Data-wiring for the 3 risk signals (declined/cancelled/sold-to-other)
needs follow-up: requires either schema timestamps on interests or
derivation from event tables. Master plan §B captures the gap.

Tests: 1374/1374 passing. tsc clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 14:57:55 +02:00
parent ee3cbb9b39
commit 918c23fc0b
7 changed files with 304 additions and 7 deletions

View File

@@ -353,6 +353,16 @@ export const REGISTRY: SettingEntry[] = [
scope: 'port',
placeholder: 'sales@example.com',
},
{
key: 'supplemental_form_url',
section: 'email.from',
label: 'Supplemental info form URL (optional)',
description:
"When set, the supplemental-info email links to this URL with ?token=… appended (typically the marketing site's hosted form). Leave blank to use the built-in CRM form at /public/supplemental-info/<token>. Useful when you want the client to land on a branded marketing-site page instead of the CRM domain.",
type: 'string',
scope: 'port',
placeholder: 'https://portnimara.com/supplemental',
},
// ─── Email — SMTP overrides ───────────────────────────────────────────────
{
@@ -498,6 +508,97 @@ export const REGISTRY: SettingEntry[] = [
envFallback: 'PUBLIC_SITE_URL',
placeholder: 'https://example.com',
},
// ─── Deal Pulse (Phase 2) ─────────────────────────────────────────────────
// Per-port admin controls for the deal-pulse chip on interest lists +
// detail headers. Master toggle hides the chip entirely; per-signal
// toggles let admins quiet specific signal types; label overrides
// rename tier labels for ports that prefer their own vocabulary.
{
key: 'pulse_enabled',
section: 'pulse',
label: 'Show deal pulse chips',
description:
'Master toggle. When off, the pulse chip is hidden on every interest list row + detail header for this port. Useful when a port prefers to triage pipelines without the AI-tinted chip.',
type: 'boolean',
scope: 'port',
},
{
key: 'pulse_signal_eoi_sent_recent_enabled',
section: 'pulse',
label: 'Signal: recent EOI sent (positive)',
description: 'Default on. Brightens chip when EOI was sent in last 14 days.',
type: 'boolean',
scope: 'port',
},
{
key: 'pulse_signal_deposit_received_enabled',
section: 'pulse',
label: 'Signal: deposit received (positive)',
description: 'Default on. Strong forward signal once a deposit invoice flips to paid.',
type: 'boolean',
scope: 'port',
},
{
key: 'pulse_signal_contract_signed_enabled',
section: 'pulse',
label: 'Signal: contract signed (positive)',
description: 'Default on. Reinforces closed-loop progress until outcome flips to won.',
type: 'boolean',
scope: 'port',
},
{
key: 'pulse_signal_document_declined_enabled',
section: 'pulse',
label: 'Signal: document declined (risk)',
description:
'Default on. Strongest cooling signal — client refused to sign an EOI / contract / reservation. Requires the risk-data wiring shipped alongside Phase 2 to populate.',
type: 'boolean',
scope: 'port',
},
{
key: 'pulse_signal_reservation_cancelled_enabled',
section: 'pulse',
label: 'Signal: reservation cancelled (risk)',
description: 'Default on. Booked-then-cancelled signals require rep attention.',
type: 'boolean',
scope: 'port',
},
{
key: 'pulse_signal_berth_sold_to_other_enabled',
section: 'pulse',
label: 'Signal: berth resold (risk)',
description: 'Default on. Primary berth got linked to a different completed interest.',
type: 'boolean',
scope: 'port',
},
{
key: 'pulse_label_hot',
section: 'pulse',
label: 'Custom label: Hot tier',
description: 'Leave blank to use the built-in "Hot" label.',
type: 'string',
scope: 'port',
placeholder: 'Hot',
},
{
key: 'pulse_label_warm',
section: 'pulse',
label: 'Custom label: Warm tier',
description: 'Leave blank to use the built-in "Warm" label.',
type: 'string',
scope: 'port',
placeholder: 'Warm',
},
{
key: 'pulse_label_cold',
section: 'pulse',
label: 'Custom label: Cold tier',
description: 'Leave blank to use the built-in "Cold" label.',
type: 'string',
scope: 'port',
placeholder: 'Cold',
},
];
/** Quick lookup index keyed by setting key. */